From 6d0405d129eee2faa38483c31028b62ec484f34a Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Sat, 9 Apr 2022 00:42:24 +0200 Subject: [PATCH 01/69] Version 1.1 Correction de bugs : * Avoir un logo pour les pages d'erreurs #32 * Stocker localement les assets d'un album #37 Co-authored-by: dbroqua Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/43 --- README.md | 31 +- docker-compose.yml.dev | 8 + docker-compose.yml.prod | 8 + package.json | 3 + public/img/404.svg | 383 +++++++------- public/js/main.js | 2 +- sass/index.scss | 2 +- sass/list.scss | 6 + src/app.js | 15 +- src/config/index.js | 9 + src/libs/aws.js | 72 +++ src/libs/passport.js | 25 +- src/middleware/Albums.js | 477 +----------------- src/middleware/Export.js | 453 +++++++++++++++++ src/middleware/Jobs.js | 128 +++++ src/models/albums.js | 1 + src/models/jobs.js | 24 + src/routes/jobs.js | 40 ++ .../mon-compte/ma-collection/details.ejs | 4 +- 19 files changed, 1022 insertions(+), 669 deletions(-) create mode 100644 src/libs/aws.js create mode 100644 src/middleware/Export.js create mode 100644 src/middleware/Jobs.js create mode 100644 src/models/jobs.js create mode 100644 src/routes/jobs.js diff --git a/README.md b/README.md index 1da497b..e05aa15 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Le site est accessible sur [http://localhost:PORT](http://localhost:PORT). #### Standalone -Pour la version standalone je vous conseille de faire un script embarquant les variables d'environnement que vous souhaitez modifier : +Pour la version standalone je vous conseille de faire un script embarquant les variables d'environnement que vous souhaitez modifier ([voir à la fin pour la liste des variables](#env-file)) : ```bash #! /bin/bash @@ -184,6 +184,26 @@ server { Une fois le vhost activé (lien symbolique dans le dossier site-enable) et nginx rechargé votre site sera alors accessible en https. +### Jobs + +Par défaut toute les images des albums sont affichées depuis Discogs. Cependant avec les temps les urls deviennent invalides. Pour éviter cela lors de l'ajout d'un album à votre collection un job est créé. Ce job a pour rôle de stocker les images sur un bucket s3. + +Pour lancer les jobs il faut mettre en place une tâche cron qui sera éxécutée toute les heures (par exemple). + +Exemple de crontab : +```crontab +0 * * * * curl 'http://localhost:3001/jobs' \ + -H 'JOBS_HEADER_KEY: JOBS_HEADER_VALUE' \ + -H 'Accept: application/json' +30 * * * * curl 'http://localhost:3001/jobs?state=ERROR' \ + -H 'JOBS_HEADER_KEY: JOBS_HEADER_VALUE' \ + -H 'Accept: application/json' +``` + +N'oubliez pas de remplacer `localhost:30001`, `JOBS_HEADER_KEY` et `JOBS_HEADER_VALUE` par les bonnes valeurs. + +La première ligne permet de parcourir tous les nouveaux jobs alors que la seconde permet de relancer les jobs en erreurs (après 5 tentatives le job est marqué comme définitivement perdu). + ### Fichier .env {#env-file} Voici la liste des variables configurables : @@ -198,10 +218,17 @@ FORMSPREE_ID # Id du formulaire formspree pour la page "nous-contacter" MATOMO_URL # Url vers l'instance matomo (exemple: https://analytics.darkou.fr/) MATOMO_ID # Id du site sur votre instance matomo (exemple: 1) SITE_NAME # Nom du site (utilisé dans le titre des pages) +AWS_ACCESS_KEY_ID # Clé d'accès AWS +AWS_SECRET_ACCESS_KEY # Clé secrète AWS +S3_ENDPOINT # Url de l'instance aws (s3.fr-par.scw.cloud pour scaleway france par exemple) +S3_SIGNATURE # Version de la signature AWS (s3v4 pour scaleway par exemple) +S3_BASEFOLDER # Nom du sous dossier dans lequel seront mis les pochettes des albums +S3_BUCKET # Nom du bucket +JOBS_HEADER_KEY # Nom du header utilisé pour l'identification des tâches cron (par exemple musictopus) +JOBS_HEADER_VALUE # Valeur de la clé ``` ## Contributeurs - Damien Broqua (développeur principal du projet) - Brunus (Logo et fournisseur d'idées :wink: ) - diff --git a/docker-compose.yml.dev b/docker-compose.yml.dev index 9391624..dbe38b9 100644 --- a/docker-compose.yml.dev +++ b/docker-compose.yml.dev @@ -28,6 +28,14 @@ services: MATOMO_URL: ${MATOMO_URL} MATOMO_ID: ${MATOMO_ID} SITE_NAME: ${SITE_NAME} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + S3_BASEFOLDER: ${S3_BASEFOLDER} + S3_BUCKET: ${S3_BUCKET} + S3_ENDPOINT: ${S3_ENDPOINT} + S3_SIGNATURE: ${S3_SIGNATURE} + JOBS_HEADER_KEY: ${JOBS_HEADER_KEY} + JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE} networks: - musictopus musictopus-db: diff --git a/docker-compose.yml.prod b/docker-compose.yml.prod index 9077877..c343531 100644 --- a/docker-compose.yml.prod +++ b/docker-compose.yml.prod @@ -28,6 +28,14 @@ services: MATOMO_URL: ${MATOMO_URL} MATOMO_ID: ${MATOMO_ID} SITE_NAME: ${SITE_NAME} + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} + S3_BASEFOLDER: ${S3_BASEFOLDER} + S3_BUCKET: ${S3_BUCKET} + S3_ENDPOINT: ${S3_ENDPOINT} + S3_SIGNATURE: ${S3_SIGNATURE} + JOBS_HEADER_KEY: ${JOBS_HEADER_KEY} + JOBS_HEADER_VALUE: ${JOBS_HEADER_VALUE} networks: - musictopus musictopus-db: diff --git a/package.json b/package.json index b5aea27..d574778 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@babel/cli": "^7.17.0", "@babel/core": "^7.17.2", "@babel/preset-env": "^7.16.11", + "aws-sdk": "^2.1110.0", "axios": "^0.26.0", "connect-ensure-login": "^0.1.1", "connect-flash": "^0.1.1", @@ -60,10 +61,12 @@ "mongoose-unique-validator": "^3.0.0", "npm-run-all": "^4.1.5", "passport": "^0.5.2", + "passport-custom": "^1.1.1", "passport-http": "^0.3.0", "passport-local": "^1.0.0", "rimraf": "^3.0.2", "sass": "^1.49.7", + "uuid": "^8.3.2", "vue": "^3.2.31" }, "nodemonConfig": { diff --git a/public/img/404.svg b/public/img/404.svg index b4dbe78..4cd6c0c 100644 --- a/public/img/404.svg +++ b/public/img/404.svg @@ -6,50 +6,50 @@ xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" - viewBox="0 0 168.85766 133.4734" - version="1.1" - id="MusicTopus" + width="100%" height="100%" - width="100%"> + id="MusicTopus" + version="1.1" + viewBox="0 0 168.85766 133.4734"> + offset="0" /> + stop-opacity="0" + offset="1" /> - + x2="103.29" + gradientTransform="translate(-19.041285,-22.505715)" + y1="27.309999" + x1="57.074001" /> + + offset="0" /> + offset="1" /> + transform="translate(-4.0461145,-24.740973)" + id="layer1"> + id="g4845" + transform="matrix(-1,0,0,1,16.909353,13.841748)"> - - - - - + + + + + + style="fill:#ec8479;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#fbb9b8;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - + + transform="translate(4.0461145,24.740973)" + id="path4" /> + style="opacity:1;fill:#ffaf62;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.41468528;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" + rx="6.7384396" + ry="5.6584005" /> + id="path4788" + style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.41468525;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" + rx="1.1466434" + ry="0.96285915" /> + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#feb6b9;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#eea6a7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#feb6b7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#ffd7d7;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#feb6b9;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + transform="rotate(19.617168,87.590538,52.720911)" + id="g4740"> + + + + + + + + id="path4744" /> - - - - - - - + cx="105.70718" + id="circle4655" + style="opacity:1;fill:#ffffff;fill-opacity:0.94117647;fill-rule:nonzero;stroke:none;stroke-width:0.14161019;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:fill markers stroke" /> - + + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#dd6258;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + style="fill:#7f2625;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - + style="fill:none;stroke:#301818;stroke-width:4.46500015;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + + style="opacity:1;vector-effect:none;fill:#7f2625;fill-opacity:1;stroke:none;stroke-width:1.48535442;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + style="stroke-width:42.36951447"> + id="path2" /> + id="path4-9" /> + transform="matrix(-0.0125514,0,0,0.0125514,96.697579,101.08859)"> + style="fill:#29abe2;stroke-width:79.67237854" /> + style="fill:#ffffff;stroke-width:79.67237854" /> + + - - + id="path4700" /> diff --git a/public/js/main.js b/public/js/main.js index 895683a..f203a63 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -105,7 +105,7 @@ function switchTheme(e) { document.addEventListener('DOMContentLoaded', () => { const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); if ($navbarBurgers.length > 0) { - $navbarBurgers.forEach( el => { + $navbarBurgers.forEach( el => { el.addEventListener('click', () => { const target = el.dataset.target; const $target = document.getElementById(target); diff --git a/sass/index.scss b/sass/index.scss index 3a53fd2..373b302 100644 --- a/sass/index.scss +++ b/sass/index.scss @@ -46,4 +46,4 @@ @import './ajouter-un-album'; @import './collection'; @import './ma-collection-details'; -@import './composants'; \ No newline at end of file +@import './composants'; diff --git a/sass/list.scss b/sass/list.scss index 9dbd03a..fbdc7c2 100644 --- a/sass/list.scss +++ b/sass/list.scss @@ -23,6 +23,12 @@ background-color: var(--default-color); } + &:nth-child(4n), + &:nth-child(4n-1) + { + background-color: var(--default-color); + } + &:first-child, &:nth-child(2) { border-top: 2px solid var(--border-color); diff --git a/src/app.js b/src/app.js index 6d1e507..c8a9574 100644 --- a/src/app.js +++ b/src/app.js @@ -7,6 +7,8 @@ import flash from "connect-flash"; import session from "express-session"; import MongoStore from "connect-mongo"; +import passportConfig from "./libs/passport"; + import config, { env, mongoDbUri, secret } from "./config"; import { isXhr } from "./helpers"; @@ -15,15 +17,13 @@ import indexRouter from "./routes"; import maCollectionRouter from "./routes/ma-collection"; import collectionRouter from "./routes/collection"; +import importJobsRouter from "./routes/jobs"; + import importAlbumRouterApiV1 from "./routes/api/v1/albums"; import importSearchRouterApiV1 from "./routes/api/v1/search"; import importMeRouterApiV1 from "./routes/api/v1/me"; -// Mongoose schema init -require("./models/users"); -require("./models/albums"); - -require("./libs/passport")(passport); +passportConfig(passport); mongoose .connect(mongoDbUri, { useNewUrlParser: true, useUnifiedTopology: true }) @@ -46,10 +46,10 @@ const sess = { const app = express(); -app.use(express.json()); -app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(flash()); +app.use(express.json({ limit: "50mb" })); +app.use(express.urlencoded({ extended: false, limit: "50mb" })); app.use(session(sess)); @@ -85,6 +85,7 @@ app.use( app.use("/", indexRouter); app.use("/ma-collection", maCollectionRouter); app.use("/collection", collectionRouter); +app.use("/jobs", importJobsRouter); app.use("/api/v1/albums", importAlbumRouterApiV1); app.use("/api/v1/search", importSearchRouterApiV1); app.use("/api/v1/me", importMeRouterApiV1); diff --git a/src/config/index.js b/src/config/index.js index 19f16ea..17b341d 100644 --- a/src/config/index.js +++ b/src/config/index.js @@ -8,4 +8,13 @@ module.exports = { matomoUrl: process.env.MATOMO_URL || "", matomoId: process.env.MATOMO_ID || "", siteName: process.env.SITE_NAME || "MusicTopus", + awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID, + awsSecretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + s3BaseFolder: process.env.S3_BASEFOLDER || "dev", + s3Bucket: process.env.S3_BUCKET || "musictopus", + s3Endpoint: process.env.S3_ENDPOINT || "s3.fr-par.scw.cloud", + s3Signature: process.env.S3_SIGNATURE || "s3v4", + jobsHeaderKey: process.env.JOBS_HEADER_KEY || "musictopus", + jobsHeaderValue: + process.env.JOBS_HEADER_VALUE || "ooYee9xok7eigo2shiePohyoGh1eepew", }; diff --git a/src/libs/aws.js b/src/libs/aws.js new file mode 100644 index 0000000..f65521b --- /dev/null +++ b/src/libs/aws.js @@ -0,0 +1,72 @@ +import AWS from "aws-sdk"; +import fs from "fs"; +import path from "path"; +import axios from "axios"; +import { v4 as uuid } from "uuid"; + +import { + awsAccessKeyId, + awsSecretAccessKey, + s3BaseFolder, + s3Endpoint, + s3Bucket, + s3Signature, +} from "../config"; + +AWS.config.update({ + accessKeyId: awsAccessKeyId, + secretAccessKey: awsSecretAccessKey, +}); +/** + * Fonction permettant de stocker un fichier local sur S3 + * @param {String} filename + * @param {String} file + * @param {Boolean} deleteFile + * + * @return {String} + */ +export const uploadFromFile = async (filename, file, deleteFile = false) => { + const data = await fs.readFileSync(file); + + const base64data = Buffer.from(data, "binary"); + const dest = path.join(s3BaseFolder, filename); + + const s3 = new AWS.S3({ + endpoint: s3Endpoint, + signatureVersion: s3Signature, + }); + + await s3 + .putObject({ + Bucket: s3Bucket, + Key: dest, + Body: base64data, + ACL: "public-read", + }) + .promise(); + + if (deleteFile) { + fs.unlinkSync(file); + } + + return `https://${s3Bucket}.${s3Endpoint}/${dest}`; +}; + +/** + * Fonction permettant de stocker un fichier provenant d'une URL sur S3 + * @param {String} url + * + * @return {String} + */ +export const uploadFromUrl = async (url) => { + const filename = `${uuid()}.jpg`; + const file = `/tmp/${filename}`; + + const { data } = await axios.get(url, { responseType: "arraybuffer" }); + + fs.writeFileSync(file, data); + + return uploadFromFile(filename, file, true); + + // return s3Object; +}; diff --git a/src/libs/passport.js b/src/libs/passport.js index af0ab0b..a3ed7fe 100644 --- a/src/libs/passport.js +++ b/src/libs/passport.js @@ -1,11 +1,13 @@ /* eslint-disable func-names */ -const mongoose = require("mongoose"); -const LocalStrategy = require("passport-local").Strategy; -const { BasicStrategy } = require("passport-http"); +import { Strategy as LocalStrategy } from "passport-local"; +import { BasicStrategy } from "passport-http"; +import { Strategy as CustomStrategy } from "passport-custom"; -const Users = mongoose.model("Users"); +import Users from "../models/users"; -module.exports = function (passport) { +import { jobsHeaderKey, jobsHeaderValue } from "../config"; + +export default (passport) => { passport.serializeUser((user, done) => { done(null, user); }); @@ -55,4 +57,17 @@ module.exports = function (passport) { .catch(done); }) ); + passport.use( + "jobs", + new CustomStrategy((req, next) => { + const apiKey = req.headers[jobsHeaderKey]; + + if (apiKey === jobsHeaderValue) { + return next(null, { + username: "jobs", + }); + } + return next(null, false, "Oops! Identifiants incorrects"); + }) + ); }; diff --git a/src/middleware/Albums.js b/src/middleware/Albums.js index 6caa29b..84f5c5d 100644 --- a/src/middleware/Albums.js +++ b/src/middleware/Albums.js @@ -1,468 +1,18 @@ import moment from "moment"; -import momenttz from "moment-timezone"; -import xl from "excel4node"; import Pages from "./Pages"; +import Export from "./Export"; import AlbumsModel from "../models/albums"; +import JobsModel from "../models/jobs"; import UsersModel from "../models/users"; import ErrorEvent from "../libs/error"; +// import { uploadFromUrl } from "../libs/aws"; /** * Classe permettant la gestion des albums d'un utilisateur */ class Albums extends Pages { - /** - * Méthode permettant de remplacer certains cartactères par leur équivalents html - * @param {String} str - * - * @return {String} - */ - static replaceSpecialChars(str) { - if (!str) { - return ""; - } - let final = str.toString(); - const find = ["&", "<", ">"]; - const replace = ["&", "<", ">"]; - - for (let i = 0; i < find.length; i += 1) { - final = final.replace(new RegExp(find[i], "g"), replace[i]); - } - - return final; - } - - /** - * Méthode permettant de convertir les rows en csv - * @param {Array} rows - * - * @return {string} - */ - static async convertToCsv(rows) { - let data = - "Artiste;Titre;Genre;Styles;Pays;Année;Date de sortie;Format\n\r"; - - for (let i = 0; i < rows.length; i += 1) { - const { - artists_sort, - title, - genres, - styles, - country, - year, - released, - formats, - } = rows[i]; - - let format = ""; - for (let j = 0; j < formats.length; j += 1) { - format += `${format !== "" ? ", " : ""}${formats[j].name}`; - } - - data += `${artists_sort};${title};${genres.join()};${styles.join()};${country};${year};${released};${format}\n\r`; - } - - return data; - } - - /** - * Méthode permettant de convertir les rows en fichier xls - * @param {Array} rows - * - * @return {Object} - */ - static async convertToXls(rows) { - const wb = new xl.Workbook(); - const ws = wb.addWorksheet("MusicTopus"); - - const headerStyle = wb.createStyle({ - font: { - color: "#FFFFFF", - size: 11, - }, - fill: { - type: "pattern", - patternType: "solid", - bgColor: "#595959", - fgColor: "#595959", - }, - }); - const style = wb.createStyle({ - font: { - color: "#000000", - size: 11, - }, - numberFormat: "0000", - }); - - const header = [ - "Artiste", - "Titre", - "Genre", - "Styles", - "Pays", - "Année", - "Date de sortie", - "Format", - ]; - for (let i = 0; i < header.length; i += 1) { - ws.cell(1, i + 1) - .string(header[i]) - .style(headerStyle); - } - - for (let i = 0; i < rows.length; i += 1) { - const currentRow = i + 2; - const { - artists_sort, - title, - genres, - styles, - country, - year, - released, - formats, - } = rows[i]; - - let format = ""; - for (let j = 0; j < formats.length; j += 1) { - format += `${format !== "" ? ", " : ""}${formats[j].name}`; - } - - ws.cell(currentRow, 1).string(artists_sort).style(style); - ws.cell(currentRow, 2).string(title).style(style); - ws.cell(currentRow, 3).string(genres.join()).style(style); - ws.cell(currentRow, 4).string(styles.join()).style(style); - if (country) { - ws.cell(currentRow, 5).string(country).style(style); - } - if (year) { - ws.cell(currentRow, 6).number(year).style(style); - } - if (released) { - ws.cell(currentRow, 7) - .date(momenttz.tz(released, "Europe/Paris").hour(12)) - .style({ numberFormat: "dd/mm/yyyy" }); - } - ws.cell(currentRow, 8).string(format).style(style); - } - - return wb; - } - - /** - * Méthode permettant de convertir les rows en csv pour importer dans MusicTopus - * @param {Array} rows - * - * @return {string} - */ - static async convertToXml(rows) { - let data = '\n\r'; - - for (let i = 0; i < rows.length; i += 1) { - const { - discogsId, - year, - released, - uri, - artists, - artists_sort, - labels, - series, - companies, - formats, - title, - country, - notes, - identifiers, - videos, - genres, - styles, - tracklist, - extraartists, - images, - thumb, - } = rows[i]; - - let artistsList = ""; - let labelList = ""; - let serieList = ""; - let companiesList = ""; - let formatsList = ""; - let identifiersList = ""; - let videosList = ""; - let genresList = ""; - let stylesList = ""; - let tracklistList = ""; - let extraartistsList = ""; - let imagesList = ""; - - for (let j = 0; j < artists.length; j += 1) { - artistsList += ` - ${Albums.replaceSpecialChars(artists[j].name)} - ${Albums.replaceSpecialChars(artists[j].anv)} - ${Albums.replaceSpecialChars(artists[j].join)} - ${Albums.replaceSpecialChars(artists[j].role)} - ${Albums.replaceSpecialChars( - artists[j].tracks - )} - ${Albums.replaceSpecialChars(artists[j].id)} - ${Albums.replaceSpecialChars( - artists[j].resource_url - )} - ${Albums.replaceSpecialChars( - artists[j].thumbnail_url - )} - `; - } - - for (let j = 0; j < labels.length; j += 1) { - labelList += ` - `; - } - - for (let j = 0; j < series.length; j += 1) { - serieList += ` - ${Albums.replaceSpecialChars(series[j].name)} - ${Albums.replaceSpecialChars(series[j].catno)} - ${Albums.replaceSpecialChars( - series[j].entity_type - )} - ${Albums.replaceSpecialChars( - series[j].entity_type_name - )} - ${Albums.replaceSpecialChars(series[j].id)} - ${Albums.replaceSpecialChars( - series[j].resource_url - )} - ${Albums.replaceSpecialChars( - series[j].thumbnail_url - )} - - `; - } - - for (let j = 0; j < companies.length; j += 1) { - companiesList += ` - ${Albums.replaceSpecialChars(companies[j].name)} - ${Albums.replaceSpecialChars(companies[j].catno)} - ${Albums.replaceSpecialChars( - companies[j].entity_type - )} - ${Albums.replaceSpecialChars( - companies[j].entity_type_name - )} - ${Albums.replaceSpecialChars(companies[j].id)} - ${Albums.replaceSpecialChars( - companies[j].resource_url - )} - ${Albums.replaceSpecialChars( - companies[j].thumbnail_url - )} - - `; - } - - for (let j = 0; j < formats.length; j += 1) { - let descriptions = ""; - if (formats[j].descriptions) { - for ( - let k = 0; - k < formats[j].descriptions.length; - k += 1 - ) { - descriptions += `${formats[j].descriptions[k]} - `; - } - } - formatsList += ` - ${Albums.replaceSpecialChars(formats[j].name)} - ${Albums.replaceSpecialChars(formats[j].qty)} - ${Albums.replaceSpecialChars(formats[j].text)} - - ${descriptions} - - - `; - } - - for (let j = 0; j < identifiers.length; j += 1) { - identifiersList += ` - ${Albums.replaceSpecialChars(identifiers[j].type)} - ${Albums.replaceSpecialChars( - identifiers[j].value - )} - ${Albums.replaceSpecialChars( - identifiers[j].description - )} - - `; - } - - for (let j = 0; j < videos.length; j += 1) { - videosList += ` - `; - } - - for (let j = 0; j < genres.length; j += 1) { - genresList += `${Albums.replaceSpecialChars( - genres[j] - )} - `; - } - - for (let j = 0; j < styles.length; j += 1) { - stylesList += ` - `; - } - - for (let j = 0; j < tracklist.length; j += 1) { - tracklistList += ` - ${Albums.replaceSpecialChars(tracklist[j].title)} - - `; - } - - for (let j = 0; j < extraartists.length; j += 1) { - extraartistsList += ` - ${Albums.replaceSpecialChars(extraartists[j].name)} - ${Albums.replaceSpecialChars(extraartists[j].anv)} - ${Albums.replaceSpecialChars(extraartists[j].join)} - ${Albums.replaceSpecialChars(extraartists[j].role)} - ${Albums.replaceSpecialChars( - extraartists[j].tracks - )} - ${Albums.replaceSpecialChars(extraartists[j].id)} - ${Albums.replaceSpecialChars( - extraartists[j].resource_url - )} - ${Albums.replaceSpecialChars( - extraartists[j].thumbnail_url - )} - - `; - } - - for (let j = 0; j < images.length; j += 1) { - imagesList += ` - ${Albums.replaceSpecialChars(images[j].uri)} - ${Albums.replaceSpecialChars( - images[j].resource_url - )} - ${Albums.replaceSpecialChars( - images[j].resource_url - )} - - `; - } - - data += ` - - ${discogsId} - ${Albums.replaceSpecialChars(title)} - ${Albums.replaceSpecialChars(artists_sort)} - - ${artistsList} - - ${year} - ${Albums.replaceSpecialChars(country)} - ${released} - ${uri} - ${thumb} - - ${labelList} - - - ${serieList} - - - ${companiesList} - - - ${formatsList} - - ${Albums.replaceSpecialChars(notes)} - - ${identifiersList} - - - ${videosList} - - - ${genresList} - - - ${stylesList} - - - ${tracklistList} - - - ${extraartistsList} - - - ${imagesList} - - `; - } - - return `${data}`; - } - - /** - * Méthode permettant de convertir les rows en csv pour importer dans MusicTopus - * @param {Array} rows - * - * @return {string} - */ - static async convertToMusicTopus(rows) { - let data = "itemId;createdAt;updatedAt\n\r"; - - for (let i = 0; i < rows.length; i += 1) { - const { discogsId, createdAt, updatedAt } = rows[i]; - - data += `${discogsId};${createdAt};${updatedAt}\n\r`; - } - - data += "v1.0"; - - return data; - } - /** * Méthode permettant d'ajouter un album dans une collection * @param {Object} req @@ -482,7 +32,18 @@ class Albums extends Pages { const album = new AlbumsModel(data); - return album.save(); + await album.save(); + + const jobData = { + model: "Albums", + id: album._id, + }; + + const job = new JobsModel(jobData); + + job.save(); + + return album; } /** @@ -593,13 +154,13 @@ class Albums extends Pages { switch (exportFormat) { case "csv": - return Albums.convertToCsv(rows); + return Export.convertToCsv(rows); case "xls": - return Albums.convertToXls(rows); + return Export.convertToXls(rows); case "xml": - return Albums.convertToXml(rows); + return Export.convertToXml(rows); case "musictopus": - return Albums.convertToMusicTopus(rows); + return Export.convertToMusicTopus(rows); case "json": default: return { diff --git a/src/middleware/Export.js b/src/middleware/Export.js new file mode 100644 index 0000000..380f025 --- /dev/null +++ b/src/middleware/Export.js @@ -0,0 +1,453 @@ +import momenttz from "moment-timezone"; +import xl from "excel4node"; + +class Export { + /** + * Méthode permettant de remplacer certains cartactères par leur équivalents html + * @param {String} str + * + * @return {String} + */ + static replaceSpecialChars(str) { + if (!str) { + return ""; + } + let final = str.toString(); + const find = ["&", "<", ">"]; + const replace = ["&", "<", ">"]; + + for (let i = 0; i < find.length; i += 1) { + final = final.replace(new RegExp(find[i], "g"), replace[i]); + } + + return final; + } + + /** + * Méthode permettant de convertir les rows en csv + * @param {Array} rows + * + * @return {string} + */ + static async convertToCsv(rows) { + let data = + "Artiste;Titre;Genre;Styles;Pays;Année;Date de sortie;Format\n\r"; + + for (let i = 0; i < rows.length; i += 1) { + const { + artists_sort, + title, + genres, + styles, + country, + year, + released, + formats, + } = rows[i]; + + let format = ""; + for (let j = 0; j < formats.length; j += 1) { + format += `${format !== "" ? ", " : ""}${formats[j].name}`; + } + + data += `${artists_sort};${title};${genres.join()};${styles.join()};${country};${year};${released};${format}\n\r`; + } + + return data; + } + + /** + * Méthode permettant de convertir les rows en fichier xls + * @param {Array} rows + * + * @return {Object} + */ + static async convertToXls(rows) { + const wb = new xl.Workbook(); + const ws = wb.addWorksheet("MusicTopus"); + + const headerStyle = wb.createStyle({ + font: { + color: "#FFFFFF", + size: 11, + }, + fill: { + type: "pattern", + patternType: "solid", + bgColor: "#595959", + fgColor: "#595959", + }, + }); + const style = wb.createStyle({ + font: { + color: "#000000", + size: 11, + }, + numberFormat: "0000", + }); + + const header = [ + "Artiste", + "Titre", + "Genre", + "Styles", + "Pays", + "Année", + "Date de sortie", + "Format", + ]; + for (let i = 0; i < header.length; i += 1) { + ws.cell(1, i + 1) + .string(header[i]) + .style(headerStyle); + } + + for (let i = 0; i < rows.length; i += 1) { + const currentRow = i + 2; + const { + artists_sort, + title, + genres, + styles, + country, + year, + released, + formats, + } = rows[i]; + + let format = ""; + for (let j = 0; j < formats.length; j += 1) { + format += `${format !== "" ? ", " : ""}${formats[j].name}`; + } + + ws.cell(currentRow, 1).string(artists_sort).style(style); + ws.cell(currentRow, 2).string(title).style(style); + ws.cell(currentRow, 3).string(genres.join()).style(style); + ws.cell(currentRow, 4).string(styles.join()).style(style); + if (country) { + ws.cell(currentRow, 5).string(country).style(style); + } + if (year) { + ws.cell(currentRow, 6).number(year).style(style); + } + if (released) { + ws.cell(currentRow, 7) + .date(momenttz.tz(released, "Europe/Paris").hour(12)) + .style({ numberFormat: "dd/mm/yyyy" }); + } + ws.cell(currentRow, 8).string(format).style(style); + } + + return wb; + } + + /** + * Méthode permettant de convertir les rows en csv pour importer dans MusicTopus + * @param {Array} rows + * + * @return {string} + */ + static async convertToXml(rows) { + let data = '\n\r'; + + for (let i = 0; i < rows.length; i += 1) { + const { + discogsId, + year, + released, + uri, + artists, + artists_sort, + labels, + series, + companies, + formats, + title, + country, + notes, + identifiers, + videos, + genres, + styles, + tracklist, + extraartists, + images, + thumb, + } = rows[i]; + + let artistsList = ""; + let labelList = ""; + let serieList = ""; + let companiesList = ""; + let formatsList = ""; + let identifiersList = ""; + let videosList = ""; + let genresList = ""; + let stylesList = ""; + let tracklistList = ""; + let extraartistsList = ""; + let imagesList = ""; + + for (let j = 0; j < artists.length; j += 1) { + artistsList += ` + ${Export.replaceSpecialChars(artists[j].name)} + ${Export.replaceSpecialChars(artists[j].anv)} + ${Export.replaceSpecialChars(artists[j].join)} + ${Export.replaceSpecialChars(artists[j].role)} + ${Export.replaceSpecialChars(artists[j].tracks)} + ${Export.replaceSpecialChars(artists[j].id)} + ${Export.replaceSpecialChars( + artists[j].resource_url + )} + ${Export.replaceSpecialChars( + artists[j].thumbnail_url + )} + `; + } + + for (let j = 0; j < labels.length; j += 1) { + labelList += ` + `; + } + + for (let j = 0; j < series.length; j += 1) { + serieList += ` + ${Export.replaceSpecialChars(series[j].name)} + ${Export.replaceSpecialChars(series[j].catno)} + ${Export.replaceSpecialChars( + series[j].entity_type + )} + ${Export.replaceSpecialChars( + series[j].entity_type_name + )} + ${Export.replaceSpecialChars(series[j].id)} + ${Export.replaceSpecialChars( + series[j].resource_url + )} + ${Export.replaceSpecialChars( + series[j].thumbnail_url + )} + + `; + } + + for (let j = 0; j < companies.length; j += 1) { + companiesList += ` + ${Export.replaceSpecialChars(companies[j].name)} + ${Export.replaceSpecialChars(companies[j].catno)} + ${Export.replaceSpecialChars( + companies[j].entity_type + )} + ${Export.replaceSpecialChars( + companies[j].entity_type_name + )} + ${Export.replaceSpecialChars(companies[j].id)} + ${Export.replaceSpecialChars( + companies[j].resource_url + )} + ${Export.replaceSpecialChars( + companies[j].thumbnail_url + )} + + `; + } + + for (let j = 0; j < formats.length; j += 1) { + let descriptions = ""; + if (formats[j].descriptions) { + for ( + let k = 0; + k < formats[j].descriptions.length; + k += 1 + ) { + descriptions += `${formats[j].descriptions[k]} + `; + } + } + formatsList += ` + ${Export.replaceSpecialChars(formats[j].name)} + ${Export.replaceSpecialChars(formats[j].qty)} + ${Export.replaceSpecialChars(formats[j].text)} + + ${descriptions} + + + `; + } + + for (let j = 0; j < identifiers.length; j += 1) { + identifiersList += ` + ${Export.replaceSpecialChars(identifiers[j].type)} + ${Export.replaceSpecialChars(identifiers[j].value)} + ${Export.replaceSpecialChars( + identifiers[j].description + )} + + `; + } + + for (let j = 0; j < videos.length; j += 1) { + videosList += ` + `; + } + + for (let j = 0; j < genres.length; j += 1) { + genresList += `${Export.replaceSpecialChars( + genres[j] + )} + `; + } + + for (let j = 0; j < styles.length; j += 1) { + stylesList += ` + `; + } + + for (let j = 0; j < tracklist.length; j += 1) { + tracklistList += ` + ${Export.replaceSpecialChars(tracklist[j].title)} + + `; + } + + for (let j = 0; j < extraartists.length; j += 1) { + extraartistsList += ` + ${Export.replaceSpecialChars(extraartists[j].name)} + ${Export.replaceSpecialChars(extraartists[j].anv)} + ${Export.replaceSpecialChars(extraartists[j].join)} + ${Export.replaceSpecialChars(extraartists[j].role)} + ${Export.replaceSpecialChars( + extraartists[j].tracks + )} + ${Export.replaceSpecialChars(extraartists[j].id)} + ${Export.replaceSpecialChars( + extraartists[j].resource_url + )} + ${Export.replaceSpecialChars( + extraartists[j].thumbnail_url + )} + + `; + } + + for (let j = 0; j < images.length; j += 1) { + imagesList += ` + ${Export.replaceSpecialChars(images[j].uri)} + ${Export.replaceSpecialChars( + images[j].resource_url + )} + ${Export.replaceSpecialChars( + images[j].resource_url + )} + + `; + } + + data += ` + + ${discogsId} + ${Export.replaceSpecialChars(title)} + ${Export.replaceSpecialChars(artists_sort)} + + ${artistsList} + + ${year} + ${Export.replaceSpecialChars(country)} + ${released} + ${uri} + ${thumb} + + ${labelList} + + + ${serieList} + + + ${companiesList} + + + ${formatsList} + + ${Export.replaceSpecialChars(notes)} + + ${identifiersList} + + + ${videosList} + + + ${genresList} + + + ${stylesList} + + + ${tracklistList} + + + ${extraartistsList} + + + ${imagesList} + +`; + } + + return `${data}`; + } + + /** + * Méthode permettant de convertir les rows en csv pour importer dans MusicTopus + * @param {Array} rows + * + * @return {string} + */ + static async convertToMusicTopus(rows) { + let data = "itemId;createdAt;updatedAt\n\r"; + + for (let i = 0; i < rows.length; i += 1) { + const { discogsId, createdAt, updatedAt } = rows[i]; + + data += `${discogsId};${createdAt};${updatedAt}\n\r`; + } + + data += "v1.0"; + + return data; + } +} + +export default Export; diff --git a/src/middleware/Jobs.js b/src/middleware/Jobs.js new file mode 100644 index 0000000..ccb2cf9 --- /dev/null +++ b/src/middleware/Jobs.js @@ -0,0 +1,128 @@ +/* eslint-disable no-await-in-loop */ +import ErrorEvent from "../libs/error"; +import { uploadFromUrl } from "../libs/aws"; +import { getAlbumDetails } from "../helpers"; + +import JobsModel from "../models/jobs"; +import AlbumsModel from "../models/albums"; + +class Jobs { + /** + * Méthode permettant de télécharger toute les images d'un album + * @param {ObjectId} itemId + */ + static async importAlbumAssets(itemId) { + const album = await AlbumsModel.findById(itemId); + + if (!album) { + throw new ErrorEvent( + 404, + "Item non trouvé", + `L'album avant l'id ${itemId} n'existe plus dans la collection` + ); + } + + const item = await getAlbumDetails(album.discogsId); + + if (!item) { + throw new ErrorEvent( + 404, + "Erreur de communication", + "Erreur lors de la récupération des informations sur Discogs" + ); + } + + if (item.thumb) { + album.thumb = await uploadFromUrl(item.thumb); + album.thumbType = "local"; + } + const { images } = item; + if (images && images.length > 0) { + for (let i = 0; i < images.length; i += 1) { + images[i].uri150 = await uploadFromUrl(images[i].uri150); + images[i].uri = await uploadFromUrl(images[i].uri); + } + } + + album.images = images; + + await album.save(); + + return true; + } + + /** + * Point d'entrée + * @param {String} state + * + * @return {Object} + */ + async run(state = "NEW") { + const job = await JobsModel.findOne({ + state, + tries: { + $lte: 5, + }, + }); + + if (!job) { + return { message: "All jobs done" }; + } + + job.state = "IN-PROGRESS"; + + await job.save(); + + try { + switch (job.model) { + case "Albums": + await Jobs.importAlbumAssets(job.id); + break; + default: + throw new ErrorEvent( + 500, + "Job inconnu", + `Le job avec l'id ${job._id} n'est pas un job valide` + ); + } + + job.state = "SUCCESS"; + + await job.save(); + + return this.run(state); + } catch (err) { + job.state = "ERROR"; + job.lastTry = new Date(); + job.lastErrorMessage = err.message; + job.tries += 1; + + await job.save(); + + throw err; + } + } + + /** + * Méthode permettant de créer tous les jobs + * + * @return {Object} + */ + static async populate() { + const albums = await AlbumsModel.find(); + + for (let i = 0; i < albums.length; i += 1) { + const jobData = { + model: "Albums", + id: albums[i]._id, + }; + + const job = new JobsModel(jobData); + await job.save(); + } + + return { message: `${albums.length} jobs ajouté à la file d'attente` }; + } +} + +export default Jobs; diff --git a/src/models/albums.js b/src/models/albums.js index 0180dad..e08b7ad 100644 --- a/src/models/albums.js +++ b/src/models/albums.js @@ -29,6 +29,7 @@ const AlbumSchema = new mongoose.Schema( extraartists: Array, images: Array, thumb: String, + thumbType: String, }, { timestamps: true } ); diff --git a/src/models/jobs.js b/src/models/jobs.js new file mode 100644 index 0000000..ba3727d --- /dev/null +++ b/src/models/jobs.js @@ -0,0 +1,24 @@ +import mongoose from "mongoose"; + +const { Schema } = mongoose; + +const JobSchema = new mongoose.Schema( + { + model: String, + id: Schema.Types.ObjectId, + state: { + type: String, + enum: ["NEW", "IN-PROGRESS", "ERROR", "SUCCESS"], + default: "NEW", + }, + lastTry: Date, + lastErrorMessage: String, + tries: { + type: Number, + default: 0, + }, + }, + { timestamps: true } +); + +export default mongoose.model("Jobs", JobSchema); diff --git a/src/routes/jobs.js b/src/routes/jobs.js new file mode 100644 index 0000000..6bac899 --- /dev/null +++ b/src/routes/jobs.js @@ -0,0 +1,40 @@ +import express from "express"; +import passport from "passport"; + +import Jobs from "../middleware/Jobs"; + +// eslint-disable-next-line new-cap +const router = express.Router(); + +router.route("/").get( + passport.authenticate(["jobs"], { + session: false, + }), + async (req, res, next) => { + try { + const job = new Jobs(); + const data = await job.run(req.query.state); + + return res.status(200).json(data).end(); + } catch (err) { + return next(err); + } + } +); + +router.route("/populate").get( + passport.authenticate(["jobs"], { + session: false, + }), + async (req, res, next) => { + try { + const data = await Jobs.populate(); + + return res.status(200).json(data).end(); + } catch (err) { + return next(err); + } + } +); + +export default router; diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index 12490c0..bd97f90 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -170,7 +170,7 @@ } }, created() { - this.setTrackList(); + this.setTrackList(); this.setIdentifiers(); window.addEventListener("keydown", this.changeImage); @@ -231,7 +231,7 @@ }, showGallery(event) { const item = event.target.tagName === 'IMG' ? event.target.parentElement : event.target; - + const { index, } = item.dataset; -- 2.39.5 From fba4232d17015439552f2992c037bdc8eb8ba718 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Mon, 29 Aug 2022 07:38:24 +0200 Subject: [PATCH 02/69] =?UTF-8?q?#47=20-=20Changer=20la=20couleur=20du=20b?= =?UTF-8?q?urger=20en=20fonction=20du=20th=C3=A8me?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sass/navbar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sass/navbar.scss b/sass/navbar.scss index f9583b9..fdca308 100644 --- a/sass/navbar.scss +++ b/sass/navbar.scss @@ -54,7 +54,7 @@ position: relative; width: 3.25rem; margin-left: auto; - color: rgba(0,0,0,.7); + color: var(--font-color); @include respond-to("medium-up") { display: none; -- 2.39.5 From 080471eb3708d17475be43ec76e4acded2571717 Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Mon, 29 Aug 2022 07:40:14 +0200 Subject: [PATCH 03/69] =?UTF-8?q?#47=20-=20Changer=20la=20couleur=20du=20b?= =?UTF-8?q?urger=20en=20fonction=20du=20th=C3=A8me=20(#53)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dbroqua Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/53 --- sass/navbar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sass/navbar.scss b/sass/navbar.scss index f9583b9..fdca308 100644 --- a/sass/navbar.scss +++ b/sass/navbar.scss @@ -54,7 +54,7 @@ position: relative; width: 3.25rem; margin-left: auto; - color: rgba(0,0,0,.7); + color: var(--font-color); @include respond-to("medium-up") { display: none; -- 2.39.5 From 1377b4c0c15a7a6f2946531e9eda2b212ec78aa3 Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Mon, 29 Aug 2022 08:13:06 +0200 Subject: [PATCH 04/69] #51 - Avoir une animation au chargement de la liste (#54) Co-authored-by: dbroqua Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/54 --- public/img/loading-dark.gif | Bin 0 -> 60970 bytes public/img/loading-light.gif | Bin 0 -> 60932 bytes sass/colors.scss | 4 ++++ sass/index.scss | 1 + sass/loader.scss | 13 +++++++++++++ views/pages/mon-compte/ma-collection/index.ejs | 10 ++++++++-- 6 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 public/img/loading-dark.gif create mode 100644 public/img/loading-light.gif create mode 100644 sass/loader.scss diff --git a/public/img/loading-dark.gif b/public/img/loading-dark.gif new file mode 100644 index 0000000000000000000000000000000000000000..54c888bbe42332d61ee4d8acf5a12cfb5b09af1e GIT binary patch literal 60970 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 literal 0 HcmV?d00001 diff --git a/public/img/loading-light.gif b/public/img/loading-light.gif new file mode 100644 index 0000000000000000000000000000000000000000..66472af7bbb22ba369116e9151d5b4ac67e0fce5 GIT binary patch literal 60932 zcmeHwd012T7VSBi2uXkd85sjID-dLq$rA_=ks-(+AYecQq-vuwI-USwQZZCiw1Qz! z(4qxJMMawc3M#E2prD|DC^!}roNZs!UfX+b`@KGG@BO?l{Dpjc=bS9gUjFvpYp?A! z$J1d}Gz37vGXU@jNeqr!5wdXQ!i3elC94xxW+bl6OkTS&W!DP0?W)A|{Y;6mZ1;Tp( z1D}>b7~p~whY%)iaum!XqHNyKqbsuZLsY1d2i*6^X?z=_UCY?-j-OYjlP0cf>PD$q zu-UIdjJ@{FKI&bGw*h(!RCnP9zJ3}Heywdq@?+Ld?`YOV{!C!ZFJIK0>tX?1m}AdcA6@ zsR`|aqHLF#7=1nZ)dL|^-;Ii&4I-9BFg z`o7s&J)A$|*x2w!<~fsFn{2Km+}iALr}|cbvp0$O?vH@_?H?gW&QzaOV96i$FGk># zNT{p{y%S^Q!v)0g{z!^`3=F}La0?mxJOVceuoV$&9UqzqOuJCZR!p8_@}l5xkT?L# zFVe#xeUO~N(4hlaUlT9Qj093E=Iy~7U6`81NNB~*z3Sb6{Rn{BLMwmT;z&E_->eHh zBXFtums)dXTa5SdhC|x*w2@-Q#21&qoh*xk{j~=#0qfl@Hm9*xTso|eeBQDw#0HSo zhgteaUg%})O`rP=^+QL}RpFjBDaj1Lt+~ZjH>N{Lyz0qbk#$OOoP_@*HkYjC%;&6K zx%7$4d1D(To(tz{tzy*RPk{~3_aZm3rN}y^Sa1V?9fTsuTE|ICW@smD15&MSN6w{c z@p`h`PmsV2A0?p7hsI_Cf#{^%t&r{BWB*Px0bm&U%f^-V)P3PF{?D)~g z>k?QX@(OOVXA4Y&zt2roanRz@^<$gVOeeJlMNKeRr5(3@M%}7mQP~Tg8glg>p$89rI+B%oIj0y40)<#E*zT-8~E zFlj5|*WdL++o{J?6KxE-_91txRVQ|20071<++j8B7K~Re@OBv6HX6Q#MxZ52jsgCM zkk=pAjMoV|k7Koq9=_Z*iVz7wgJRm%v7s%bG>qgG_s~#o5!s?)Mxme+Nz}s<0cqJl ziTAXqfiT7qw^EdrQSU4$23-M`yV~3%W@e%(`u7ITQ-{$v^fXUxqwP#ydC^JNYf{st zGQL!77{!Trc`NzKA$sb){#kveI>8~TC-Yj#E%X?GKzW6NW6~di#MJ(RH9tx3Ae^~9 z=vmsqT3%nzxpR}H_}Bmn@CDDQqMVN&BMSZYqt!5qC}_UBGD;M^Ug6xxf)Gzb25U95 zv+`^1zb0qDuu&}ZNhz9~ivGrkkYD=0guo|IQ46>1Efo=f-V<9P6v5t_%a9xy^8;05 zy_px1j5F|-bQ^6VydS`uo6=l(frCO!aC($&!Vriqq~$&?;*Yxt%2Bv*V(pRpUJ4$4 zMiE@nHV0)1jBzZk&R{x82_VOG7Q*Qo%hQHZIc??B68Em_fARxHJ(D-AXMTGW0aLcDe$6wEm(!`}8WP6+d~)x-#rVQ`UrsQfeXU z`rYHil_&|7?uA3t!fP>iuLP~sQsVDiLYU*Q4W}o-RHq%fod>yNkj}ae5i_9st!F_S=*}#O-aw=NRaYLjUfPUV>g89<*cK07 zB%F9@5LT7|)T+#WqLt2q0`RKINa9Xu>ZKOdFHR}HXh>L)SDo(qthX^359-{WKDn#=)uX1$FNt2(u~8vqhxS;4z)p(ytY6Is2vv@jl+uW*Ib{cjcP_lKfc z-1mo~PpcNm+H1e%A$hgvFAfd)<^Hn>d^{By)$>_KZg?zKmY)Gq)Yk|r#^9W!DiA-@ zloWY#tMn`$0dQ(*N5_DDn3+LZCs&!BMLDZ*X=&h$0Hm#P)a|>$!$J_erdD5@QU%wm zniI+J(4uTa{88_Lru2<^lkvTAiO@+ylh1r!;#3(;C3*zO!gw#jg=!S;mVJd=IDHqR0Q z{*jA)dXf6K%p)&S|7Qb5ez(68fsdyheOZy(4YN~W`uifag1vd12#Q{mRmw@n!Mqj5 z0E#x#g5%u-b?0%=9Ek(C1UvwsB8s1r@1hQ=oWs?N82dx>=*qFd*X!Ki6Y5)@8px{E z*@wOFx^-WC0=9j}QJNI?hAiQkrf zI6%p5CgoXL3Z7WNu7F;M4rxpJ-xCJi?zb;k#7^&I020l`Qw zx+9j$gRUIZo<1WDXIC!7DwfWi;zXNu9#gxjW(YiX6I7;6bK~I3e!O`qTJPLLQ-+n- zcXQ}I^*kyR-8@IrXQXeHpba0FS1xoIdeny72DYExRTR6V9FgeS*s&`XB`F}!V>klC zNVKDN2=>tNs*IeoD$ZIs@e21l2`bN$I>s}APL8!1v_5d@T#?fFre}z&S$>z>4^{~+iz}CcpYknve*OH6i_uAd1K9G6Io~ zWIwb94-Pq`W9&fYSx0B1kUCjsnRJtNE_q=bo*T761Mb~!8z)ZFjY+syo8gaE+l+XKFHCX2 zP^Wuf*=w#n3@Ga%iROh9{;0D8Bfi7ys*#)`Fm1WR^Z0S$w@24o*DzQ!@9w*<8IHYA zwCU)=Sev9pjk~1*H|^t?cv6!T?+W(pf}YNyUPdnr<84DLJJ?D#e0#f9H6{!_76c&G zlB7trl%SFjh|JfXOBt^U22|Q?xRJ`9%<_C{$7Ff_Z$#4{R6=aZl1guRX7<^}jr_s? zE(AV-ks4);Bx{N>-ZjM#IGJuy%y_F?M9tU~3jiQSNKerz!SUSM@Gj_Hwn5tbsMNVuhU3 z{rNGQsp<}w#?WK<3~*&L!jtZ^v}2XPeZK-D4%i$fOUN)<8k^IGR{A%nShp`vuo-v9 zFMhKAPMAdR3m|Kn#+?(iVIdmNw=C-CmB#{g?w){ifQ5f2!#brGBjyd?>$tW#E<1tl z!l}Qh62lWi&n4ILSqS@=*5h|d*Xd;~^zYg1K?XgvZ-31~BDi8fkoGo-8x5vNGIYX# zn?WGsiU>_xUnE5p(1Rt&QpLTX@6-LiGsf|bk<>oqXdhQ2`jDgj_FyFMiTzz6BERl` z1c6UrqI#ZQW_u=0pZHcmouOd&r6=)SdtN;zhRLfp^~V@k2T9;@L_h_IaS&?ELg z&L1}ZVSQ*(>do&qyI;98hgE*3w9w-Wrh_+}1az`a-ZqsqEV&dpk@`4@Y1RC=YT2?K zvsD0ALP2<#e+#3u3jC4&h7!yiTKYpJ9-QBe)OCp`fld=a18%fhuSkL4_dQ(9Hxo$! zhte_{giv8IZe-{#W=NN~D2c9RDHnxYM%xUfcKtzne*ZjqpX@*KcFS+_86ogVR0PVX zh;H(ZiV!lTkoP+(0x2`N&0L!f>;27ykTvB-VcoUv1Tg;^<@~JSo27^w z+3iZuik3@&K~771;4$_+@wzoyN)R-f(++N+b^KuE&M(Co4O=+s7^DKHrbRjI(H?=z zzcu{9(oB-xddtNWlBNVCaByZY(MzJS< z)ZaxwMoAx8!7iwQ-pZ)eh%y;7LO^# z(130G*$14pxit*&7~&hM35gL74GRxDoBJ4Cr0i!YL>pQ#Uh;;8WzP5r4rd6;loHK} zzCZvX3m=+i0la`mffl6+1XHwaG*+aLW(Q-BR}YHVrycvh(0JlizEU?FE4ecDMd>m6 zJcgMhvF)hSLpRi^Kxlb^qO*VO=`P8N&D(DIp>DjMbq-&@=H=cqeJ?$}cY2lJT_ZaE z((3_xZ-J!>1Sb}p%F};NFI*ddZnF2>q5G=rO_ie}L&vk_Hn|YpPd1czlK5x4VAWX? zod9hQQU8+pwu6IyU@kg{wIo1vGw0tFL+?6hX@UPxEB?mn;QwKo>|gvqWM&}pwCs0Z zXZa65BLrmJ^O05``LYp?CfQ6qllRu)Lsqp`<#r@UWyK-N47YbxYow#OH}B;W@GRc5 zB+-{=l@a)jnmIQ)T(TrHpR(Jc*yPHR3>$)i2DRFpOB zxvK6+gkz?iL|ljH5F<=niNtzS7)PJxfaXHOXk_5mZl|%)_h3a;b7skEXcXK;-c8A6 z1k?ng7TCtnzS+8L!ASrCKO7UAUOMHACq8trH_6DO)ndr6>%Qo~tCR1?zE)*%G3r#5 z&g+1c{Wx^chY5>gi>{E_e0 z)$>5XArjU_qR#^+P(VWb6iH{E*DQ~l?qom#^)M>hBnGZUQ zr~dC6>GztU3XczcG3uXC4E+oHd?-cBL`t$l3?CUZMJ%b)vxwu|6Br6`(WzvaSgKHreVMBk;w}|pJ_D8A6tfi zjFLXVVhDlRiLvIsGdn{N_2c-GK?DbnT5I-@K2(MRU6j`DuC2e9nN3eKo8lb32ZOHW z%oZN+uAqRj-Kdx!h|E^>q(;F9CKFpLU8z3p(D8V|F|BDPCAG(EE+Og8kEeEuYv-NT zML#0|wDOSe^r+95G7ANl5qT^P43`x%|GP4l*};CM%LhsDZbR3ExR%})UH{C8YO$zucF!s1<69>ltAq6#QBOBi6M5$U} z8wbUs-8*B}7(EZIAI-?5YVd?#)I%Kw#7&;XX?hlyPU%?DN~f!42A^UepAf_$`iP3$ z*%o}KLe|2AQfA?cO_-d1exX8y+XhuIDBRf(2v^k}ba@ z11?d9VjNCcL&C#>*Img8+v7oza_$+A+-UGxMsm70aGnBP6H?A7xA5RGgeXAfiukJ5 z>QbxnG%Q1$R(7+bc-lOsA$C5l>i2GUtEu>jg+dW|Gd;;x{8@v8jZ8u}ua?S;8< z;@OaJP>-(Oqg0qNFtVNHPnxb>=~uenFv^4gjwGBqthw53O#3JO?E6lO{5L`MYrOEC zhN(@hqw|)9|c4t5q-rkD+-tLST>-pC141u@Qv@w_2oxwPn-I*^hP%K2t?9L+7 z1tyoU3e!m@jww7e0#$3*2rB2!o1U&SuJ6_e_tea>3gd$nGP^TFzFAsL*sauSx3%45c4yOs z4}qp{EgZ#*&{qb#EbAH3YXgR9!7vc`*mP1m!guHPPSA&ZhZyDiuyQbC5^NJ8Rh4NBu4wc={{8+E<$N=7H56C)!d z#_H`ceWmJ}0JfGqBt#~i^{#|SO)WTo>Wzo#LkI+zt!*@D%9v<93+U4Z^HR#t2b`v5 z?!H)b(EYR%HTU(YdZ0zoSw)ofy*13sS)N`{Gnl-rrQd1(vm1R>{h^XWtf=1hz@zML zVE38`KbsdnHtFtvGJ{7S9tH+%zf8PI7e8sPE_3mtow?fRj$tb#Rf@MJyvU^*Fiafv zrXj&s=E*nRDU#zsD=1}GuPyt^%J7;8?#dkryK=$?UP2E8iiJv9sZmR@%h~E^9q%N; z8ffpY?n1vg5lt)jzb0jWa(~eKq)aXu`t?1QfA`rU@Civ-i>yNAlSNj0Yd|d#D9XGR z9b~SrKOo6WzXVaYJ_huxKqT<$5dMl3OSU${7R2P^@?C2=yeh#yx*3BRKKKp$8xOsp z7KU`6R52nQ*T4*L3jkA`VoWX`dDx=pp#p0~bl0*KJYKACsog$`$WI5E*z*nJy}86R z4Pu_&6F)I==&9BcJ*e}0)s2U^ozX$HdlY*I6HGOBB($$J{U)>uS zaZ+s9+yKYJqaMiK^|W18$SvwAUm!bNt=u}nF2it6E!-s8?Rnm-1?qnJG`9mBIc_Fjw};{2Gb3KX2u4(u@Q z%}Y&p1`f2qnqEB6M6rVnpM#qQ=Plu14454O}6?2`yM}h zM7K6@Dj-WR|0h*pC;DC(n}tAXB}19^sx}e7uUI^1QsMQ>x#trWlD*%XC@)6K!}fm$ zfsa4;7|FIGBG2I{Yz8=HWf@0 zq86Y>)?&*fR}N`Uhl9=RhS7=&;(5a}x0x(a{$k2P`&mo1vs58cFE(l`-2sAR@vEhY>mzo zqcNdq_~UttzdNnUWs0QSf!I4LadJLiD>bD)I)P^u*pm@>3 zN`g^TwPq1Nx>Ct#z0zAEs&l*OR>@B@h^$#gK{7%>l|GGd^YME5L3b;H-|#vBuX z92x<&4yKxB%433_3EJ!2ONsLanaDwUz`w;@1 z!Il{sxj&#wq8D|kX)el6gGZ59T}=Ibl%Pp>{5kE^IPL+Se&XI6vt7C26g?EEOt}20 zk74la`QzkoEFAV2k8h#Q5JKpm!v-burp|lyZ_w2{-t8BH=I(^*BaOexoIh10$wIB@ zEkrMhBtvQT=l2EyijB!z*HV~&q+GtQ9P|5d=aVec;7(s&tX7 z(jXwT595N%-G09}cB?@1Lv{M&I7ptG{r26JfB(OMz$b7}4fn0R(0iw|s<+LtU!2ZJ zF;UQ4dm#l&bE2)x^ED7N&58-}q~h*!P$PntFa!rE>g$e^I+7XL`lL-e#T~sn!tL3C2DNej%Y-i z%J)xO%v6~$ax=Z@Ain_$=ukBsTpntZ;*PIab6F>>zP=gfF_mBcy|ro7sWUE#@14#t z<*SmEAb29DYR&pMg*-h60IhDt96e8eYcKQ#Rr}{cOaCjXJ=do$s-_2^)!T*ARP5dP zB;>W|`r=5;cr48wGw<9UPrO}lm^&>(L6WH*%v%^pWbt%RBXfvR{zyVJ3NCJ%To#wNq%GC{&2lglHhnAz;D4U?}=`9NE+h&yq z;0Fsz6)g-ZPptx4@X@Ij{D$rD;L&{gWhF?qmIGL=9$vjbFgBdMQ`2IwZ*VA~lu(P{ zFb6NG7Ho7!m*Hz`SQ{-s?25x*x%MytEYCu6ympa0p4@7lqK+!EonOy z;vG)CoGPHErFv~QIrS*DpfA;pKmE-87n_l}GkN~~sa}WJ>z4b6mc8*qpI=rV5qaOG zdto51cSk48Mz@L31FBP{9;BA}K?{hp25O_;0uz$1~ zF^}~R??w6H=+j?e`4#>*5cq_{Q9YLx*QAIA#!wIhqBm1##Mbb5RRJYfi-;oe5vd2p zs{%n}l<9Ys&`e_5=*hBuhu(%!J#@x5Y`n*2Bchqe4XlXPiz4zSN5f7!DRkRl^#Gts z?Zmwrna5Cg3A$aUMI!66Rj3$ggnU!HD6>)3l)-1vBpD)w6gN15l{hT&eCDWi>WR(0 z5>>0`19Y(!r(&CC2-8gRwDq`S@a$M~Mrvz^g< zWvhax*Rnk&k&!Pw2vMn?#R-DfL8r8@+dmuNo(Vu7FmE}^(KCy_{OF-UaoSw%WC@$f zr}hZwk?`(#vI}w0kB}H71r>|05;01Z#{&_s`V5E|a*@eDuSWb;yy)}HjB@&7jh zPyQ+1BOoK7kJJw3xAUYjw=)tgsG(RYlgB^;GqG3|i4Y^0L0Jq&b)cBQ^i0s`ZekNK zrHM-X6|xa_?`cuum-TQLiqh)nLZp;mI}(Bp53OampC!!B^elc(Nq|eVbsYw#*IB}d z6sI`GDPBx=4NG;yq=wB3b{k~4!I@ZJ$Hw^CZfM6dEZ7&;n{P-uaB>J;1uoA|GFu@C z1P{AA+CF491tJbQt;BfKVlqKLCDhGch6$M8EHG2Zlio$P#%225d_dx}3E)@DEL*WR z?xM~NW#Y#DIx?~*l-4d_JM#j|Ah5W%DPSe;y(08J|FUinLL<3 zUj$?XWdD)YLoFh(A{$R-uC0{0EnZS2**Y)=yNok-X;>1?abgJ@izud`cLihw?NQK= zWAc*|&nZSy$JXZR-RnUjDX}$-DG5qR^`f^gzwFk$J&A@F%r(RrtyrJ^=WOpE0s0Fx zu%nx+1z!X->w>Qcuub``rW_XwlWRPEX4@DjGOJcJ3~^^1WVvLycm4=g7(>T=G$DsmJ=DEhVyJgPer0~E(1 zPN}FePj4JzW#v$fs^d7}aPtok9&2+^te3qP5ng5TJW5OMrT!6jJVk>Z#ZIgIye^mF)lPEE#Xf>OVP7_MvMpH$D5S zqeFhZ|0n{VN=7>|RzAP@o>@SRTvjX^;kxxHi6_;|D#bwFTyL@jnN1@aEW5VCtOo>- zS=93&)2o}wMMffqO|wuzL4OljWCNqNaMoGKN-~EoA>!>lIjiUEXE(a2_dm#$TEZxG ztD+wKO8zBa*|fwdMfwP0kCM?$PB?F(Rc6x4nY?zSzO8bn_P!}PW4yIe)zSgOwTVF? zm3=G;*N>#SpU*N32Ocs7{zG{CXZ_PBR>kB{aRlx|{^ZTJ^Rrj4{?fcLBSVp(+i@tu zejL3ACsyA*@Z_FX>rs68PfKK;jIGGFs>0+Knz@G^sTAxo1U~8B1tAa6z0v# z6<$@tpo2VuqnR%JvUx}vwF|9E5L0p;l(Ja9M#p}89x8DDVDRDoVG+ltYZ>K39{BiZL*V1fL;7zQCi-NN-j0^3gINT7+tBw0AEa16=7oCWjw~vPSW8@K+698ecCgu? z2?y_~5+q@ayJeQE#HHL`(p4cSU!WA@0EmIGOzM?Ko32l=Iu&u(FZAGfQZK@4b)WX} z?NvitLO@iSM%}~@t-93l5xi{02SU}Ve;Fq!9-UWsf8O#m3J@>!m~W4|Xe~*|S*?8; zEvCiXC=Zfpi(c}ZP^7R31OU>9f-!})2D%&K0u)lCMOy=;$u`ik$7B*1h$xC9@pNAVUtNbG zcaQ>!89}UUysxN7W;a9&#Hc(t7l>#@2NQis2mWATqSY@dqCesC@$WSvx#`*877FqU z|2Gkk5z$BX?DZQVK@)nXf}YmfeTg8J!jBO_?@SmOZ4rE#iU?^RIdhq#MCLSPU6;R! zPZER>)M;8|Vm%KH0<^3~czZm6QiAG+2hj zj`x&x7G|w!5+)2i3rDUs^b@V?Pl(KmNq2pg2Bu%?QZw&g2OPG6`_$FbCkfDkMadO| zhHlIYrz_C+ofV2w$JW7K3SQ}!HMKxBgjdUr@8)YRGx|CjJKzDtdMTa@nE!-c`E?-R z(AF6N*0*+`(Oj$qK&1o;5K8PZDsojMRglN*WP=%Qzwdh%EBZ@vH2JvMe=~IC_x`I9 z_=Fto?YNmw)-NWED1yCp5K-p$;RmX|vpuUqh%NNq*`5J-^XYGG&jy8P&u^nD70LPm zrE1FICfuM05Ft`CoRKK5=`UZR8UtJm1iDWE1P7V7E664+1!wTuQscu3s5G|(lc|Ij zypoaqh~}Uh2I+CjtOcGwl8GeEWSZ<6jJ}05G$fhxQp>KCWO&Upx%PtJSqP`+FHajv z8j@zBoYH! zyMgBDqy>N?XE2OS3OXB@ryhmckxSl9>)!PHlE$6CsECjsZ-FK!qrW;FGR*B*5XEk`W%+YKc(wH$EL z)~e(J2e0K)YafwnZS~O#sY+4R7q>zSb5(LJu<0pPA%s{xBSxaYsuLB74oWHBF`6mL z+-U^&!lPhe*+x6KZTn~&ULhCL`=A3Mb6-RemHNklNavV^=}rMl)e06KlK!% zW1)@Yb(Wj?RG$am3JrnvF0p&&)5*OH)76xqZs2)z5^IUNUB`gCI&YhVuiZ(abANwb zV#2gvT|hp1d63-q?7ti<^4tH_2*}vyW0wcjFyEer)ZV$DK?nuP@~B!KT-8~E(2s}! zU;w>IslXd8VvM!moY7DNZ74yoA5rRE$xr&m0|StJXbzyXvsRs~MZRw322}73-5O4H z!94|(GjV(Ig)=T;7)Z@2CsnsAqWp9Xu>)Myx1})EBBA{W%)zfsHQVeUFcuV&-0;N- zq@zG7(jYuR>BZ5-d3#gO`9%~}Wi*teYnsb;4=n?B%u>_-iCX?c3CMhw?f1OwRx{;m zl}&st2)?JR8^x^R3?*kAMHoAezF4OpSTU%3A=3Hi>6U8Gt#gx{k{iCyd!AmkXX&;n zDiA{s=FbNwd}Y#UFC___a<=TMs;@fNq&r(H<@XjQru{P3iUdAIHT_<+DDRQ|)o;H1 zdjA~+J|X{lYl1Ih9F)v&5tjK5kt}87tvolc66bUr46R_zLy+w-9Pb_pI!~nZ(Ff)o{&Fi0xTfmfU$WfLlk*a&yjh8P?iPy= zwQ6m4R`O{S*&H1D4xsrYowOEk2q~#6H;3vy=HSb~_H~?{qZ|TeGHP+je1NIzUvyv%S8q^1J?R2z-35_15!j5gKRf zJ`(^S2!W*CLr51Y$p;-LalHWZrc^$9R*uMrt`?9}0?UxG?`wvx1K<)~LM;4#`bf<9f%Tc)uWz z@)W^sEWVqO=agoY_eRLxgmmkv5V=Z;DMa<jU}hNh0OVa+q4VeI z)s}jb#&d#!XXi<*^*%~OX|7cmB>iG$Pr|Lu4*$G>gH2g7Qt0u)0bkyv|J&Yh`Gr3- z1Z0%-u`?VkZ)Z5ZA`+oCqAKG342R79OejVeINQi}M{^MrS3mv*@mLem?}*=cS{WRO zq|xlKjyxy?Sl|>#yWR~4XVHz=*l)n*ER1x#sHP9bPrI>NN zw^gA;R1vN=uNKh*AP+>n35BL#%>-9F=I$Dj*)q=Axw?gWY&0Z`qCA7QfDg_?=)Jk7 zWTuLkhZPiRFtk)H{J~8wxI1~gVPU2sv1LM|!cuJ^SI#QZc&foXTa-d%(z#rL*f6OOg3Q!3zMr6+1|v zm*LcIfpgw=UT~1McmSf#sFescgP9A1ku?-`%$J7AFjA|D-4$kk^bgcSmLEKbKDtCL zOWNe6qCfF=%YWswK|n@CA6cT#uV+RhWa^>m4h%pXe+i|^$0ac&Rh^|Mqn3K!V0Bk1 z(Q_UH8oa~8d6X!py@U+>0Yr^j{dkWtaxXQ3;$U`ckq~fgrd0Bhpz8vqO=`8k%rE;- zeE~8CHxe@fFvRKNWz~{uut{d8FOt-*@CQ|lC)Ymcczh?1vdME5q&rMX+Wm`?Qb;{uz(u{0`SV9u-0(=1$>U`k^#izD z|Hy-=^4F%>r<)Y%uCNQ8ILL~z{dug#=_62}sZ*cL*pY%!i`yz-pnaUL944SMw(ftXj#z3U;JBwcjXl zXzg+#`%Iemz4jcJT=h~Id~*xveY_F(wAc9!Y64%Upb1lhud0J6EWF^$Cle#^8noly ze)oQ|?SLn_f0QJ^2Jp22;#%3bSuy$cHTP*Zs`@yTj+SWdA2?PU{9^$26ZAv!X4;>4 zz2(31IUyh;qmL9<%M8)pb@1P=aSi;{5Dkau8#)&P=w* z3`L%fH8}|%5GpFj|k0K7|g8_$B^oS+nqDR6yXO##JjVyt4DFBi)&aD#Q zsn-ypl3GNTl!%u#y>^z}D8XnD_Y&q!{(w=jrJ6HlKJ+`If@hj{yqMN`RK*;{xF$Zf zth7N_W{3t`$UKJ%J%h|8sjVpIfVuS-pR8!R_Uu6BP( zsNULCQF!{Wu(#CVL4kU{zGyHqdR;lrq*XP=S#ccNfPGV?i+B>rX81pw6)Q+@(KAs+ zPfVbsY~_a31zxHRcCZi^*#TuQ7aE8?s9Z>p0{yso}R3^PbOjkNg#~CYnU^*yslw1%6Y4q2hcs7Rf(VB$o00}597&n6LZKaGvfe& z30enPA$kbB{@JhMXeWT5ylx3F@ zxwGszj&S*vN=5xun=Wp(VmAiq<@BH5%6FF**`@}WSqW~O(A#z(DuH#kr!Z#j+_j0x zPd$GC%Dp4R-SCf_8q;;=hl-!C+(<_l1dEQP&%4FYHJQ?y$e6r;>D1yocTA(pQ?J8L zrT+PhM(I@7KsXG6i&mCm+Ba0)D>lBiQT2fW;6V#jp!h4s1~D=yE(}I+e;-w_2Di9F zl7`)v+6B==dEipbY?I;NZSH^AX7=gC^PyBEw;}pFqe6b|za4>(Cmxve&mLW!%xe;Y;Gyr5BSeGD?FB)owmNJQpQi|4rz>m_Gveep{-=N?;< zaa6;C+a?K|(ScRJj!=T6w=(yoNw1-~@bXqd3e~3_K3X$W5SSTL+)GP{7Aa4CBY2@z7*QOk&|RK#qVMf&T^q{|7ck^JM@4 literal 0 HcmV?d00001 diff --git a/sass/colors.scss b/sass/colors.scss index 441062c..83b5325 100644 --- a/sass/colors.scss +++ b/sass/colors.scss @@ -56,6 +56,8 @@ $pagination-hover-color: rgb(115, 151, 186); --button-link-text-color: #2C364A; + --loader-img: url('/img/loading-light.gif'); + --nord0: #{$nord0}; --nord1: #{$nord1}; --nord2: #{$nord2}; @@ -94,4 +96,6 @@ $pagination-hover-color: rgb(115, 151, 186); --border-color: #{$nord1}; --button-link-text-color: #{$white}; + + --loader-img: url('/img/loading-dark.gif'); } \ No newline at end of file diff --git a/sass/index.scss b/sass/index.scss index 373b302..1979bb1 100644 --- a/sass/index.scss +++ b/sass/index.scss @@ -39,6 +39,7 @@ @import './icons'; @import './list'; @import './box'; +@import './loader'; @import './error'; @import './500'; diff --git a/sass/loader.scss b/sass/loader.scss new file mode 100644 index 0000000..a2ecace --- /dev/null +++ b/sass/loader.scss @@ -0,0 +1,13 @@ +.loader { + display: flex; + flex-direction: column; + align-items: center; + + .animation { + background-image: var(--loader-img); + background-repeat: no-repeat; + background-position: center center; + width: 64px; + height: 64px; + } +} \ No newline at end of file diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index f8c5f2e..54f129a 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -89,6 +89,12 @@
+
+
+
+ Chargement des données en cours… +
+
{{ item.artists_sort}} - {{ item.title }} @@ -226,6 +232,7 @@ methods: { fetch() { this.loading = true; + this.total = 0; let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; if ( this.artist ) { @@ -247,9 +254,8 @@ axios.get(url) .then( response => { this.items = response.data.rows; - this.total = response.data.count; + this.total = response.data.count || 0; this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); - }) .catch((err) => { showToastr(err.response?.data?.message || "Impossible de charger votre collection"); -- 2.39.5 From 827dcb9ccc0a409fd834d0a037f92f5af9727923 Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Mon, 29 Aug 2022 08:36:43 +0200 Subject: [PATCH 05/69] =?UTF-8?q?#50=20-=20Sur=20ma=20collection=20les=20f?= =?UTF-8?q?iltres=20g=C3=A9n=C3=A8rent=20une=20nouvelle=20url=20(#55)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: dbroqua Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/55 --- views/pages/collection.ejs | 78 ++++++++++++++----- .../pages/mon-compte/ma-collection/index.ejs | 69 ++++++++++++---- 2 files changed, 112 insertions(+), 35 deletions(-) diff --git a/views/pages/collection.ejs b/views/pages/collection.ejs index 8ba62cd..d7f4114 100644 --- a/views/pages/collection.ejs +++ b/views/pages/collection.ejs @@ -85,6 +85,12 @@
+
+
+
+ Chargement des données en cours… +
+
{{ item.artists_sort}} - {{ item.title }} @@ -172,8 +178,56 @@ methods: { fetch() { this.loading = true; + this.total = 0; + + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const entries = urlParams.entries(); + + for(const entry of entries) { + console.log(`${entry[0]}: ${entry[1]}`); + switch(entry[0]) { + case 'artists_sort': + this.artist = entry[1]; + break; + default: + this[entry[0]] = entry[1]; + } + } let url = `/api/v1/albums?userId=${this.userId}&page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if ( this.artist ) { + url += `&artists_sort=${this.artist}`; + } + if ( this.format ) { + url += `&format=${this.format}`; + } + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.genre ) { + url += `&genre=${this.genre}`; + } + if ( this.style ) { + url += `&style=${this.style}`; + } + + axios.get(url) + .then( response => { + this.items = response.data.rows; + this.total = response.data.count || 0; + this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); + + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de charger cette collection"); + }) + .finally(() => { + this.loading = false; + }); + }, + changeUrl() { + let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; if ( this.artist ) { url += `&artists_sort=${this.artist.replace('&', '%26')}`; } @@ -190,38 +244,26 @@ url += `&style=${this.style.replace('&', '%26')}`; } - axios.get(url) - .then( response => { - this.items = response.data.rows; - this.total = response.data.count; - this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); - - }) - .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de charger cette collection"); - }) - .finally(() => { - this.loading = false; - }); + location.href = url; }, next(event) { event.preventDefault(); this.page += 1; - this.fetch(); + this.changeUrl(); }, previous(event) { event.preventDefault(); this.page -= 1; - this.fetch(); + this.changeUrl(); }, goTo(page) { this.page = page; - this.fetch(); + this.changeUrl(); }, changeSort() { const [sort,order] = this.sortOrder.split('-'); @@ -229,12 +271,12 @@ this.order = order; this.page = 1; - this.fetch(); + this.changeUrl(); }, changeFilter() { this.page = 1; - this.fetch(); + this.changeUrl(); }, showMoreFilters() { this.moreFilters = !this.moreFilters; diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index 54f129a..b54d728 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -234,7 +234,53 @@ this.loading = true; this.total = 0; + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const entries = urlParams.entries(); + + for(const entry of entries) { + console.log(`${entry[0]}: ${entry[1]}`); + switch(entry[0]) { + case 'artists_sort': + this.artist = entry[1]; + break; + default: + this[entry[0]] = entry[1]; + } + } + let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if ( this.artist ) { + url += `&artists_sort=${this.artist}`; + } + if ( this.format ) { + url += `&format=${this.format}`; + } + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.genre ) { + url += `&genre=${this.genre}`; + } + if ( this.style ) { + url += `&style=${this.style}`; + } + + axios.get(url) + .then( response => { + this.items = response.data.rows; + this.total = response.data.count || 0; + this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de charger votre collection"); + }) + .finally(() => { + this.loading = false; + }); + }, + changeUrl() { + let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; if ( this.artist ) { url += `&artists_sort=${this.artist.replace('&', '%26')}`; } @@ -251,37 +297,26 @@ url += `&style=${this.style.replace('&', '%26')}`; } - axios.get(url) - .then( response => { - this.items = response.data.rows; - this.total = response.data.count || 0; - this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); - }) - .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de charger votre collection"); - }) - .finally(() => { - this.loading = false; - }); + location.href = url; }, next(event) { event.preventDefault(); this.page += 1; - this.fetch(); + this.changeUrl(); }, previous(event) { event.preventDefault(); this.page -= 1; - this.fetch(); + this.changeUrl(); }, goTo(page) { this.page = page; - this.fetch(); + this.changeUrl(); }, changeSort() { const [sort,order] = this.sortOrder.split('-'); @@ -289,12 +324,12 @@ this.order = order; this.page = 1; - this.fetch(); + this.changeUrl(); }, changeFilter() { this.page = 1; - this.fetch(); + this.changeUrl(); }, showMoreFilters() { this.moreFilters = !this.moreFilters; -- 2.39.5 From d473899b203ed67ac9536ca1c199fedf5f87af4f Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Mon, 29 Aug 2022 14:00:35 +0200 Subject: [PATCH 06/69] Update 'README.md' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03e3247..dfc5bb9 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ C'est terminé ! Le site est accessible sur [http://localhost:3001](http://localhost:3001). -:information_source: Information : Vous pouvez, et vous dreviez, également regarder du côté de `systemd`, `pm2` ou encore `supervisor` pour que le service démarre en même temps que votre serveur. +:information_source: Information : Vous pouvez, et vous devriez, également regarder du côté de `systemd`, `pm2` ou encore `supervisor` pour que le service démarre en même temps que votre serveur. ### Aller plus loin -- 2.39.5 From 63207647438075bcba13679ed51933946e78cc86 Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Mon, 29 Aug 2022 23:22:28 +0200 Subject: [PATCH 07/69] #56 (#57) Co-authored-by: dbroqua Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/57 --- src/helpers/index.js | 18 ++++- src/routes/api/v1/search.js | 7 +- views/pages/ajouter-un-album.ejs | 113 +++++++++++++++++++++++++++++-- 3 files changed, 127 insertions(+), 11 deletions(-) diff --git a/src/helpers/index.js b/src/helpers/index.js index 128f594..3a81cc7 100644 --- a/src/helpers/index.js +++ b/src/helpers/index.js @@ -5,13 +5,25 @@ import { discogsToken } from "../config"; export const getBaseUrl = (req) => `${req.protocol}://${req.get("host")}`; -export const searchSong = async (q) => { +export const searchSong = async (q, format, year, country) => { const dis = new Discogs({ userToken: discogsToken }).database(); - const res = await dis.search({ + const params = { q, type: "release", - }); + }; + + if (format) { + params.format = format; + } + if (year) { + params.year = year; + } + if (country) { + params.country = country; + } + + const res = await dis.search(params); return res; }; diff --git a/src/routes/api/v1/search.js b/src/routes/api/v1/search.js index 76e1c7e..267ec0f 100644 --- a/src/routes/api/v1/search.js +++ b/src/routes/api/v1/search.js @@ -9,7 +9,12 @@ const router = express.Router(); router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => { try { - const data = await searchSong(req.query.q); + const data = await searchSong( + req.query.q, + req.query.format || null, + req.query.year || null, + req.query.country || null + ); sendResponse(req, res, data); } catch (err) { diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index 2cd4ab4..549140e 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -1,19 +1,37 @@

Ajouter un album

-
-
-
+ +
+
- +
- +
+
+ + +
+
+ + +
+
+ + +
+
+
-
+ +
@@ -158,10 +176,77 @@ data() { return { q: '', + year: '', + country: '', + format: '', loading: false, items: [], details: {}, modalIsVisible: false, + formats: [ + 'Vinyl', + 'Acetate', + 'Flexi-disc', + 'Lathe Cut', + 'Mighty Tiny', + 'Shellac', + 'Sopic', + 'Pathé Disc', + 'Edison Disc', + 'Cylinder', + 'CD', + 'CDr', + 'CDV', + 'DVD', + 'DVDr', + 'HD DVD', + 'HD DVD-R', + 'Blu-ray', + 'Blu-ray-R', + 'Ultra HD Blu-ray', + 'SACD', + '4-Track Cartridge', + '8-Track Cartridge', + 'Cassette', + 'DC-International', + 'Elcaset', + 'PlayTape', + 'RCA Tape Cartridge', + 'DAT', + 'DCC', + 'Microcassette', + 'NT Cassette', + 'Pocket Rocker', + 'Revere Magnetic Stereo Tape Ca', + 'Tefifon', + 'Reel-To-Reel', + 'Sabamobil', + 'Betacam', + 'Betacam SP', + 'Betamax', + 'Cartrivision', + 'MiniDV', + 'Super VHS', + 'U-matic', + 'VHS', + 'Video 2000', + 'Video8', + 'Film Reel', + 'HitClips', + 'Laserdisc', + 'SelectaVision', + 'VHD', + 'Wire Recording', + 'Minidisc', + 'MVD', + 'UMD', + 'Floppy Disk', + 'File', + 'Memory Stick', + 'Hybrid', + 'All Media', + 'Box Set', + ] } }, methods: { @@ -173,8 +258,19 @@ } this.loading = true; + let url = `/api/v1/search?q=${this.q}`; - axios.get(`/api/v1/search?q=${this.q}`) + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.country ) { + url += `&country=${this.country}`; + } + if ( this.format ) { + url += `&format=${this.format}`; + } + + axios.get(url) .then( response => { const { results, @@ -242,6 +338,9 @@ showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…"); }); }, + orderedItems(items) { + return items.sort(); + } } }).mount('#app'); -- 2.39.5 From adea857666cab0c11e9a80a9ad3ab6eb486564c0 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 30 Aug 2022 15:17:14 +0200 Subject: [PATCH 08/69] =?UTF-8?q?#49=20-=20Mise=20=C3=A0=20jour=20d'une=20?= =?UTF-8?q?fiche?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fontello.json | 118 ++++++++++++++++++ public/font/icon.eot | Bin 9976 -> 10308 bytes public/font/icon.svg | 2 + public/font/icon.ttf | Bin 9824 -> 10156 bytes public/font/icon.woff | Bin 6224 -> 6444 bytes public/font/icon.woff2 | Bin 5208 -> 5400 bytes sass/icons.scss | 13 +- sass/ma-collection-details.scss | 22 ++++ src/middleware/Albums.js | 27 +++- src/routes/api/v1/albums.js | 10 ++ views/pages/composants.ejs | 1 + .../mon-compte/ma-collection/details.ejs | 50 +++++++- 12 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 fontello.json diff --git a/fontello.json b/fontello.json new file mode 100644 index 0000000..11e03d9 --- /dev/null +++ b/fontello.json @@ -0,0 +1,118 @@ +{ + "name": "icon", + "css_prefix_text": "icon-", + "css_use_suffix": false, + "hinting": true, + "units_per_em": 1000, + "ascent": 850, + "glyphs": [ + { + "uid": "44e04715aecbca7f266a17d5a7863c68", + "css": "plus", + "code": 59392, + "src": "fontawesome" + }, + { + "uid": "8b80d36d4ef43889db10bc1f0dc9a862", + "css": "user", + "code": 59393, + "src": "fontawesome" + }, + { + "uid": "9dd9e835aebe1060ba7190ad2b2ed951", + "css": "search", + "code": 59394, + "src": "fontawesome" + }, + { + "uid": "bf882b30900da12fca090d9796bc3030", + "css": "mail", + "code": 59395, + "src": "fontawesome" + }, + { + "uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1", + "css": "link", + "code": 59396, + "src": "fontawesome" + }, + { + "uid": "e15f0d620a7897e2035c18c80142f6d9", + "css": "link-ext", + "code": 61582, + "src": "fontawesome" + }, + { + "uid": "9bc2902722abb366a213a052ade360bc", + "css": "spin", + "code": 59449, + "src": "fontelico" + }, + { + "uid": "bbfb51903f40597f0b70fd75bc7b5cac", + "css": "trash", + "code": 61944, + "src": "fontawesome" + }, + { + "uid": "d73eceadda1f594cec0536087539afbf", + "css": "heart", + "code": 59397, + "src": "fontawesome" + }, + { + "uid": "cce5e05853d0798a4d10077ef613387c", + "css": "blind", + "code": 62109, + "src": "fontawesome" + }, + { + "uid": "567e3e257f2cc8fba2c12bf691c9f2d8", + "css": "moon", + "code": 61830, + "src": "fontawesome" + }, + { + "uid": "aa035df0908c4665c269b7b09a5596f3", + "css": "sun", + "code": 61829, + "src": "fontawesome" + }, + { + "uid": "c5fd349cbd3d23e4ade333789c29c729", + "css": "eye", + "code": 59398, + "src": "fontawesome" + }, + { + "uid": "d870630ff8f81e6de3958ecaeac532f2", + "css": "left-open", + "code": 59399, + "src": "fontawesome" + }, + { + "uid": "399ef63b1e23ab1b761dfbb5591fa4da", + "css": "right-open", + "code": 59400, + "src": "fontawesome" + }, + { + "uid": "895405dfac8a3b7b2f23b183c6608ee6", + "css": "export", + "code": 59401, + "src": "fontawesome" + }, + { + "uid": "4aad6bb50b02c18508aae9cbe14e784e", + "css": "share", + "code": 61920, + "src": "fontawesome" + }, + { + "uid": "a73c5deb486c8d66249811642e5d719a", + "css": "refresh", + "code": 59402, + "src": "fontawesome" + } + ] +} \ No newline at end of file diff --git a/public/font/icon.eot b/public/font/icon.eot index a17c0f6c12920aacff321df497143f171c000437..424368fa06b91335d76d8252af48d4c7c53d3c37 100644 GIT binary patch delta 852 zcmX9+T}TvB6h3$E?48*kXE&XlU12F(_s8<5SaT=wr>(3+UC^k&ENoj>H*(uW7qbL+ ziGE(fvZ2UGkO*Jup;!2gu(6a8vQIYz6B0xPRj!4Q7T?SL^=X z1}Nu|+J<7I8PpvZ-#{-84c{7k-Lkp|fRCVjDVc~3>`xTF0?02h9!{bl9Hw#P%gBAn zRCfIM_Y1pNEN`RGHJpyeyrO>%z}bepDis^gkg$Z!IERr}+=!(T+W1y2@`u>qY$iRL zbw7RM#0F|G{xLI>$V@IYEdT@^+GEm3ZfYAOePA94Mgiu47T;>nTkw$cz(W>w2O0%f zw`g~{daDm~3+SeO=KQ8o%w*aoxATMwc3W|y_2LRps_oE;e%xvu0ao;l1_1MZO&Nd^ zfX#abKtg}t0NBy58qjCBi6hVfIKQn~1AredeB;Z-90#!bp7v2I_I3s?M=qZ9bIg2* zn3#)DP9%i#U0@cOXl8i{XqsZBY>X`nd9ki|P09*O h@(&Kv=3PFH$l8+YgeZ(821gR3NzLZ2EvW8K(tkx8vfTgx delta 519 zcmX>S@WYqwhZ+Mzg4#qjGnTHc|DQ~BsIG5hU|J0`V0CB3-Yhf cW-hVYOh84?BvdBzNIFdplhobZD!HEz05P+K=l}o! diff --git a/public/font/icon.svg b/public/font/icon.svg index d5ac2f6..52ad914 100644 --- a/public/font/icon.svg +++ b/public/font/icon.svg @@ -26,6 +26,8 @@ + + diff --git a/public/font/icon.ttf b/public/font/icon.ttf index 4ccfa6dc9101474be2e0317776cd1611e5a2667a..6688a7902b56ba7925888de4b4596ec0eea25cde 100644 GIT binary patch delta 823 zcmX9+OHUI~6h3F}^v?9bwDvJANuyBOg1jU}S_u#F3E`n))CAFBErlAXoj`-A2_?qI z#-L`R5hIZp!$udHCWIJWxbYW|xNsp0LRhd6q6xaeGwqyw^Ue32dnV`HdslnkpMFx2 z%K_jS0JxG!$F+s?j~|l$03ggJCbMPa)8qS1FJ(j8f;mO2eo^&3ML0m)(e`lls(l9qC8Z;JlWZ$oihW^H2k|l>efQC$;IN zmL-5n$J3obys4kV%>G3{N&+kZD}2=wTWH2b5FtyxgMtBUSoBS7v z^F@FYAm1RC$~ggG_ahr*R_dn{E_Yl!781Dm0GhZLnII!Fu@}rTm(6@eB3IxhVMauO zae++(GGGWZAOkna{F))y05Aq|plSP0o(P9R(NMU_9klR$HR$#zSP=+UN<0v5!-LTl z%tcgg$GsSfL}L^!7*SmwH?P4!IC>bv6#*_$7&6w>j*Su38;eF%))QQt>wnSOW%aA7 z@=s9(b6?ePtov>y_ldUavPE$_itbshhfJ@@mXT~2T%YTIX%zX;#PIp2_U5iyYoT8C zTl<35*xhb9RAR$rsx_RxqUK^@sa4H)+R7z=N8axkvbX*LJ1d;m&&FlC8vw+re$N#{ zm;TvRhXeY$E9QA)l1;PR$tU@WG$Yp+u7)cgasLo#nc@u!=+-0Nqp~!f92rker1Y$} KF8|v5RrwF_-KxR> delta 492 zcmXw#%PT}t9LK+R?qxjYg&9V98;Xpkyb=vrQSvA(C|qM63)kGnr18jO!D1GoQ?eOZ zNRoxRk%f@_33kLREF|(MHCFttoAc{?zUT8jr}H}Oj(4t?E-nLH4}ejRB+Ai;n=9fY zK)2+X4i`~QEoYxPlTY?bW7XfY#HWM?zc?jRZXwbX}&%{f!@WB5pNMY0#Z2QUKsd!q2P>y_KBcJEZ)nH0#<>zToNNPS7$mRt|Bg) z6eTZ4GsiI2Cpcwrm2~vy5+^H^ch4V8t#tngq7N|Bb51 z)98)65D=o=Ld645sqBTTNiTs~4z*aNu1x9G8MxHDzYTy4=Husj8)-2K>A?G%CK!N^ z6-_XbuWEvcJgy05@`NV!(>`OGkfF2+xhcwuu~47)_;VZ`F*kp~%s;7z-gg)e{+(S~ z8@NoCusT_Vt=1M25=rw(@nm=MvtIpA^_7`Q$abrvMO}6W3)|b+x!tL}+AmDM=308~ diff --git a/public/font/icon.woff b/public/font/icon.woff index 32621989e6e774018e5a3e688d923ab43ac1a6f8..9543cf5a12ebb323fdf0bf25648a423d72c14c39 100644 GIT binary patch delta 3937 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 delta 3716 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 literal 5208 zcmV-e6sPNVPew8T0RR9102EjN4*&oF0487n02BcL0RR9100000000000000000000 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 SOZR}g=t0ffALdsEwoCy}C+chf diff --git a/sass/icons.scss b/sass/icons.scss index 4250307..7b2150b 100644 --- a/sass/icons.scss +++ b/sass/icons.scss @@ -1,11 +1,11 @@ @font-face { font-family: 'icon'; - src: url('/font/icon.eot?80770511'); - src: url('/font/icon.eot?80770511#iefix') format('embedded-opentype'), - url('/font/icon.woff2?80770511') format('woff2'), - url('/font/icon.woff?80770511') format('woff'), - url('/font/icon.ttf?80770511') format('truetype'), - url('/font/icon.svg?80770511#icon') format('svg'); + src: url('/font/icon.eot?41426785'); + src: url('/font/icon.eot?41426785#iefix') format('embedded-opentype'), + url('/font/icon.woff2?41426785') format('woff2'), + url('/font/icon.woff?41426785') format('woff'), + url('/font/icon.ttf?41426785') format('truetype'), + url('/font/icon.svg?41426785#icon') format('svg'); font-weight: normal; font-style: normal; } @@ -42,6 +42,7 @@ .icon-left-open:before { content: '\e807'; } /* '' */ .icon-right-open:before { content: '\e808'; } /* '' */ .icon-export:before { content: '\e809'; } /* '' */ +.icon-refresh:before { content: '\e80a'; } /* '' */ .icon-spin:before { content: '\e839'; } /* '' */ .icon-link-ext:before { content: '\f08e'; } /* '' */ .icon-sun:before { content: '\f185'; } /* '' */ diff --git a/sass/ma-collection-details.scss b/sass/ma-collection-details.scss index 952a026..00a8946 100644 --- a/sass/ma-collection-details.scss +++ b/sass/ma-collection-details.scss @@ -1,4 +1,26 @@ .ma-collection-details { + h1 { + i { + cursor: pointer; + + &.icon-trash { + color: $danger-color; + @include transition() {} + + &:hover { + color: $danger-color-hl; + } + } + &.icon-refresh { + color: $primary-color; + @include transition() {} + + &:hover { + color: $primary-color-hl; + } + } + } + } .galerie { display: flex; flex-wrap: wrap; diff --git a/src/middleware/Albums.js b/src/middleware/Albums.js index 312675c..5363c2c 100644 --- a/src/middleware/Albums.js +++ b/src/middleware/Albums.js @@ -7,7 +7,8 @@ import AlbumsModel from "../models/albums"; import JobsModel from "../models/jobs"; import UsersModel from "../models/users"; import ErrorEvent from "../libs/error"; -// import { uploadFromUrl } from "../libs/aws"; + +import { getAlbumDetails } from "../helpers"; /** * Classe permettant la gestion des albums d'un utilisateur @@ -182,6 +183,30 @@ class Albums extends Pages { } } + /** + * Méthode permettant de mettre à jour un album + * + * @return {Object} + */ + async patchOne() { + const { itemId: _id } = this.req.params; + const { _id: User } = this.req.user; + const album = await AlbumsModel.findOne({ + _id, + User, + }); + + if (!album) { + throw new ErrorEvent(404, "Impossible de trouver cet album"); + } + + const values = await getAlbumDetails(album.discogsId); + + await album.updateOne(values); + + return album; + } + /** * Méthode permettant de supprimer un élément d'une collection * @return {Boolean} diff --git a/src/routes/api/v1/albums.js b/src/routes/api/v1/albums.js index e1caa1f..596b502 100644 --- a/src/routes/api/v1/albums.js +++ b/src/routes/api/v1/albums.js @@ -47,6 +47,16 @@ router router .route("/:itemId") + .patch(ensureLoggedIn("/connexion"), async (req, res, next) => { + try { + const albums = new Albums(req); + const data = await albums.patchOne(); + + sendResponse(req, res, data); + } catch (err) { + next(err); + } + }) .delete(ensureLoggedIn("/connexion"), async (req, res, next) => { try { const albums = new Albums(req); diff --git a/views/pages/composants.ejs b/views/pages/composants.ejs index 1cba4e5..cdd1776 100644 --- a/views/pages/composants.ejs +++ b/views/pages/composants.ejs @@ -355,6 +355,7 @@ .icon-left-open .icon-right-open .icon-export + .icon-refresh .icon-share .icon-spin .icon-sun diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index bd97f90..9385544 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -1,6 +1,10 @@
-

{{item.artists_sort}} - {{item.title}}

+

+ {{item.artists_sort}} - {{item.title}} + + +

@@ -153,6 +157,20 @@
+ +
\ No newline at end of file -- 2.39.5 From b8b3df2932af40b4cfc09dbc83c731f8b342e10f Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 30 Aug 2022 15:30:16 +0200 Subject: [PATCH 09/69] #52 - Afficher toute les infos du format d'un album --- views/pages/mon-compte/ma-collection/details.ejs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index 9385544..41c0b24 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -74,6 +74,9 @@
  • {{format.name}} +
- +

-- 2.39.5 From a74c67e241597eee0b3dcd041974201d712d32df Mon Sep 17 00:00:00 2001 From: dbroqua Date: Fri, 28 Oct 2022 22:06:56 +0200 Subject: [PATCH 17/69] #64 - Depuis un album pouvoir voir tous les albums de cet artiste --- views/pages/mon-compte/ma-collection/details.ejs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index a505509..8f5b0ef 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -1,7 +1,7 @@

- {{item.artists_sort}} - {{item.title}} + {{item.artists_sort}} - {{item.title}}

@@ -327,6 +327,9 @@ this.toggleModal(); }); }, + goToArtist() { + return ""; + }, }, }).mount('#app'); \ No newline at end of file -- 2.39.5 From 8f9e9025872e33077ed918cc74258a4193f6e6f8 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Fri, 28 Oct 2022 22:40:02 +0200 Subject: [PATCH 18/69] #66 - Compiler le JS avant de l'envoyer au client --- .gitignore | 2 +- gulpfile.js | 46 +++++ javascripts/ajouter-un-album.js | 171 ++++++++++++++++ javascripts/collection.js | 133 +++++++++++++ javascripts/conctact.js | 42 ++++ {public/js => javascripts}/main.js | 9 +- javascripts/mon-compte/index.js | 26 +++ .../mon-compte/ma-collection/details.js | 158 +++++++++++++++ .../mon-compte/ma-collection/exporter.js | 18 ++ javascripts/mon-compte/ma-collection/index.js | 178 +++++++++++++++++ package.json | 11 +- src/app.js | 8 - views/index.ejs | 5 +- views/pages/ajouter-un-album.ejs | 178 +---------------- views/pages/collection.ejs | 139 +------------ views/pages/mon-compte/index.ejs | 28 +-- .../mon-compte/ma-collection/details.ejs | 159 +-------------- .../mon-compte/ma-collection/exporter.ejs | 25 +-- .../pages/mon-compte/ma-collection/index.ejs | 186 +----------------- views/pages/nous-contacter.ejs | 49 +---- 20 files changed, 809 insertions(+), 762 deletions(-) create mode 100644 gulpfile.js create mode 100644 javascripts/ajouter-un-album.js create mode 100644 javascripts/collection.js create mode 100644 javascripts/conctact.js rename {public/js => javascripts}/main.js (97%) create mode 100644 javascripts/mon-compte/index.js create mode 100644 javascripts/mon-compte/ma-collection/details.js create mode 100644 javascripts/mon-compte/ma-collection/exporter.js create mode 100644 javascripts/mon-compte/ma-collection/index.js diff --git a/.gitignore b/.gitignore index a8dcba6..3054b59 100644 --- a/.gitignore +++ b/.gitignore @@ -121,6 +121,6 @@ dist dist yarn.lock public/css -public/css +public/js docker-compose.yml dump diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..8e76f38 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,46 @@ +const { parallel, src, dest } = require("gulp"); + +const sourcemaps = require("gulp-sourcemaps"); +const concat = require("gulp-concat"); + +const gulp = require("gulp"); +const uglify = require("gulp-uglify"); +const babel = require("gulp-babel"); + +const sourceJs = "javascripts/**/*.js"; +const sourceRemoteJS = [ + "./node_modules/vue/dist/vue.global.prod.js", + "./node_modules/axios/dist/axios.min.js", +]; + +const destination = "public/js"; + +// TASKS ---------------------------------------------------------------------- + +const compileJs = function () { + return gulp + .src(sourceJs) + .pipe(sourcemaps.init()) + .pipe(concat("main.js")) + .pipe( + babel({ + presets: ["@babel/env"], + }) + ) + .pipe(uglify()) + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(destination)); +}; +const compileRemoteJs = function () { + return gulp + .src(sourceRemoteJS) + .pipe(sourcemaps.init()) + .pipe(concat("libs.js")) + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(destination)); +}; +// ---------------------------------------------------------------------------- + +// COMMANDS ------------------------------------------------------------------- +exports.default = parallel(compileJs, compileRemoteJs); +// ---------------------------------------------------------------------------- diff --git a/javascripts/ajouter-un-album.js b/javascripts/ajouter-un-album.js new file mode 100644 index 0000000..d46d5d4 --- /dev/null +++ b/javascripts/ajouter-un-album.js @@ -0,0 +1,171 @@ +Vue.createApp({ + data() { + return { + q: '', + year: '', + country: '', + format: '', + loading: false, + items: [], + details: {}, + modalIsVisible: false, + formats: [ + 'Vinyl', + 'Acetate', + 'Flexi-disc', + 'Lathe Cut', + 'Mighty Tiny', + 'Shellac', + 'Sopic', + 'Pathé Disc', + 'Edison Disc', + 'Cylinder', + 'CD', + 'CDr', + 'CDV', + 'DVD', + 'DVDr', + 'HD DVD', + 'HD DVD-R', + 'Blu-ray', + 'Blu-ray-R', + 'Ultra HD Blu-ray', + 'SACD', + '4-Track Cartridge', + '8-Track Cartridge', + 'Cassette', + 'DC-International', + 'Elcaset', + 'PlayTape', + 'RCA Tape Cartridge', + 'DAT', + 'DCC', + 'Microcassette', + 'NT Cassette', + 'Pocket Rocker', + 'Revere Magnetic Stereo Tape Ca', + 'Tefifon', + 'Reel-To-Reel', + 'Sabamobil', + 'Betacam', + 'Betacam SP', + 'Betamax', + 'Cartrivision', + 'MiniDV', + 'Super VHS', + 'U-matic', + 'VHS', + 'Video 2000', + 'Video8', + 'Film Reel', + 'HitClips', + 'Laserdisc', + 'SelectaVision', + 'VHD', + 'Wire Recording', + 'Minidisc', + 'MVD', + 'UMD', + 'Floppy Disk', + 'File', + 'Memory Stick', + 'Hybrid', + 'All Media', + 'Box Set', + ] + } + }, + methods: { + search(event) { + event.preventDefault(); + + if ( this.loading ) { + return false; + } + + this.loading = true; + let url = `/api/v1/search?q=${this.q}`; + + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.country ) { + url += `&country=${this.country}`; + } + if ( this.format ) { + url += `&format=${this.format}`; + } + + axios.get(url) + .then( response => { + const { + results, + } = response.data; + let items = []; + + for (let i = 0 ; i < results.length ; i += 1 ) { + const { + id, + title, + thumb, + year, + country, + format, + genre, + style, + } = results[i]; + items.push({ + id, + title, + thumb, + year, + country, + format, + genre, + style, + }); + } + + this.items = items; + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Aucun résultat trouvé :/"); + }) + .finally(() => { + this.loading = false; + }); + }, + toggleModal() { + this.modalIsVisible = !this.modalIsVisible; + }, + loadDetails(discogsId) { + axios.get(`/api/v1/search/${discogsId}`) + .then( response => { + const { + data, + } = response; + + this.details = data; + this.toggleModal(); + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de charger les détails de cet album"); + }) + .finally(() => { + this.loading = false; + }); + }, + add() { + axios.post('/api/v1/albums', this.details) + .then(() => { + window.location.href = '/ma-collection'; + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…"); + }); + }, + orderedItems(items) { + return items.sort(); + } + } +}).mount('#ajouter-album'); \ No newline at end of file diff --git a/javascripts/collection.js b/javascripts/collection.js new file mode 100644 index 0000000..c3b3561 --- /dev/null +++ b/javascripts/collection.js @@ -0,0 +1,133 @@ +if ( typeof userId !== 'undefined' ) { + Vue.createApp({ + data() { + return { + loading: false, + moreFilters: false, + items: [], + total: 0, + page: 1, + totalPages: 1, + limit: 16, + artist: '', + format: '', + year: '', + genre: '', + style: '', + sortOrder: 'artists_sort-asc', + sort: 'artists_sort', + order: 'asc', + userId, + } + }, + created() { + this.fetch(); + }, + methods: { + fetch() { + this.loading = true; + this.total = 0; + + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const entries = urlParams.entries(); + + for(const entry of entries) { + switch(entry[0]) { + case 'artists_sort': + this.artist = entry[1]; + break; + default: + this[entry[0]] = entry[1]; + } + } + + let url = `/api/v1/albums?userId=${this.userId}&page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if ( this.artist ) { + url += `&artists_sort=${this.artist.replace('&', '%26')}`; + } + if ( this.format ) { + url += `&format=${this.format.replace('&', '%26')}`; + } + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.genre ) { + url += `&genre=${this.genre.replace('&', '%26')}`; + } + if ( this.style ) { + url += `&style=${this.style.replace('&', '%26')}`; + } + + axios.get(url) + .then( response => { + this.items = response.data.rows; + this.total = response.data.count || 0; + this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); + + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de charger cette collection"); + }) + .finally(() => { + this.loading = false; + }); + }, + changeUrl() { + let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if ( this.artist ) { + url += `&artists_sort=${this.artist.replace('&', '%26')}`; + } + if ( this.format ) { + url += `&format=${this.format.replace('&', '%26')}`; + } + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.genre ) { + url += `&genre=${this.genre.replace('&', '%26')}`; + } + if ( this.style ) { + url += `&style=${this.style.replace('&', '%26')}`; + } + + location.href = url; + }, + next(event) { + event.preventDefault(); + + this.page += 1; + + this.changeUrl(); + }, + previous(event) { + event.preventDefault(); + + this.page -= 1; + + this.changeUrl(); + }, + goTo(page) { + this.page = page; + + this.changeUrl(); + }, + changeSort() { + const [sort,order] = this.sortOrder.split('-'); + this.sort = sort; + this.order = order; + this.page = 1; + + this.changeUrl(); + }, + changeFilter() { + this.page = 1; + + this.changeUrl(); + }, + showMoreFilters() { + this.moreFilters = !this.moreFilters; + } + } + }).mount('#collection-publique'); +} \ No newline at end of file diff --git a/javascripts/conctact.js b/javascripts/conctact.js new file mode 100644 index 0000000..51ed1cb --- /dev/null +++ b/javascripts/conctact.js @@ -0,0 +1,42 @@ +if ( typeof contactMethod !== 'undefined' && contactMethod === 'smtp' ) { + Vue.createApp({ + data() { + return { + email: '', + name: '', + message: '', + captcha: '', + loading: false, + } + }, + methods: { + send(event) { + event.preventDefault(); + + if ( this.loading ) { + return false; + } + + this.loading = true; + + const { + email, + message, + name, + captcha, + } = this; + + axios.post('/api/v1/contact', {email, name, message, captcha}) + .then( () => { + showToastr("Message correctement envoyé", true); + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible d'envoyer votre message", false); + }) + .finally(() => { + this.loading = false; + }) + }, + }, + }).mount('#contact'); +} \ No newline at end of file diff --git a/public/js/main.js b/javascripts/main.js similarity index 97% rename from public/js/main.js rename to javascripts/main.js index f203a63..0b0592e 100644 --- a/public/js/main.js +++ b/javascripts/main.js @@ -1,8 +1,13 @@ -/** +const { + protocol, + host +} = window.location; + + /** * Fonction permettant d'afficher un message dans un toastr * @param {String} message */ - function showToastr(message, success = false) { +function showToastr(message, success = false) { let x = document.getElementById("toastr"); if ( message ) { x.getElementsByTagName("SPAN")[0].innerHTML = message; diff --git a/javascripts/mon-compte/index.js b/javascripts/mon-compte/index.js new file mode 100644 index 0000000..ccc1d02 --- /dev/null +++ b/javascripts/mon-compte/index.js @@ -0,0 +1,26 @@ +if ( typeof email !== 'undefined' && typeof username !== 'undefined' ) { + Vue.createApp({ + data() { + return { + email, + username, + oldPassword: '', + password: '', + passwordConfirm: '', + loading: false, + } + }, + methods: { + async updateProfil(event) { + // try { + // if ( this.password !== this.passwordConfirm ) { + // throw "La confirnation du mot de passe ne correspond pas"; + // } + // } catch(err) { + // event.preventDefault(); + // showToastr(err); + // } + }, + } + }).mount('#mon-compte'); +} \ No newline at end of file diff --git a/javascripts/mon-compte/ma-collection/details.js b/javascripts/mon-compte/ma-collection/details.js new file mode 100644 index 0000000..26f069f --- /dev/null +++ b/javascripts/mon-compte/ma-collection/details.js @@ -0,0 +1,158 @@ +if ( typeof item !== 'undefined' ) { + Vue.createApp({ + data() { + return { + item, + tracklist: [], + identifiers: [], + modalIsVisible: false, + identifiersMode: 'preview', + identifiersPreviewLength: 16, + preview: null, + index: null, + showModalDelete: false, + } + }, + created() { + this.setTrackList(); + this.setIdentifiers(); + + window.addEventListener("keydown", this.changeImage); + }, + destroyed() { + window.removeEventListener('keydown', this.changeImage); + }, + methods: { + setIdentifiers() { + this.identifiers = []; + + let max = this.identifiersMode == 'preview' && this.item.identifiers.length > this.identifiersPreviewLength ? this.identifiersPreviewLength : this.item.identifiers.length; + + for ( let i = 0 ; i < max ; i += 1 ) { + this.identifiers.push(this.item.identifiers[i]); + } + }, + setTrackList() { + let subTrack = { + type: null, + title: null, + tracks: [], + }; + for (let i = 0 ; i < this.item.tracklist.length ; i += 1 ) { + const { + type_, + title, + position, + duration, + extraartists, + } = this.item.tracklist[i]; + + if ( type_ === 'heading' ) { + if ( subTrack.type ) { + this.tracklist.push(subTrack); + subTrack = { + type: null, + title: null, + tracks: [], + }; + } + + subTrack.type = type_; + subTrack.title = title; + } else { + subTrack.tracks.push({ + title, + position, + duration, + extraartists + }); + } + } + this.tracklist.push(subTrack); + }, + setImage() { + this.preview = this.item.images[this.index].uri; + }, + showGallery(event) { + const item = event.target.tagName === 'IMG' ? event.target.parentElement : event.target; + + const { + index, + } = item.dataset; + + this.index = Number(index); + this.modalIsVisible = true; + + this.setImage(); + }, + toggleModal() { + this.modalIsVisible = !this.modalIsVisible; + }, + previous() { + this.index = this.index > 0 ? this.index - 1 : this.item.images.length -1; + this.setImage(); + }, + next() { + this.index = (this.index +1) === this.item.images.length ? 0 : this.index + 1; + this.setImage(); + }, + changeImage(event) { + const direction = event.code; + + if ( this.modalIsVisible && ['ArrowRight', 'ArrowLeft', 'Escape'].indexOf(direction) !== -1 ) { + switch (direction) { + case 'ArrowRight': + return this.next(); + case 'ArrowLeft': + return this.previous(); + default: + this.modalIsVisible = false; + return true; + } + } + }, + showAllIdentifiers() { + this.identifiersMode = 'all'; + this.setIdentifiers(); + }, + showLessIdentifiers() { + this.identifiersMode = 'preview'; + this.setIdentifiers(); + + document.querySelector('#identifiers').scrollIntoView({ behavior: 'smooth' }); + }, + showConfirmDelete() { + this.toggleModal(); + }, + toggleModal() { + this.showModalDelete = !this.showModalDelete; + }, + updateItem() { + showToastr("Mise à jour en cours…", true); + axios.patch(`/api/v1/albums/${this.item._id}`) + .then( (res) => { + showToastr("Mise à jour réalisée avec succès", true); + this.item = res.data; + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de mettre à jour cet album", false); + }); + }, + deleteItem() { + axios.delete(`/api/v1/albums/${this.item._id}`) + .then( () => { + window.location.href = "/ma-collection"; + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + }) + .finally(() => { + this.toggleModal(); + }); + }, + goToArtist() { + return ""; + }, + }, + }).mount('#ma-collection-details'); +} \ No newline at end of file diff --git a/javascripts/mon-compte/ma-collection/exporter.js b/javascripts/mon-compte/ma-collection/exporter.js new file mode 100644 index 0000000..f5dcbcb --- /dev/null +++ b/javascripts/mon-compte/ma-collection/exporter.js @@ -0,0 +1,18 @@ +Vue.createApp({ + data() { + return { + format: 'xml', + } + }, + created() { + }, + destroyed() { + }, + methods: { + exportCollection(event) { + event.preventDefault(); + + window.open(`/api/v1/albums?exportFormat=${this.format}`, '_blank'); + } + }, +}).mount('#exporter'); \ No newline at end of file diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js new file mode 100644 index 0000000..7e2bdf5 --- /dev/null +++ b/javascripts/mon-compte/ma-collection/index.js @@ -0,0 +1,178 @@ +if ( typeof isPublicCollection !== 'undefined' ) { + Vue.createApp({ + data() { + return { + loading: false, + moreFilters: false, + items: [], + total: 0, + page: 1, + totalPages: 1, + limit: 16, + artist: '', + format: '', + year: '', + genre: '', + style: '', + sortOrder: 'artists_sort-asc', + sort: 'artists_sort', + order: 'asc', + itemId: null, + showModalDelete: false, + showModalShare: false, + shareLink: `${protocol}//${host}/collection/<%= user._id %>`, + isPublicCollection, + } + }, + created() { + this.fetch(); + }, + methods: { + fetch() { + this.loading = true; + this.total = 0; + + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const entries = urlParams.entries(); + + for(const entry of entries) { + switch(entry[0]) { + case 'artists_sort': + this.artist = entry[1]; + break; + default: + this[entry[0]] = entry[1]; + } + } + + let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if ( this.artist ) { + url += `&artists_sort=${this.artist.replace('&', '%26')}`; + } + if ( this.format ) { + url += `&format=${this.format.replace('&', '%26')}`; + } + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.genre ) { + url += `&genre=${this.genre.replace('&', '%26')}`; + } + if ( this.style ) { + url += `&style=${this.style.replace('&', '%26')}`; + } + + axios.get(url) + .then( response => { + this.items = response.data.rows; + this.total = response.data.count || 0; + this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de charger votre collection"); + }) + .finally(() => { + this.loading = false; + }); + }, + changeUrl() { + let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if ( this.artist ) { + url += `&artists_sort=${this.artist.replace('&', '%26')}`; + } + if ( this.format ) { + url += `&format=${this.format.replace('&', '%26')}`; + } + if ( this.year ) { + url += `&year=${this.year}`; + } + if ( this.genre ) { + url += `&genre=${this.genre.replace('&', '%26')}`; + } + if ( this.style ) { + url += `&style=${this.style.replace('&', '%26')}`; + } + + location.href = url; + }, + next(event) { + event.preventDefault(); + + this.page += 1; + + this.changeUrl(); + }, + previous(event) { + event.preventDefault(); + + this.page -= 1; + + this.changeUrl(); + }, + goTo(page) { + this.page = page; + + this.changeUrl(); + }, + changeSort() { + const [sort,order] = this.sortOrder.split('-'); + this.sort = sort; + this.order = order; + this.page = 1; + + this.changeUrl(); + }, + changeFilter() { + this.page = 1; + + this.changeUrl(); + }, + showMoreFilters() { + this.moreFilters = !this.moreFilters; + }, + toggleModal() { + this.showModalDelete = !this.showModalDelete; + }, + toggleModalShare() { + this.showModalShare = !this.showModalShare; + }, + showConfirmDelete(itemId) { + this.itemId = itemId; + this.toggleModal(); + }, + deleteItem() { + axios.delete(`/api/v1/albums/${this.itemId}`) + .then( () => { + this.fetch(); + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + }) + .finally(() => { + this.toggleModal(); + }); + }, + shareCollection() { + axios.patch(`/api/v1/me`, { + isPublicCollection: !this.isPublicCollection, + }) + .then( (res) => { + this.isPublicCollection = res.data.isPublicCollection; + + if ( this.isPublicCollection ) { + showToastr("Votre collection est désormais publique", true); + } else { + showToastr("Votre collection n'est plus partagée", true); + } + }) + .catch((err) => { + showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + }) + .finally(() => { + this.toggleModalShare(); + }); + }, + } + }).mount('#ma-collection'); +} \ No newline at end of file diff --git a/package.json b/package.json index 2ee4492..b3dffed 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "description": "Simple application to manage your CD/Vinyl collection", "scripts": { "start": "node ./dist/bin/www", - "run:all": "npm-run-all build sass start", + "run:all": "npm-run-all build sass uglify start", "watch": "nodemon -e js,scss", "sass": "npx sass sass/index.scss public/css/main.css -s compressed --color", + "uglify": "npx gulp", "prebuild": "rimraf dist", "build": "babel ./src --out-dir dist --copy-files", "test": "jest", @@ -55,6 +56,11 @@ "excel4node": "^1.7.2", "express": "^4.17.2", "express-session": "^1.17.2", + "gulp": "^4.0.2", + "gulp-babel": "^8.0.0", + "gulp-concat": "^2.6.1", + "gulp-sourcemaps": "^3.0.0", + "gulp-uglify": "^3.0.2", "joi": "^17.6.0", "knacss": "^8.0.4", "mongoose": "^6.2.1", @@ -75,7 +81,8 @@ "exec": "yarn run:all", "watch": [ "src/*", - "sass/*" + "sass/*", + "javascripts/*" ], "ignore": [ "**/__tests__/**", diff --git a/src/app.js b/src/app.js index 7c40a27..6fa4688 100644 --- a/src/app.js +++ b/src/app.js @@ -75,14 +75,6 @@ app.set("views", path.join(__dirname, "../views")); app.set("view engine", "ejs"); app.use(express.static(path.join(__dirname, "../public"))); -app.use( - "/libs/vue", - express.static(path.join(__dirname, "../node_modules/vue/dist")) -); -app.use( - "/libs/axios", - express.static(path.join(__dirname, "../node_modules/axios/dist")) -); app.use("/", indexRouter); app.use("/mon-compte", monCompteRouter); diff --git a/views/index.ejs b/views/index.ejs index 2386a83..a310984 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -16,9 +16,8 @@ - - - + + <% if ( config.matomoUrl ) { %> diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index a7306ff..f32ea67 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -1,4 +1,4 @@ -
+

Ajouter un album

@@ -169,178 +169,4 @@
-
- - + \ No newline at end of file diff --git a/views/pages/collection.ejs b/views/pages/collection.ejs index 9960983..3272d8b 100644 --- a/views/pages/collection.ejs +++ b/views/pages/collection.ejs @@ -1,4 +1,4 @@ -
+

Collection de <%= page.username %>

@@ -146,140 +146,5 @@
diff --git a/views/pages/mon-compte/index.ejs b/views/pages/mon-compte/index.ejs index d8f7f6a..af7b634 100644 --- a/views/pages/mon-compte/index.ejs +++ b/views/pages/mon-compte/index.ejs @@ -1,4 +1,4 @@ -
+

Mon compte

@@ -72,28 +72,6 @@
diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index 8f5b0ef..e8615b4 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -1,4 +1,4 @@ -
+

{{item.artists_sort}} - {{item.title}} @@ -176,160 +176,5 @@

\ No newline at end of file diff --git a/views/pages/mon-compte/ma-collection/exporter.ejs b/views/pages/mon-compte/ma-collection/exporter.ejs index 13e3f98..28cc559 100644 --- a/views/pages/mon-compte/ma-collection/exporter.ejs +++ b/views/pages/mon-compte/ma-collection/exporter.ejs @@ -1,4 +1,4 @@ -
+

Exporter ma collection

Les formats CSV et Excel sont facilement lisiblent par un humain. Dans ces 2 formats vous trouverez seulement les informations principales de vos albums, à savoir : @@ -44,25 +44,4 @@ Exporter -

- - \ No newline at end of file +
\ No newline at end of file diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index fb67cd3..38993c8 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -1,4 +1,4 @@ -
+

Ma collection @@ -196,185 +196,5 @@

+ const isPublicCollection = <%= user.isPublicCollection ? 'true' : 'false' %>; + \ No newline at end of file diff --git a/views/pages/nous-contacter.ejs b/views/pages/nous-contacter.ejs index 276b6f4..f179135 100644 --- a/views/pages/nous-contacter.ejs +++ b/views/pages/nous-contacter.ejs @@ -1,4 +1,4 @@ -
+

Nous contacter

id="contact" method="POST" action="https://formspree.io/f/<%= config.formspreeId %>" <% } %>>
@@ -34,47 +34,6 @@
-<% if (config.mailMethod === 'smtp' ) { %> - -<% } %> \ No newline at end of file + \ No newline at end of file -- 2.39.5 From 980586d8ebc308d5a373f80788559861391cc580 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Fri, 28 Oct 2022 22:45:38 +0200 Subject: [PATCH 19/69] Correction mineure sur le refresh d'un album --- javascripts/mon-compte/ma-collection/details.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/javascripts/mon-compte/ma-collection/details.js b/javascripts/mon-compte/ma-collection/details.js index 26f069f..3f63290 100644 --- a/javascripts/mon-compte/ma-collection/details.js +++ b/javascripts/mon-compte/ma-collection/details.js @@ -133,6 +133,10 @@ if ( typeof item !== 'undefined' ) { .then( (res) => { showToastr("Mise à jour réalisée avec succès", true); this.item = res.data; + + this.setTrackList(); + this.setIdentifiers(); + this.showLessIdentifiers(); }) .catch((err) => { showToastr(err.response?.data?.message || "Impossible de mettre à jour cet album", false); -- 2.39.5 From e01f01337c615a448b192281abc26365fe21c05d Mon Sep 17 00:00:00 2001 From: dbroqua Date: Fri, 28 Oct 2022 22:56:04 +0200 Subject: [PATCH 20/69] Lint --- .eslintrc.js | 73 ++++--- javascripts/ajouter-un-album.js | 192 +++++++++--------- javascripts/collection.js | 96 +++++---- javascripts/conctact.js | 39 ++-- javascripts/main.js | 108 +++++----- javascripts/mon-compte/index.js | 19 +- .../mon-compte/ma-collection/details.js | 146 +++++++------ .../mon-compte/ma-collection/exporter.js | 16 +- javascripts/mon-compte/ma-collection/index.js | 129 +++++++----- .../mon-compte/ma-collection/details.ejs | 2 +- 10 files changed, 450 insertions(+), 370 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 143bf10..1f95ab5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,36 +1,43 @@ module.exports = { - env: { - browser: true, - es2020: true, - node: true, - jquery: true, - }, - extends: ['airbnb-base', 'prettier'], - plugins: ['prettier'], - parserOptions: { - ecmaVersion: 11, - sourceType: 'module', - }, - rules: { - 'prettier/prettier': ['error'], - 'no-underscore-dangle': [ - 'error', - { - allow: ['_id', 'artists_sort', 'type_'], - }, - ], - 'camelcase': [ - 'error', - { - allow: ['artists_sort',] - }, - ], - }, - ignorePatterns: ['public/libs/**/*.js', 'public/js/main.js', 'dist/**'], - overrides: [ - { - files: ['**/*.js'], - excludedFiles: '*.ejs', + env: { + browser: true, + es2020: true, + node: true, + jquery: true, + }, + extends: ["airbnb-base", "prettier"], + plugins: ["prettier"], + parserOptions: { + ecmaVersion: 11, + sourceType: "module", + }, + rules: { + "prettier/prettier": ["error"], + "no-underscore-dangle": [ + "error", + { + allow: ["_id", "artists_sort", "type_"], + }, + ], + camelcase: [ + "error", + { + allow: ["artists_sort"], + }, + ], + }, + ignorePatterns: ["public/libs/**/*.js", "public/js/main.js", "dist/**"], + overrides: [ + { + files: ["**/*.js"], + excludedFiles: "*.ejs", + }, + ], + globals: { + Vue: true, + axios: true, + showToastr: true, + protocol: true, + host: true, }, - ], }; diff --git a/javascripts/ajouter-un-album.js b/javascripts/ajouter-un-album.js index d46d5d4..eb55697 100644 --- a/javascripts/ajouter-un-album.js +++ b/javascripts/ajouter-un-album.js @@ -1,109 +1,108 @@ Vue.createApp({ data() { return { - q: '', - year: '', - country: '', - format: '', + q: "", + year: "", + country: "", + format: "", loading: false, items: [], details: {}, modalIsVisible: false, formats: [ - 'Vinyl', - 'Acetate', - 'Flexi-disc', - 'Lathe Cut', - 'Mighty Tiny', - 'Shellac', - 'Sopic', - 'Pathé Disc', - 'Edison Disc', - 'Cylinder', - 'CD', - 'CDr', - 'CDV', - 'DVD', - 'DVDr', - 'HD DVD', - 'HD DVD-R', - 'Blu-ray', - 'Blu-ray-R', - 'Ultra HD Blu-ray', - 'SACD', - '4-Track Cartridge', - '8-Track Cartridge', - 'Cassette', - 'DC-International', - 'Elcaset', - 'PlayTape', - 'RCA Tape Cartridge', - 'DAT', - 'DCC', - 'Microcassette', - 'NT Cassette', - 'Pocket Rocker', - 'Revere Magnetic Stereo Tape Ca', - 'Tefifon', - 'Reel-To-Reel', - 'Sabamobil', - 'Betacam', - 'Betacam SP', - 'Betamax', - 'Cartrivision', - 'MiniDV', - 'Super VHS', - 'U-matic', - 'VHS', - 'Video 2000', - 'Video8', - 'Film Reel', - 'HitClips', - 'Laserdisc', - 'SelectaVision', - 'VHD', - 'Wire Recording', - 'Minidisc', - 'MVD', - 'UMD', - 'Floppy Disk', - 'File', - 'Memory Stick', - 'Hybrid', - 'All Media', - 'Box Set', - ] - } + "Vinyl", + "Acetate", + "Flexi-disc", + "Lathe Cut", + "Mighty Tiny", + "Shellac", + "Sopic", + "Pathé Disc", + "Edison Disc", + "Cylinder", + "CD", + "CDr", + "CDV", + "DVD", + "DVDr", + "HD DVD", + "HD DVD-R", + "Blu-ray", + "Blu-ray-R", + "Ultra HD Blu-ray", + "SACD", + "4-Track Cartridge", + "8-Track Cartridge", + "Cassette", + "DC-International", + "Elcaset", + "PlayTape", + "RCA Tape Cartridge", + "DAT", + "DCC", + "Microcassette", + "NT Cassette", + "Pocket Rocker", + "Revere Magnetic Stereo Tape Ca", + "Tefifon", + "Reel-To-Reel", + "Sabamobil", + "Betacam", + "Betacam SP", + "Betamax", + "Cartrivision", + "MiniDV", + "Super VHS", + "U-matic", + "VHS", + "Video 2000", + "Video8", + "Film Reel", + "HitClips", + "Laserdisc", + "SelectaVision", + "VHD", + "Wire Recording", + "Minidisc", + "MVD", + "UMD", + "Floppy Disk", + "File", + "Memory Stick", + "Hybrid", + "All Media", + "Box Set", + ], + }; }, methods: { search(event) { event.preventDefault(); - if ( this.loading ) { + if (this.loading) { return false; } this.loading = true; let url = `/api/v1/search?q=${this.q}`; - if ( this.year ) { + if (this.year) { url += `&year=${this.year}`; } - if ( this.country ) { + if (this.country) { url += `&country=${this.country}`; } - if ( this.format ) { + if (this.format) { url += `&format=${this.format}`; } - axios.get(url) - .then( response => { - const { - results, - } = response.data; - let items = []; + return axios + .get(url) + .then((response) => { + const { results } = response.data; + const items = []; - for (let i = 0 ; i < results.length ; i += 1 ) { + for (let i = 0; i < results.length; i += 1) { const { id, title, @@ -129,7 +128,10 @@ Vue.createApp({ this.items = items; }) .catch((err) => { - showToastr(err.response?.data?.message || "Aucun résultat trouvé :/"); + showToastr( + err.response?.data?.message || + "Aucun résultat trouvé :/" + ); }) .finally(() => { this.loading = false; @@ -139,33 +141,39 @@ Vue.createApp({ this.modalIsVisible = !this.modalIsVisible; }, loadDetails(discogsId) { - axios.get(`/api/v1/search/${discogsId}`) - .then( response => { - const { - data, - } = response; + axios + .get(`/api/v1/search/${discogsId}`) + .then((response) => { + const { data } = response; this.details = data; this.toggleModal(); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de charger les détails de cet album"); + showToastr( + err.response?.data?.message || + "Impossible de charger les détails de cet album" + ); }) .finally(() => { this.loading = false; }); }, add() { - axios.post('/api/v1/albums', this.details) + axios + .post("/api/v1/albums", this.details) .then(() => { - window.location.href = '/ma-collection'; + window.location.href = "/ma-collection"; }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…"); + showToastr( + err.response?.data?.message || + "Impossible d'ajouter cet album pour le moment…" + ); }); }, orderedItems(items) { return items.sort(); - } - } -}).mount('#ajouter-album'); \ No newline at end of file + }, + }, +}).mount("#ajouter-album"); diff --git a/javascripts/collection.js b/javascripts/collection.js index c3b3561..beed2c6 100644 --- a/javascripts/collection.js +++ b/javascripts/collection.js @@ -1,4 +1,4 @@ -if ( typeof userId !== 'undefined' ) { +if (typeof userId !== "undefined") { Vue.createApp({ data() { return { @@ -9,16 +9,17 @@ if ( typeof userId !== 'undefined' ) { page: 1, totalPages: 1, limit: 16, - artist: '', - format: '', - year: '', - genre: '', - style: '', - sortOrder: 'artists_sort-asc', - sort: 'artists_sort', - order: 'asc', + artist: "", + format: "", + year: "", + genre: "", + style: "", + sortOrder: "artists_sort-asc", + sort: "artists_sort", + order: "asc", + // eslint-disable-next-line no-undef userId, - } + }; }, created() { this.fetch(); @@ -32,42 +33,49 @@ if ( typeof userId !== 'undefined' ) { const urlParams = new URLSearchParams(queryString); const entries = urlParams.entries(); - for(const entry of entries) { - switch(entry[0]) { - case 'artists_sort': - this.artist = entry[1]; + // eslint-disable-next-line no-restricted-syntax + for (const entry of entries) { + const [key, value] = entry; + switch (key) { + case "artists_sort": + this.artist = value; break; default: - this[entry[0]] = entry[1]; + this[key] = value; } } let url = `/api/v1/albums?userId=${this.userId}&page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; - if ( this.artist ) { - url += `&artists_sort=${this.artist.replace('&', '%26')}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; } - if ( this.format ) { - url += `&format=${this.format.replace('&', '%26')}`; + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; } - if ( this.year ) { + if (this.year) { url += `&year=${this.year}`; } - if ( this.genre ) { - url += `&genre=${this.genre.replace('&', '%26')}`; + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; } - if ( this.style ) { - url += `&style=${this.style.replace('&', '%26')}`; + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; } - axios.get(url) - .then( response => { + axios + .get(url) + .then((response) => { this.items = response.data.rows; this.total = response.data.count || 0; - this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); - + this.totalPages = + parseInt(response.data.count / this.limit, 10) + + (response.data.count % this.limit > 0 ? 1 : 0); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de charger cette collection"); + showToastr( + err.response?.data?.message || + "Impossible de charger cette collection" + ); }) .finally(() => { this.loading = false; @@ -75,23 +83,23 @@ if ( typeof userId !== 'undefined' ) { }, changeUrl() { let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; - if ( this.artist ) { - url += `&artists_sort=${this.artist.replace('&', '%26')}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; } - if ( this.format ) { - url += `&format=${this.format.replace('&', '%26')}`; + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; } - if ( this.year ) { + if (this.year) { url += `&year=${this.year}`; } - if ( this.genre ) { - url += `&genre=${this.genre.replace('&', '%26')}`; + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; } - if ( this.style ) { - url += `&style=${this.style.replace('&', '%26')}`; + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; } - location.href = url; + window.location.href = url; }, next(event) { event.preventDefault(); @@ -113,7 +121,7 @@ if ( typeof userId !== 'undefined' ) { this.changeUrl(); }, changeSort() { - const [sort,order] = this.sortOrder.split('-'); + const [sort, order] = this.sortOrder.split("-"); this.sort = sort; this.order = order; this.page = 1; @@ -127,7 +135,7 @@ if ( typeof userId !== 'undefined' ) { }, showMoreFilters() { this.moreFilters = !this.moreFilters; - } - } - }).mount('#collection-publique'); -} \ No newline at end of file + }, + }, + }).mount("#collection-publique"); +} diff --git a/javascripts/conctact.js b/javascripts/conctact.js index 51ed1cb..1fbde34 100644 --- a/javascripts/conctact.js +++ b/javascripts/conctact.js @@ -1,42 +1,43 @@ -if ( typeof contactMethod !== 'undefined' && contactMethod === 'smtp' ) { +// eslint-disable-next-line no-undef +if (typeof contactMethod !== "undefined" && contactMethod === "smtp") { Vue.createApp({ data() { return { - email: '', - name: '', - message: '', - captcha: '', + email: "", + name: "", + message: "", + captcha: "", loading: false, - } + }; }, methods: { send(event) { event.preventDefault(); - if ( this.loading ) { + if (this.loading) { return false; } this.loading = true; - const { - email, - message, - name, - captcha, - } = this; + const { email, message, name, captcha } = this; - axios.post('/api/v1/contact', {email, name, message, captcha}) - .then( () => { + return axios + .post("/api/v1/contact", { email, name, message, captcha }) + .then(() => { showToastr("Message correctement envoyé", true); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible d'envoyer votre message", false); + showToastr( + err.response?.data?.message || + "Impossible d'envoyer votre message", + false + ); }) .finally(() => { this.loading = false; - }) + }); }, }, - }).mount('#contact'); -} \ No newline at end of file + }).mount("#contact"); +} diff --git a/javascripts/main.js b/javascripts/main.js index 0b0592e..388ab22 100644 --- a/javascripts/main.js +++ b/javascripts/main.js @@ -1,30 +1,30 @@ -const { - protocol, - host -} = window.location; +/* eslint-disable no-unused-vars */ +const { protocol, host } = window.location; - /** +/** * Fonction permettant d'afficher un message dans un toastr * @param {String} message */ function showToastr(message, success = false) { - let x = document.getElementById("toastr"); - if ( message ) { + const x = document.getElementById("toastr"); + if (message) { x.getElementsByTagName("SPAN")[0].innerHTML = message; } x.className = `${x.className} show`.replace("sucess", ""); - if ( success ) { + if (success) { x.className = `${x.className} success`; } - setTimeout(function(){ x.className = x.className.replace("show", ""); }, 3000); -}; + setTimeout(() => { + x.className = x.className.replace("show", ""); + }, 3000); +} /** * Fonction permettant de masquer le toastr */ function hideToastr() { - let x = document.getElementById("toastr"); + const x = document.getElementById("toastr"); x.className = x.className.replace("show", ""); x.getElementsByTagName("SPAN")[0].innerHTML = ""; @@ -37,17 +37,17 @@ function hideToastr() { * * @return {String} */ -function getCookie(cname, defaultValue = 'false') { - let name = cname + "="; - let decodedCookie = decodeURIComponent(document.cookie); - let ca = decodedCookie.split(';'); - for(let i = 0; i < ca.length; i+=1) { +function getCookie(cname, defaultValue = "false") { + const name = `${cname}=`; + const decodedCookie = decodeURIComponent(document.cookie); + const ca = decodedCookie.split(";"); + for (let i = 0; i < ca.length; i += 1) { let c = ca[i]; - while (c.charAt(0) == ' ') { - c = c.substring(1); + while (c.charAt(0) === " ") { + c = c.substring(1); } - if (c.indexOf(name) == 0) { - return c.substring(name.length, c.length); + if (c.indexOf(name) === 0) { + return c.substring(name.length, c.length); } } return defaultValue; @@ -61,9 +61,9 @@ function getCookie(cname, defaultValue = 'false') { */ function setCookie(cname, cvalue, exdays = 30) { const d = new Date(); - d.setTime(d.getTime() + (exdays*24*60*60*1000)); - let expires = "expires="+ d.toUTCString(); - document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; + d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); + const expires = `expires=${d.toUTCString()}`; + document.cookie = `${cname}=${cvalue};${expires};path=/`; } /** @@ -71,9 +71,9 @@ function setCookie(cname, cvalue, exdays = 30) { * @param {String} value */ function setAriaTheme(value) { - let body = document.body; - if ( value === 'true' ) { - let classesString = body.className || ""; + const { body } = document; + if (value === "true") { + const classesString = body.className || ""; if (classesString.indexOf("is-accessible") === -1) { body.classList.add("is-accessible"); } @@ -86,11 +86,11 @@ function setAriaTheme(value) { * Fonction de (dé)charger le thème accessible */ function switchAriaTheme() { - let body = document.body; + const { body } = document; body.classList.toggle("is-accessible"); - setCookie('ariatheme', body.classList.contains("is-accessible")); + setCookie("ariatheme", body.classList.contains("is-accessible")); } /** @@ -98,46 +98,54 @@ function switchAriaTheme() { * @param {Object} e */ function switchTheme(e) { - const theme = e.target.checked ? 'dark' : 'light'; + const theme = e.target.checked ? "dark" : "light"; - document.documentElement.setAttribute('data-theme', theme); - setCookie('theme', theme); + document.documentElement.setAttribute("data-theme", theme); + setCookie("theme", theme); } /** * Ensemble d'actions effectuées au chargement de la page */ -document.addEventListener('DOMContentLoaded', () => { - const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); +document.addEventListener("DOMContentLoaded", () => { + const $navbarBurgers = Array.prototype.slice.call( + document.querySelectorAll(".navbar-burger"), + 0 + ); if ($navbarBurgers.length > 0) { - $navbarBurgers.forEach( el => { - el.addEventListener('click', () => { - const target = el.dataset.target; + $navbarBurgers.forEach((el) => { + el.addEventListener("click", () => { + const { target } = el.dataset; const $target = document.getElementById(target); - el.classList.toggle('is-active'); - $target.classList.toggle('is-active'); + el.classList.toggle("is-active"); + $target.classList.toggle("is-active"); }); }); } const switchAriaThemeBtn = document.querySelector("#switchAriaTheme"); - if ( switchAriaThemeBtn ) { + if (switchAriaThemeBtn) { switchAriaThemeBtn.addEventListener("click", switchAriaTheme); } - setAriaTheme(getCookie('ariatheme')); + setAriaTheme(getCookie("ariatheme")); - const toggleSwitch = document.querySelector('.theme-switch input[type="checkbox"]'); - if ( toggleSwitch ) { - toggleSwitch.addEventListener('change', switchTheme, false); + const toggleSwitch = document.querySelector( + '.theme-switch input[type="checkbox"]' + ); + if (toggleSwitch) { + toggleSwitch.addEventListener("change", switchTheme, false); } - let currentThemeIsDark = getCookie('theme'); - if ( currentThemeIsDark === 'false' && window.matchMedia ) { - currentThemeIsDark = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; + let currentThemeIsDark = getCookie("theme"); + if (currentThemeIsDark === "false" && window.matchMedia) { + currentThemeIsDark = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light"; } - switchTheme({target: {checked: currentThemeIsDark === 'dark'}}); - if ( toggleSwitch) { - toggleSwitch.checked = currentThemeIsDark === 'dark'; + switchTheme({ target: { checked: currentThemeIsDark === "dark" } }); + if (toggleSwitch) { + toggleSwitch.checked = currentThemeIsDark === "dark"; } -}); \ No newline at end of file +}); diff --git a/javascripts/mon-compte/index.js b/javascripts/mon-compte/index.js index ccc1d02..ed40f03 100644 --- a/javascripts/mon-compte/index.js +++ b/javascripts/mon-compte/index.js @@ -1,16 +1,19 @@ -if ( typeof email !== 'undefined' && typeof username !== 'undefined' ) { +if (typeof email !== "undefined" && typeof username !== "undefined") { Vue.createApp({ data() { return { + // eslint-disable-next-line no-undef email, + // eslint-disable-next-line no-undef username, - oldPassword: '', - password: '', - passwordConfirm: '', + oldPassword: "", + password: "", + passwordConfirm: "", loading: false, - } + }; }, methods: { + // eslint-disable-next-line no-unused-vars async updateProfil(event) { // try { // if ( this.password !== this.passwordConfirm ) { @@ -21,6 +24,6 @@ if ( typeof email !== 'undefined' && typeof username !== 'undefined' ) { // showToastr(err); // } }, - } - }).mount('#mon-compte'); -} \ No newline at end of file + }, + }).mount("#mon-compte"); +} diff --git a/javascripts/mon-compte/ma-collection/details.js b/javascripts/mon-compte/ma-collection/details.js index 3f63290..fe8d079 100644 --- a/javascripts/mon-compte/ma-collection/details.js +++ b/javascripts/mon-compte/ma-collection/details.js @@ -1,17 +1,18 @@ -if ( typeof item !== 'undefined' ) { +if (typeof item !== "undefined") { Vue.createApp({ data() { return { + // eslint-disable-next-line no-undef item, tracklist: [], identifiers: [], modalIsVisible: false, - identifiersMode: 'preview', + identifiersMode: "preview", identifiersPreviewLength: 16, preview: null, index: null, showModalDelete: false, - } + }; }, created() { this.setTrackList(); @@ -20,65 +21,65 @@ if ( typeof item !== 'undefined' ) { window.addEventListener("keydown", this.changeImage); }, destroyed() { - window.removeEventListener('keydown', this.changeImage); + window.removeEventListener("keydown", this.changeImage); }, methods: { setIdentifiers() { this.identifiers = []; - let max = this.identifiersMode == 'preview' && this.item.identifiers.length > this.identifiersPreviewLength ? this.identifiersPreviewLength : this.item.identifiers.length; + const max = + this.identifiersMode === "preview" && + this.item.identifiers.length > this.identifiersPreviewLength + ? this.identifiersPreviewLength + : this.item.identifiers.length; - for ( let i = 0 ; i < max ; i += 1 ) { + for (let i = 0; i < max; i += 1) { this.identifiers.push(this.item.identifiers[i]); } }, setTrackList() { let subTrack = { - type: null, - title: null, - tracks: [], - }; - for (let i = 0 ; i < this.item.tracklist.length ; i += 1 ) { - const { - type_, - title, - position, - duration, - extraartists, - } = this.item.tracklist[i]; + type: null, + title: null, + tracks: [], + }; + for (let i = 0; i < this.item.tracklist.length; i += 1) { + const { type_, title, position, duration, extraartists } = + this.item.tracklist[i]; - if ( type_ === 'heading' ) { - if ( subTrack.type ) { - this.tracklist.push(subTrack); - subTrack = { - type: null, - title: null, - tracks: [], - }; + if (type_ === "heading") { + if (subTrack.type) { + this.tracklist.push(subTrack); + subTrack = { + type: null, + title: null, + tracks: [], + }; + } + + subTrack.type = type_; + subTrack.title = title; + } else { + subTrack.tracks.push({ + title, + position, + duration, + extraartists, + }); } - - subTrack.type = type_; - subTrack.title = title; - } else { - subTrack.tracks.push({ - title, - position, - duration, - extraartists - }); } - } - this.tracklist.push(subTrack); + this.tracklist.push(subTrack); }, setImage() { this.preview = this.item.images[this.index].uri; }, showGallery(event) { - const item = event.target.tagName === 'IMG' ? event.target.parentElement : event.target; + const item = + event.target.tagName === "IMG" + ? event.target.parentElement + : event.target; - const { - index, - } = item.dataset; + const { index } = item.dataset; this.index = Number(index); this.modalIsVisible = true; @@ -89,48 +90,63 @@ if ( typeof item !== 'undefined' ) { this.modalIsVisible = !this.modalIsVisible; }, previous() { - this.index = this.index > 0 ? this.index - 1 : this.item.images.length -1; + this.index = + this.index > 0 + ? this.index - 1 + : this.item.images.length - 1; this.setImage(); }, next() { - this.index = (this.index +1) === this.item.images.length ? 0 : this.index + 1; + this.index = + this.index + 1 === this.item.images.length + ? 0 + : this.index + 1; this.setImage(); }, changeImage(event) { const direction = event.code; - if ( this.modalIsVisible && ['ArrowRight', 'ArrowLeft', 'Escape'].indexOf(direction) !== -1 ) { + if ( + this.modalIsVisible && + ["ArrowRight", "ArrowLeft", "Escape"].indexOf(direction) !== + -1 + ) { switch (direction) { - case 'ArrowRight': + case "ArrowRight": return this.next(); - case 'ArrowLeft': + case "ArrowLeft": return this.previous(); default: this.modalIsVisible = false; return true; } } + + return true; }, showAllIdentifiers() { - this.identifiersMode = 'all'; + this.identifiersMode = "all"; this.setIdentifiers(); }, showLessIdentifiers() { - this.identifiersMode = 'preview'; + this.identifiersMode = "preview"; this.setIdentifiers(); - document.querySelector('#identifiers').scrollIntoView({ behavior: 'smooth' }); + document + .querySelector("#identifiers") + .scrollIntoView({ behavior: "smooth" }); }, showConfirmDelete() { - this.toggleModal(); + this.toggleModalDelete(); }, - toggleModal() { + toggleModalDelete() { this.showModalDelete = !this.showModalDelete; }, updateItem() { showToastr("Mise à jour en cours…", true); - axios.patch(`/api/v1/albums/${this.item._id}`) - .then( (res) => { + axios + .patch(`/api/v1/albums/${this.item._id}`) + .then((res) => { showToastr("Mise à jour réalisée avec succès", true); this.item = res.data; @@ -139,24 +155,32 @@ if ( typeof item !== 'undefined' ) { this.showLessIdentifiers(); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de mettre à jour cet album", false); + showToastr( + err.response?.data?.message || + "Impossible de mettre à jour cet album", + false + ); }); }, deleteItem() { - axios.delete(`/api/v1/albums/${this.item._id}`) - .then( () => { + axios + .delete(`/api/v1/albums/${this.item._id}`) + .then(() => { window.location.href = "/ma-collection"; }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + showToastr( + err.response?.data?.message || + "Impossible de supprimer cet album" + ); }) .finally(() => { - this.toggleModal(); + this.toggleModalDelete(); }); }, goToArtist() { return ""; }, }, - }).mount('#ma-collection-details'); -} \ No newline at end of file + }).mount("#ma-collection-details"); +} diff --git a/javascripts/mon-compte/ma-collection/exporter.js b/javascripts/mon-compte/ma-collection/exporter.js index f5dcbcb..b391436 100644 --- a/javascripts/mon-compte/ma-collection/exporter.js +++ b/javascripts/mon-compte/ma-collection/exporter.js @@ -1,18 +1,16 @@ Vue.createApp({ data() { return { - format: 'xml', - } - }, - created() { - }, - destroyed() { + format: "xml", + }; }, + created() {}, + destroyed() {}, methods: { exportCollection(event) { event.preventDefault(); - window.open(`/api/v1/albums?exportFormat=${this.format}`, '_blank'); - } + window.open(`/api/v1/albums?exportFormat=${this.format}`, "_blank"); + }, }, -}).mount('#exporter'); \ No newline at end of file +}).mount("#exporter"); diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js index 7e2bdf5..967e3f2 100644 --- a/javascripts/mon-compte/ma-collection/index.js +++ b/javascripts/mon-compte/ma-collection/index.js @@ -1,4 +1,4 @@ -if ( typeof isPublicCollection !== 'undefined' ) { +if (typeof isPublicCollection !== "undefined") { Vue.createApp({ data() { return { @@ -9,20 +9,21 @@ if ( typeof isPublicCollection !== 'undefined' ) { page: 1, totalPages: 1, limit: 16, - artist: '', - format: '', - year: '', - genre: '', - style: '', - sortOrder: 'artists_sort-asc', - sort: 'artists_sort', - order: 'asc', + artist: "", + format: "", + year: "", + genre: "", + style: "", + sortOrder: "artists_sort-asc", + sort: "artists_sort", + order: "asc", itemId: null, showModalDelete: false, showModalShare: false, shareLink: `${protocol}//${host}/collection/<%= user._id %>`, + // eslint-disable-next-line no-undef isPublicCollection, - } + }; }, created() { this.fetch(); @@ -36,41 +37,49 @@ if ( typeof isPublicCollection !== 'undefined' ) { const urlParams = new URLSearchParams(queryString); const entries = urlParams.entries(); - for(const entry of entries) { - switch(entry[0]) { - case 'artists_sort': - this.artist = entry[1]; + // eslint-disable-next-line no-restricted-syntax + for (const entry of entries) { + const [key, value] = entry; + switch (key) { + case "artists_sort": + this.artist = value; break; default: - this[entry[0]] = entry[1]; + this[key] = value; } } let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; - if ( this.artist ) { - url += `&artists_sort=${this.artist.replace('&', '%26')}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; } - if ( this.format ) { - url += `&format=${this.format.replace('&', '%26')}`; + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; } - if ( this.year ) { + if (this.year) { url += `&year=${this.year}`; } - if ( this.genre ) { - url += `&genre=${this.genre.replace('&', '%26')}`; + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; } - if ( this.style ) { - url += `&style=${this.style.replace('&', '%26')}`; + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; } - axios.get(url) - .then( response => { + axios + .get(url) + .then((response) => { this.items = response.data.rows; this.total = response.data.count || 0; - this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0); + this.totalPages = + parseInt(response.data.count / this.limit, 10) + + (response.data.count % this.limit > 0 ? 1 : 0); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de charger votre collection"); + showToastr( + err.response?.data?.message || + "Impossible de charger votre collection" + ); }) .finally(() => { this.loading = false; @@ -78,23 +87,23 @@ if ( typeof isPublicCollection !== 'undefined' ) { }, changeUrl() { let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; - if ( this.artist ) { - url += `&artists_sort=${this.artist.replace('&', '%26')}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; } - if ( this.format ) { - url += `&format=${this.format.replace('&', '%26')}`; + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; } - if ( this.year ) { + if (this.year) { url += `&year=${this.year}`; } - if ( this.genre ) { - url += `&genre=${this.genre.replace('&', '%26')}`; + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; } - if ( this.style ) { - url += `&style=${this.style.replace('&', '%26')}`; + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; } - location.href = url; + window.location.href = url; }, next(event) { event.preventDefault(); @@ -116,7 +125,7 @@ if ( typeof isPublicCollection !== 'undefined' ) { this.changeUrl(); }, changeSort() { - const [sort,order] = this.sortOrder.split('-'); + const [sort, order] = this.sortOrder.split("-"); this.sort = sort; this.order = order; this.page = 1; @@ -142,37 +151,51 @@ if ( typeof isPublicCollection !== 'undefined' ) { this.toggleModal(); }, deleteItem() { - axios.delete(`/api/v1/albums/${this.itemId}`) - .then( () => { + axios + .delete(`/api/v1/albums/${this.itemId}`) + .then(() => { this.fetch(); }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + showToastr( + err.response?.data?.message || + "Impossible de supprimer cet album" + ); }) .finally(() => { this.toggleModal(); }); }, shareCollection() { - axios.patch(`/api/v1/me`, { - isPublicCollection: !this.isPublicCollection, - }) - .then( (res) => { + axios + .patch(`/api/v1/me`, { + isPublicCollection: !this.isPublicCollection, + }) + .then((res) => { this.isPublicCollection = res.data.isPublicCollection; - if ( this.isPublicCollection ) { - showToastr("Votre collection est désormais publique", true); + if (this.isPublicCollection) { + showToastr( + "Votre collection est désormais publique", + true + ); } else { - showToastr("Votre collection n'est plus partagée", true); + showToastr( + "Votre collection n'est plus partagée", + true + ); } }) .catch((err) => { - showToastr(err.response?.data?.message || "Impossible de supprimer cet album"); + showToastr( + err.response?.data?.message || + "Impossible de supprimer cet album" + ); }) .finally(() => { this.toggleModalShare(); }); }, - } - }).mount('#ma-collection'); -} \ No newline at end of file + }, + }).mount("#ma-collection"); +} diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index e8615b4..475ee79 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -169,7 +169,7 @@
- +
-- 2.39.5 From 1d59ee3b7136321af40218b5529abedc4730da88 Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Sun, 30 Oct 2022 21:48:49 +0100 Subject: [PATCH 21/69] Version 1.4 (#67) Co-authored-by: dbroqua Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/67 --- .eslintrc.js | 73 ++++--- .gitignore | 2 +- gulpfile.js | 46 ++++ javascripts/ajouter-un-album.js | 179 ++++++++++++++++ javascripts/collection.js | 141 ++++++++++++ javascripts/conctact.js | 43 ++++ javascripts/main.js | 151 +++++++++++++ javascripts/mon-compte/index.js | 29 +++ .../mon-compte/ma-collection/details.js | 186 ++++++++++++++++ .../mon-compte/ma-collection/exporter.js | 16 ++ javascripts/mon-compte/ma-collection/index.js | 201 ++++++++++++++++++ package.json | 11 +- public/js/main.js | 138 ------------ src/app.js | 8 - views/index.ejs | 5 +- views/pages/ajouter-un-album.ejs | 178 +--------------- views/pages/collection.ejs | 139 +----------- views/pages/mon-compte/index.ejs | 28 +-- .../mon-compte/ma-collection/details.ejs | 167 +-------------- .../mon-compte/ma-collection/exporter.ejs | 25 +-- .../pages/mon-compte/ma-collection/index.ejs | 186 +--------------- views/pages/nous-contacter.ejs | 49 +---- 22 files changed, 1067 insertions(+), 934 deletions(-) create mode 100644 gulpfile.js create mode 100644 javascripts/ajouter-un-album.js create mode 100644 javascripts/collection.js create mode 100644 javascripts/conctact.js create mode 100644 javascripts/main.js create mode 100644 javascripts/mon-compte/index.js create mode 100644 javascripts/mon-compte/ma-collection/details.js create mode 100644 javascripts/mon-compte/ma-collection/exporter.js create mode 100644 javascripts/mon-compte/ma-collection/index.js delete mode 100644 public/js/main.js diff --git a/.eslintrc.js b/.eslintrc.js index 143bf10..1f95ab5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,36 +1,43 @@ module.exports = { - env: { - browser: true, - es2020: true, - node: true, - jquery: true, - }, - extends: ['airbnb-base', 'prettier'], - plugins: ['prettier'], - parserOptions: { - ecmaVersion: 11, - sourceType: 'module', - }, - rules: { - 'prettier/prettier': ['error'], - 'no-underscore-dangle': [ - 'error', - { - allow: ['_id', 'artists_sort', 'type_'], - }, - ], - 'camelcase': [ - 'error', - { - allow: ['artists_sort',] - }, - ], - }, - ignorePatterns: ['public/libs/**/*.js', 'public/js/main.js', 'dist/**'], - overrides: [ - { - files: ['**/*.js'], - excludedFiles: '*.ejs', + env: { + browser: true, + es2020: true, + node: true, + jquery: true, + }, + extends: ["airbnb-base", "prettier"], + plugins: ["prettier"], + parserOptions: { + ecmaVersion: 11, + sourceType: "module", + }, + rules: { + "prettier/prettier": ["error"], + "no-underscore-dangle": [ + "error", + { + allow: ["_id", "artists_sort", "type_"], + }, + ], + camelcase: [ + "error", + { + allow: ["artists_sort"], + }, + ], + }, + ignorePatterns: ["public/libs/**/*.js", "public/js/main.js", "dist/**"], + overrides: [ + { + files: ["**/*.js"], + excludedFiles: "*.ejs", + }, + ], + globals: { + Vue: true, + axios: true, + showToastr: true, + protocol: true, + host: true, }, - ], }; diff --git a/.gitignore b/.gitignore index a8dcba6..3054b59 100644 --- a/.gitignore +++ b/.gitignore @@ -121,6 +121,6 @@ dist dist yarn.lock public/css -public/css +public/js docker-compose.yml dump diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..8e76f38 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,46 @@ +const { parallel, src, dest } = require("gulp"); + +const sourcemaps = require("gulp-sourcemaps"); +const concat = require("gulp-concat"); + +const gulp = require("gulp"); +const uglify = require("gulp-uglify"); +const babel = require("gulp-babel"); + +const sourceJs = "javascripts/**/*.js"; +const sourceRemoteJS = [ + "./node_modules/vue/dist/vue.global.prod.js", + "./node_modules/axios/dist/axios.min.js", +]; + +const destination = "public/js"; + +// TASKS ---------------------------------------------------------------------- + +const compileJs = function () { + return gulp + .src(sourceJs) + .pipe(sourcemaps.init()) + .pipe(concat("main.js")) + .pipe( + babel({ + presets: ["@babel/env"], + }) + ) + .pipe(uglify()) + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(destination)); +}; +const compileRemoteJs = function () { + return gulp + .src(sourceRemoteJS) + .pipe(sourcemaps.init()) + .pipe(concat("libs.js")) + .pipe(sourcemaps.write(".")) + .pipe(gulp.dest(destination)); +}; +// ---------------------------------------------------------------------------- + +// COMMANDS ------------------------------------------------------------------- +exports.default = parallel(compileJs, compileRemoteJs); +// ---------------------------------------------------------------------------- diff --git a/javascripts/ajouter-un-album.js b/javascripts/ajouter-un-album.js new file mode 100644 index 0000000..eb55697 --- /dev/null +++ b/javascripts/ajouter-un-album.js @@ -0,0 +1,179 @@ +Vue.createApp({ + data() { + return { + q: "", + year: "", + country: "", + format: "", + loading: false, + items: [], + details: {}, + modalIsVisible: false, + formats: [ + "Vinyl", + "Acetate", + "Flexi-disc", + "Lathe Cut", + "Mighty Tiny", + "Shellac", + "Sopic", + "Pathé Disc", + "Edison Disc", + "Cylinder", + "CD", + "CDr", + "CDV", + "DVD", + "DVDr", + "HD DVD", + "HD DVD-R", + "Blu-ray", + "Blu-ray-R", + "Ultra HD Blu-ray", + "SACD", + "4-Track Cartridge", + "8-Track Cartridge", + "Cassette", + "DC-International", + "Elcaset", + "PlayTape", + "RCA Tape Cartridge", + "DAT", + "DCC", + "Microcassette", + "NT Cassette", + "Pocket Rocker", + "Revere Magnetic Stereo Tape Ca", + "Tefifon", + "Reel-To-Reel", + "Sabamobil", + "Betacam", + "Betacam SP", + "Betamax", + "Cartrivision", + "MiniDV", + "Super VHS", + "U-matic", + "VHS", + "Video 2000", + "Video8", + "Film Reel", + "HitClips", + "Laserdisc", + "SelectaVision", + "VHD", + "Wire Recording", + "Minidisc", + "MVD", + "UMD", + "Floppy Disk", + "File", + "Memory Stick", + "Hybrid", + "All Media", + "Box Set", + ], + }; + }, + methods: { + search(event) { + event.preventDefault(); + + if (this.loading) { + return false; + } + + this.loading = true; + let url = `/api/v1/search?q=${this.q}`; + + if (this.year) { + url += `&year=${this.year}`; + } + if (this.country) { + url += `&country=${this.country}`; + } + if (this.format) { + url += `&format=${this.format}`; + } + + return axios + .get(url) + .then((response) => { + const { results } = response.data; + const items = []; + + for (let i = 0; i < results.length; i += 1) { + const { + id, + title, + thumb, + year, + country, + format, + genre, + style, + } = results[i]; + items.push({ + id, + title, + thumb, + year, + country, + format, + genre, + style, + }); + } + + this.items = items; + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Aucun résultat trouvé :/" + ); + }) + .finally(() => { + this.loading = false; + }); + }, + toggleModal() { + this.modalIsVisible = !this.modalIsVisible; + }, + loadDetails(discogsId) { + axios + .get(`/api/v1/search/${discogsId}`) + .then((response) => { + const { data } = response; + + this.details = data; + this.toggleModal(); + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de charger les détails de cet album" + ); + }) + .finally(() => { + this.loading = false; + }); + }, + add() { + axios + .post("/api/v1/albums", this.details) + .then(() => { + window.location.href = "/ma-collection"; + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible d'ajouter cet album pour le moment…" + ); + }); + }, + orderedItems(items) { + return items.sort(); + }, + }, +}).mount("#ajouter-album"); diff --git a/javascripts/collection.js b/javascripts/collection.js new file mode 100644 index 0000000..beed2c6 --- /dev/null +++ b/javascripts/collection.js @@ -0,0 +1,141 @@ +if (typeof userId !== "undefined") { + Vue.createApp({ + data() { + return { + loading: false, + moreFilters: false, + items: [], + total: 0, + page: 1, + totalPages: 1, + limit: 16, + artist: "", + format: "", + year: "", + genre: "", + style: "", + sortOrder: "artists_sort-asc", + sort: "artists_sort", + order: "asc", + // eslint-disable-next-line no-undef + userId, + }; + }, + created() { + this.fetch(); + }, + methods: { + fetch() { + this.loading = true; + this.total = 0; + + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const entries = urlParams.entries(); + + // eslint-disable-next-line no-restricted-syntax + for (const entry of entries) { + const [key, value] = entry; + switch (key) { + case "artists_sort": + this.artist = value; + break; + default: + this[key] = value; + } + } + + let url = `/api/v1/albums?userId=${this.userId}&page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; + } + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; + } + if (this.year) { + url += `&year=${this.year}`; + } + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; + } + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; + } + + axios + .get(url) + .then((response) => { + this.items = response.data.rows; + this.total = response.data.count || 0; + this.totalPages = + parseInt(response.data.count / this.limit, 10) + + (response.data.count % this.limit > 0 ? 1 : 0); + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de charger cette collection" + ); + }) + .finally(() => { + this.loading = false; + }); + }, + changeUrl() { + let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; + } + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; + } + if (this.year) { + url += `&year=${this.year}`; + } + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; + } + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; + } + + window.location.href = url; + }, + next(event) { + event.preventDefault(); + + this.page += 1; + + this.changeUrl(); + }, + previous(event) { + event.preventDefault(); + + this.page -= 1; + + this.changeUrl(); + }, + goTo(page) { + this.page = page; + + this.changeUrl(); + }, + changeSort() { + const [sort, order] = this.sortOrder.split("-"); + this.sort = sort; + this.order = order; + this.page = 1; + + this.changeUrl(); + }, + changeFilter() { + this.page = 1; + + this.changeUrl(); + }, + showMoreFilters() { + this.moreFilters = !this.moreFilters; + }, + }, + }).mount("#collection-publique"); +} diff --git a/javascripts/conctact.js b/javascripts/conctact.js new file mode 100644 index 0000000..1fbde34 --- /dev/null +++ b/javascripts/conctact.js @@ -0,0 +1,43 @@ +// eslint-disable-next-line no-undef +if (typeof contactMethod !== "undefined" && contactMethod === "smtp") { + Vue.createApp({ + data() { + return { + email: "", + name: "", + message: "", + captcha: "", + loading: false, + }; + }, + methods: { + send(event) { + event.preventDefault(); + + if (this.loading) { + return false; + } + + this.loading = true; + + const { email, message, name, captcha } = this; + + return axios + .post("/api/v1/contact", { email, name, message, captcha }) + .then(() => { + showToastr("Message correctement envoyé", true); + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible d'envoyer votre message", + false + ); + }) + .finally(() => { + this.loading = false; + }); + }, + }, + }).mount("#contact"); +} diff --git a/javascripts/main.js b/javascripts/main.js new file mode 100644 index 0000000..388ab22 --- /dev/null +++ b/javascripts/main.js @@ -0,0 +1,151 @@ +/* eslint-disable no-unused-vars */ +const { protocol, host } = window.location; + +/** + * Fonction permettant d'afficher un message dans un toastr + * @param {String} message + */ +function showToastr(message, success = false) { + const x = document.getElementById("toastr"); + if (message) { + x.getElementsByTagName("SPAN")[0].innerHTML = message; + } + + x.className = `${x.className} show`.replace("sucess", ""); + if (success) { + x.className = `${x.className} success`; + } + setTimeout(() => { + x.className = x.className.replace("show", ""); + }, 3000); +} + +/** + * Fonction permettant de masquer le toastr + */ +function hideToastr() { + const x = document.getElementById("toastr"); + + x.className = x.className.replace("show", ""); + x.getElementsByTagName("SPAN")[0].innerHTML = ""; +} + +/** + * Fonction permettant de récupérer la valeur d'un cookie + * @param {String} cname + * @param {String} defaultValue + * + * @return {String} + */ +function getCookie(cname, defaultValue = "false") { + const name = `${cname}=`; + const decodedCookie = decodeURIComponent(document.cookie); + const ca = decodedCookie.split(";"); + for (let i = 0; i < ca.length; i += 1) { + let c = ca[i]; + while (c.charAt(0) === " ") { + c = c.substring(1); + } + if (c.indexOf(name) === 0) { + return c.substring(name.length, c.length); + } + } + return defaultValue; +} + +/** + * Fonction permettant de créer un cookie + * @param {String} cname + * @param {String} cvalue + * @param {Number} exdays + */ +function setCookie(cname, cvalue, exdays = 30) { + const d = new Date(); + d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); + const expires = `expires=${d.toUTCString()}`; + document.cookie = `${cname}=${cvalue};${expires};path=/`; +} + +/** + * Fonction de (dé)charger le thème accessible + * @param {String} value + */ +function setAriaTheme(value) { + const { body } = document; + if (value === "true") { + const classesString = body.className || ""; + if (classesString.indexOf("is-accessible") === -1) { + body.classList.add("is-accessible"); + } + } else { + body.classList.remove("is-accessible"); + } +} + +/** + * Fonction de (dé)charger le thème accessible + */ +function switchAriaTheme() { + const { body } = document; + + body.classList.toggle("is-accessible"); + + setCookie("ariatheme", body.classList.contains("is-accessible")); +} + +/** + * Fonction permettant de switcher de thème clair/sombre + * @param {Object} e + */ +function switchTheme(e) { + const theme = e.target.checked ? "dark" : "light"; + + document.documentElement.setAttribute("data-theme", theme); + setCookie("theme", theme); +} + +/** + * Ensemble d'actions effectuées au chargement de la page + */ +document.addEventListener("DOMContentLoaded", () => { + const $navbarBurgers = Array.prototype.slice.call( + document.querySelectorAll(".navbar-burger"), + 0 + ); + if ($navbarBurgers.length > 0) { + $navbarBurgers.forEach((el) => { + el.addEventListener("click", () => { + const { target } = el.dataset; + const $target = document.getElementById(target); + + el.classList.toggle("is-active"); + $target.classList.toggle("is-active"); + }); + }); + } + + const switchAriaThemeBtn = document.querySelector("#switchAriaTheme"); + if (switchAriaThemeBtn) { + switchAriaThemeBtn.addEventListener("click", switchAriaTheme); + } + setAriaTheme(getCookie("ariatheme")); + + const toggleSwitch = document.querySelector( + '.theme-switch input[type="checkbox"]' + ); + if (toggleSwitch) { + toggleSwitch.addEventListener("change", switchTheme, false); + } + + let currentThemeIsDark = getCookie("theme"); + if (currentThemeIsDark === "false" && window.matchMedia) { + currentThemeIsDark = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light"; + } + switchTheme({ target: { checked: currentThemeIsDark === "dark" } }); + if (toggleSwitch) { + toggleSwitch.checked = currentThemeIsDark === "dark"; + } +}); diff --git a/javascripts/mon-compte/index.js b/javascripts/mon-compte/index.js new file mode 100644 index 0000000..ed40f03 --- /dev/null +++ b/javascripts/mon-compte/index.js @@ -0,0 +1,29 @@ +if (typeof email !== "undefined" && typeof username !== "undefined") { + Vue.createApp({ + data() { + return { + // eslint-disable-next-line no-undef + email, + // eslint-disable-next-line no-undef + username, + oldPassword: "", + password: "", + passwordConfirm: "", + loading: false, + }; + }, + methods: { + // eslint-disable-next-line no-unused-vars + async updateProfil(event) { + // try { + // if ( this.password !== this.passwordConfirm ) { + // throw "La confirnation du mot de passe ne correspond pas"; + // } + // } catch(err) { + // event.preventDefault(); + // showToastr(err); + // } + }, + }, + }).mount("#mon-compte"); +} diff --git a/javascripts/mon-compte/ma-collection/details.js b/javascripts/mon-compte/ma-collection/details.js new file mode 100644 index 0000000..fe8d079 --- /dev/null +++ b/javascripts/mon-compte/ma-collection/details.js @@ -0,0 +1,186 @@ +if (typeof item !== "undefined") { + Vue.createApp({ + data() { + return { + // eslint-disable-next-line no-undef + item, + tracklist: [], + identifiers: [], + modalIsVisible: false, + identifiersMode: "preview", + identifiersPreviewLength: 16, + preview: null, + index: null, + showModalDelete: false, + }; + }, + created() { + this.setTrackList(); + this.setIdentifiers(); + + window.addEventListener("keydown", this.changeImage); + }, + destroyed() { + window.removeEventListener("keydown", this.changeImage); + }, + methods: { + setIdentifiers() { + this.identifiers = []; + + const max = + this.identifiersMode === "preview" && + this.item.identifiers.length > this.identifiersPreviewLength + ? this.identifiersPreviewLength + : this.item.identifiers.length; + + for (let i = 0; i < max; i += 1) { + this.identifiers.push(this.item.identifiers[i]); + } + }, + setTrackList() { + let subTrack = { + type: null, + title: null, + tracks: [], + }; + for (let i = 0; i < this.item.tracklist.length; i += 1) { + const { type_, title, position, duration, extraartists } = + this.item.tracklist[i]; + + if (type_ === "heading") { + if (subTrack.type) { + this.tracklist.push(subTrack); + subTrack = { + type: null, + title: null, + tracks: [], + }; + } + + subTrack.type = type_; + subTrack.title = title; + } else { + subTrack.tracks.push({ + title, + position, + duration, + extraartists, + }); + } + } + this.tracklist.push(subTrack); + }, + setImage() { + this.preview = this.item.images[this.index].uri; + }, + showGallery(event) { + const item = + event.target.tagName === "IMG" + ? event.target.parentElement + : event.target; + + const { index } = item.dataset; + + this.index = Number(index); + this.modalIsVisible = true; + + this.setImage(); + }, + toggleModal() { + this.modalIsVisible = !this.modalIsVisible; + }, + previous() { + this.index = + this.index > 0 + ? this.index - 1 + : this.item.images.length - 1; + this.setImage(); + }, + next() { + this.index = + this.index + 1 === this.item.images.length + ? 0 + : this.index + 1; + this.setImage(); + }, + changeImage(event) { + const direction = event.code; + + if ( + this.modalIsVisible && + ["ArrowRight", "ArrowLeft", "Escape"].indexOf(direction) !== + -1 + ) { + switch (direction) { + case "ArrowRight": + return this.next(); + case "ArrowLeft": + return this.previous(); + default: + this.modalIsVisible = false; + return true; + } + } + + return true; + }, + showAllIdentifiers() { + this.identifiersMode = "all"; + this.setIdentifiers(); + }, + showLessIdentifiers() { + this.identifiersMode = "preview"; + this.setIdentifiers(); + + document + .querySelector("#identifiers") + .scrollIntoView({ behavior: "smooth" }); + }, + showConfirmDelete() { + this.toggleModalDelete(); + }, + toggleModalDelete() { + this.showModalDelete = !this.showModalDelete; + }, + updateItem() { + showToastr("Mise à jour en cours…", true); + axios + .patch(`/api/v1/albums/${this.item._id}`) + .then((res) => { + showToastr("Mise à jour réalisée avec succès", true); + this.item = res.data; + + this.setTrackList(); + this.setIdentifiers(); + this.showLessIdentifiers(); + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de mettre à jour cet album", + false + ); + }); + }, + deleteItem() { + axios + .delete(`/api/v1/albums/${this.item._id}`) + .then(() => { + window.location.href = "/ma-collection"; + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de supprimer cet album" + ); + }) + .finally(() => { + this.toggleModalDelete(); + }); + }, + goToArtist() { + return ""; + }, + }, + }).mount("#ma-collection-details"); +} diff --git a/javascripts/mon-compte/ma-collection/exporter.js b/javascripts/mon-compte/ma-collection/exporter.js new file mode 100644 index 0000000..b391436 --- /dev/null +++ b/javascripts/mon-compte/ma-collection/exporter.js @@ -0,0 +1,16 @@ +Vue.createApp({ + data() { + return { + format: "xml", + }; + }, + created() {}, + destroyed() {}, + methods: { + exportCollection(event) { + event.preventDefault(); + + window.open(`/api/v1/albums?exportFormat=${this.format}`, "_blank"); + }, + }, +}).mount("#exporter"); diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js new file mode 100644 index 0000000..967e3f2 --- /dev/null +++ b/javascripts/mon-compte/ma-collection/index.js @@ -0,0 +1,201 @@ +if (typeof isPublicCollection !== "undefined") { + Vue.createApp({ + data() { + return { + loading: false, + moreFilters: false, + items: [], + total: 0, + page: 1, + totalPages: 1, + limit: 16, + artist: "", + format: "", + year: "", + genre: "", + style: "", + sortOrder: "artists_sort-asc", + sort: "artists_sort", + order: "asc", + itemId: null, + showModalDelete: false, + showModalShare: false, + shareLink: `${protocol}//${host}/collection/<%= user._id %>`, + // eslint-disable-next-line no-undef + isPublicCollection, + }; + }, + created() { + this.fetch(); + }, + methods: { + fetch() { + this.loading = true; + this.total = 0; + + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + const entries = urlParams.entries(); + + // eslint-disable-next-line no-restricted-syntax + for (const entry of entries) { + const [key, value] = entry; + switch (key) { + case "artists_sort": + this.artist = value; + break; + default: + this[key] = value; + } + } + + let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; + } + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; + } + if (this.year) { + url += `&year=${this.year}`; + } + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; + } + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; + } + + axios + .get(url) + .then((response) => { + this.items = response.data.rows; + this.total = response.data.count || 0; + this.totalPages = + parseInt(response.data.count / this.limit, 10) + + (response.data.count % this.limit > 0 ? 1 : 0); + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de charger votre collection" + ); + }) + .finally(() => { + this.loading = false; + }); + }, + changeUrl() { + let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`; + if (this.artist) { + url += `&artists_sort=${this.artist.replace("&", "%26")}`; + } + if (this.format) { + url += `&format=${this.format.replace("&", "%26")}`; + } + if (this.year) { + url += `&year=${this.year}`; + } + if (this.genre) { + url += `&genre=${this.genre.replace("&", "%26")}`; + } + if (this.style) { + url += `&style=${this.style.replace("&", "%26")}`; + } + + window.location.href = url; + }, + next(event) { + event.preventDefault(); + + this.page += 1; + + this.changeUrl(); + }, + previous(event) { + event.preventDefault(); + + this.page -= 1; + + this.changeUrl(); + }, + goTo(page) { + this.page = page; + + this.changeUrl(); + }, + changeSort() { + const [sort, order] = this.sortOrder.split("-"); + this.sort = sort; + this.order = order; + this.page = 1; + + this.changeUrl(); + }, + changeFilter() { + this.page = 1; + + this.changeUrl(); + }, + showMoreFilters() { + this.moreFilters = !this.moreFilters; + }, + toggleModal() { + this.showModalDelete = !this.showModalDelete; + }, + toggleModalShare() { + this.showModalShare = !this.showModalShare; + }, + showConfirmDelete(itemId) { + this.itemId = itemId; + this.toggleModal(); + }, + deleteItem() { + axios + .delete(`/api/v1/albums/${this.itemId}`) + .then(() => { + this.fetch(); + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de supprimer cet album" + ); + }) + .finally(() => { + this.toggleModal(); + }); + }, + shareCollection() { + axios + .patch(`/api/v1/me`, { + isPublicCollection: !this.isPublicCollection, + }) + .then((res) => { + this.isPublicCollection = res.data.isPublicCollection; + + if (this.isPublicCollection) { + showToastr( + "Votre collection est désormais publique", + true + ); + } else { + showToastr( + "Votre collection n'est plus partagée", + true + ); + } + }) + .catch((err) => { + showToastr( + err.response?.data?.message || + "Impossible de supprimer cet album" + ); + }) + .finally(() => { + this.toggleModalShare(); + }); + }, + }, + }).mount("#ma-collection"); +} diff --git a/package.json b/package.json index 2ee4492..b3dffed 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,10 @@ "description": "Simple application to manage your CD/Vinyl collection", "scripts": { "start": "node ./dist/bin/www", - "run:all": "npm-run-all build sass start", + "run:all": "npm-run-all build sass uglify start", "watch": "nodemon -e js,scss", "sass": "npx sass sass/index.scss public/css/main.css -s compressed --color", + "uglify": "npx gulp", "prebuild": "rimraf dist", "build": "babel ./src --out-dir dist --copy-files", "test": "jest", @@ -55,6 +56,11 @@ "excel4node": "^1.7.2", "express": "^4.17.2", "express-session": "^1.17.2", + "gulp": "^4.0.2", + "gulp-babel": "^8.0.0", + "gulp-concat": "^2.6.1", + "gulp-sourcemaps": "^3.0.0", + "gulp-uglify": "^3.0.2", "joi": "^17.6.0", "knacss": "^8.0.4", "mongoose": "^6.2.1", @@ -75,7 +81,8 @@ "exec": "yarn run:all", "watch": [ "src/*", - "sass/*" + "sass/*", + "javascripts/*" ], "ignore": [ "**/__tests__/**", diff --git a/public/js/main.js b/public/js/main.js deleted file mode 100644 index f203a63..0000000 --- a/public/js/main.js +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Fonction permettant d'afficher un message dans un toastr - * @param {String} message - */ - function showToastr(message, success = false) { - let x = document.getElementById("toastr"); - if ( message ) { - x.getElementsByTagName("SPAN")[0].innerHTML = message; - } - - x.className = `${x.className} show`.replace("sucess", ""); - if ( success ) { - x.className = `${x.className} success`; - } - setTimeout(function(){ x.className = x.className.replace("show", ""); }, 3000); -}; - -/** - * Fonction permettant de masquer le toastr - */ -function hideToastr() { - let x = document.getElementById("toastr"); - - x.className = x.className.replace("show", ""); - x.getElementsByTagName("SPAN")[0].innerHTML = ""; -} - -/** - * Fonction permettant de récupérer la valeur d'un cookie - * @param {String} cname - * @param {String} defaultValue - * - * @return {String} - */ -function getCookie(cname, defaultValue = 'false') { - let name = cname + "="; - let decodedCookie = decodeURIComponent(document.cookie); - let ca = decodedCookie.split(';'); - for(let i = 0; i < ca.length; i+=1) { - let c = ca[i]; - while (c.charAt(0) == ' ') { - c = c.substring(1); - } - if (c.indexOf(name) == 0) { - return c.substring(name.length, c.length); - } - } - return defaultValue; -} - -/** - * Fonction permettant de créer un cookie - * @param {String} cname - * @param {String} cvalue - * @param {Number} exdays - */ -function setCookie(cname, cvalue, exdays = 30) { - const d = new Date(); - d.setTime(d.getTime() + (exdays*24*60*60*1000)); - let expires = "expires="+ d.toUTCString(); - document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; -} - -/** - * Fonction de (dé)charger le thème accessible - * @param {String} value - */ -function setAriaTheme(value) { - let body = document.body; - if ( value === 'true' ) { - let classesString = body.className || ""; - if (classesString.indexOf("is-accessible") === -1) { - body.classList.add("is-accessible"); - } - } else { - body.classList.remove("is-accessible"); - } -} - -/** - * Fonction de (dé)charger le thème accessible - */ -function switchAriaTheme() { - let body = document.body; - - body.classList.toggle("is-accessible"); - - setCookie('ariatheme', body.classList.contains("is-accessible")); -} - -/** - * Fonction permettant de switcher de thème clair/sombre - * @param {Object} e - */ -function switchTheme(e) { - const theme = e.target.checked ? 'dark' : 'light'; - - document.documentElement.setAttribute('data-theme', theme); - setCookie('theme', theme); -} - -/** - * Ensemble d'actions effectuées au chargement de la page - */ -document.addEventListener('DOMContentLoaded', () => { - const $navbarBurgers = Array.prototype.slice.call(document.querySelectorAll('.navbar-burger'), 0); - if ($navbarBurgers.length > 0) { - $navbarBurgers.forEach( el => { - el.addEventListener('click', () => { - const target = el.dataset.target; - const $target = document.getElementById(target); - - el.classList.toggle('is-active'); - $target.classList.toggle('is-active'); - }); - }); - } - - const switchAriaThemeBtn = document.querySelector("#switchAriaTheme"); - if ( switchAriaThemeBtn ) { - switchAriaThemeBtn.addEventListener("click", switchAriaTheme); - } - setAriaTheme(getCookie('ariatheme')); - - const toggleSwitch = document.querySelector('.theme-switch input[type="checkbox"]'); - if ( toggleSwitch ) { - toggleSwitch.addEventListener('change', switchTheme, false); - } - - let currentThemeIsDark = getCookie('theme'); - if ( currentThemeIsDark === 'false' && window.matchMedia ) { - currentThemeIsDark = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; - } - switchTheme({target: {checked: currentThemeIsDark === 'dark'}}); - if ( toggleSwitch) { - toggleSwitch.checked = currentThemeIsDark === 'dark'; - } -}); \ No newline at end of file diff --git a/src/app.js b/src/app.js index 7c40a27..6fa4688 100644 --- a/src/app.js +++ b/src/app.js @@ -75,14 +75,6 @@ app.set("views", path.join(__dirname, "../views")); app.set("view engine", "ejs"); app.use(express.static(path.join(__dirname, "../public"))); -app.use( - "/libs/vue", - express.static(path.join(__dirname, "../node_modules/vue/dist")) -); -app.use( - "/libs/axios", - express.static(path.join(__dirname, "../node_modules/axios/dist")) -); app.use("/", indexRouter); app.use("/mon-compte", monCompteRouter); diff --git a/views/index.ejs b/views/index.ejs index 2386a83..a310984 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -16,9 +16,8 @@ - - - + + <% if ( config.matomoUrl ) { %> diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index a7306ff..f32ea67 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -1,4 +1,4 @@ -
+

Ajouter un album

@@ -169,178 +169,4 @@
- - - + \ No newline at end of file diff --git a/views/pages/collection.ejs b/views/pages/collection.ejs index 9960983..3272d8b 100644 --- a/views/pages/collection.ejs +++ b/views/pages/collection.ejs @@ -1,4 +1,4 @@ -
+

Collection de <%= page.username %>

@@ -146,140 +146,5 @@
diff --git a/views/pages/mon-compte/index.ejs b/views/pages/mon-compte/index.ejs index d8f7f6a..af7b634 100644 --- a/views/pages/mon-compte/index.ejs +++ b/views/pages/mon-compte/index.ejs @@ -1,4 +1,4 @@ -
+

Mon compte

@@ -72,28 +72,6 @@
diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index 41c0b24..475ee79 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -1,7 +1,7 @@ -
+

- {{item.artists_sort}} - {{item.title}} + {{item.artists_sort}} - {{item.title}}

@@ -120,14 +120,13 @@
- +
@@ -170,164 +169,12 @@
- +
\ No newline at end of file diff --git a/views/pages/mon-compte/ma-collection/exporter.ejs b/views/pages/mon-compte/ma-collection/exporter.ejs index 13e3f98..28cc559 100644 --- a/views/pages/mon-compte/ma-collection/exporter.ejs +++ b/views/pages/mon-compte/ma-collection/exporter.ejs @@ -1,4 +1,4 @@ -
+

Exporter ma collection

Les formats CSV et Excel sont facilement lisiblent par un humain. Dans ces 2 formats vous trouverez seulement les informations principales de vos albums, à savoir : @@ -44,25 +44,4 @@ Exporter -

- - \ No newline at end of file +
\ No newline at end of file diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index fb67cd3..38993c8 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -1,4 +1,4 @@ -
+

Ma collection @@ -196,185 +196,5 @@

+ const isPublicCollection = <%= user.isPublicCollection ? 'true' : 'false' %>; + \ No newline at end of file diff --git a/views/pages/nous-contacter.ejs b/views/pages/nous-contacter.ejs index 276b6f4..f179135 100644 --- a/views/pages/nous-contacter.ejs +++ b/views/pages/nous-contacter.ejs @@ -1,4 +1,4 @@ -
+

Nous contacter

id="contact" method="POST" action="https://formspree.io/f/<%= config.formspreeId %>" <% } %>>
@@ -34,47 +34,6 @@
-<% if (config.mailMethod === 'smtp' ) { %> - -<% } %> \ No newline at end of file + \ No newline at end of file -- 2.39.5 From a56db99a8160cc52ec46e548df34699dd3647eb9 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Wed, 2 Nov 2022 09:48:05 +0100 Subject: [PATCH 22/69] #69 - Partager ma collection --- javascripts/mon-compte/ma-collection/index.js | 2 +- views/pages/mon-compte/ma-collection/index.ejs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js index 967e3f2..1cfb05e 100644 --- a/javascripts/mon-compte/ma-collection/index.js +++ b/javascripts/mon-compte/ma-collection/index.js @@ -20,7 +20,7 @@ if (typeof isPublicCollection !== "undefined") { itemId: null, showModalDelete: false, showModalShare: false, - shareLink: `${protocol}//${host}/collection/<%= user._id %>`, + shareLink: `${protocol}//${host}/collection/${userId}`, // eslint-disable-next-line no-undef isPublicCollection, }; diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index 38993c8..5dffd9f 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -197,4 +197,5 @@ \ No newline at end of file -- 2.39.5 From 9fe49eca270e9d46221d038c301693f58108aba9 Mon Sep 17 00:00:00 2001 From: Damien Broqua Date: Wed, 2 Nov 2022 09:56:59 +0100 Subject: [PATCH 23/69] Version 1.4.1 (#70) - #69 Partager ma collection Co-authored-by: dbroqua Reviewed-on: https://git.darkou.fr/dbroqua/MusicTopus/pulls/70 --- javascripts/mon-compte/ma-collection/index.js | 2 +- views/pages/mon-compte/ma-collection/index.ejs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js index 967e3f2..1cfb05e 100644 --- a/javascripts/mon-compte/ma-collection/index.js +++ b/javascripts/mon-compte/ma-collection/index.js @@ -20,7 +20,7 @@ if (typeof isPublicCollection !== "undefined") { itemId: null, showModalDelete: false, showModalShare: false, - shareLink: `${protocol}//${host}/collection/<%= user._id %>`, + shareLink: `${protocol}//${host}/collection/${userId}`, // eslint-disable-next-line no-undef isPublicCollection, }; diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index 38993c8..5dffd9f 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -197,4 +197,5 @@ \ No newline at end of file -- 2.39.5 From d446735450e3f15cb9b175cfdfa1c65a904e1779 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 17 Jan 2023 16:24:54 +0100 Subject: [PATCH 24/69] Utilisation de NPX --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b3dffed..b5607af 100644 --- a/package.json +++ b/package.json @@ -5,13 +5,13 @@ "scripts": { "start": "node ./dist/bin/www", "run:all": "npm-run-all build sass uglify start", - "watch": "nodemon -e js,scss", + "watch": "npx nodemon -e js,scss", "sass": "npx sass sass/index.scss public/css/main.css -s compressed --color", "uglify": "npx gulp", "prebuild": "rimraf dist", - "build": "babel ./src --out-dir dist --copy-files", + "build": "npx babel ./src --out-dir dist --copy-files", "test": "jest", - "prepare": "husky install" + "prepare": "npx husky install" }, "engines": { "node": "16.x", @@ -78,7 +78,7 @@ "vue": "^3.2.31" }, "nodemonConfig": { - "exec": "yarn run:all", + "exec": "npm run run:all", "watch": [ "src/*", "sass/*", -- 2.39.5 From dff1d2baf0d057e5542da66014b614052c268908 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 17 Jan 2023 16:37:13 +0100 Subject: [PATCH 25/69] =?UTF-8?q?#75=20-=20Num=C3=A9rotation=20de=20la=20t?= =?UTF-8?q?racklist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- views/pages/mon-compte/ma-collection/details.ejs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index 475ee79..c868db0 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -20,16 +20,16 @@
-- 2.39.5 From 8822056c1f40f6d3e96b4c2f7a37aa996a3e2890 Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 17 Jan 2023 16:54:58 +0100 Subject: [PATCH 26/69] =?UTF-8?q?#76=20-=20Avoir=20plus=20de=20d=C3=A9tail?= =?UTF-8?q?s=20sur=20le=20support=20physique=20sur=20la=20modale=20d'ajout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mon-compte/ma-collection/details.js | 12 ++++++-- views/pages/ajouter-un-album.ejs | 28 +++++++++++++++---- .../mon-compte/ma-collection/details.ejs | 5 ++++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/javascripts/mon-compte/ma-collection/details.js b/javascripts/mon-compte/ma-collection/details.js index fe8d079..5486244 100644 --- a/javascripts/mon-compte/ma-collection/details.js +++ b/javascripts/mon-compte/ma-collection/details.js @@ -38,14 +38,21 @@ if (typeof item !== "undefined") { } }, setTrackList() { + this.tracklist = []; let subTrack = { type: null, title: null, tracks: [], }; for (let i = 0; i < this.item.tracklist.length; i += 1) { - const { type_, title, position, duration, extraartists } = - this.item.tracklist[i]; + const { + type_, + title, + position, + duration, + artists, + extraartists, + } = this.item.tracklist[i]; if (type_ === "heading") { if (subTrack.type) { @@ -65,6 +72,7 @@ if (typeof item !== "undefined") { position, duration, extraartists, + artists, }); } } diff --git a/views/pages/ajouter-un-album.ejs b/views/pages/ajouter-un-album.ejs index f32ea67..2141963 100644 --- a/views/pages/ajouter-un-album.ejs +++ b/views/pages/ajouter-un-album.ejs @@ -1,7 +1,7 @@

Ajouter un album

-
+
@@ -87,7 +87,14 @@
    -
  1. {{ track.title }} ({{track.duration}})
  2. +
  3. + {{ track.title }} ({{track.duration}}) +
      +
    • + {{extra.role}} : {{extra.name}} +
    • +
    +
@@ -129,10 +136,19 @@
Format -
- - {{format.name}} - +
    +
  • + {{format.name}} + + +
  • +

diff --git a/views/pages/mon-compte/ma-collection/details.ejs b/views/pages/mon-compte/ma-collection/details.ejs index c868db0..dc56b3a 100644 --- a/views/pages/mon-compte/ma-collection/details.ejs +++ b/views/pages/mon-compte/ma-collection/details.ejs @@ -23,6 +23,11 @@
  • {{track.position || (index+1)}} - {{ track.title }} +
      +
    • + {{extra.name}} +
    • +
    • {{extra.role}} : {{extra.name}} -- 2.39.5 From fe3ed3e91f68d9de7be1c57b44ff0a93ec6fd58f Mon Sep 17 00:00:00 2001 From: dbroqua Date: Tue, 17 Jan 2023 17:08:41 +0100 Subject: [PATCH 27/69] #73 - Savoir sur quelle page on est --- javascripts/mon-compte/ma-collection/index.js | 6 +++++- views/pages/mon-compte/ma-collection/index.ejs | 7 ++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/javascripts/mon-compte/ma-collection/index.js b/javascripts/mon-compte/ma-collection/index.js index 1cfb05e..a2d93c5 100644 --- a/javascripts/mon-compte/ma-collection/index.js +++ b/javascripts/mon-compte/ma-collection/index.js @@ -6,7 +6,8 @@ if (typeof isPublicCollection !== "undefined") { moreFilters: false, items: [], total: 0, - page: 1, + // eslint-disable-next-line no-undef + page: query.page || 1, totalPages: 1, limit: 16, artist: "", @@ -20,9 +21,12 @@ if (typeof isPublicCollection !== "undefined") { itemId: null, showModalDelete: false, showModalShare: false, + // eslint-disable-next-line no-undef shareLink: `${protocol}//${host}/collection/${userId}`, // eslint-disable-next-line no-undef isPublicCollection, + // eslint-disable-next-line no-undef + query, }; }, created() { diff --git a/views/pages/mon-compte/ma-collection/index.ejs b/views/pages/mon-compte/ma-collection/index.ejs index 5dffd9f..c061d9a 100644 --- a/views/pages/mon-compte/ma-collection/index.ejs +++ b/views/pages/mon-compte/ma-collection/index.ejs @@ -135,12 +135,12 @@