Compare commits
25 commits
Author | SHA1 | Date | |
---|---|---|---|
|
478b54da53 | ||
|
c94bfb570a | ||
|
3d3228f12d | ||
|
f5d49d27b4 | ||
|
14f46ae112 | ||
|
48287f2bae | ||
|
8289ed7116 | ||
|
e3f47a7bf7 | ||
|
948ccf9419 | ||
|
47a8935ec5 | ||
|
ad8b8f4767 | ||
|
6ee117b583 | ||
|
4f707ece88 | ||
|
a4a3933c6d | ||
|
bed5139a27 | ||
|
5b2758afca | ||
|
68414e3e71 | ||
|
d692090022 | ||
|
061e72c459 | ||
|
bf2e9be3b7 | ||
|
d4e6d23459 | ||
|
0ea6a21b90 | ||
|
6b2f7b61cb | ||
|
f1220fc05a | ||
|
8d22435b90 |
64 changed files with 2437 additions and 615 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -124,3 +124,4 @@ public/css
|
||||||
public/js
|
public/js
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
dump
|
dump
|
||||||
|
data
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
MusicTopus est une application Web (que vous pouvez auto-héberger) et un site Web (sur lequel vous pouvez créer un compte) permettant de gérer votre liste des CDs et Vinyles et de l'utiliser facilement n'importe où.
|
MusicTopus est une application Web (que vous pouvez auto-héberger) et un site Web (sur lequel vous pouvez créer un compte) permettant de gérer votre liste des CDs et Vinyles et de l'utiliser facilement n'importe où.
|
||||||
|
|
||||||
Le code source est publié sous licence libre [GNU GPL-3.0-or-later](LICENSE) et est disponible sur [git.darkou.fr](https://git.darkou.fr/dbroqua/MusicTopus).
|
Le code source est publié sous licence libre [GNU GPL-3.0-or-later](LICENSE) et est disponible sur [forge.darkou.fr](https://forge.darkou.fr/contact/MusicTopus).
|
||||||
|
|
||||||
## Utilisation
|
## Utilisation
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ En mode standalone il vous faudra :
|
||||||
Quelque que soit la méthode, la première étape est de cloner le projet :
|
Quelque que soit la méthode, la première étape est de cloner le projet :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.darkou.fr/dbroqua/MusicTopus.git
|
git clone https://forge.darkou.fr/contact/MusicTopus.git
|
||||||
```
|
```
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
@ -240,5 +240,5 @@ MAIL_TO # Adresse mail du contact qui recevra les messages de la page "nous cont
|
||||||
|
|
||||||
## Contributeurs
|
## Contributeurs
|
||||||
|
|
||||||
- Damien Broqua (développeur principal du projet)
|
- [DarKou](https://darkou.link/) (développeur principal du projet)
|
||||||
- Brunus (Logo et fournisseur d'idées :wink: )
|
- [Brunus](https://www.brunuslab.net/) (Logo et fournisseur d'idées 😉 )
|
||||||
|
|
|
@ -55,6 +55,7 @@ services:
|
||||||
- musictopus
|
- musictopus
|
||||||
volumes:
|
volumes:
|
||||||
- ./dump:/dump
|
- ./dump:/dump
|
||||||
|
- ./data:/data/db
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
musictopus:
|
musictopus:
|
||||||
|
|
|
@ -6,6 +6,12 @@
|
||||||
"units_per_em": 1000,
|
"units_per_em": 1000,
|
||||||
"ascent": 850,
|
"ascent": 850,
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"uid": "ca90da02d2c6a3183f2458e4dc416285",
|
||||||
|
"css": "adjust",
|
||||||
|
"code": 59408,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"uid": "44e04715aecbca7f266a17d5a7863c68",
|
"uid": "44e04715aecbca7f266a17d5a7863c68",
|
||||||
"css": "plus",
|
"css": "plus",
|
||||||
|
|
|
@ -10,6 +10,7 @@ const babel = require("gulp-babel");
|
||||||
const sourceJs = "javascripts/**/*.js";
|
const sourceJs = "javascripts/**/*.js";
|
||||||
const sourceRemoteJS = [
|
const sourceRemoteJS = [
|
||||||
"./node_modules/vue/dist/vue.global.prod.js",
|
"./node_modules/vue/dist/vue.global.prod.js",
|
||||||
|
"./node_modules/chart.js/dist/chart.umd.js",
|
||||||
"./node_modules/axios/dist/axios.min.js",
|
"./node_modules/axios/dist/axios.min.js",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-undef */
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -78,6 +79,12 @@ Vue.createApp({
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
created() {
|
||||||
|
window.addEventListener("keydown", this.keyDown);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
window.removeEventListener("keydown", this.keyDown);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
search(event) {
|
search(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -171,12 +178,15 @@ Vue.createApp({
|
||||||
this.submitting = true;
|
this.submitting = true;
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.post("/api/v1/albums", {
|
.post(`/api/v1/${action}`, {
|
||||||
album: this.details,
|
album: this.details,
|
||||||
share: this.share,
|
share: this.share,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.location.href = "/ma-collection";
|
window.location.href =
|
||||||
|
action === "albums"
|
||||||
|
? "/ma-collection"
|
||||||
|
: "/ma-liste-de-souhaits";
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.submitting = false;
|
this.submitting = false;
|
||||||
|
@ -189,5 +199,13 @@ Vue.createApp({
|
||||||
orderedItems(items) {
|
orderedItems(items) {
|
||||||
return items.sort();
|
return items.sort();
|
||||||
},
|
},
|
||||||
|
keyDown(event) {
|
||||||
|
const keycode = event.code;
|
||||||
|
|
||||||
|
if (this.modalIsVisible && keycode === "Escape") {
|
||||||
|
event.preventDefault();
|
||||||
|
this.modalIsVisible = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}).mount("#ajouter-album");
|
}).mount("#ajouter-album");
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-undef */
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -7,8 +8,8 @@ Vue.createApp({
|
||||||
total: 0,
|
total: 0,
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
page: query.page || 1,
|
page: query.page || 1,
|
||||||
totalPages: 1,
|
|
||||||
limit: 16,
|
limit: 16,
|
||||||
|
totalPages: 1,
|
||||||
artist: "",
|
artist: "",
|
||||||
format: "",
|
format: "",
|
||||||
year: "",
|
year: "",
|
||||||
|
@ -34,10 +35,18 @@ Vue.createApp({
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
|
|
||||||
|
window.addEventListener("keydown", this.keyDown);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
window.removeEventListener("keydown", this.keyDown);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatParams(param) {
|
formatParams(param) {
|
||||||
return param.replace("&", "%26").replace("+", "%2B");
|
return param
|
||||||
|
.replace("&", "%26")
|
||||||
|
.replace("+", "%2B")
|
||||||
|
.replace('"', "%22");
|
||||||
},
|
},
|
||||||
fetch() {
|
fetch() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
|
@ -57,7 +66,7 @@ Vue.createApp({
|
||||||
const [key, value] = entry;
|
const [key, value] = entry;
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "artists_sort":
|
case "artists_sort":
|
||||||
this.artist = value;
|
this.artist = value.replaceAll('"', "%22");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (["order", "sort"].indexOf(key) !== -1) {
|
if (["order", "sort"].indexOf(key) !== -1) {
|
||||||
|
@ -69,7 +78,7 @@ Vue.createApp({
|
||||||
|
|
||||||
this.sortOrder = `${sortOrder.sort}-${sortOrder.order}`;
|
this.sortOrder = `${sortOrder.sort}-${sortOrder.order}`;
|
||||||
|
|
||||||
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
let url = `/api/v1/${action}?page=${this.page}&sort=${this.sort}&order=${this.order}`;
|
||||||
if (this.artist) {
|
if (this.artist) {
|
||||||
url += `&artist=${this.formatParams(this.artist)}`;
|
url += `&artist=${this.formatParams(this.artist)}`;
|
||||||
}
|
}
|
||||||
|
@ -94,6 +103,7 @@ Vue.createApp({
|
||||||
.get(url)
|
.get(url)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
this.items = response.data.rows;
|
this.items = response.data.rows;
|
||||||
|
this.limit = response.data.limit;
|
||||||
this.total = response.data.count || 0;
|
this.total = response.data.count || 0;
|
||||||
this.totalPages =
|
this.totalPages =
|
||||||
parseInt(response.data.count / this.limit, 10) +
|
parseInt(response.data.count / this.limit, 10) +
|
||||||
|
@ -180,7 +190,7 @@ Vue.createApp({
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return axios
|
return axios
|
||||||
.delete(`/api/v1/albums/${this.itemId}`)
|
.delete(`/api/v1/${action}/${this.itemId}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
})
|
})
|
||||||
|
@ -240,5 +250,16 @@ Vue.createApp({
|
||||||
|
|
||||||
return render;
|
return render;
|
||||||
},
|
},
|
||||||
|
keyDown(event) {
|
||||||
|
const keycode = event.code;
|
||||||
|
if (this.showModalDelete && keycode === "Escape") {
|
||||||
|
event.preventDefault();
|
||||||
|
this.showModalDelete = false;
|
||||||
|
}
|
||||||
|
if (this.showModalShare && keycode === "Escape") {
|
||||||
|
event.preventDefault();
|
||||||
|
this.showModalShare = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}).mount("#collection");
|
}).mount("#collection");
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
if (typeof email !== "undefined" && typeof username !== "undefined") {
|
if (typeof email !== "undefined" && typeof username !== "undefined") {
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
|
@ -12,15 +11,21 @@ if (typeof email !== "undefined" && typeof username !== "undefined") {
|
||||||
password: "",
|
password: "",
|
||||||
passwordConfirm: "",
|
passwordConfirm: "",
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
|
pagination,
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
mastodon: mastodon || {
|
mastodon: mastodon || {
|
||||||
publish: false,
|
publish: false,
|
||||||
url: "",
|
url: "",
|
||||||
token: "",
|
token: "",
|
||||||
message:
|
message:
|
||||||
"Je viens d'ajouter {artist} - {album} à ma collection !",
|
"Je viens d'ajouter {artist} - {album} à ma collection !",
|
||||||
|
wantlist:
|
||||||
|
"Je viens d'ajouter {artist} - {album} à ma liste de souhaits !",
|
||||||
},
|
},
|
||||||
|
delete: false,
|
||||||
},
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
|
deleting: false,
|
||||||
errors: [],
|
errors: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -57,8 +62,13 @@ if (typeof email !== "undefined" && typeof username !== "undefined") {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
async updateProfil() {
|
async updateProfil() {
|
||||||
this.errors = [];
|
this.errors = [];
|
||||||
const { oldPassword, password, passwordConfirm, mastodon } =
|
const {
|
||||||
this.formData;
|
oldPassword,
|
||||||
|
password,
|
||||||
|
passwordConfirm,
|
||||||
|
mastodon,
|
||||||
|
pagination,
|
||||||
|
} = this.formData;
|
||||||
|
|
||||||
if (password && !oldPassword) {
|
if (password && !oldPassword) {
|
||||||
this.errors.push("emptyPassword");
|
this.errors.push("emptyPassword");
|
||||||
|
@ -83,6 +93,8 @@ if (typeof email !== "undefined" && typeof username !== "undefined") {
|
||||||
data.oldPassword = oldPassword;
|
data.oldPassword = oldPassword;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.pagination = pagination;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await axios.patch(`/api/v1/me`, data);
|
await axios.patch(`/api/v1/me`, data);
|
||||||
|
|
||||||
|
@ -98,6 +110,20 @@ if (typeof email !== "undefined" && typeof username !== "undefined") {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
async deleteAccount() {
|
||||||
|
try {
|
||||||
|
await axios.delete(`/api/v1/me`);
|
||||||
|
|
||||||
|
showToastr("Compte supprimé", true);
|
||||||
|
|
||||||
|
window.location.href = "/se-deconnecter";
|
||||||
|
} catch (err) {
|
||||||
|
showToastr(
|
||||||
|
err.response?.data?.message ||
|
||||||
|
"Impossible de mettre à jour votre profil"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}).mount("#mon-compte");
|
}).mount("#mon-compte");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-undef */
|
||||||
if (typeof item !== "undefined") {
|
if (typeof item !== "undefined") {
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
|
@ -25,10 +26,10 @@ if (typeof item !== "undefined") {
|
||||||
this.setTrackList();
|
this.setTrackList();
|
||||||
this.setIdentifiers();
|
this.setIdentifiers();
|
||||||
|
|
||||||
window.addEventListener("keydown", this.changeImage);
|
window.addEventListener("keydown", this.keyDown);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
window.removeEventListener("keydown", this.changeImage);
|
window.removeEventListener("keydown", this.keyDown);
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
shareMessage(message) {
|
shareMessage(message) {
|
||||||
|
@ -40,6 +41,8 @@ if (typeof item !== "undefined") {
|
||||||
this.shareMessageTransformed = message
|
this.shareMessageTransformed = message
|
||||||
.replaceAll("{artist}", this.item.artists[0].name)
|
.replaceAll("{artist}", this.item.artists[0].name)
|
||||||
.replaceAll("{format}", this.item.formats[0].name)
|
.replaceAll("{format}", this.item.formats[0].name)
|
||||||
|
.replaceAll("{genres}", this.item.genres.join(", "))
|
||||||
|
.replaceAll("{styles}", this.item.styles.join(", "))
|
||||||
.replaceAll("{year}", this.item.year)
|
.replaceAll("{year}", this.item.year)
|
||||||
.replaceAll("{video}", video)
|
.replaceAll("{video}", video)
|
||||||
.replaceAll("{album}", this.item.title);
|
.replaceAll("{album}", this.item.title);
|
||||||
|
@ -139,10 +142,10 @@ if (typeof item !== "undefined") {
|
||||||
this.setImage();
|
this.setImage();
|
||||||
},
|
},
|
||||||
changeImage(event) {
|
changeImage(event) {
|
||||||
|
event.preventDefault();
|
||||||
const direction = event.code;
|
const direction = event.code;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
this.modalIsVisible &&
|
|
||||||
["ArrowRight", "ArrowLeft", "Escape"].indexOf(direction) !==
|
["ArrowRight", "ArrowLeft", "Escape"].indexOf(direction) !==
|
||||||
-1
|
-1
|
||||||
) {
|
) {
|
||||||
|
@ -159,6 +162,20 @@ if (typeof item !== "undefined") {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
keyDown(event) {
|
||||||
|
const keycode = event.code;
|
||||||
|
if (this.modalIsVisible) {
|
||||||
|
this.changeImage(event);
|
||||||
|
}
|
||||||
|
if (this.showModalDelete && keycode === "Escape") {
|
||||||
|
event.preventDefault();
|
||||||
|
this.showModalDelete = false;
|
||||||
|
}
|
||||||
|
if (this.showModalShare && keycode === "Escape") {
|
||||||
|
event.preventDefault();
|
||||||
|
this.showModalShare = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
showAllIdentifiers() {
|
showAllIdentifiers() {
|
||||||
this.identifiersMode = "all";
|
this.identifiersMode = "all";
|
||||||
this.setIdentifiers();
|
this.setIdentifiers();
|
||||||
|
@ -180,7 +197,7 @@ if (typeof item !== "undefined") {
|
||||||
updateItem() {
|
updateItem() {
|
||||||
showToastr("Mise à jour en cours…", true);
|
showToastr("Mise à jour en cours…", true);
|
||||||
axios
|
axios
|
||||||
.patch(`/api/v1/albums/${this.item._id}`)
|
.patch(`/api/v1/${action}/${this.item._id}`)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
showToastr("Mise à jour réalisée avec succès", true);
|
showToastr("Mise à jour réalisée avec succès", true);
|
||||||
this.item = res.data;
|
this.item = res.data;
|
||||||
|
@ -199,9 +216,12 @@ if (typeof item !== "undefined") {
|
||||||
},
|
},
|
||||||
deleteItem() {
|
deleteItem() {
|
||||||
axios
|
axios
|
||||||
.delete(`/api/v1/albums/${this.item._id}`)
|
.delete(`/api/v1/${action}/${this.item._id}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.location.href = "/ma-collection";
|
window.location.href =
|
||||||
|
action === "albums"
|
||||||
|
? "/ma-collection"
|
||||||
|
: "/ma-liste-de-souhaits";
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
showToastr(
|
showToastr(
|
||||||
|
@ -222,7 +242,7 @@ if (typeof item !== "undefined") {
|
||||||
}
|
}
|
||||||
this.shareSubmiting = true;
|
this.shareSubmiting = true;
|
||||||
axios
|
axios
|
||||||
.post(`/api/v1/albums/${this.item._id}/share`, {
|
.post(`/api/v1/${action}/${this.item._id}/share`, {
|
||||||
message: this.shareMessageTransformed,
|
message: this.shareMessageTransformed,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-undef */
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -10,7 +11,10 @@ Vue.createApp({
|
||||||
exportCollection(event) {
|
exportCollection(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
window.open(`/api/v1/albums?exportFormat=${this.format}`, "_blank");
|
window.open(
|
||||||
|
`/api/v1/${action}?exportFormat=${this.format}`,
|
||||||
|
"_blank"
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).mount("#exporter");
|
}).mount("#exporter");
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable no-undef */
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -66,11 +67,11 @@ Vue.createApp({
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await axios.get(
|
const res = await axios.get(
|
||||||
`/api/v1/albums?discogsId=${release_id}`
|
`/api/v1/${action}?discogsId=${release_id}`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (res.status === 204) {
|
if (res.status === 204) {
|
||||||
await axios.post("/api/v1/albums", {
|
await axios.post(`/api/v1/${action}`, {
|
||||||
discogsId: release_id,
|
discogsId: release_id,
|
||||||
share: false,
|
share: false,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,19 +1,3 @@
|
||||||
/**
|
|
||||||
* Fonction permettant de sauvegarder dans le stockage local le choix du thème
|
|
||||||
* @param {String} scheme
|
|
||||||
*/
|
|
||||||
function saveColorScheme(scheme) {
|
|
||||||
localStorage.setItem("theme", scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fonction permettant de changer le thème du site
|
|
||||||
* @param {String} scheme
|
|
||||||
*/
|
|
||||||
function setColorScheme(scheme) {
|
|
||||||
document.documentElement.setAttribute("data-theme", scheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fonction permettant de récupérer le thème du système
|
* Fonction permettant de récupérer le thème du système
|
||||||
* @return {String}
|
* @return {String}
|
||||||
|
@ -28,10 +12,56 @@ function getPreferredColorScheme() {
|
||||||
return "light";
|
return "light";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} scheme
|
||||||
|
*/
|
||||||
|
function setPictoOnMenu(scheme) {
|
||||||
|
document.querySelectorAll(".icon-theme").forEach((item) => {
|
||||||
|
item.classList.add("hidden");
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.querySelector(`.icon-theme.theme-${scheme}`)
|
||||||
|
.classList.remove("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction permettant de sauvegarder dans le stockage local le choix du thème
|
||||||
|
* @param {String} scheme
|
||||||
|
*/
|
||||||
|
function saveColorScheme(scheme) {
|
||||||
|
localStorage.setItem("theme", scheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction permettant de changer le thème du site
|
||||||
|
* @param {String} scheme
|
||||||
|
*/
|
||||||
|
function setColorScheme(scheme) {
|
||||||
|
document.documentElement.setAttribute(
|
||||||
|
"data-theme",
|
||||||
|
scheme === "system" ? getPreferredColorScheme() : scheme
|
||||||
|
);
|
||||||
|
|
||||||
|
setPictoOnMenu(scheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction déclenchée lorsqu'un utilisateur clique sur un bouton dans le menu déroulant
|
||||||
|
* @param {Object} e
|
||||||
|
*/
|
||||||
|
function changeTheme(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const scheme = this.dataset.value;
|
||||||
|
|
||||||
|
saveColorScheme(scheme);
|
||||||
|
setColorScheme(scheme);
|
||||||
|
}
|
||||||
|
|
||||||
// INFO: On place un event sur le bouton
|
// INFO: On place un event sur le bouton
|
||||||
const toggleSwitch = document.querySelector(
|
const buttonsTheme = document.getElementsByClassName("theme");
|
||||||
'.theme-switch input[type="checkbox"]'
|
// INFO: On récupère du local storage (ou des préférences navigateur) le thème actuel
|
||||||
);
|
const currentTheme = localStorage.getItem("theme") || getPreferredColorScheme();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Event permettant de détecter les changements de thème du système
|
* Event permettant de détecter les changements de thème du système
|
||||||
|
@ -44,28 +74,14 @@ if (window.matchMedia) {
|
||||||
if (selectedColorScheme === "system") {
|
if (selectedColorScheme === "system") {
|
||||||
const preferedColorScheme = getPreferredColorScheme();
|
const preferedColorScheme = getPreferredColorScheme();
|
||||||
setColorScheme(preferedColorScheme);
|
setColorScheme(preferedColorScheme);
|
||||||
|
|
||||||
toggleSwitch.checked = preferedColorScheme === "dark";
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentTheme = localStorage.getItem("theme") || getPreferredColorScheme();
|
|
||||||
|
|
||||||
// INFO: Au chargement de la page on détecte le thème à charger
|
// INFO: Au chargement de la page on détecte le thème à charger
|
||||||
setColorScheme(currentTheme);
|
setColorScheme(currentTheme);
|
||||||
|
|
||||||
toggleSwitch.checked = currentTheme === "dark";
|
// INFO: On place un event au click sur chacun des boutons du menu
|
||||||
|
for (let i = 0; i < buttonsTheme.length; i += 1) {
|
||||||
toggleSwitch.addEventListener(
|
buttonsTheme[i].addEventListener("click", changeTheme, false);
|
||||||
"change",
|
}
|
||||||
(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const scheme = e.target.checked ? "dark" : "light";
|
|
||||||
|
|
||||||
saveColorScheme(scheme);
|
|
||||||
setColorScheme(scheme);
|
|
||||||
},
|
|
||||||
false
|
|
||||||
);
|
|
||||||
|
|
17
package-lock.json
generated
17
package-lock.json
generated
|
@ -15,6 +15,7 @@
|
||||||
"@babel/core": "^7.17.2",
|
"@babel/core": "^7.17.2",
|
||||||
"@babel/preset-env": "^7.16.11",
|
"@babel/preset-env": "^7.16.11",
|
||||||
"axios": "^0.26.0",
|
"axios": "^0.26.0",
|
||||||
|
"chart.js": "^4.4.1",
|
||||||
"connect-ensure-login": "^0.1.1",
|
"connect-ensure-login": "^0.1.1",
|
||||||
"connect-flash": "^0.1.1",
|
"connect-flash": "^0.1.1",
|
||||||
"connect-mongo": "^4.6.0",
|
"connect-mongo": "^4.6.0",
|
||||||
|
@ -5257,6 +5258,11 @@
|
||||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@kurkle/color": {
|
||||||
|
"version": "0.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
|
||||||
|
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
|
||||||
|
},
|
||||||
"node_modules/@mongodb-js/saslprep": {
|
"node_modules/@mongodb-js/saslprep": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.4.tgz",
|
||||||
|
@ -7913,6 +7919,17 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chart.js": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@kurkle/color": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"pnpm": ">=7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.5.3",
|
"version": "3.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git@git.darkou.fr:dbroqua/MusicTopus.git"
|
"url": "git@forge.darkou.fr:contact/MusicTopus.git"
|
||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Damien Broqua",
|
"name": "Damien Broqua",
|
||||||
|
@ -45,6 +45,7 @@
|
||||||
"@babel/core": "^7.17.2",
|
"@babel/core": "^7.17.2",
|
||||||
"@babel/preset-env": "^7.16.11",
|
"@babel/preset-env": "^7.16.11",
|
||||||
"axios": "^0.26.0",
|
"axios": "^0.26.0",
|
||||||
|
"chart.js": "^4.4.1",
|
||||||
"connect-ensure-login": "^0.1.1",
|
"connect-ensure-login": "^0.1.1",
|
||||||
"connect-flash": "^0.1.1",
|
"connect-flash": "^0.1.1",
|
||||||
"connect-mongo": "^4.6.0",
|
"connect-mongo": "^4.6.0",
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
<link href="/css/main.css" rel="stylesheet" />
|
<link href="/css/main.css" rel="stylesheet" />
|
||||||
<script src="/js/main.js"></script>
|
<script src="/js/main.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="body-500">
|
<body class="body-500 flex flex-col align-items-center justify-center">
|
||||||
<img src="/img/404.svg" alt="Image représentant la mascotte tenant un vinyle cassé" />
|
<img src="/img/404.svg" alt="Image représentant la mascotte tenant un vinyle cassé" />
|
||||||
Nous sommes désolé mais quelque chose a mal tourné de notre côté.
|
Nous sommes désolé mais quelque chose a mal tourné de notre côté.
|
||||||
<br />
|
<br />
|
||||||
|
|
Binary file not shown.
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" standalone="no"?>
|
<?xml version="1.0" standalone="no"?>
|
||||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg">
|
<svg xmlns="http://www.w3.org/2000/svg">
|
||||||
<metadata>Copyright (C) 2022 by original authors @ fontello.com</metadata>
|
<metadata>Copyright (C) 2024 by original authors @ fontello.com</metadata>
|
||||||
<defs>
|
<defs>
|
||||||
<font id="icon" horiz-adv-x="1000" >
|
<font id="icon" horiz-adv-x="1000" >
|
||||||
<font-face font-family="icon" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
<font-face font-family="icon" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
||||||
|
@ -28,6 +28,8 @@
|
||||||
|
|
||||||
<glyph glyph-name="refresh" unicode="" d="M843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-36 90-57t105-20q74 0 139 37t104 99q6 10 30 66 4 13 16 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" />
|
<glyph glyph-name="refresh" unicode="" d="M843 261q0-3 0-4-36-150-150-243t-267-93q-81 0-157 31t-136 88l-72-72q-11-11-25-11t-25 11-11 25v250q0 14 11 25t25 11h250q14 0 25-11t10-25-10-25l-77-77q40-36 90-57t105-20q74 0 139 37t104 99q6 10 30 66 4 13 16 13h107q8 0 13-6t5-12z m14 446v-250q0-14-10-25t-26-11h-250q-14 0-25 11t-10 25 10 25l77 77q-82 77-194 77-75 0-140-37t-104-99q-6-10-29-66-5-13-17-13h-111q-7 0-13 6t-5 12v4q36 150 151 243t268 93q81 0 158-31t137-88l72 72q11 11 25 11t26-11 10-25z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="adjust" unicode="" d="M429 46v608q-83 0-153-41t-110-111-41-152 41-152 110-111 153-41z m428 304q0-117-57-215t-156-156-215-58-216 58-155 156-58 215 58 215 155 156 216 58 215-58 156-156 57-215z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
<glyph glyph-name="spin" unicode="" d="M855 9c-189-190-520-172-705 13-190 190-200 494-28 695 11 13 21 26 35 34 36 23 85 18 117-13 30-31 35-76 16-112-5-9-9-15-16-22-140-151-145-379-8-516 153-153 407-121 542 34 106 122 142 297 77 451-83 198-305 291-510 222l0 1c236 82 492-24 588-252 71-167 37-355-72-493-11-15-23-29-36-42z" horiz-adv-x="1000" />
|
<glyph glyph-name="spin" unicode="" d="M855 9c-189-190-520-172-705 13-190 190-200 494-28 695 11 13 21 26 35 34 36 23 85 18 117-13 30-31 35-76 16-112-5-9-9-15-16-22-140-151-145-379-8-516 153-153 407-121 542 34 106 122 142 297 77 451-83 198-305 291-510 222l0 1c236 82 492-24 588-252 71-167 37-355-72-493-11-15-23-29-36-42z" horiz-adv-x="1000" />
|
||||||
|
|
||||||
<glyph glyph-name="link-ext" unicode="" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
<glyph glyph-name="link-ext" unicode="" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
||||||
|
|
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 8.3 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
52
public/img/emoji-lmhf.svg
Normal file
52
public/img/emoji-lmhf.svg
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="101.99548mm"
|
||||||
|
height="105.96579mm"
|
||||||
|
viewBox="0 0 101.99548 105.96579"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
sodipodi:docname="emoji-lmhf.svg"
|
||||||
|
inkscape:version="1.3 (0e150ed, 2023-07-21)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="1.1893044"
|
||||||
|
inkscape:cx="119.39752"
|
||||||
|
inkscape:cy="159.33684"
|
||||||
|
inkscape:window-width="1680"
|
||||||
|
inkscape:window-height="1050"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Calque 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-63.500001,-183.88551)">
|
||||||
|
<path
|
||||||
|
id="path3"
|
||||||
|
style="stroke-width:0.264583"
|
||||||
|
d="M 140.00179 183.88654 C 139.72084 183.87717 139.44206 183.94473 139.04836 184.1134 C 136.28797 185.85595 133.21443 186.98396 130.07992 187.85788 L 129.46032 188.02427 C 128.87929 188.18222 128.29871 188.32762 127.71107 188.45991 L 126.94677 188.61959 C 123.89269 189.25644 120.958 189.51422 117.83777 189.49602 C 117.08741 189.48762 116.33633 189.4799 115.58571 189.4769 C 115.08432 189.4759 114.58332 189.47373 114.08193 189.47173 L 113.36104 189.46708 C 109.28222 189.44618 105.4276 188.68676 101.52817 187.51836 C 99.942257 187.03814 98.350432 186.57803 96.758436 186.11845 L 96.255107 185.97375 C 94.072297 185.34325 91.89001 184.72889 89.640524 184.38625 C 84.279277 183.86476 80.278726 184.58056 76.071327 187.98707 L 75.617607 188.355 C 70.996664 191.93005 66.675167 196.44447 63.581133 201.42604 C 63.44646 202.11343 63.446708 202.11343 63.835381 202.91432 C 64.211883 203.49826 64.563589 204.07506 64.890614 204.68889 L 65.307126 205.50434 L 65.75361 206.37147 C 66.941853 208.68472 68.134949 210.99349 69.388014 213.27234 A 270.71584 270.71584 0 0 1 73.226022 220.49516 C 74.541529 223.05977 75.863803 225.6168 77.254717 228.14172 A 231.14582 231.14582 0 0 1 80.035426 233.33417 C 81.342466 235.86306 82.661834 238.38438 84.008827 240.89236 C 87.107874 246.66004 90.121993 252.4691 93.115764 258.29182 A 15.884278 19.809179 74.412015 0 0 82.061142 257.98899 A 15.884278 19.809179 74.412015 0 0 65.505563 277.7883 A 15.884278 19.809179 74.412015 0 0 87.653049 289.31421 A 15.884278 19.809179 74.412015 0 0 92.464641 287.74997 C 92.614415 287.72841 92.764119 287.70888 92.915776 287.6678 C 93.68189 287.25802 94.424731 286.81279 95.154399 286.35057 A 15.884278 19.809179 74.412015 0 0 97.914954 284.38532 C 97.996889 284.31871 98.075774 284.24926 98.1568 284.18171 A 15.884278 19.809179 74.412015 0 0 98.692168 283.70422 C 100.96899 281.69105 102.85559 279.28691 104.04378 276.3455 C 105.03914 273.0898 104.84133 269.15631 103.29034 266.13372 C 102.38282 264.4007 101.52217 262.64555 100.67603 260.88184 L 100.27347 260.04055 C 99.609102 258.65414 98.944791 257.26759 98.286508 255.87853 A 453.37994 453.37994 0 0 0 93.67387 246.42536 C 92.275548 243.63877 90.875763 240.85317 89.512367 238.04964 L 89.094821 237.18716 L 88.698463 236.37636 L 88.341379 235.63377 C 87.928365 234.77917 87.680755 234.00673 87.523857 233.06959 C 90.747802 230.29437 94.362898 227.87812 98.136646 225.91808 L 98.636357 225.66125 C 104.14763 222.77334 110.39629 221.64425 116.55154 223.19629 C 120.15702 224.21652 123.89156 224.44269 127.62219 224.45409 L 128.23662 224.45409 C 133.97357 224.46176 139.46335 223.62026 144.8754 221.64858 L 145.46761 221.42792 C 151.57153 219.16864 159.69875 215.41182 163.72386 210.05084 C 163.74608 208.8827 163.27854 208.01251 162.71048 207.00709 L 162.40094 206.47896 C 162.07207 205.90905 161.74116 205.34111 161.40514 204.77571 L 161.07802 204.23 C 158.47638 199.88079 155.43722 195.98977 151.75766 192.50204 L 151.28844 192.05917 C 148.75453 189.6303 146.10197 187.42905 143.17886 185.48438 L 142.68948 185.16347 A 24.645408 24.645408 0 0 0 140.96969 184.12167 L 140.96969 184.12115 C 140.56607 183.98195 140.28275 183.89591 140.00179 183.88654 z " />
|
||||||
|
<path
|
||||||
|
fill="#fb161f"
|
||||||
|
d="m 164.78205,211.63826 c 0.99828,1.87642 0.82392,3.22395 0.23892,5.22737 -1.40414,4.37356 -3.45546,8.0862 -6.58892,11.44138 l -0.49715,0.57044 c -4.91728,5.36839 -12.63412,8.33914 -19.61091,9.74831 l -0.71676,0.14605 c -3.04139,0.5416 -6.10844,0.47757 -9.18872,0.48366 -2.86411,0.01 -5.42766,0.0212 -8.08646,1.22237 l -0.63235,0.26379 c -0.56488,0.24342 -1.11495,0.50535 -1.66608,0.77788 l -0.56171,0.25796 c -1.03214,0.48684 -1.03214,0.48684 -1.59332,1.42875 -0.054,0.86704 0.13097,1.28588 0.52361,2.05714 l 0.39661,0.79296 0.44,0.8509 0.44873,0.88926 c 0.39529,0.78237 0.79375,1.56289 1.19407,2.34262 0.39846,0.77814 0.79269,1.5584 1.18745,2.33839 a 1296.0032,1296.0032 0 0 0 1.82509,3.58907 c 0.68263,1.33879 1.35732,2.68102 2.02645,4.02643 a 123.3342,123.3342 0 0 0 2.11667,4.06506 c 0.79375,1.48167 1.53564,2.97709 2.21588,4.51432 l 0.22648,0.50535 c 1.33721,3.09483 1.48511,5.88301 0.40482,9.08156 -1.76001,4.25741 -5.17287,7.43744 -9.34615,9.28132 a 48.934687,48.934687 0 0 1 -1.5875,0.56224 l -0.72125,0.25506 c -4.4069,1.28878 -9.81816,1.12606 -13.90835,-0.9652 -1.26233,-0.70115 -2.40401,-1.36763 -3.362062,-2.46486 -0.0164,-0.52917 -0.0164,-0.52917 0.264582,-1.05833 l 0.60484,-0.47043 c 2.09629,-1.71635 3.4753,-4.1701 4.42224,-6.67332 l 0.25533,-0.63051 c 1.00145,-2.79664 0.81994,-5.7142 -0.26247,-8.4492 -0.27675,-0.76332 -0.4101,-1.4949 -0.52202,-2.29738 1.90103,-1.00938 3.73168,-1.85578 5.82083,-2.38125 l 0.94668,-0.2831 c 1.397,-0.34819 2.78183,-0.30374 4.2127,-0.27914 l 0.81227,0.007 c 0.65537,0.006 1.31074,0.0148 1.96585,0.0259 -0.34581,-1.11443 -0.76941,-2.12196 -1.3073,-3.15648 l -0.46435,-0.89588 -0.49371,-0.94192 -0.50615,-0.97102 a 174.52181,174.52181 0 0 0 -3.27263,-5.97376 c -0.55351,-0.98187 -1.05833,-1.98305 -1.55522,-2.99456 -1.00145,-2.0193 -2.04258,-4.01584 -3.10092,-6.00604 -0.30559,-0.5752 -0.61039,-1.15093 -0.91493,-1.72667 a 459.3672,459.3672 0 0 0 -3.0607,-5.69277 l -0.82153,-1.5076 a 230.40975,230.40975 0 0 0 -0.77258,-1.39541 c -0.76835,-1.4097 -1.39912,-2.57175 -1.45706,-4.19206 3.48721,-2.60271 7.94914,-3.37344 12.23856,-2.794 0.89006,0.15743 1.77536,0.33047 2.66092,0.51197 4.24127,0.84852 8.29892,1.33323 12.61718,1.32292 l 0.6866,-5.3e-4 c 3.66051,-0.0106 7.12602,-0.27649 10.69049,-1.15729 l 0.72496,-0.16748 c 8.95694,-2.08809 16.36395,-6.75217 23.31402,-12.63386 0.56727,-0.42783 0.56727,-0.42783 1.09643,-0.42783 z"
|
||||||
|
id="path4"
|
||||||
|
style="stroke-width:0.264583" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6.9 KiB |
1
public/img/lmhf.svg
Normal file
1
public/img/lmhf.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 24 KiB |
|
@ -1,16 +1,9 @@
|
||||||
.body-500 {
|
.body-500 {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: larger;
|
font-size: larger;
|
||||||
line-height: 200%;
|
line-height: 200%;
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
img {
|
img {
|
||||||
max-width: 60%;
|
max-width: 60%;
|
||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
|
|
|
@ -3,18 +3,21 @@
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: var(--box-shadow-color) 0px 3px 6px 0px;
|
box-shadow: var(--box-shadow-color) 0px 3px 6px 0px;
|
||||||
color: var(--font-color);
|
color: var(--font-color);
|
||||||
display: block;
|
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
width: calc(100% - 2rem);
|
|
||||||
margin: auto;
|
|
||||||
@include transition() {}
|
@include transition() {}
|
||||||
|
|
||||||
|
&.mini {
|
||||||
|
margin: auto;
|
||||||
|
width: calc(100% - 2rem);
|
||||||
|
|
||||||
@include respond-to("small-up") {
|
@include respond-to("small-up") {
|
||||||
width: 65%;
|
width: 65%;
|
||||||
}
|
}
|
||||||
@include respond-to("medium-up") {
|
@include respond-to("medium-up") {
|
||||||
width: 35%;
|
width: 35%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
|
|
@ -4,30 +4,6 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.filters {
|
|
||||||
display: flex;
|
|
||||||
justify-content: end;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
|
|
||||||
.field {
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
|
|
||||||
select {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include respond-to("small-up") {
|
|
||||||
width: 33%;
|
|
||||||
&:last-child {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include respond-to("small") {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.showMoreFilters {
|
.showMoreFilters {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -53,8 +29,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.total {
|
|
||||||
margin: 0.75rem 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -45,6 +45,7 @@ $close-background-dark: rgba(240,240,240,.6);
|
||||||
--bg-color: #{darken($white, 5%)};
|
--bg-color: #{darken($white, 5%)};
|
||||||
--bg-alternate-color: #{darken($white, 8%)};
|
--bg-alternate-color: #{darken($white, 8%)};
|
||||||
--font-color: #{$nord3};
|
--font-color: #{$nord3};
|
||||||
|
--hover-font-color: #{lighten($nord3, 16%)};
|
||||||
--footer-color: #{$darken-white};
|
--footer-color: #{$darken-white};
|
||||||
--link-color: #{$nord1};
|
--link-color: #{$nord1};
|
||||||
|
|
||||||
|
@ -88,6 +89,7 @@ $close-background-dark: rgba(240,240,240,.6);
|
||||||
--bg-color: #{lighten($nord0, 2%)};
|
--bg-color: #{lighten($nord0, 2%)};
|
||||||
--bg-alternate-color: #{lighten($nord3, 8%)};
|
--bg-alternate-color: #{lighten($nord3, 8%)};
|
||||||
--font-color: #{$nord6};
|
--font-color: #{$nord6};
|
||||||
|
--hover-font-color: #{darken($nord6, 16%)};
|
||||||
--footer-color: #{$nord1};
|
--footer-color: #{$nord1};
|
||||||
--link-color: #{$nord4};
|
--link-color: #{$nord4};
|
||||||
|
|
||||||
|
|
|
@ -71,77 +71,3 @@
|
||||||
padding-right: 2.4rem;
|
padding-right: 2.4rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-switch-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
em {
|
|
||||||
margin-left: 10px;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-switch {
|
|
||||||
display: inline-block;
|
|
||||||
height: 34px;
|
|
||||||
position: relative;
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-switch input {
|
|
||||||
display:none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
|
||||||
background-color: #ccc;
|
|
||||||
bottom: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
left: 0;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
transition: .4s;
|
|
||||||
@include transition() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider:before {
|
|
||||||
background-color: #fff;
|
|
||||||
bottom: 4px;
|
|
||||||
content: '\f185';
|
|
||||||
height: 26px;
|
|
||||||
left: 4px;
|
|
||||||
position: absolute;
|
|
||||||
transition: .4s;
|
|
||||||
width: 26px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
font-family: "icon";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: normal;
|
|
||||||
display: inline-block;
|
|
||||||
text-decoration: inherit;
|
|
||||||
text-align: center;
|
|
||||||
font-variant: normal;
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .slider {
|
|
||||||
background-color: $primary-color;
|
|
||||||
@include transition() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .slider:before {
|
|
||||||
transform: translateX(26px);
|
|
||||||
content: '\f186';
|
|
||||||
background-color: var(--input-active-color);
|
|
||||||
@include transition() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider.round {
|
|
||||||
border-radius: 34px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider.round:before {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,66 +1,79 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'icon';
|
font-family: 'icon';
|
||||||
src: url('/font/icon.eot?41426785');
|
src: url('/font/icon.eot?15219908');
|
||||||
src: url('/font/icon.eot?41426785#iefix') format('embedded-opentype'),
|
src: url('/font/icon.eot?15219908#iefix') format('embedded-opentype'),
|
||||||
url('/font/icon.woff2?41426785') format('woff2'),
|
url('/font/icon.woff2?15219908') format('woff2'),
|
||||||
url('/font/icon.woff?41426785') format('woff'),
|
url('/font/icon.woff?15219908') format('woff'),
|
||||||
url('/font/icon.ttf?41426785') format('truetype'),
|
url('/font/icon.ttf?15219908') format('truetype'),
|
||||||
url('/font/icon.svg?41426785#icon') format('svg');
|
url('/font/icon.svg?15219908#icon') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
|
||||||
[class^="icon-"]:before, [class*=" icon-"]:before {
|
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
|
||||||
|
/*
|
||||||
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||||
|
@font-face {
|
||||||
|
font-family: 'icon';
|
||||||
|
src: url('../font/icon.svg?15219908#icon') format('svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
[class^="icon-"]:before, [class*=" icon-"]:before {
|
||||||
font-family: "icon";
|
font-family: "icon";
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
speak: never;
|
||||||
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
text-decoration: inherit;
|
text-decoration: inherit;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
margin-right: .2em;
|
margin-right: .2em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
/* opacity: .8; */
|
||||||
|
|
||||||
|
/* For safety - reset parent styles, that can break glyph codes*/
|
||||||
font-variant: normal;
|
font-variant: normal;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
|
|
||||||
|
/* fix buttons height, for twitter bootstrap */
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
|
|
||||||
|
/* Animation center compensation - margins should be symmetric */
|
||||||
|
/* remove if not needed */
|
||||||
margin-left: .2em;
|
margin-left: .2em;
|
||||||
|
|
||||||
|
/* you can be more comfortable with increased icons size */
|
||||||
|
/* font-size: 120%; */
|
||||||
|
|
||||||
|
/* Font smoothing. That was taken from TWBS */
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
|
||||||
|
|
||||||
.icon-plus:before { content: '\e800'; } /* '' */
|
/* Uncomment for 3D effect */
|
||||||
.icon-user:before { content: '\e801'; } /* '' */
|
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
|
||||||
.icon-search:before { content: '\e802'; } /* '' */
|
|
||||||
.icon-mail:before { content: '\e803'; } /* '' */
|
|
||||||
.icon-link:before { content: '\e804'; } /* '' */
|
|
||||||
.icon-heart:before { content: '\e805'; } /* '' */
|
|
||||||
.icon-eye:before { content: '\e806'; } /* '' */
|
|
||||||
.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'; } /* '' */
|
|
||||||
.icon-moon:before { content: '\f186'; } /* '' */
|
|
||||||
.icon-share:before { content: '\f1e0'; } /* '' */
|
|
||||||
.icon-trash:before { content: '\f1f8'; } /* '' */
|
|
||||||
.icon-blind:before { content: '\f29d'; } /* '' */
|
|
||||||
|
|
||||||
.animate-spin {
|
|
||||||
animation: spin 2s infinite linear;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
@keyframes spin {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
.icon-plus:before { content: '\e800'; } /* '' */
|
||||||
transform: rotate(359deg);
|
.icon-user:before { content: '\e801'; } /* '' */
|
||||||
}
|
.icon-search:before { content: '\e802'; } /* '' */
|
||||||
|
.icon-mail:before { content: '\e803'; } /* '' */
|
||||||
|
.icon-link:before { content: '\e804'; } /* '' */
|
||||||
|
.icon-heart:before { content: '\e805'; } /* '' */
|
||||||
|
.icon-eye:before { content: '\e806'; } /* '' */
|
||||||
|
.icon-left-open:before { content: '\e807'; } /* '' */
|
||||||
|
.icon-right-open:before { content: '\e808'; } /* '' */
|
||||||
|
.icon-export:before { content: '\e809'; } /* '' */
|
||||||
|
.icon-refresh:before { content: '\e80a'; } /* '' */
|
||||||
|
.icon-adjust:before { content: '\e810'; } /* '' */
|
||||||
|
.icon-spin:before { content: '\e839'; } /* '' */
|
||||||
|
.icon-link-ext:before { content: '\f08e'; } /* '' */
|
||||||
|
.icon-sun:before { content: '\f185'; } /* '' */
|
||||||
|
.icon-moon:before { content: '\f186'; } /* '' */
|
||||||
|
.icon-share:before { content: '\f1e0'; } /* '' */
|
||||||
|
.icon-trash:before { content: '\f1f8'; } /* '' */
|
||||||
|
.icon-blind:before { content: '\f29d'; } /* '' */
|
||||||
|
|
||||||
|
.lmhf {
|
||||||
|
height: 16px;
|
||||||
}
|
}
|
|
@ -16,6 +16,7 @@
|
||||||
@import './list';
|
@import './list';
|
||||||
@import './box';
|
@import './box';
|
||||||
@import './loader';
|
@import './loader';
|
||||||
|
@import './table';
|
||||||
|
|
||||||
@import './error';
|
@import './error';
|
||||||
@import './messages.scss';
|
@import './messages.scss';
|
||||||
|
|
|
@ -103,7 +103,7 @@
|
||||||
|
|
||||||
.navbar-item {
|
.navbar-item {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
padding: .5rem .75rem;
|
padding: .5rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
@ -117,7 +117,6 @@
|
||||||
@include respond-to("medium-up") {
|
@include respond-to("medium-up") {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
color: rgba(0,0,0,.7);
|
|
||||||
|
|
||||||
.navbar-dropdown {
|
.navbar-dropdown {
|
||||||
background-color: var(--default-color);
|
background-color: var(--default-color);
|
||||||
|
@ -127,8 +126,7 @@
|
||||||
box-shadow: 0 8px 8px rgba(10,10,10,.1);
|
box-shadow: 0 8px 8px rgba(10,10,10,.1);
|
||||||
display: none;
|
display: none;
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
left: 0;
|
min-width: 280px;
|
||||||
min-width: 100%;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
}
|
}
|
||||||
|
@ -138,7 +136,7 @@
|
||||||
|
|
||||||
.navbar-link {
|
.navbar-link {
|
||||||
background-color: var(--default-hl-color);
|
background-color: var(--default-hl-color);
|
||||||
color: rgba(0,0,0,.7);
|
color: var(--hover-font-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-dropdown {
|
.navbar-dropdown {
|
||||||
|
@ -167,15 +165,6 @@
|
||||||
@include respond-to("medium-up") {
|
@include respond-to("medium-up") {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
align-items: center;
|
|
||||||
display: inline-flex;
|
|
||||||
justify-content: center;
|
|
||||||
height: 1.5rem;
|
|
||||||
width: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
border: 3px solid transparent;
|
border: 3px solid transparent;
|
||||||
|
@ -196,11 +185,22 @@
|
||||||
right: 1.125em;
|
right: 1.125em;
|
||||||
|
|
||||||
@include respond-to("medium-up") {
|
@include respond-to("medium-up") {
|
||||||
border-color: rgba(0,0,0,.7);
|
border-color: var(--font-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
align-items: center;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
.navbar-menu {
|
.navbar-menu {
|
||||||
display: none;
|
display: none;
|
||||||
background-color: var(--default-color);
|
background-color: var(--default-color);
|
||||||
|
@ -260,13 +260,7 @@
|
||||||
background-color: var(--font-color);
|
background-color: var(--font-color);
|
||||||
border: none;
|
border: none;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
margin: .5rem 0;
|
margin: .5rem 0 0 1.5rem;
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-item {
|
|
||||||
cursor: pointer;
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
padding-right: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include respond-to("medium-up") {
|
@include respond-to("medium-up") {
|
||||||
|
@ -277,16 +271,19 @@
|
||||||
box-shadow: 0 8px 8px rgba(10,10,10,.1);
|
box-shadow: 0 8px 8px rgba(10,10,10,.1);
|
||||||
display: none;
|
display: none;
|
||||||
font-size: .875rem;
|
font-size: .875rem;
|
||||||
left: 0;
|
right: 0;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
|
|
||||||
.navbar-item {
|
hr {
|
||||||
white-space: nowrap;
|
margin: 0.5rem 0;
|
||||||
padding: .375rem 1rem;
|
|
||||||
padding-right: 3rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// .navbar-item {
|
||||||
|
// white-space: nowrap;
|
||||||
|
// padding-block: .375rem;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,3 +318,9 @@
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
ul {
|
||||||
|
padding-inline: 16px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,8 @@
|
||||||
.pagination {
|
.pagination {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
text-align: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin: 0.75rem 0;
|
margin: 0.75rem 0;
|
||||||
|
|
||||||
|
|
||||||
.pagination-list {
|
.pagination-list {
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
text-align: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
@include respond-to("small") {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagination-link {
|
.pagination-link {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
|
|
23
sass/table.scss
Normal file
23
sass/table.scss
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
table {
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 0.75rem;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
tr {
|
||||||
|
border-bottom: 2px solid var(--font-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tbody {
|
||||||
|
tr {
|
||||||
|
background-color: var(--default-color);
|
||||||
|
|
||||||
|
&:nth-child(2n) {
|
||||||
|
background-color: var(--bg-alternate-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,18 +9,20 @@ import MongoStore from "connect-mongo";
|
||||||
|
|
||||||
import passportConfig from "./libs/passport";
|
import passportConfig from "./libs/passport";
|
||||||
|
|
||||||
import config, { env, mongoDbUri, secret } from "./config";
|
import config, { env, mongoDbUri, port, secret } from "./config";
|
||||||
|
|
||||||
import { isXhr } from "./helpers";
|
import { isXhr } from "./helpers";
|
||||||
|
|
||||||
import indexRouter from "./routes";
|
import indexRouter from "./routes";
|
||||||
import maCollectionRouter from "./routes/ma-collection";
|
import maCollectionRouter from "./routes/ma-collection";
|
||||||
|
import wantlistRouter from "./routes/wantlist";
|
||||||
import monCompteRouter from "./routes/mon-compte";
|
import monCompteRouter from "./routes/mon-compte";
|
||||||
import collectionRouter from "./routes/collection";
|
import collectionRouter from "./routes/collection";
|
||||||
|
|
||||||
import importJobsRouter from "./routes/jobs";
|
import importJobsRouter from "./routes/jobs";
|
||||||
|
|
||||||
import importAlbumRouterApiV1 from "./routes/api/v1/albums";
|
import importAlbumRouterApiV1 from "./routes/api/v1/albums";
|
||||||
|
import importWantlistRouterApiV1 from "./routes/api/v1/wantlist";
|
||||||
import importSearchRouterApiV1 from "./routes/api/v1/search";
|
import importSearchRouterApiV1 from "./routes/api/v1/search";
|
||||||
import importMastodonRouterApiV1 from "./routes/api/v1/mastodon";
|
import importMastodonRouterApiV1 from "./routes/api/v1/mastodon";
|
||||||
import importMeRouterApiV1 from "./routes/api/v1/me";
|
import importMeRouterApiV1 from "./routes/api/v1/me";
|
||||||
|
@ -81,9 +83,11 @@ app.use(express.static(path.join(__dirname, "../public")));
|
||||||
app.use("/", indexRouter);
|
app.use("/", indexRouter);
|
||||||
app.use("/mon-compte", monCompteRouter);
|
app.use("/mon-compte", monCompteRouter);
|
||||||
app.use("/ma-collection", maCollectionRouter);
|
app.use("/ma-collection", maCollectionRouter);
|
||||||
|
app.use("/ma-liste-de-souhaits", wantlistRouter);
|
||||||
app.use("/collection", collectionRouter);
|
app.use("/collection", collectionRouter);
|
||||||
app.use("/jobs", importJobsRouter);
|
app.use("/jobs", importJobsRouter);
|
||||||
app.use("/api/v1/albums", importAlbumRouterApiV1);
|
app.use("/api/v1/albums", importAlbumRouterApiV1);
|
||||||
|
app.use("/api/v1/wantlist", importWantlistRouterApiV1);
|
||||||
app.use("/api/v1/search", importSearchRouterApiV1);
|
app.use("/api/v1/search", importSearchRouterApiV1);
|
||||||
app.use("/api/v1/mastodon", importMastodonRouterApiV1);
|
app.use("/api/v1/mastodon", importMastodonRouterApiV1);
|
||||||
app.use("/api/v1/me", importMeRouterApiV1);
|
app.use("/api/v1/me", importMeRouterApiV1);
|
||||||
|
@ -150,4 +154,6 @@ app.use((error, req, res, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(`Server listening on port ${port}!`);
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|
|
@ -53,3 +53,28 @@ export const isXhr = (req) => {
|
||||||
|
|
||||||
return is;
|
return is;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de récupérer les éléments distincts d'une collection
|
||||||
|
* @param {Object} model
|
||||||
|
* @param {String} field
|
||||||
|
* @param {import("mongoose").ObjectId} user
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const getAllDistincts = async (model, field, user) => {
|
||||||
|
const distincts = await model
|
||||||
|
.find(
|
||||||
|
{
|
||||||
|
User: user,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
sort: {
|
||||||
|
[field]: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.distinct(field);
|
||||||
|
|
||||||
|
return distincts;
|
||||||
|
};
|
||||||
|
|
|
@ -34,6 +34,23 @@ export const sendResponse = (req, res, data) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setHashTags = (arr) => {
|
||||||
|
let hashTags = "";
|
||||||
|
|
||||||
|
for (let i = 0; i < arr.length; i += 1) {
|
||||||
|
let currentHash = `${hashTags ? ", " : ""} #`;
|
||||||
|
const currentItem = arr[i].split(" ");
|
||||||
|
for (let j = 0; j < currentItem.length; j += 1) {
|
||||||
|
currentHash +=
|
||||||
|
currentItem[j].toLowerCase().charAt(0).toUpperCase() +
|
||||||
|
currentItem[j].toLowerCase().slice(1);
|
||||||
|
}
|
||||||
|
hashTags += currentHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hashTags;
|
||||||
|
};
|
||||||
|
|
||||||
export default (res, page) => {
|
export default (res, page) => {
|
||||||
res.status(200).render("index", page.render());
|
res.status(200).render("index", page.render());
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { format as formatDate } from "date-fns";
|
import { format as formatDate, startOfYear, subYears } from "date-fns";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
|
||||||
import Mastodon from "mastodon";
|
import Mastodon from "mastodon";
|
||||||
|
@ -12,7 +12,8 @@ import JobsModel from "../models/jobs";
|
||||||
import UsersModel from "../models/users";
|
import UsersModel from "../models/users";
|
||||||
import ErrorEvent from "../libs/error";
|
import ErrorEvent from "../libs/error";
|
||||||
|
|
||||||
import { getAlbumDetails } from "../helpers";
|
import { getAlbumDetails, getAllDistincts } from "../helpers";
|
||||||
|
import { setHashTags } from "../libs/format";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Classe permettant la gestion des albums d'un utilisateur
|
* Classe permettant la gestion des albums d'un utilisateur
|
||||||
|
@ -42,8 +43,11 @@ class Albums extends Pages {
|
||||||
discogsId: albumDetails.id,
|
discogsId: albumDetails.id,
|
||||||
User: user._id,
|
User: user._id,
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
data.released = data.released
|
data.released = data.released
|
||||||
|
? typeof data.released === "string"
|
||||||
? new Date(data.released.replace("-00", "-01"))
|
? new Date(data.released.replace("-00", "-01"))
|
||||||
|
: data.released
|
||||||
: null;
|
: null;
|
||||||
delete data.id;
|
delete data.id;
|
||||||
|
|
||||||
|
@ -83,6 +87,9 @@ class Albums extends Pages {
|
||||||
)
|
)
|
||||||
.replaceAll("{artist}", data.artists[0].name)
|
.replaceAll("{artist}", data.artists[0].name)
|
||||||
.replaceAll("{format}", data.formats[0].name)
|
.replaceAll("{format}", data.formats[0].name)
|
||||||
|
.replaceAll("{#genres}", setHashTags(data.genres))
|
||||||
|
.replaceAll("{styles}", data.styles.join(", "))
|
||||||
|
.replaceAll("{#styles}", setHashTags(data.styles))
|
||||||
.replaceAll("{year}", data.year)
|
.replaceAll("{year}", data.year)
|
||||||
.replaceAll("{video}", video)
|
.replaceAll("{video}", video)
|
||||||
.replaceAll("{album}", data.title)}
|
.replaceAll("{album}", data.title)}
|
||||||
|
@ -140,26 +147,22 @@ Publié automatiquement via #musictopus`;
|
||||||
return album;
|
return album;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
constructor(req, viewname) {
|
||||||
* Méthode permettant de récupérer les éléments distincts d'une collection
|
super(req, viewname);
|
||||||
* @param {String} field
|
|
||||||
* @param {ObjectId} user
|
|
||||||
* @return {Array}
|
|
||||||
*/
|
|
||||||
static async getAllDistincts(field, user) {
|
|
||||||
const distincts = await AlbumsModel.find(
|
|
||||||
{
|
|
||||||
User: user,
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
{
|
|
||||||
sort: {
|
|
||||||
[field]: 1,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
).distinct(field);
|
|
||||||
|
|
||||||
return distincts;
|
this.colors = [
|
||||||
|
"#2e3440",
|
||||||
|
"#d8dee9",
|
||||||
|
"#8fbcbb",
|
||||||
|
"#5e81ac",
|
||||||
|
"#d08770",
|
||||||
|
"#bf616a",
|
||||||
|
"#ebcb8b",
|
||||||
|
"#a3be8c",
|
||||||
|
"#b48ead",
|
||||||
|
];
|
||||||
|
|
||||||
|
this.setPageContent("action", "albums");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,7 +172,6 @@ Publié automatiquement via #musictopus`;
|
||||||
async getAll() {
|
async getAll() {
|
||||||
const {
|
const {
|
||||||
page,
|
page,
|
||||||
limit,
|
|
||||||
exportFormat = "json",
|
exportFormat = "json",
|
||||||
sort = "artists_sort",
|
sort = "artists_sort",
|
||||||
order = "asc",
|
order = "asc",
|
||||||
|
@ -183,6 +185,8 @@ Publié automatiquement via #musictopus`;
|
||||||
discogsId,
|
discogsId,
|
||||||
} = this.req.query;
|
} = this.req.query;
|
||||||
|
|
||||||
|
const limit = this.req.user?.pagination || 16;
|
||||||
|
|
||||||
let userId = this.req.user?._id;
|
let userId = this.req.user?._id;
|
||||||
|
|
||||||
const where = {};
|
const where = {};
|
||||||
|
@ -248,7 +252,7 @@ Publié automatiquement via #musictopus`;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (page && limit) {
|
if (exportFormat === "json" && page && limit) {
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
|
@ -280,6 +284,7 @@ Publié automatiquement via #musictopus`;
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
rows,
|
rows,
|
||||||
|
limit,
|
||||||
count,
|
count,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -437,20 +442,28 @@ Publié automatiquement via #musictopus`;
|
||||||
* Méthode permettant de créer la page "ma-collection"
|
* Méthode permettant de créer la page "ma-collection"
|
||||||
*/
|
*/
|
||||||
async loadMyCollection() {
|
async loadMyCollection() {
|
||||||
const artists = await Albums.getAllDistincts(
|
const artists = await getAllDistincts(
|
||||||
|
AlbumsModel,
|
||||||
"artists.name",
|
"artists.name",
|
||||||
this.req.user._id
|
this.req.user._id
|
||||||
);
|
);
|
||||||
const formats = await Albums.getAllDistincts(
|
const formats = await getAllDistincts(
|
||||||
|
AlbumsModel,
|
||||||
"formats.name",
|
"formats.name",
|
||||||
this.req.user._id
|
this.req.user._id
|
||||||
);
|
);
|
||||||
const years = await Albums.getAllDistincts("year", this.req.user._id);
|
const years = await getAllDistincts(
|
||||||
const genres = await Albums.getAllDistincts(
|
AlbumsModel,
|
||||||
|
"year",
|
||||||
|
this.req.user._id
|
||||||
|
);
|
||||||
|
const genres = await getAllDistincts(
|
||||||
|
AlbumsModel,
|
||||||
"genres",
|
"genres",
|
||||||
this.req.user._id
|
this.req.user._id
|
||||||
);
|
);
|
||||||
const styles = await Albums.getAllDistincts(
|
const styles = await getAllDistincts(
|
||||||
|
AlbumsModel,
|
||||||
"styles",
|
"styles",
|
||||||
this.req.user._id
|
this.req.user._id
|
||||||
);
|
);
|
||||||
|
@ -500,6 +513,230 @@ Publié automatiquement via #musictopus`;
|
||||||
await this.loadItem();
|
await this.loadItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant d'afficher des statistiques au sujet de ma collection
|
||||||
|
*/
|
||||||
|
async statistics() {
|
||||||
|
const { _id: User } = this.req.user;
|
||||||
|
const top = {};
|
||||||
|
const byGenres = {};
|
||||||
|
const byStyles = {};
|
||||||
|
const byFormats = {};
|
||||||
|
const top10 = [];
|
||||||
|
let byStyles10 = [];
|
||||||
|
const max = this.colors.length - 1;
|
||||||
|
const startYear = startOfYear(new Date());
|
||||||
|
const lastStartYear = startOfYear(subYears(new Date(), 1));
|
||||||
|
const currentYear = {
|
||||||
|
year: formatDate(startYear, "yyyy"),
|
||||||
|
total: 0,
|
||||||
|
byGenres: {},
|
||||||
|
byStyles: {},
|
||||||
|
byFormats: {},
|
||||||
|
};
|
||||||
|
const lastYear = {
|
||||||
|
year: formatDate(lastStartYear, "yyyy"),
|
||||||
|
total: 0,
|
||||||
|
byGenres: {},
|
||||||
|
byStyles: {},
|
||||||
|
byFormats: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorsCount = this.colors.length;
|
||||||
|
|
||||||
|
const albums = await AlbumsModel.find({
|
||||||
|
User,
|
||||||
|
artists: { $exists: true, $not: { $size: 0 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < albums.length; i += 1) {
|
||||||
|
const currentFormats = [];
|
||||||
|
const { artists, genres, styles, formats, createdAt } = albums[i];
|
||||||
|
|
||||||
|
// INFO: On s'occupe de la rétrospective
|
||||||
|
if (createdAt > startYear) {
|
||||||
|
currentYear.total += 1;
|
||||||
|
} else if (createdAt > lastStartYear) {
|
||||||
|
lastYear.total += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: On regroupe les artistes par nom pour en faire le top10
|
||||||
|
for (let j = 0; j < artists.length; j += 1) {
|
||||||
|
const { name } = artists[j];
|
||||||
|
if (!top[name]) {
|
||||||
|
top[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
top[name].count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: On regroupe les genres
|
||||||
|
for (let j = 0; j < genres.length; j += 1) {
|
||||||
|
const name = genres[j];
|
||||||
|
if (!byGenres[name]) {
|
||||||
|
byGenres[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: this.colors[
|
||||||
|
Object.keys(byGenres).length % colorsCount
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
byGenres[name].count += 1;
|
||||||
|
|
||||||
|
// INFO: On s'occupe de la rétrospective
|
||||||
|
if (createdAt > startYear) {
|
||||||
|
if (!currentYear.byGenres[name]) {
|
||||||
|
currentYear.total += 1;
|
||||||
|
currentYear.byGenres[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: byGenres[name].color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
currentYear.byGenres[name].count += 1;
|
||||||
|
} else if (createdAt > lastStartYear) {
|
||||||
|
if (!lastYear.byGenres[name]) {
|
||||||
|
lastYear.byGenres[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: byGenres[name].color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
lastYear.byGenres[name].count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: On regroupe les styles
|
||||||
|
for (let j = 0; j < styles.length; j += 1) {
|
||||||
|
const name = styles[j];
|
||||||
|
if (!byStyles[name]) {
|
||||||
|
byStyles[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: this.colors[
|
||||||
|
Object.keys(byStyles).length % colorsCount
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
byStyles[name].count += 1;
|
||||||
|
|
||||||
|
// INFO: On s'occupe de la rétrospective
|
||||||
|
if (createdAt > startYear) {
|
||||||
|
if (!currentYear.byStyles[name]) {
|
||||||
|
currentYear.total += 1;
|
||||||
|
currentYear.byStyles[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: byStyles[name].color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
currentYear.byStyles[name].count += 1;
|
||||||
|
} else if (createdAt > lastStartYear) {
|
||||||
|
if (!lastYear.byStyles[name]) {
|
||||||
|
lastYear.byStyles[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: byStyles[name].color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
lastYear.byStyles[name].count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: On regroupe les formats
|
||||||
|
for (let j = 0; j < formats.length; j += 1) {
|
||||||
|
const { name } = formats[j];
|
||||||
|
// INFO: On évite qu'un album avec 2 vinyles soit compté 2x
|
||||||
|
if (!currentFormats.includes(name)) {
|
||||||
|
if (!byFormats[name]) {
|
||||||
|
byFormats[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: this.colors[
|
||||||
|
Object.keys(byFormats).length % colorsCount
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
byFormats[name].count += 1;
|
||||||
|
currentFormats.push(name);
|
||||||
|
|
||||||
|
// INFO: On s'occupe de la rétrospective
|
||||||
|
if (createdAt > startYear) {
|
||||||
|
if (!currentYear.byFormats[name]) {
|
||||||
|
currentYear.total += 1;
|
||||||
|
currentYear.byFormats[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: byFormats[name].color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
currentYear.byFormats[name].count += 1;
|
||||||
|
} else if (createdAt > lastStartYear) {
|
||||||
|
if (!lastYear.byFormats[name]) {
|
||||||
|
lastYear.byFormats[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: byFormats[name].color,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
lastYear.byFormats[name].count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: On convertit le top en tableau
|
||||||
|
Object.keys(top).forEach((index) => {
|
||||||
|
top10.push(top[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// INFO: On convertit les styles en tableau
|
||||||
|
Object.keys(byStyles).forEach((index) => {
|
||||||
|
byStyles10.push(byStyles[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// INFO: On ordonne les artistes par quantité d'albums
|
||||||
|
top10.sort((a, b) => (a.count > b.count ? -1 : 1));
|
||||||
|
|
||||||
|
// INFO: On ordonne les styles par quantité
|
||||||
|
byStyles10.sort((a, b) => (a.count > b.count ? -1 : 1));
|
||||||
|
const tmp = [];
|
||||||
|
|
||||||
|
// INFO: On recupère le top N des styles et on mets le reste dans le label "autre"
|
||||||
|
for (let i = 0; i < byStyles10.length; i += 1) {
|
||||||
|
if (i < max) {
|
||||||
|
tmp.push({
|
||||||
|
...byStyles10[i],
|
||||||
|
color: this.colors[max - i],
|
||||||
|
});
|
||||||
|
} else if (i === max) {
|
||||||
|
tmp.push({
|
||||||
|
name: "Autre",
|
||||||
|
count: 0,
|
||||||
|
color: this.colors[0],
|
||||||
|
});
|
||||||
|
tmp[max].count += byStyles10[i].count;
|
||||||
|
} else {
|
||||||
|
tmp[max].count += byStyles10[i].count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byStyles10 = tmp;
|
||||||
|
|
||||||
|
this.setPageTitle("Mes statistiques");
|
||||||
|
this.setPageContent("top10", top10.splice(0, 10));
|
||||||
|
this.setPageContent("byGenres", byGenres);
|
||||||
|
this.setPageContent("byStyles", byStyles10);
|
||||||
|
this.setPageContent("byFormats", byFormats);
|
||||||
|
this.setPageContent("currentYear", currentYear);
|
||||||
|
this.setPageContent("lastYear", lastYear);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Méthode permettant de créer la page "collection/:userId"
|
* Méthode permettant de créer la page "collection/:userId"
|
||||||
*/
|
*/
|
||||||
|
@ -516,14 +753,22 @@ Publié automatiquement via #musictopus`;
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const artists = await Albums.getAllDistincts("artists.name", userId);
|
const artists = await getAllDistincts(
|
||||||
const formats = await Albums.getAllDistincts("formats.name", userId);
|
AlbumsModel,
|
||||||
const years = await Albums.getAllDistincts("year", userId);
|
"artists.name",
|
||||||
const genres = await Albums.getAllDistincts("genres", userId);
|
userId
|
||||||
const styles = await Albums.getAllDistincts("styles", userId);
|
);
|
||||||
|
const formats = await getAllDistincts(
|
||||||
|
AlbumsModel,
|
||||||
|
"formats.name",
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
const years = await getAllDistincts(AlbumsModel, "year", userId);
|
||||||
|
const genres = await getAllDistincts(AlbumsModel, "genres", userId);
|
||||||
|
const styles = await getAllDistincts(AlbumsModel, "styles", userId);
|
||||||
|
|
||||||
this.setPageContent("username", user.username);
|
|
||||||
this.setPageTitle(`Collection publique de ${user.username}`);
|
this.setPageTitle(`Collection publique de ${user.username}`);
|
||||||
|
this.setPageContent("username", user.username);
|
||||||
this.setPageContent("artists", artists);
|
this.setPageContent("artists", artists);
|
||||||
this.setPageContent("formats", formats);
|
this.setPageContent("formats", formats);
|
||||||
this.setPageContent("years", years);
|
this.setPageContent("years", years);
|
||||||
|
|
|
@ -123,8 +123,8 @@ class Export {
|
||||||
|
|
||||||
ws.cell(currentRow, 1).string(artists_sort).style(style);
|
ws.cell(currentRow, 1).string(artists_sort).style(style);
|
||||||
ws.cell(currentRow, 2).string(title).style(style);
|
ws.cell(currentRow, 2).string(title).style(style);
|
||||||
ws.cell(currentRow, 3).string(genres.join()).style(style);
|
ws.cell(currentRow, 3).string(genres.join(", ")).style(style);
|
||||||
ws.cell(currentRow, 4).string(styles.join()).style(style);
|
ws.cell(currentRow, 4).string(styles.join(", ")).style(style);
|
||||||
if (country) {
|
if (country) {
|
||||||
ws.cell(currentRow, 5).string(country).style(style);
|
ws.cell(currentRow, 5).string(country).style(style);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { getAlbumDetails } from "../helpers";
|
||||||
|
|
||||||
import JobsModel from "../models/jobs";
|
import JobsModel from "../models/jobs";
|
||||||
import AlbumsModel from "../models/albums";
|
import AlbumsModel from "../models/albums";
|
||||||
|
import WantListModel from "../models/wantlist";
|
||||||
|
|
||||||
class Jobs {
|
class Jobs {
|
||||||
/**
|
/**
|
||||||
|
@ -51,6 +52,50 @@ class Jobs {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de télécharger toute les images d'un album
|
||||||
|
* @param {ObjectId} itemId
|
||||||
|
*/
|
||||||
|
static async importAlbumForWantListAssets(itemId) {
|
||||||
|
const album = await WantListModel.findById(itemId);
|
||||||
|
|
||||||
|
if (!album) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
404,
|
||||||
|
"Item non trouvé",
|
||||||
|
`L'album avec 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
|
* Point d'entrée
|
||||||
* @param {String} state
|
* @param {String} state
|
||||||
|
@ -78,6 +123,9 @@ class Jobs {
|
||||||
case "Albums":
|
case "Albums":
|
||||||
await Jobs.importAlbumAssets(job.id);
|
await Jobs.importAlbumAssets(job.id);
|
||||||
break;
|
break;
|
||||||
|
case "WantList":
|
||||||
|
await Jobs.importAlbumForWantListAssets(job.id);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new ErrorEvent(
|
throw new ErrorEvent(
|
||||||
500,
|
500,
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import Joi from "joi";
|
import Joi from "joi";
|
||||||
|
|
||||||
import UsersModel from "../models/users";
|
import UsersModel from "../models/users";
|
||||||
|
import AlbumsModel from "../models/albums";
|
||||||
|
import WantlistModel from "../models/wantlist";
|
||||||
import Pages from "./Pages";
|
import Pages from "./Pages";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,6 +18,7 @@ class Me extends Pages {
|
||||||
const { _id } = this.req.user;
|
const { _id } = this.req.user;
|
||||||
|
|
||||||
const schema = Joi.object({
|
const schema = Joi.object({
|
||||||
|
pagination: Joi.number(),
|
||||||
isPublicCollection: Joi.boolean(),
|
isPublicCollection: Joi.boolean(),
|
||||||
oldPassword: Joi.string(),
|
oldPassword: Joi.string(),
|
||||||
password: Joi.string(),
|
password: Joi.string(),
|
||||||
|
@ -25,6 +28,7 @@ class Me extends Pages {
|
||||||
url: Joi.string().uri().allow(null, ""),
|
url: Joi.string().uri().allow(null, ""),
|
||||||
token: Joi.string().allow(null, ""),
|
token: Joi.string().allow(null, ""),
|
||||||
message: Joi.string().allow(null, ""),
|
message: Joi.string().allow(null, ""),
|
||||||
|
wantlist: Joi.string().allow(null, ""),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -45,6 +49,10 @@ class Me extends Pages {
|
||||||
user.salt = value.password;
|
user.salt = value.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.pagination) {
|
||||||
|
user.pagination = value.pagination;
|
||||||
|
}
|
||||||
|
|
||||||
if (value.isPublicCollection !== undefined) {
|
if (value.isPublicCollection !== undefined) {
|
||||||
user.isPublicCollection = value.isPublicCollection;
|
user.isPublicCollection = value.isPublicCollection;
|
||||||
}
|
}
|
||||||
|
@ -63,6 +71,23 @@ class Me extends Pages {
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de supprimer un utilisateur et toutes ses données
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
async deleteMe() {
|
||||||
|
const { _id } = this.req.user;
|
||||||
|
|
||||||
|
await AlbumsModel.deleteMany({ User: _id });
|
||||||
|
await WantlistModel.deleteMany({ User: _id });
|
||||||
|
await UsersModel.deleteOne({ _id });
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleted: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Me;
|
export default Me;
|
||||||
|
|
690
src/middleware/Wantlist.js
Normal file
690
src/middleware/Wantlist.js
Normal file
|
@ -0,0 +1,690 @@
|
||||||
|
import { format as formatDate } from "date-fns";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
import Mastodon from "mastodon";
|
||||||
|
import { v4 } from "uuid";
|
||||||
|
import axios from "axios";
|
||||||
|
import Pages from "./Pages";
|
||||||
|
import Export from "./Export";
|
||||||
|
|
||||||
|
import WantListModel from "../models/wantlist";
|
||||||
|
import JobsModel from "../models/jobs";
|
||||||
|
import UsersModel from "../models/users";
|
||||||
|
import ErrorEvent from "../libs/error";
|
||||||
|
|
||||||
|
import { getAlbumDetails, getAllDistincts } from "../helpers";
|
||||||
|
import { setHashTags } from "../libs/format";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classe permettant la gestion da la liste de souhaits d'un utilisateur
|
||||||
|
*/
|
||||||
|
class Wantlist extends Pages {
|
||||||
|
/**
|
||||||
|
* Méthode permettant d'ajouter un album dans une collection
|
||||||
|
* @param {Object} req
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
static async postAddOne(req) {
|
||||||
|
const { body, user } = req;
|
||||||
|
const { share, discogsId } = body;
|
||||||
|
|
||||||
|
let albumDetails = body.album;
|
||||||
|
if (discogsId) {
|
||||||
|
albumDetails = await getAlbumDetails(discogsId);
|
||||||
|
body.id = discogsId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!albumDetails) {
|
||||||
|
throw new ErrorEvent(406, "Aucun album à ajouter");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
...albumDetails,
|
||||||
|
discogsId: albumDetails.id,
|
||||||
|
User: user._id,
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
data.released = data.released
|
||||||
|
? typeof data.released === "string"
|
||||||
|
? new Date(data.released.replace("-00", "-01"))
|
||||||
|
: data.released
|
||||||
|
: null;
|
||||||
|
delete data.id;
|
||||||
|
|
||||||
|
const album = new WantListModel(data);
|
||||||
|
|
||||||
|
await album.save();
|
||||||
|
|
||||||
|
const jobData = {
|
||||||
|
model: "WantList",
|
||||||
|
id: album._id,
|
||||||
|
};
|
||||||
|
const job = new JobsModel(jobData);
|
||||||
|
|
||||||
|
job.save();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const User = await UsersModel.findOne({ _id: user._id });
|
||||||
|
|
||||||
|
const { mastodon: mastodonConfig } = User;
|
||||||
|
|
||||||
|
const { publish, token, url, wantlist } = mastodonConfig;
|
||||||
|
|
||||||
|
if (share && publish && url && token) {
|
||||||
|
const M = new Mastodon({
|
||||||
|
access_token: token,
|
||||||
|
api_url: url,
|
||||||
|
});
|
||||||
|
|
||||||
|
const video =
|
||||||
|
data.videos && data.videos.length > 0
|
||||||
|
? data.videos[0].uri
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const status = `${(
|
||||||
|
wantlist ||
|
||||||
|
"Je viens d'ajouter {artist} - {album} à ma liste de souhaits !"
|
||||||
|
)
|
||||||
|
.replaceAll("{artist}", data.artists[0].name)
|
||||||
|
.replaceAll("{format}", data.formats[0].name)
|
||||||
|
.replaceAll("{genres}", data.genres.join(", "))
|
||||||
|
.replaceAll("{#genres}", setHashTags(data.genres))
|
||||||
|
.replaceAll("{styles}", data.styles.join(", "))
|
||||||
|
.replaceAll("{#styles}", setHashTags(data.styles))
|
||||||
|
.replaceAll("{year}", data.year)
|
||||||
|
.replaceAll("{video}", video)
|
||||||
|
.replaceAll("{album}", data.title)}
|
||||||
|
|
||||||
|
Publié automatiquement via #musictopus`;
|
||||||
|
|
||||||
|
const media_ids = [];
|
||||||
|
|
||||||
|
if (data.images.length > 0) {
|
||||||
|
for (let i = 0; i < data.images.length; i += 1) {
|
||||||
|
if (media_ids.length === 4) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = `${v4()}.jpg`;
|
||||||
|
const file = `/tmp/${filename}`;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data: buff } = await axios.get(
|
||||||
|
data.images[i].uri,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0",
|
||||||
|
},
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(file, buff);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data: media } = await M.post("media", {
|
||||||
|
file: fs.createReadStream(file),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { id } = media;
|
||||||
|
|
||||||
|
media_ids.push(id);
|
||||||
|
|
||||||
|
fs.unlinkSync(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await M.post("statuses", { status, media_ids });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
500,
|
||||||
|
"Mastodon",
|
||||||
|
"Album ajouté à votre collection mais impossible de publier sur Mastodon"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return album;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(req, viewname) {
|
||||||
|
super(req, viewname);
|
||||||
|
|
||||||
|
this.colors = [
|
||||||
|
"#2e3440",
|
||||||
|
"#d8dee9",
|
||||||
|
"#8fbcbb",
|
||||||
|
"#5e81ac",
|
||||||
|
"#d08770",
|
||||||
|
"#bf616a",
|
||||||
|
"#ebcb8b",
|
||||||
|
"#a3be8c",
|
||||||
|
"#b48ead",
|
||||||
|
];
|
||||||
|
|
||||||
|
this.setPageContent("action", "wantlist");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de récupérer la liste des albums d'une collection
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
async getAll() {
|
||||||
|
const {
|
||||||
|
page,
|
||||||
|
exportFormat = "json",
|
||||||
|
sort = "artists_sort",
|
||||||
|
order = "asc",
|
||||||
|
artist,
|
||||||
|
format,
|
||||||
|
year,
|
||||||
|
genre,
|
||||||
|
style,
|
||||||
|
userId: collectionUserId,
|
||||||
|
discogsIds,
|
||||||
|
discogsId,
|
||||||
|
} = this.req.query;
|
||||||
|
|
||||||
|
const limit = this.req.user?.pagination || 16;
|
||||||
|
|
||||||
|
let userId = this.req.user?._id;
|
||||||
|
|
||||||
|
const where = {};
|
||||||
|
|
||||||
|
if (artist) {
|
||||||
|
where["artists.name"] = artist;
|
||||||
|
}
|
||||||
|
if (format) {
|
||||||
|
where["formats.name"] = format;
|
||||||
|
}
|
||||||
|
if (year) {
|
||||||
|
where.year = year;
|
||||||
|
}
|
||||||
|
if (genre) {
|
||||||
|
where.genres = genre;
|
||||||
|
}
|
||||||
|
if (style) {
|
||||||
|
where.styles = style;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.req.user && !collectionUserId) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
401,
|
||||||
|
"Collection",
|
||||||
|
"Cette collection n'est pas publique"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collectionUserId) {
|
||||||
|
const userIsSharingCollection = await UsersModel.findById(
|
||||||
|
collectionUserId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!userIsSharingCollection ||
|
||||||
|
!userIsSharingCollection.isPublicCollection
|
||||||
|
) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
401,
|
||||||
|
"Collection",
|
||||||
|
"Cette collection n'est pas publique"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
userId = userIsSharingCollection._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discogsIds) {
|
||||||
|
where.discogsId = { $in: discogsIds };
|
||||||
|
}
|
||||||
|
if (discogsId) {
|
||||||
|
where.discogsId = Number(discogsId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = await WantListModel.count({
|
||||||
|
User: userId,
|
||||||
|
...where,
|
||||||
|
});
|
||||||
|
|
||||||
|
let params = {
|
||||||
|
sort: {
|
||||||
|
[sort]: order.toLowerCase() === "asc" ? 1 : -1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (exportFormat === "json" && page && limit) {
|
||||||
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
|
params = {
|
||||||
|
...params,
|
||||||
|
skip,
|
||||||
|
limit,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await WantListModel.find(
|
||||||
|
{
|
||||||
|
User: userId,
|
||||||
|
...where,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (exportFormat) {
|
||||||
|
case "csv":
|
||||||
|
return Export.convertToCsv(rows);
|
||||||
|
case "xls":
|
||||||
|
return Export.convertToXls(rows);
|
||||||
|
case "xml":
|
||||||
|
return Export.convertToXml(rows);
|
||||||
|
case "musictopus":
|
||||||
|
return Export.convertToMusicTopus(rows);
|
||||||
|
case "json":
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
rows,
|
||||||
|
limit,
|
||||||
|
count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de récupérer le détails d'un album
|
||||||
|
*
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
async getOne() {
|
||||||
|
const { itemId: _id } = this.req.params;
|
||||||
|
const { _id: User } = this.req.user;
|
||||||
|
const album = await WantListModel.findOne({
|
||||||
|
_id,
|
||||||
|
User,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...album.toJSON(),
|
||||||
|
released: album.released
|
||||||
|
? formatDate(album.released, "MM/dd/yyyy")
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 query = {
|
||||||
|
_id,
|
||||||
|
User,
|
||||||
|
};
|
||||||
|
const album = await WantListModel.findOne(query);
|
||||||
|
|
||||||
|
if (!album) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
404,
|
||||||
|
"Mise à jour",
|
||||||
|
"Impossible de trouver cet album"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = await getAlbumDetails(album.discogsId);
|
||||||
|
|
||||||
|
await WantListModel.findOneAndUpdate(query, values, { new: true });
|
||||||
|
|
||||||
|
return this.getOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de supprimer un élément d'une collection
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
async deleteOne() {
|
||||||
|
const res = await WantListModel.findOneAndDelete({
|
||||||
|
User: this.req.user._id,
|
||||||
|
_id: this.req.params.itemId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ErrorEvent(
|
||||||
|
404,
|
||||||
|
"Suppression",
|
||||||
|
"Impossible de trouver cet album"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async shareOne() {
|
||||||
|
const { message: status } = this.req.body;
|
||||||
|
const { itemId: _id } = this.req.params;
|
||||||
|
const { _id: User } = this.req.user;
|
||||||
|
const query = {
|
||||||
|
_id,
|
||||||
|
User,
|
||||||
|
};
|
||||||
|
|
||||||
|
const album = await WantListModel.findOne(query);
|
||||||
|
|
||||||
|
if (!album) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
404,
|
||||||
|
"Mise à jour",
|
||||||
|
"Impossible de trouver cet album"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mastodon: mastodonConfig } = this.req.user;
|
||||||
|
const { publish, token, url } = mastodonConfig;
|
||||||
|
|
||||||
|
if (publish && url && token) {
|
||||||
|
const M = new Mastodon({
|
||||||
|
access_token: token,
|
||||||
|
api_url: url,
|
||||||
|
});
|
||||||
|
|
||||||
|
const media_ids = [];
|
||||||
|
|
||||||
|
if (album.images.length > 0) {
|
||||||
|
for (let i = 0; i < album.images.length; i += 1) {
|
||||||
|
if (media_ids.length === 4) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = `${v4()}.jpg`;
|
||||||
|
const file = `/tmp/${filename}`;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data: buff } = await axios.get(
|
||||||
|
album.images[i].uri,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"User-Agent":
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0",
|
||||||
|
},
|
||||||
|
responseType: "arraybuffer",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.writeFileSync(file, buff);
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const { data: media } = await M.post("media", {
|
||||||
|
file: fs.createReadStream(file),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { id } = media;
|
||||||
|
|
||||||
|
media_ids.push(id);
|
||||||
|
|
||||||
|
fs.unlinkSync(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await M.post("statuses", { status, media_ids });
|
||||||
|
} else {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
406,
|
||||||
|
`Vous n'avez pas configuré vos options de partage sur votre compte`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de créer la page "ma-collection"
|
||||||
|
*/
|
||||||
|
async loadMyCollection() {
|
||||||
|
const artists = await getAllDistincts(
|
||||||
|
WantListModel,
|
||||||
|
"artists.name",
|
||||||
|
this.req.user._id
|
||||||
|
);
|
||||||
|
const formats = await getAllDistincts(
|
||||||
|
WantListModel,
|
||||||
|
"formats.name",
|
||||||
|
this.req.user._id
|
||||||
|
);
|
||||||
|
const years = await getAllDistincts(
|
||||||
|
WantListModel,
|
||||||
|
"year",
|
||||||
|
this.req.user._id
|
||||||
|
);
|
||||||
|
const genres = await getAllDistincts(
|
||||||
|
WantListModel,
|
||||||
|
"genres",
|
||||||
|
this.req.user._id
|
||||||
|
);
|
||||||
|
const styles = await getAllDistincts(
|
||||||
|
WantListModel,
|
||||||
|
"styles",
|
||||||
|
this.req.user._id
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setPageContent("artists", artists);
|
||||||
|
this.setPageContent("formats", formats);
|
||||||
|
this.setPageContent("years", years);
|
||||||
|
this.setPageContent("genres", genres);
|
||||||
|
this.setPageContent("styles", styles);
|
||||||
|
this.setPageTitle("Ma collection");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant d'afficher le détails d'un album
|
||||||
|
*/
|
||||||
|
async loadItem() {
|
||||||
|
const item = await this.getOne();
|
||||||
|
|
||||||
|
this.setPageContent("item", item);
|
||||||
|
this.setPageTitle(
|
||||||
|
`Détails de l'album ${item.title} de ${item.artists_sort}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de choisir un album de manière aléatoire dans la collection d'un utilisateur
|
||||||
|
*/
|
||||||
|
async onAir() {
|
||||||
|
const { _id: User } = this.req.user;
|
||||||
|
const count = await WantListModel.count({
|
||||||
|
User,
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = await WantListModel.find(
|
||||||
|
{
|
||||||
|
User,
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
skip: Math.floor(Math.random() * (count + 1)),
|
||||||
|
limit: 1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.req.params.itemId = items[0]._id;
|
||||||
|
|
||||||
|
await this.loadItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant d'afficher des statistiques au sujet de ma collection
|
||||||
|
*/
|
||||||
|
async statistics() {
|
||||||
|
const { _id: User } = this.req.user;
|
||||||
|
const top = {};
|
||||||
|
const byGenres = {};
|
||||||
|
const byStyles = {};
|
||||||
|
const byFormats = {};
|
||||||
|
const top10 = [];
|
||||||
|
let byStyles10 = [];
|
||||||
|
const max = this.colors.length - 1;
|
||||||
|
|
||||||
|
const colorsCount = this.colors.length;
|
||||||
|
|
||||||
|
const albums = await WantListModel.find({
|
||||||
|
User,
|
||||||
|
artists: { $exists: true, $not: { $size: 0 } },
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let i = 0; i < albums.length; i += 1) {
|
||||||
|
const currentFormats = [];
|
||||||
|
const { artists, genres, styles, formats } = albums[i];
|
||||||
|
|
||||||
|
// INFO: On regroupe les artistes par nom pour en faire le top10
|
||||||
|
for (let j = 0; j < artists.length; j += 1) {
|
||||||
|
const { name } = artists[j];
|
||||||
|
if (!top[name]) {
|
||||||
|
top[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
top[name].count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: On regroupe les genres
|
||||||
|
for (let j = 0; j < genres.length; j += 1) {
|
||||||
|
const name = genres[j];
|
||||||
|
if (!byGenres[name]) {
|
||||||
|
byGenres[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: this.colors[
|
||||||
|
Object.keys(byGenres).length % colorsCount
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
byGenres[name].count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: On regroupe les styles
|
||||||
|
for (let j = 0; j < styles.length; j += 1) {
|
||||||
|
const name = styles[j];
|
||||||
|
if (!byStyles[name]) {
|
||||||
|
byStyles[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: this.colors[
|
||||||
|
Object.keys(byStyles).length % colorsCount
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
byStyles[name].count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: On regroupe les formats
|
||||||
|
for (let j = 0; j < formats.length; j += 1) {
|
||||||
|
const { name } = formats[j];
|
||||||
|
// INFO: On évite qu'un album avec 2 vinyles soit compté 2x
|
||||||
|
if (!currentFormats.includes(name)) {
|
||||||
|
if (!byFormats[name]) {
|
||||||
|
byFormats[name] = {
|
||||||
|
name,
|
||||||
|
count: 0,
|
||||||
|
color: this.colors[
|
||||||
|
Object.keys(byFormats).length % colorsCount
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
byFormats[name].count += 1;
|
||||||
|
currentFormats.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// INFO: On convertit le top en tableau
|
||||||
|
Object.keys(top).forEach((index) => {
|
||||||
|
top10.push(top[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// INFO: On convertit les styles en tableau
|
||||||
|
Object.keys(byStyles).forEach((index) => {
|
||||||
|
byStyles10.push(byStyles[index]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// INFO: On ordonne les artistes par quantité d'albums
|
||||||
|
top10.sort((a, b) => (a.count > b.count ? -1 : 1));
|
||||||
|
|
||||||
|
// INFO: On ordonne les styles par quantité
|
||||||
|
byStyles10.sort((a, b) => (a.count > b.count ? -1 : 1));
|
||||||
|
const tmp = [];
|
||||||
|
|
||||||
|
// INFO: On recupère le top N des styles et on mets le reste dans le label "autre"
|
||||||
|
for (let i = 0; i < byStyles10.length; i += 1) {
|
||||||
|
if (i < max) {
|
||||||
|
tmp.push({
|
||||||
|
...byStyles10[i],
|
||||||
|
color: this.colors[max - i],
|
||||||
|
});
|
||||||
|
} else if (i === max) {
|
||||||
|
tmp.push({
|
||||||
|
name: "Autre",
|
||||||
|
count: 0,
|
||||||
|
color: this.colors[0],
|
||||||
|
});
|
||||||
|
tmp[max].count += byStyles10[i].count;
|
||||||
|
} else {
|
||||||
|
tmp[max].count += byStyles10[i].count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byStyles10 = tmp;
|
||||||
|
|
||||||
|
this.setPageTitle("Mes statistiques");
|
||||||
|
this.setPageContent("top10", top10.splice(0, 10));
|
||||||
|
this.setPageContent("byGenres", byGenres);
|
||||||
|
this.setPageContent("byStyles", byStyles10);
|
||||||
|
this.setPageContent("byFormats", byFormats);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Méthode permettant de créer la page "collection/:userId"
|
||||||
|
*/
|
||||||
|
async loadPublicCollection() {
|
||||||
|
const { userId } = this.req.params;
|
||||||
|
|
||||||
|
const user = await UsersModel.findById(userId);
|
||||||
|
|
||||||
|
if (!user || !user.isPublicCollection) {
|
||||||
|
throw new ErrorEvent(
|
||||||
|
401,
|
||||||
|
"Collection non partagée",
|
||||||
|
"Cet utilisateur ne souhaite pas partager sa collection"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const artists = await getAllDistincts(
|
||||||
|
WantListModel,
|
||||||
|
"artists.name",
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
const formats = await getAllDistincts(
|
||||||
|
WantListModel,
|
||||||
|
"formats.name",
|
||||||
|
userId
|
||||||
|
);
|
||||||
|
const years = await getAllDistincts(WantListModel, "year", userId);
|
||||||
|
const genres = await getAllDistincts(WantListModel, "genres", userId);
|
||||||
|
const styles = await getAllDistincts(WantListModel, "styles", userId);
|
||||||
|
|
||||||
|
this.setPageTitle(`Collection publique de ${user.username}`);
|
||||||
|
this.setPageContent("username", user.username);
|
||||||
|
this.setPageContent("artists", artists);
|
||||||
|
this.setPageContent("formats", formats);
|
||||||
|
this.setPageContent("years", years);
|
||||||
|
this.setPageContent("genres", genres);
|
||||||
|
this.setPageContent("styles", styles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Wantlist;
|
|
@ -25,6 +25,10 @@ const UserSchema = new mongoose.Schema(
|
||||||
},
|
},
|
||||||
hash: String,
|
hash: String,
|
||||||
salt: String,
|
salt: String,
|
||||||
|
pagination: {
|
||||||
|
type: Number,
|
||||||
|
default: 16,
|
||||||
|
},
|
||||||
isPublicCollection: {
|
isPublicCollection: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -34,6 +38,7 @@ const UserSchema = new mongoose.Schema(
|
||||||
token: String,
|
token: String,
|
||||||
url: String,
|
url: String,
|
||||||
message: String,
|
message: String,
|
||||||
|
wantlist: String,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
37
src/models/wantlist.js
Normal file
37
src/models/wantlist.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import mongoose from "mongoose";
|
||||||
|
|
||||||
|
const { Schema } = mongoose;
|
||||||
|
|
||||||
|
const WantListSchema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
User: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Users",
|
||||||
|
},
|
||||||
|
discogsId: Number,
|
||||||
|
year: Number,
|
||||||
|
released: Date,
|
||||||
|
uri: String,
|
||||||
|
artists: Array,
|
||||||
|
artists_sort: String,
|
||||||
|
labels: Array,
|
||||||
|
series: Array,
|
||||||
|
companies: Array,
|
||||||
|
formats: Array,
|
||||||
|
title: String,
|
||||||
|
country: String,
|
||||||
|
notes: String,
|
||||||
|
identifiers: Array,
|
||||||
|
videos: Array,
|
||||||
|
genres: Array,
|
||||||
|
styles: Array,
|
||||||
|
tracklist: Array,
|
||||||
|
extraartists: Array,
|
||||||
|
images: Array,
|
||||||
|
thumb: String,
|
||||||
|
thumbType: String,
|
||||||
|
},
|
||||||
|
{ timestamps: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
export default mongoose.model("WantList", WantListSchema);
|
|
@ -15,6 +15,16 @@ router
|
||||||
const me = new Me(req);
|
const me = new Me(req);
|
||||||
const data = await me.patchMe();
|
const data = await me.patchMe();
|
||||||
|
|
||||||
|
return sendResponse(req, res, data);
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.delete(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const me = new Me(req);
|
||||||
|
const data = await me.deleteMe();
|
||||||
|
|
||||||
return sendResponse(req, res, data);
|
return sendResponse(req, res, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
84
src/routes/api/v1/wantlist.js
Normal file
84
src/routes/api/v1/wantlist.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import express from "express";
|
||||||
|
import { ensureLoggedIn } from "connect-ensure-login";
|
||||||
|
|
||||||
|
import { sendResponse } from "../../../libs/format";
|
||||||
|
import Albums from "../../../middleware/Wantlist";
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/")
|
||||||
|
.get(async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const albums = new Albums(req, null);
|
||||||
|
const data = await albums.getAll();
|
||||||
|
const { exportFormat = "json" } = req.query;
|
||||||
|
|
||||||
|
switch (exportFormat) {
|
||||||
|
case "csv":
|
||||||
|
case "musictopus":
|
||||||
|
res.header("Content-Type", "text/csv");
|
||||||
|
res.attachment("export-musictopus.csv");
|
||||||
|
return res.status(200).send(data);
|
||||||
|
case "xml":
|
||||||
|
res.header("Content-type", "text/xml");
|
||||||
|
res.attachment("export-musictopus.xml");
|
||||||
|
return res.status(200).send(data);
|
||||||
|
case "xls":
|
||||||
|
return data.write("musictopus.xls", res);
|
||||||
|
case "json":
|
||||||
|
default:
|
||||||
|
return sendResponse(req, res, data);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return next(err);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.post(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const data = await Albums.postAddOne(req);
|
||||||
|
|
||||||
|
sendResponse(req, res, data);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/:itemId")
|
||||||
|
.patch(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const albums = new Albums(req, null);
|
||||||
|
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, null);
|
||||||
|
const data = await albums.deleteOne();
|
||||||
|
|
||||||
|
sendResponse(req, res, data);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/:itemId/share")
|
||||||
|
.post(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const albums = new Albums(req, null);
|
||||||
|
const data = await albums.shareOne();
|
||||||
|
|
||||||
|
sendResponse(req, res, data);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -104,8 +104,23 @@ router
|
||||||
try {
|
try {
|
||||||
const page = new Pages(req, "ajouter-un-album");
|
const page = new Pages(req, "ajouter-un-album");
|
||||||
|
|
||||||
|
page.setPageContent("action", "albums");
|
||||||
page.setPageTitle("Ajouter un album");
|
page.setPageTitle("Ajouter un album");
|
||||||
|
|
||||||
|
render(res, page);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router
|
||||||
|
.route("/ajouter-a-ma-liste-de-souhaits")
|
||||||
|
.get(ensureLoggedIn("/connexion"), (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const page = new Pages(req, "ajouter-un-album");
|
||||||
|
|
||||||
|
page.setPageContent("action", "wantlist");
|
||||||
|
page.setPageTitle("Ajouter un album à ma liste de souhaits");
|
||||||
|
|
||||||
render(res, page);
|
render(res, page);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
|
|
|
@ -38,6 +38,23 @@ router
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/statistiques")
|
||||||
|
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const page = new Albums(
|
||||||
|
req,
|
||||||
|
"mon-compte/ma-collection/statistiques"
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.statistics();
|
||||||
|
|
||||||
|
render(res, page);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
router
|
router
|
||||||
.route("/exporter")
|
.route("/exporter")
|
||||||
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
|
99
src/routes/wantlist.js
Normal file
99
src/routes/wantlist.js
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
import express from "express";
|
||||||
|
import { ensureLoggedIn } from "connect-ensure-login";
|
||||||
|
|
||||||
|
import Albums from "../middleware/Wantlist";
|
||||||
|
|
||||||
|
import render from "../libs/format";
|
||||||
|
|
||||||
|
// eslint-disable-next-line new-cap
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const page = new Albums(req, "collection");
|
||||||
|
|
||||||
|
await page.loadMyCollection();
|
||||||
|
|
||||||
|
if (page.getPageContent("artists").length > 0) {
|
||||||
|
render(res, page);
|
||||||
|
} else {
|
||||||
|
res.redirect("/ajouter-a-ma-liste-de-souhaits");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/on-air")
|
||||||
|
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const page = new Albums(req, "mon-compte/ma-collection/details");
|
||||||
|
|
||||||
|
await page.onAir();
|
||||||
|
|
||||||
|
render(res, page);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/statistiques")
|
||||||
|
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const page = new Albums(
|
||||||
|
req,
|
||||||
|
"mon-compte/ma-collection/statistiques"
|
||||||
|
);
|
||||||
|
|
||||||
|
await page.statistics();
|
||||||
|
|
||||||
|
render(res, page);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/exporter")
|
||||||
|
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const page = new Albums(req, "mon-compte/ma-collection/exporter");
|
||||||
|
|
||||||
|
page.setPageTitle("Exporter ma collection");
|
||||||
|
|
||||||
|
render(res, page);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
router
|
||||||
|
.route("/importer")
|
||||||
|
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const page = new Albums(req, "mon-compte/ma-collection/importer");
|
||||||
|
|
||||||
|
page.setPageTitle("Importer une collection");
|
||||||
|
|
||||||
|
render(res, page);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router
|
||||||
|
.route("/:itemId")
|
||||||
|
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const page = new Albums(req, "mon-compte/ma-collection/details");
|
||||||
|
|
||||||
|
await page.loadItem();
|
||||||
|
|
||||||
|
render(res, page);
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -12,7 +12,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
<ul v-if="track.extraartists && track.extraartists.length > 0" class="sm-hidden">
|
<ul v-if="track.extraartists && track.extraartists.length > 0" class="sm-hidden">
|
||||||
<li v-for="extra in track.extraartists" class=" ml-4">
|
<li v-for="extra in track.extraartists" class=" ml-4">
|
||||||
<small>{{extra.role}} : {{extra.name}}</small>
|
<small>{{extra.role}} : <a :href="`/ma-collection?page=1&limit=16&sort=year&order=asc&artist=${extra.name}`">{{extra.name}}</a></small>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
@ -130,4 +130,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -4,7 +4,7 @@
|
||||||
<option value="">Tous</option>
|
<option value="">Tous</option>
|
||||||
<%
|
<%
|
||||||
for (let i = 0; i < page.artists.length; i += 1 ) {
|
for (let i = 0; i < page.artists.length; i += 1 ) {
|
||||||
__append(`<option value="${page.artists[i]}">${page.artists[i]}</option>`);
|
__append(`<option value="${page.artists[i].replaceAll('"','%22')}">${page.artists[i]}</option>`);
|
||||||
}
|
}
|
||||||
%>
|
%>
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<div class="filters">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-10">
|
||||||
<%- include('./artist') %>
|
<%- include('./artist') %>
|
||||||
<%- include('./format') %>
|
<%- include('./format') %>
|
||||||
<%- include('./sort') %>
|
<%- include('./sort') %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filters" v-if="moreFilters">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-10" v-if="moreFilters">
|
||||||
<%- include('./year') %>
|
<%- include('./year') %>
|
||||||
<%- include('./genre') %>
|
<%- include('./genre') %>
|
||||||
<%- include('./style') %>
|
<%- include('./style') %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span @click="showMoreFilters" class="showMoreFilters">
|
<span @click="showMoreFilters" class="showMoreFilters mt-10">
|
||||||
<template v-if="!moreFilters">Voir plus de filtres</template>
|
<template v-if="!moreFilters">Voir plus de filtres</template>
|
||||||
<template v-if="moreFilters">Voir moins de filtres</template>
|
<template v-if="moreFilters">Voir moins de filtres</template>
|
||||||
<i class="icon-left-open down" v-if="!moreFilters"></i>
|
<i class="icon-left-open down" v-if="!moreFilters"></i>
|
||||||
|
|
129
views/index.ejs
129
views/index.ejs
|
@ -16,6 +16,9 @@
|
||||||
|
|
||||||
<link href="/css/main.css" rel="stylesheet" />
|
<link href="/css/main.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<script src="/js/libs.js"></script>
|
||||||
|
<script defer src="/js/main.js"></script>
|
||||||
|
|
||||||
<% if ( config.matomoUrl ) { %>
|
<% if ( config.matomoUrl ) { %>
|
||||||
<!-- Matomo -->
|
<!-- Matomo -->
|
||||||
<script>
|
<script>
|
||||||
|
@ -66,12 +69,51 @@
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<div class="navbar-end">
|
<ul class="navbar-end">
|
||||||
<a class="navbar-item" href="/nous-contacter">
|
<li class="navbar-item">
|
||||||
|
<a href="/nous-contacter">
|
||||||
Nous contacter
|
Nous contacter
|
||||||
</a>
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="navbar-item has-dropdown">
|
||||||
|
<a class="navbar-link">
|
||||||
|
<i class="icon-adjust theme-system icon-theme hidden"></i>
|
||||||
|
<i class="icon-sun theme-light icon-theme hidden"></i>
|
||||||
|
<i class="icon-moon theme-dark icon-theme hidden"></i>
|
||||||
|
<span>
|
||||||
|
Thème
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<ul class="navbar-dropdown">
|
||||||
|
<li>
|
||||||
|
<button class="navbar-item theme" data-value="system">
|
||||||
|
<i class="icon-adjust"></i>
|
||||||
|
<span>
|
||||||
|
Système
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="navbar-item theme" data-value="light">
|
||||||
|
<i class="icon-sun"></i>
|
||||||
|
<span>
|
||||||
|
Clair
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<button class="navbar-item theme" data-value="dark">
|
||||||
|
<i class="icon-moon"></i>
|
||||||
|
<span>
|
||||||
|
Sombre
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
<% if ( user ) { %>
|
<% if ( user ) { %>
|
||||||
<div class="navbar-item has-dropdown">
|
<li class="navbar-item has-dropdown">
|
||||||
<a class="navbar-link">
|
<a class="navbar-link">
|
||||||
<i class="icon-user"></i>
|
<i class="icon-user"></i>
|
||||||
<span>
|
<span>
|
||||||
|
@ -79,48 +121,80 @@
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="navbar-dropdown">
|
<ul class="navbar-dropdown">
|
||||||
|
<li>
|
||||||
<a class="navbar-item" href="/mon-compte">
|
<a class="navbar-item" href="/mon-compte">
|
||||||
Mon compte
|
Mon compte
|
||||||
</a>
|
</a>
|
||||||
<hr />
|
</li>
|
||||||
<a class="navbar-item" href="/ma-collection">
|
<li><hr /></li>
|
||||||
Ma collection
|
<li>
|
||||||
</a>
|
<a class="navbar-item" href="/ma-collection">Ma collection</a>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
<a class="navbar-item" href="/ma-collection/on-air">
|
<a class="navbar-item" href="/ma-collection/on-air">
|
||||||
On air
|
On air
|
||||||
</a>
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="navbar-item" href="/ma-collection/statistiques">
|
||||||
|
Statistiques
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
<a class="navbar-item" href="/ma-collection/exporter">
|
<a class="navbar-item" href="/ma-collection/exporter">
|
||||||
Exporter ma collection
|
Exporter
|
||||||
</a>
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
<a class="navbar-item" href="/ma-collection/importer">
|
<a class="navbar-item" href="/ma-collection/importer">
|
||||||
Importer une collection
|
Importer
|
||||||
</a>
|
</a>
|
||||||
<hr />
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><hr /></li>
|
||||||
|
<li>
|
||||||
|
<a class="navbar-item" href="/ma-liste-de-souhaits">
|
||||||
|
Ma liste de souhaits
|
||||||
|
</a>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a class="navbar-item" href="/ajouter-a-ma-liste-de-souhaits">
|
||||||
|
Ajouter
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="navbar-item" href="/ma-liste-de-souhaits/exporter">
|
||||||
|
Exporter
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="navbar-item" href="/ma-liste-de-souhaits/importer">
|
||||||
|
Importer
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li><hr /></li>
|
||||||
|
<li>
|
||||||
<a class="navbar-item is-danger" href="/se-deconnecter">
|
<a class="navbar-item is-danger" href="/se-deconnecter">
|
||||||
Déconnexion
|
Déconnexion
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
<div class="navbar-item apparence">
|
|
||||||
<div class="theme-switch-wrapper">
|
|
||||||
<label class="theme-switch" for="checkbox" aria-label="Passer du thème clair au thème sombre et inversement">
|
|
||||||
<input type="checkbox" id="checkbox" />
|
|
||||||
<div class="slider round"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% if ( !user ) { %>
|
<% if ( !user ) { %>
|
||||||
<div class="navbar-item">
|
<li class="navbar-item">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a class="button is-primary" href="/connexion">
|
<a class="button is-primary" href="/connexion">
|
||||||
<strong>Connexion</strong>
|
<strong>Connexion</strong>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</nav>
|
</nav>
|
||||||
|
@ -182,13 +256,10 @@
|
||||||
<strong title="Merci Brunus ! 😜">MusicTopus</strong> par <a href="https://www.darkou.link" target="_blank" rel="noopener noreferrer">Damien Broqua <i class="icon-link"></i></a>.
|
<strong title="Merci Brunus ! 😜">MusicTopus</strong> par <a href="https://www.darkou.link" target="_blank" rel="noopener noreferrer">Damien Broqua <i class="icon-link"></i></a>.
|
||||||
Logo réalisé par Brunus avec <a href="https://inkscape.org/fr/" target="_blank" rel="noopener noreferrer">Inkscape <i class="icon-link"></i></a>.
|
Logo réalisé par Brunus avec <a href="https://inkscape.org/fr/" target="_blank" rel="noopener noreferrer">Inkscape <i class="icon-link"></i></a>.
|
||||||
<br />
|
<br />
|
||||||
Le code source est sous licence <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html" target="_blank" rel="noopener noreferrer">GNU GPL-3.0-or-later <i class="icon-link"></i></a> et disponible sur <a href="https://git.darkou.fr/dbroqua/MusicTopus" target="_blank">git.darkou.fr <i class="icon-link"></i></a>.
|
Le code source est sous licence <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html" target="_blank" rel="noopener noreferrer">GNU GPL-3.0-or-later <i class="icon-link"></i></a> et disponible sur <a href="https://forge.darkou.fr/contact/MusicTopus" target="_blank">forge.darkou.fr <i class="icon-link"></i></a>.
|
||||||
<br />
|
<br />
|
||||||
Fait avec ❤️ à Bordeaux.
|
Fait avec <span role="img" aria-label="amour">❤️</span> à Bordeaux. <img src="/img/emoji-lmhf.svg" class="lmhf" alt="lmhf" alt="Love Music, Hate Fascism" title="Love Music, Hate Fascism" />
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script defer src="/js/libs.js"></script>
|
|
||||||
<script defer src="/js/main.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -200,4 +200,5 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const canPublish = <%- (user.mastodon && user.mastodon.publish) || false %>;
|
const canPublish = <%- (user.mastodon && user.mastodon.publish) || false %>;
|
||||||
|
const action = "<%- page.action %>";
|
||||||
</script>
|
</script>
|
|
@ -3,6 +3,7 @@
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<main class="layout-maxed collection" id="collection">
|
<main class="layout-maxed collection" id="collection">
|
||||||
|
<% if (page.action === 'albums') { %>
|
||||||
<h1>
|
<h1>
|
||||||
<% if ( pageType === 'private' ) {
|
<% if ( pageType === 'private' ) {
|
||||||
__append('Ma collection <i class="icon-share" @click="toggleModalShare" aria-label="Partager ma collection" title="Votre collection sera visible en lecture aux personnes ayant le lien de partage"></i>');
|
__append('Ma collection <i class="icon-share" @click="toggleModalShare" aria-label="Partager ma collection" title="Votre collection sera visible en lecture aux personnes ayant le lien de partage"></i>');
|
||||||
|
@ -17,6 +18,9 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
<% } else { %>
|
||||||
|
<h1>Ma liste de souhaits</h1>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<%- include('../components/filters/index') %>
|
<%- include('../components/filters/index') %>
|
||||||
|
|
||||||
|
@ -30,7 +34,7 @@
|
||||||
<div class="item" v-if="!loading" v-for="item in items">
|
<div class="item" v-if="!loading" v-for="item in items">
|
||||||
<span class="title">
|
<span class="title">
|
||||||
<% if ( pageType === 'private' ) { %>
|
<% if ( pageType === 'private' ) { %>
|
||||||
<a :href="'/ma-collection/' + item._id">{{ renderAlbumTitle(item) }}</a>
|
<a :href="'/<%= page.action === 'albums' ? 'ma-collection' : 'ma-liste-de-souhaits' %>/' + item._id">{{ renderAlbumTitle(item) }}</a>
|
||||||
<i class="icon-trash" @click="showConfirmDelete(item._id)"></i>
|
<i class="icon-trash" @click="showConfirmDelete(item._id)"></i>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
{{ item.artists_sort}} - {{ item.title }}
|
{{ item.artists_sort}} - {{ item.title }}
|
||||||
|
@ -39,7 +43,7 @@
|
||||||
<div class="grid grid-cols-2 md:grid-cols-4">
|
<div class="grid grid-cols-2 md:grid-cols-4">
|
||||||
<div>
|
<div>
|
||||||
<% if ( pageType === 'private' ) { %>
|
<% if ( pageType === 'private' ) { %>
|
||||||
<a :href="'/ma-collection/' + item._id"><img :src="item.thumb" :alt="item.title" /></a>
|
<a :href="'/<%= page.action === 'albums' ? 'ma-collection' : 'ma-liste-de-souhaits' %>/' + item._id"><img :src="item.thumb" :alt="item.title" /></a>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<img :src="item.thumb" :alt="item.title" />
|
<img :src="item.thumb" :alt="item.title" />
|
||||||
<% } %>
|
<% } %>
|
||||||
|
@ -69,11 +73,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex justify-between md:align-items-center flex-col md:flex-row">
|
||||||
<div class="total">
|
<div class="total">
|
||||||
<strong>Nombre total d'éléments : </strong>{{total}}
|
<strong>Nombre total d'éléments : </strong>{{total}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="pagination" role="navigation" aria-label="Pagination">
|
<nav class="pagination" role="navigation" aria-label="Pagination">
|
||||||
<ul class="pagination-list">
|
<ul class="flex justify-center md:justify-end pagination-list">
|
||||||
<li v-if="page > 1">
|
<li v-if="page > 1">
|
||||||
<a class="pagination-link" @click="goTo(1)" aria-label="Aller à la première page">«</a>
|
<a class="pagination-link" @click="goTo(1)" aria-label="Aller à la première page">«</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -94,6 +100,9 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<% if ( pageType === 'private' ) { %>
|
<% if ( pageType === 'private' ) { %>
|
||||||
<div class="modal" :class="{'is-visible': showModalDelete}">
|
<div class="modal" :class="{'is-visible': showModalDelete}">
|
||||||
|
@ -147,6 +156,7 @@
|
||||||
<script>
|
<script>
|
||||||
const vueType = "<%= pageType %>";
|
const vueType = "<%= pageType %>";
|
||||||
const query = <%- JSON.stringify(query) %>;
|
const query = <%- JSON.stringify(query) %>;
|
||||||
|
const action = "<%- page.action %>";
|
||||||
<% if ( pageType === 'private' ) { %>
|
<% if ( pageType === 'private' ) { %>
|
||||||
const isPublicCollection = <%= user.isPublicCollection ? 'true' : 'false' %>;
|
const isPublicCollection = <%= user.isPublicCollection ? 'true' : 'false' %>;
|
||||||
const userId = "<%= user._id %>";
|
const userId = "<%= user._id %>";
|
||||||
|
|
|
@ -238,7 +238,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 id="boites">Les boites</h2>
|
<h2 id="boites">Les boites</h2>
|
||||||
<div class="box">
|
<div class="box mini">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<h1>
|
<h1>
|
||||||
Connexion
|
Connexion
|
||||||
|
@ -487,14 +487,6 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-item apparence">
|
|
||||||
<div class="theme-switch-wrapper">
|
|
||||||
<label class="theme-switch" for="checkbox" aria-label="Passer du thème clair au thème sombre et inversement">
|
|
||||||
<input type="checkbox" id="checkbox" />
|
|
||||||
<div class="slider round"></div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<a class="button is-danger" href="/se-deconnecter">
|
<a class="button is-danger" href="/se-deconnecter">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="box">
|
<div class="box mini">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<h1>
|
<h1>
|
||||||
Connexion
|
Connexion
|
||||||
|
|
|
@ -14,13 +14,13 @@
|
||||||
<p class="text-justify">
|
<p class="text-justify">
|
||||||
<strong>MusicTopus</strong> est une application Web (que vous pouvez auto-héberger) et un site Web (sur lequel vous pouvez créer un compte), permettant de gérer votre liste des CDs et Vinyles, et de l'utiliser facilement et n'importe où.
|
<strong>MusicTopus</strong> est une application Web (que vous pouvez auto-héberger) et un site Web (sur lequel vous pouvez créer un compte), permettant de gérer votre liste des CDs et Vinyles, et de l'utiliser facilement et n'importe où.
|
||||||
<br />
|
<br />
|
||||||
Le code source est publié sous licence libre <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html" target="_blank" rel="noopener noreferrer">GNU GPL-3.0-or-later <i class="icon-link"></i></a>. Le code source est disponible sur <a href="https://git.darkou.fr/dbroqua/MusicTopus" target="_blank">git.darkou.fr <i class="icon-link"></i></a>.
|
Le code source est publié sous licence libre <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html" target="_blank" rel="noopener noreferrer">GNU GPL-3.0-or-later <i class="icon-link"></i></a>. Le code source est disponible sur <a href="https://forge.darkou.fr/contact/MusicTopus" target="_blank">forge.darkou.fr <i class="icon-link"></i></a>.
|
||||||
</p>
|
</p>
|
||||||
<h2>
|
<h2>
|
||||||
Pourquoi utiliser MusicTopus ?
|
Pourquoi utiliser MusicTopus ?
|
||||||
</h2>
|
</h2>
|
||||||
<p class="text-justify">
|
<p class="text-justify">
|
||||||
<strong>MusicTopus</strong> est indispensable lorsqu'une collection, de CD-audios et vyniles, est devenue trop importante pour qu'on puisse se souvenir de tous les albums qu'elle contient. Consulter MusicTopus peut par exemple éviter un achat en double, et de savoir qu'on a des albums à céder ou échanger.
|
<strong>MusicTopus</strong> est indispensable lorsqu'une collection, de CD-audios et vinyles, est devenue trop importante pour qu'on puisse se souvenir de tous les albums qu'elle contient. Consulter MusicTopus peut par exemple éviter un achat en double, et de savoir qu'on a des albums à céder ou échanger.
|
||||||
<br />
|
<br />
|
||||||
Il existe déjà plusieurs applications de gestion de librairies musicales mais, (au moment de l'édition de cette présentation) aucune facilement accessible via internet, par exemple lorsqu'on est chez un disquaire.
|
Il existe déjà plusieurs applications de gestion de librairies musicales mais, (au moment de l'édition de cette présentation) aucune facilement accessible via internet, par exemple lorsqu'on est chez un disquaire.
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
Les inscriptions sur ce site sont fermées.
|
Les inscriptions sur ce site sont fermées.
|
||||||
</p>
|
</p>
|
||||||
<p class="text-justify">
|
<p class="text-justify">
|
||||||
Vous avez cependant la possibilité d'héberger vous même une version de <a href="https://www.musictopus.fr" target="_blank">MusicTopus</a> en vous rendant directement sur le <a href="https://git.darkou.fr/dbroqua/MusicTopus" target="_blank">dépot du projet</a>.
|
Vous avez cependant la possibilité d'héberger vous même une version de <a href="https://www.musictopus.fr" target="_blank">MusicTopus</a> en vous rendant directement sur le <a href="https://forge.darkou.fr/contact/MusicTopus" target="_blank">dépot du projet</a>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-center">
|
<p class="text-center">
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="box">
|
<div class="box mini">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<h1>
|
<h1>
|
||||||
Inscription
|
Inscription
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
<form method="POST" @submit.prevent="updateProfil">
|
<form method="POST" @submit.prevent="updateProfil">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||||
<div>
|
<div class="box">
|
||||||
<h2>Mes données personnelles</h2>
|
<h2>Mes données personnelles</h2>
|
||||||
<div>
|
<div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -68,7 +68,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="box">
|
||||||
<h2>Mon activité</h2>
|
<h2>Mon activité</h2>
|
||||||
<div>
|
<div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -110,6 +110,12 @@
|
||||||
id="mastodon.message"
|
id="mastodon.message"
|
||||||
v-model="formData.mastodon.message"
|
v-model="formData.mastodon.message"
|
||||||
></textarea>
|
></textarea>
|
||||||
|
<label for="mastodon.wantlist">Message pour la liste de souhaits</label>
|
||||||
|
<textarea
|
||||||
|
name="mastodon.wantlist"
|
||||||
|
id="mastodon.wantlist"
|
||||||
|
v-model="formData.mastodon.wantlist"
|
||||||
|
></textarea>
|
||||||
<small>
|
<small>
|
||||||
Variables possibles :
|
Variables possibles :
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -118,6 +124,10 @@
|
||||||
<li>{format}, exemple : Cassette</li>
|
<li>{format}, exemple : Cassette</li>
|
||||||
<li>{year}, exemple: 1984</li>
|
<li>{year}, exemple: 1984</li>
|
||||||
<li>{video}, exemple : https://www.youtube.com/watch?v=Qx0s8OqgBIw</li>
|
<li>{video}, exemple : https://www.youtube.com/watch?v=Qx0s8OqgBIw</li>
|
||||||
|
<li>{genres}, exemple : Rock</li>
|
||||||
|
<li>{#genres}, exemple : #rock</li>
|
||||||
|
<li>{styles}, exemple : Hard Rock, Heavy Metal</li>
|
||||||
|
<li>{#styles}, exemple : #hardRock, #heavyMetal</li>
|
||||||
</ul>
|
</ul>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
@ -126,6 +136,54 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="box">
|
||||||
|
<h2>Mes préférences</h2>
|
||||||
|
<div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="pagination">Pagination</label>
|
||||||
|
<select id="pagination" v-model="formData.pagination">
|
||||||
|
<option value="16">16 albums/page</option>
|
||||||
|
<option value="24">24 albums/page</option>
|
||||||
|
<option value="32">32 albums/page</option>
|
||||||
|
<option value="48">48 albums/page</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h2>Supprimer mon compte</h2>
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Vous souhaitez supprimer votre compte et vos collections ?
|
||||||
|
<br />
|
||||||
|
Rien de plus simple !
|
||||||
|
<br />
|
||||||
|
Il vous suffit de cliquer sur le bouton ci-dessous et l'on se charge de supprimer dans la seconde absolument toutes les données stockées sur cette instance vous concernant !
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="flash error">
|
||||||
|
<div class="header">
|
||||||
|
Attention
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
Toute suppression est définitive
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="delete">
|
||||||
|
<input type="checkbox" id="delete" v-model="formData.delete">
|
||||||
|
J'ai compris
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" class="button is-danger mt-10" :disabled="deleting || !formData.delete" @click="deleteAccount">
|
||||||
|
<span v-if="!deleting">Supprimer</span>
|
||||||
|
<i class="icon-spin animate-spin" v-if="deleting"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="button is-primary mt-10" :disabled="loading">
|
<button type="submit" class="button is-primary mt-10" :disabled="loading">
|
||||||
<span v-if="!loading">Mettre à jour</span>
|
<span v-if="!loading">Mettre à jour</span>
|
||||||
|
@ -139,5 +197,6 @@
|
||||||
<script>
|
<script>
|
||||||
const email = '<%= user.email %>';
|
const email = '<%= user.email %>';
|
||||||
const username = '<%= user.username %>';
|
const username = '<%= user.username %>';
|
||||||
|
const pagination = "<%= user.pagination || 16 %>";
|
||||||
const mastodon = <%- JSON.stringify(user.mastodon || {publish: false, url: '', token: '', message: ''}) %>;
|
const mastodon = <%- JSON.stringify(user.mastodon || {publish: false, url: '', token: '', message: ''}) %>;
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<h1>
|
<h1>
|
||||||
<template v-for="artist in item.artists">
|
<template v-for="artist in item.artists">
|
||||||
<a :href="`/ma-collection?page=1&limit=16&sort=year&order=asc&artist=${artist.name}`">{{artist.name}}</a>
|
<a :href="`/<%= page.action === 'albums' ? 'ma-collection' : 'ma-liste-de-souhaits' %>?page=1&limit=16&sort=year&order=asc&artist=${artist.name}`">{{artist.name}}</a>
|
||||||
<template v-if="artist.join"> {{artist.join}} </template>
|
<template v-if="artist.join"> {{artist.join}} </template>
|
||||||
</template>
|
</template>
|
||||||
- {{item.title}}
|
- {{item.title}}
|
||||||
|
@ -79,6 +79,10 @@
|
||||||
<li>{format}, exemple : Cassette</li>
|
<li>{format}, exemple : Cassette</li>
|
||||||
<li>{year}, exemple: 1984</li>
|
<li>{year}, exemple: 1984</li>
|
||||||
<li>{video}, exemple : https://www.youtube.com/watch?v=Qx0s8OqgBIw</li>
|
<li>{video}, exemple : https://www.youtube.com/watch?v=Qx0s8OqgBIw</li>
|
||||||
|
<li>{genres}, exemple : Rock</li>
|
||||||
|
<li>{#genres}, exemple : #rock</li>
|
||||||
|
<li>{styles}, exemple : Hard Rock, Heavy Metal</li>
|
||||||
|
<li>{#styles}, exemple : #hardRock, #heavyMetal</li>
|
||||||
</ul>
|
</ul>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,5 +98,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const item = <%- JSON.stringify(page.item) %>;
|
const item = <%- JSON.stringify(page.item) %>;
|
||||||
|
const action = "<%- page.action %>";
|
||||||
const canShareItem = <%= user.mastodon?.publish || false %>;
|
const canShareItem = <%= user.mastodon?.publish || false %>;
|
||||||
</script>
|
</script>
|
|
@ -1,5 +1,9 @@
|
||||||
<main class="layout-maxed" id="exporter">
|
<main class="layout-maxed" id="exporter">
|
||||||
|
<% if (page.action === 'albums') { %>
|
||||||
<h1>Exporter ma collection</h1>
|
<h1>Exporter ma collection</h1>
|
||||||
|
<% } else { %>
|
||||||
|
<h1>Exporter ma liste de souhaits</h1>
|
||||||
|
<% } %>
|
||||||
<p>
|
<p>
|
||||||
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 :
|
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 :
|
||||||
</p>
|
</p>
|
||||||
|
@ -45,3 +49,7 @@
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const action = "<%- page.action %>";
|
||||||
|
</script>
|
|
@ -1,7 +1,11 @@
|
||||||
<main class="layout-maxed" id="importer">
|
<main class="layout-maxed" id="importer">
|
||||||
|
<% if (page.action === 'albums') { %>
|
||||||
<h1>Importer une collection</h1>
|
<h1>Importer une collection</h1>
|
||||||
|
<% } else { %>
|
||||||
|
<h1>Importer une liste de souhaits</h1>
|
||||||
|
<% } %>
|
||||||
<p>
|
<p>
|
||||||
Il est actuellement possible d'importer une collection provenant de discogs.
|
Il est actuellement possible d'importer <%= page.action === 'albums' ? "une collection" : "une liste de souhaits" %> provenant de discogs.
|
||||||
<br />
|
<br />
|
||||||
Vous devez dans un premier temps vous rendre sur la page <a href="https://www.discogs.com/fr/users/export" target="_blank" rel="noopener noreferrer">Exporter</a> de discogs.
|
Vous devez dans un premier temps vous rendre sur la page <a href="https://www.discogs.com/fr/users/export" target="_blank" rel="noopener noreferrer">Exporter</a> de discogs.
|
||||||
<br />
|
<br />
|
||||||
|
@ -41,3 +45,7 @@
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const action = "<%- page.action %>";
|
||||||
|
</script>
|
238
views/pages/mon-compte/ma-collection/statistiques.ejs
Normal file
238
views/pages/mon-compte/ma-collection/statistiques.ejs
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
<main class="layout-maxed ma-collection-details" id="ma-collection-statistiques">
|
||||||
|
<h1>
|
||||||
|
Mes statistiques
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-10 mb-10">
|
||||||
|
<div class="md:col-span-2 box">
|
||||||
|
<h2>Mon top 10</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 60px;"></th>
|
||||||
|
<th>Artiste</th>
|
||||||
|
<th style="width: 100px;">Albums</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% for ( let i = 0 ; i < page.top10.length ; i += 1 ) { %>
|
||||||
|
<tr>
|
||||||
|
<td><%= i+1 %></td>
|
||||||
|
<td><%= page.top10[i].name %></td>
|
||||||
|
<td><%= page.top10[i].count %></td>
|
||||||
|
</tr>
|
||||||
|
<% } %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h2>Genres</h2>
|
||||||
|
<canvas id="byGenres"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h2>Styles</h2>
|
||||||
|
<canvas id="byStyles"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h2>Formats</h2>
|
||||||
|
<canvas id="byFormats"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><!-- histoire de faire un break --></div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h2><%= page.currentYear.year %> (<%= page.currentYear.total %> ajout<%= page.currentYear.total > 1 ? 's' : '' %>)</h2>
|
||||||
|
<h3>Genres</h3>
|
||||||
|
<canvas id="currentYearGenres"></canvas>
|
||||||
|
<h3>Styles</h3>
|
||||||
|
<canvas id="currentYearStyles"></canvas>
|
||||||
|
<h3>Formats</h3>
|
||||||
|
<canvas id="currentYearFormats"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h2><%= page.lastYear.year %> (<%= page.lastYear.total %> ajout<%= page.lastYear.total > 1 ? 's' : '' %>)</h2>
|
||||||
|
<h3>Genres</h3>
|
||||||
|
<canvas id="lastYearGenres"></canvas>
|
||||||
|
<h3>Styles</h3>
|
||||||
|
<canvas id="lastYearStyles"></canvas>
|
||||||
|
<h3>Formats</h3>
|
||||||
|
<canvas id="lastYearFormats"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const byGenres = <%- JSON.stringify(page.byGenres) %>;
|
||||||
|
const byStyles = <%- JSON.stringify(page.byStyles) %>;
|
||||||
|
const byFormats = <%- JSON.stringify(page.byFormats) %>;
|
||||||
|
|
||||||
|
const currentYear = <%- JSON.stringify(page.currentYear) %>;
|
||||||
|
const lastYear = <%- JSON.stringify(page.lastYear) %>;
|
||||||
|
|
||||||
|
const ctxGenres= document.getElementById('byGenres');
|
||||||
|
const ctxStyles = document.getElementById('byStyles');
|
||||||
|
const ctxFormats = document.getElementById('byFormats');
|
||||||
|
|
||||||
|
const ctxCurrentYearGenres = document.getElementById('currentYearGenres');
|
||||||
|
const ctxCurrentYearStyles = document.getElementById('currentYearStyles');
|
||||||
|
const ctxCurrentYearFormats = document.getElementById('currentYearFormats');
|
||||||
|
|
||||||
|
const ctxLastYearGenres = document.getElementById('lastYearGenres');
|
||||||
|
const ctxLastYearStyles = document.getElementById('lastYearStyles');
|
||||||
|
const ctxLastYearFormats = document.getElementById('lastYearFormats');
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
responsive: true,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
display: false,
|
||||||
|
position: 'bottom',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new Chart(ctxGenres, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: Object.keys(byGenres).map((index) => {return byGenres[index].name}),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
backgroundColor: Object.keys(byGenres).map((index) => {return byGenres[index].color}),
|
||||||
|
data: Object.keys(byGenres).map((index) => {return byGenres[index].count}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
new Chart(ctxCurrentYearGenres, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: Object.keys(currentYear.byGenres).map((index) => {return currentYear.byGenres[index].name}),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
backgroundColor: Object.keys(currentYear.byGenres).map((index) => {return currentYear.byGenres[index].color}),
|
||||||
|
data: Object.keys(currentYear.byGenres).map((index) => {return currentYear.byGenres[index].count}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
new Chart(ctxLastYearGenres, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: Object.keys(lastYear.byGenres).map((index) => {return lastYear.byGenres[index].name}),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
backgroundColor: Object.keys(lastYear.byGenres).map((index) => {return lastYear.byGenres[index].color}),
|
||||||
|
data: Object.keys(lastYear.byGenres).map((index) => {return lastYear.byGenres[index].count}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
|
||||||
|
const styleLabels = [];
|
||||||
|
const styleBg = [];
|
||||||
|
const styleData = [];
|
||||||
|
for ( let i = 0 ; i < byStyles.length ; i += 1 ) {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
count,
|
||||||
|
} = byStyles[i];
|
||||||
|
|
||||||
|
styleLabels.push(name);
|
||||||
|
styleBg.push(color);
|
||||||
|
styleData.push(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
new Chart(ctxStyles, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: styleLabels,
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
backgroundColor: styleBg,
|
||||||
|
data: styleData,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
new Chart(ctxCurrentYearStyles, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: Object.keys(currentYear.byStyles).map((index) => {return currentYear.byStyles[index].name}),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
backgroundColor: Object.keys(currentYear.byStyles).map((index) => {return currentYear.byStyles[index].color}),
|
||||||
|
data: Object.keys(currentYear.byStyles).map((index) => {return currentYear.byStyles[index].count}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
new Chart(ctxLastYearStyles, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: Object.keys(lastYear.byStyles).map((index) => {return lastYear.byStyles[index].name}),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
backgroundColor: Object.keys(lastYear.byStyles).map((index) => {return lastYear.byStyles[index].color}),
|
||||||
|
data: Object.keys(lastYear.byStyles).map((index) => {return lastYear.byStyles[index].count}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(ctxFormats, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: Object.keys(byFormats).map((index) => {return byFormats[index].name}),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
backgroundColor: Object.keys(byFormats).map((index) => {return byFormats[index].color}),
|
||||||
|
data: Object.keys(byFormats).map((index) => {return byFormats[index].count}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(ctxCurrentYearFormats, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: Object.keys(currentYear.byFormats).map((index) => {return currentYear.byFormats[index].name}),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
backgroundColor: Object.keys(currentYear.byFormats).map((index) => {return currentYear.byFormats[index].color}),
|
||||||
|
data: Object.keys(currentYear.byFormats).map((index) => {return currentYear.byFormats[index].count}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
|
||||||
|
new Chart(ctxLastYearFormats, {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: Object.keys(lastYear.byFormats).map((index) => {return lastYear.byFormats[index].name}),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
backgroundColor: Object.keys(lastYear.byFormats).map((index) => {return lastYear.byFormats[index].color}),
|
||||||
|
data: Object.keys(lastYear.byFormats).map((index) => {return lastYear.byFormats[index].count}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
|
@ -1,4 +1,4 @@
|
||||||
<section class="box" id="contact">
|
<section class="box mini" id="contact">
|
||||||
<h1>Nous contacter</h1>
|
<h1>Nous contacter</h1>
|
||||||
<form @submit="send" <% if (config.mailMethod === 'formspree' ) { %> id="contact" method="POST" action="https://formspree.io/f/<%= config.formspreeId %>" <% } %>>
|
<form @submit="send" <% if (config.mailMethod === 'formspree' ) { %> id="contact" method="POST" action="https://formspree.io/f/<%= config.formspreeId %>" <% } %>>
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-16">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-16">
|
||||||
|
|
Loading…
Add table
Reference in a new issue