Compare commits
No commits in common. "master" and "1.4" have entirely different histories.
81 changed files with 1557 additions and 21870 deletions
|
@ -22,13 +22,7 @@ module.exports = {
|
||||||
camelcase: [
|
camelcase: [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
allow: [
|
allow: ["artists_sort"],
|
||||||
"artists_sort",
|
|
||||||
"access_token",
|
|
||||||
"api_url",
|
|
||||||
"media_ids",
|
|
||||||
"release_id",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -124,4 +124,3 @@ public/css
|
||||||
public/js
|
public/js
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
dump
|
dump
|
||||||
data
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ version: "2.4"
|
||||||
services:
|
services:
|
||||||
musictopus-www:
|
musictopus-www:
|
||||||
container_name: musictopus-www
|
container_name: musictopus-www
|
||||||
image: "node:18"
|
image: "node:16"
|
||||||
restart: always
|
restart: always
|
||||||
user: "node"
|
user: "node"
|
||||||
working_dir: /home/node/app
|
working_dir: /home/node/app
|
||||||
|
@ -55,7 +55,6 @@ services:
|
||||||
- musictopus
|
- musictopus
|
||||||
volumes:
|
volumes:
|
||||||
- ./dump:/dump
|
- ./dump:/dump
|
||||||
- ./data:/data/db
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
musictopus:
|
musictopus:
|
||||||
|
|
|
@ -3,7 +3,7 @@ version: "2.4"
|
||||||
services:
|
services:
|
||||||
musictopus-www:
|
musictopus-www:
|
||||||
container_name: musictopus-www
|
container_name: musictopus-www
|
||||||
image: "node:18"
|
image: "node:16"
|
||||||
restart: always
|
restart: always
|
||||||
user: "node"
|
user: "node"
|
||||||
working_dir: /home/node/app
|
working_dir: /home/node/app
|
||||||
|
|
|
@ -6,12 +6,6 @@
|
||||||
"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,7 +10,6 @@ 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,9 +1,6 @@
|
||||||
/* eslint-disable no-undef */
|
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
share: canPublish,
|
|
||||||
q: "",
|
q: "",
|
||||||
year: "",
|
year: "",
|
||||||
country: "",
|
country: "",
|
||||||
|
@ -12,7 +9,6 @@ Vue.createApp({
|
||||||
items: [],
|
items: [],
|
||||||
details: {},
|
details: {},
|
||||||
modalIsVisible: false,
|
modalIsVisible: false,
|
||||||
submitting: false,
|
|
||||||
formats: [
|
formats: [
|
||||||
"Vinyl",
|
"Vinyl",
|
||||||
"Acetate",
|
"Acetate",
|
||||||
|
@ -79,12 +75,6 @@ Vue.createApp({
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
|
||||||
window.addEventListener("keydown", this.keyDown);
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
window.removeEventListener("keydown", this.keyDown);
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
search(event) {
|
search(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -122,7 +112,6 @@ Vue.createApp({
|
||||||
format,
|
format,
|
||||||
genre,
|
genre,
|
||||||
style,
|
style,
|
||||||
inCollection,
|
|
||||||
} = results[i];
|
} = results[i];
|
||||||
items.push({
|
items.push({
|
||||||
id,
|
id,
|
||||||
|
@ -133,7 +122,6 @@ Vue.createApp({
|
||||||
format,
|
format,
|
||||||
genre,
|
genre,
|
||||||
style,
|
style,
|
||||||
inCollection,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,24 +160,12 @@ Vue.createApp({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
add() {
|
add() {
|
||||||
if (this.submitting) {
|
axios
|
||||||
return true;
|
.post("/api/v1/albums", this.details)
|
||||||
}
|
|
||||||
this.submitting = true;
|
|
||||||
|
|
||||||
return axios
|
|
||||||
.post(`/api/v1/${action}`, {
|
|
||||||
album: this.details,
|
|
||||||
share: this.share,
|
|
||||||
})
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.location.href =
|
window.location.href = "/ma-collection";
|
||||||
action === "albums"
|
|
||||||
? "/ma-collection"
|
|
||||||
: "/ma-liste-de-souhaits";
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
this.submitting = false;
|
|
||||||
showToastr(
|
showToastr(
|
||||||
err.response?.data?.message ||
|
err.response?.data?.message ||
|
||||||
"Impossible d'ajouter cet album pour le moment…"
|
"Impossible d'ajouter cet album pour le moment…"
|
||||||
|
@ -199,13 +175,5 @@ 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,4 +1,4 @@
|
||||||
/* eslint-disable no-undef */
|
if (typeof userId !== "undefined") {
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -6,10 +6,9 @@ Vue.createApp({
|
||||||
moreFilters: false,
|
moreFilters: false,
|
||||||
items: [],
|
items: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
// eslint-disable-next-line no-undef
|
page: 1,
|
||||||
page: query.page || 1,
|
|
||||||
limit: 16,
|
|
||||||
totalPages: 1,
|
totalPages: 1,
|
||||||
|
limit: 16,
|
||||||
artist: "",
|
artist: "",
|
||||||
format: "",
|
format: "",
|
||||||
year: "",
|
year: "",
|
||||||
|
@ -18,36 +17,14 @@ Vue.createApp({
|
||||||
sortOrder: "artists_sort-asc",
|
sortOrder: "artists_sort-asc",
|
||||||
sort: "artists_sort",
|
sort: "artists_sort",
|
||||||
order: "asc",
|
order: "asc",
|
||||||
itemId: null,
|
|
||||||
showModalDelete: false,
|
|
||||||
showModalShare: false,
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
shareLink: `/collection/${userId}`,
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
isPublicCollection,
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
userId,
|
userId,
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
vueType,
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
query,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
|
|
||||||
window.addEventListener("keydown", this.keyDown);
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
window.removeEventListener("keydown", this.keyDown);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
formatParams(param) {
|
|
||||||
return param
|
|
||||||
.replace("&", "%26")
|
|
||||||
.replace("+", "%2B")
|
|
||||||
.replace('"', "%22");
|
|
||||||
},
|
|
||||||
fetch() {
|
fetch() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.total = 0;
|
this.total = 0;
|
||||||
|
@ -56,54 +33,39 @@ Vue.createApp({
|
||||||
const urlParams = new URLSearchParams(queryString);
|
const urlParams = new URLSearchParams(queryString);
|
||||||
const entries = urlParams.entries();
|
const entries = urlParams.entries();
|
||||||
|
|
||||||
const sortOrder = {
|
|
||||||
sort: "artists_sort",
|
|
||||||
order: "asc",
|
|
||||||
};
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const [key, value] = entry;
|
const [key, value] = entry;
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "artists_sort":
|
case "artists_sort":
|
||||||
this.artist = value.replaceAll('"', "%22");
|
this.artist = value;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (["order", "sort"].indexOf(key) !== -1) {
|
|
||||||
sortOrder[key] = value;
|
|
||||||
}
|
|
||||||
this[key] = value;
|
this[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sortOrder = `${sortOrder.sort}-${sortOrder.order}`;
|
let url = `/api/v1/albums?userId=${this.userId}&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 += `&artists_sort=${this.artist.replace("&", "%26")}`;
|
||||||
}
|
}
|
||||||
if (this.format) {
|
if (this.format) {
|
||||||
url += `&format=${this.formatParams(this.format)}`;
|
url += `&format=${this.format.replace("&", "%26")}`;
|
||||||
}
|
}
|
||||||
if (this.year) {
|
if (this.year) {
|
||||||
url += `&year=${this.year}`;
|
url += `&year=${this.year}`;
|
||||||
}
|
}
|
||||||
if (this.genre) {
|
if (this.genre) {
|
||||||
url += `&genre=${this.formatParams(this.genre)}`;
|
url += `&genre=${this.genre.replace("&", "%26")}`;
|
||||||
}
|
}
|
||||||
if (this.style) {
|
if (this.style) {
|
||||||
url += `&style=${this.formatParams(this.style)}`;
|
url += `&style=${this.style.replace("&", "%26")}`;
|
||||||
}
|
|
||||||
// INFO: Cas d'une collection partagée
|
|
||||||
if (this.vueType === "public" && this.userId) {
|
|
||||||
url += `&userId=${this.userId}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.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) +
|
||||||
|
@ -112,7 +74,7 @@ Vue.createApp({
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
showToastr(
|
showToastr(
|
||||||
err.response?.data?.message ||
|
err.response?.data?.message ||
|
||||||
"Impossible de charger votre collection"
|
"Impossible de charger cette collection"
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
@ -122,19 +84,19 @@ Vue.createApp({
|
||||||
changeUrl() {
|
changeUrl() {
|
||||||
let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||||
if (this.artist) {
|
if (this.artist) {
|
||||||
url += `&artists_sort=${this.formatParams(this.artist)}`;
|
url += `&artists_sort=${this.artist.replace("&", "%26")}`;
|
||||||
}
|
}
|
||||||
if (this.format) {
|
if (this.format) {
|
||||||
url += `&format=${this.formatParams(this.format)}`;
|
url += `&format=${this.format.replace("&", "%26")}`;
|
||||||
}
|
}
|
||||||
if (this.year) {
|
if (this.year) {
|
||||||
url += `&year=${this.year}`;
|
url += `&year=${this.year}`;
|
||||||
}
|
}
|
||||||
if (this.genre) {
|
if (this.genre) {
|
||||||
url += `&genre=${this.formatParams(this.genre)}`;
|
url += `&genre=${this.genre.replace("&", "%26")}`;
|
||||||
}
|
}
|
||||||
if (this.style) {
|
if (this.style) {
|
||||||
url += `&style=${this.formatParams(this.style)}`;
|
url += `&style=${this.style.replace("&", "%26")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
|
@ -174,92 +136,6 @@ Vue.createApp({
|
||||||
showMoreFilters() {
|
showMoreFilters() {
|
||||||
this.moreFilters = !this.moreFilters;
|
this.moreFilters = !this.moreFilters;
|
||||||
},
|
},
|
||||||
toggleModal() {
|
|
||||||
this.showModalDelete = !this.showModalDelete;
|
|
||||||
},
|
},
|
||||||
toggleModalShare() {
|
}).mount("#collection-publique");
|
||||||
this.showModalShare = !this.showModalShare;
|
|
||||||
},
|
|
||||||
showConfirmDelete(itemId) {
|
|
||||||
this.itemId = itemId;
|
|
||||||
this.toggleModal();
|
|
||||||
},
|
|
||||||
deleteItem() {
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
if (vueType !== "private") {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
return axios
|
|
||||||
.delete(`/api/v1/${action}/${this.itemId}`)
|
|
||||||
.then(() => {
|
|
||||||
this.fetch();
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
showToastr(
|
|
||||||
err.response?.data?.message ||
|
|
||||||
"Impossible de supprimer cet album"
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.toggleModal();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
shareCollection() {
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
if (vueType !== "private") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return axios
|
|
||||||
.patch(`/api/v1/me`, {
|
|
||||||
isPublicCollection: !this.isPublicCollection,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
this.isPublicCollection = res.data.isPublicCollection;
|
|
||||||
|
|
||||||
if (this.isPublicCollection) {
|
|
||||||
showToastr(
|
|
||||||
"Votre collection est désormais publique",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
showToastr(
|
|
||||||
"Votre collection n'est plus partagée",
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
showToastr(
|
|
||||||
err.response?.data?.message ||
|
|
||||||
"Impossible de supprimer cet album"
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.toggleModalShare();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
renderAlbumTitle(item) {
|
|
||||||
let render = "";
|
|
||||||
|
|
||||||
for (let i = 0; i < item.artists.length; i += 1) {
|
|
||||||
const { name, join } = item.artists[i];
|
|
||||||
render += `${name} ${join ? `${join} ` : ""}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
render += `- ${item.title}`;
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
const { protocol, host } = window.location;
|
const { protocol, host } = window.location;
|
||||||
|
|
||||||
let timeout = null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fonction permettant d'afficher un message dans un toastr
|
* Fonction permettant d'afficher un message dans un toastr
|
||||||
* @param {String} message
|
* @param {String} message
|
||||||
|
@ -13,23 +11,12 @@ function showToastr(message, success = false) {
|
||||||
x.getElementsByTagName("SPAN")[0].innerHTML = message;
|
x.getElementsByTagName("SPAN")[0].innerHTML = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timeout) {
|
x.className = `${x.className} show`.replace("sucess", "");
|
||||||
clearTimeout(timeout);
|
|
||||||
x.classList.remove("show");
|
|
||||||
}
|
|
||||||
|
|
||||||
x.classList.remove("success");
|
|
||||||
x.classList.remove("error");
|
|
||||||
if (success) {
|
if (success) {
|
||||||
x.classList.add("success");
|
x.className = `${x.className} success`;
|
||||||
} else {
|
|
||||||
x.classList.add("error");
|
|
||||||
}
|
}
|
||||||
|
setTimeout(() => {
|
||||||
x.classList.add("show");
|
x.className = x.className.replace("show", "");
|
||||||
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
x.classList.remove("show");
|
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +30,80 @@ function hideToastr() {
|
||||||
x.getElementsByTagName("SPAN")[0].innerHTML = "";
|
x.getElementsByTagName("SPAN")[0].innerHTML = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction permettant de récupérer la valeur d'un cookie
|
||||||
|
* @param {String} cname
|
||||||
|
* @param {String} defaultValue
|
||||||
|
*
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
function getCookie(cname, defaultValue = "false") {
|
||||||
|
const name = `${cname}=`;
|
||||||
|
const decodedCookie = decodeURIComponent(document.cookie);
|
||||||
|
const ca = decodedCookie.split(";");
|
||||||
|
for (let i = 0; i < ca.length; i += 1) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) === " ") {
|
||||||
|
c = c.substring(1);
|
||||||
|
}
|
||||||
|
if (c.indexOf(name) === 0) {
|
||||||
|
return c.substring(name.length, c.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction permettant de créer un cookie
|
||||||
|
* @param {String} cname
|
||||||
|
* @param {String} cvalue
|
||||||
|
* @param {Number} exdays
|
||||||
|
*/
|
||||||
|
function setCookie(cname, cvalue, exdays = 30) {
|
||||||
|
const d = new Date();
|
||||||
|
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000);
|
||||||
|
const expires = `expires=${d.toUTCString()}`;
|
||||||
|
document.cookie = `${cname}=${cvalue};${expires};path=/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction de (dé)charger le thème accessible
|
||||||
|
* @param {String} value
|
||||||
|
*/
|
||||||
|
function setAriaTheme(value) {
|
||||||
|
const { body } = document;
|
||||||
|
if (value === "true") {
|
||||||
|
const classesString = body.className || "";
|
||||||
|
if (classesString.indexOf("is-accessible") === -1) {
|
||||||
|
body.classList.add("is-accessible");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
body.classList.remove("is-accessible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction de (dé)charger le thème accessible
|
||||||
|
*/
|
||||||
|
function switchAriaTheme() {
|
||||||
|
const { body } = document;
|
||||||
|
|
||||||
|
body.classList.toggle("is-accessible");
|
||||||
|
|
||||||
|
setCookie("ariatheme", body.classList.contains("is-accessible"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fonction permettant de switcher de thème clair/sombre
|
||||||
|
* @param {Object} e
|
||||||
|
*/
|
||||||
|
function switchTheme(e) {
|
||||||
|
const theme = e.target.checked ? "dark" : "light";
|
||||||
|
|
||||||
|
document.documentElement.setAttribute("data-theme", theme);
|
||||||
|
setCookie("theme", theme);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensemble d'actions effectuées au chargement de la page
|
* Ensemble d'actions effectuées au chargement de la page
|
||||||
*/
|
*/
|
||||||
|
@ -62,4 +123,29 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const switchAriaThemeBtn = document.querySelector("#switchAriaTheme");
|
||||||
|
if (switchAriaThemeBtn) {
|
||||||
|
switchAriaThemeBtn.addEventListener("click", switchAriaTheme);
|
||||||
|
}
|
||||||
|
setAriaTheme(getCookie("ariatheme"));
|
||||||
|
|
||||||
|
const toggleSwitch = document.querySelector(
|
||||||
|
'.theme-switch input[type="checkbox"]'
|
||||||
|
);
|
||||||
|
if (toggleSwitch) {
|
||||||
|
toggleSwitch.addEventListener("change", switchTheme, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentThemeIsDark = getCookie("theme");
|
||||||
|
if (currentThemeIsDark === "false" && window.matchMedia) {
|
||||||
|
currentThemeIsDark = window.matchMedia("(prefers-color-scheme: dark)")
|
||||||
|
.matches
|
||||||
|
? "dark"
|
||||||
|
: "light";
|
||||||
|
}
|
||||||
|
switchTheme({ target: { checked: currentThemeIsDark === "dark" } });
|
||||||
|
if (toggleSwitch) {
|
||||||
|
toggleSwitch.checked = currentThemeIsDark === "dark";
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,6 @@ if (typeof email !== "undefined" && typeof username !== "undefined") {
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
formData: {
|
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
email,
|
email,
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
|
@ -10,119 +9,20 @@ if (typeof email !== "undefined" && typeof username !== "undefined") {
|
||||||
oldPassword: "",
|
oldPassword: "",
|
||||||
password: "",
|
password: "",
|
||||||
passwordConfirm: "",
|
passwordConfirm: "",
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
pagination,
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
mastodon: mastodon || {
|
|
||||||
publish: false,
|
|
||||||
url: "",
|
|
||||||
token: "",
|
|
||||||
message:
|
|
||||||
"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: [],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
async testMastodon() {
|
async updateProfil(event) {
|
||||||
const { url, token } = this.formData.mastodon;
|
// try {
|
||||||
|
// if ( this.password !== this.passwordConfirm ) {
|
||||||
if (!url) {
|
// throw "La confirnation du mot de passe ne correspond pas";
|
||||||
this.errors.push("emptyUrl");
|
// }
|
||||||
}
|
// } catch(err) {
|
||||||
if (!token) {
|
// event.preventDefault();
|
||||||
this.errors.push("emptyToken");
|
// showToastr(err);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (this.errors.length > 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await axios.post(`/api/v1/mastodon`, { url, token });
|
|
||||||
|
|
||||||
showToastr("Configuration valide !", true);
|
|
||||||
} catch (err) {
|
|
||||||
showToastr(
|
|
||||||
err.response?.data?.message ||
|
|
||||||
"Impossible de tester cette configuration",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
async updateProfil() {
|
|
||||||
this.errors = [];
|
|
||||||
const {
|
|
||||||
oldPassword,
|
|
||||||
password,
|
|
||||||
passwordConfirm,
|
|
||||||
mastodon,
|
|
||||||
pagination,
|
|
||||||
} = this.formData;
|
|
||||||
|
|
||||||
if (password && !oldPassword) {
|
|
||||||
this.errors.push("emptyPassword");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password !== passwordConfirm) {
|
|
||||||
this.errors.push("passwordsDiffer");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.errors.length > 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loading = true;
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
mastodon,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (password) {
|
|
||||||
data.password = password;
|
|
||||||
data.oldPassword = oldPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.pagination = pagination;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await axios.patch(`/api/v1/me`, data);
|
|
||||||
|
|
||||||
showToastr("Profil mis à jour", true);
|
|
||||||
} catch (err) {
|
|
||||||
showToastr(
|
|
||||||
err.response?.data?.message ||
|
|
||||||
"Impossible de mettre à jour votre profil"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.loading = false;
|
|
||||||
|
|
||||||
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,12 +1,9 @@
|
||||||
/* eslint-disable no-undef */
|
|
||||||
if (typeof item !== "undefined") {
|
if (typeof item !== "undefined") {
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
item,
|
item,
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
canShareItem,
|
|
||||||
tracklist: [],
|
tracklist: [],
|
||||||
identifiers: [],
|
identifiers: [],
|
||||||
modalIsVisible: false,
|
modalIsVisible: false,
|
||||||
|
@ -15,43 +12,16 @@ if (typeof item !== "undefined") {
|
||||||
preview: null,
|
preview: null,
|
||||||
index: null,
|
index: null,
|
||||||
showModalDelete: false,
|
showModalDelete: false,
|
||||||
showModalShare: false,
|
|
||||||
shareMessage: "",
|
|
||||||
shareMessageTransformed: "",
|
|
||||||
shareMessageLength: 0,
|
|
||||||
shareSubmiting: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.setTrackList();
|
this.setTrackList();
|
||||||
this.setIdentifiers();
|
this.setIdentifiers();
|
||||||
|
|
||||||
window.addEventListener("keydown", this.keyDown);
|
window.addEventListener("keydown", this.changeImage);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
window.removeEventListener("keydown", this.keyDown);
|
window.removeEventListener("keydown", this.changeImage);
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
shareMessage(message) {
|
|
||||||
const video =
|
|
||||||
this.item.videos && this.item.videos.length > 0
|
|
||||||
? this.item.videos[0].uri
|
|
||||||
: "";
|
|
||||||
|
|
||||||
this.shareMessageTransformed = message
|
|
||||||
.replaceAll("{artist}", this.item.artists[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("{video}", video)
|
|
||||||
.replaceAll("{album}", this.item.title);
|
|
||||||
|
|
||||||
this.shareMessageLength = this.shareMessageTransformed.replace(
|
|
||||||
video,
|
|
||||||
new Array(36).join("#")
|
|
||||||
).length;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setIdentifiers() {
|
setIdentifiers() {
|
||||||
|
@ -68,21 +38,14 @@ if (typeof item !== "undefined") {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setTrackList() {
|
setTrackList() {
|
||||||
this.tracklist = [];
|
|
||||||
let subTrack = {
|
let subTrack = {
|
||||||
type: null,
|
type: null,
|
||||||
title: null,
|
title: null,
|
||||||
tracks: [],
|
tracks: [],
|
||||||
};
|
};
|
||||||
for (let i = 0; i < this.item.tracklist.length; i += 1) {
|
for (let i = 0; i < this.item.tracklist.length; i += 1) {
|
||||||
const {
|
const { type_, title, position, duration, extraartists } =
|
||||||
type_,
|
this.item.tracklist[i];
|
||||||
title,
|
|
||||||
position,
|
|
||||||
duration,
|
|
||||||
artists,
|
|
||||||
extraartists,
|
|
||||||
} = this.item.tracklist[i];
|
|
||||||
|
|
||||||
if (type_ === "heading") {
|
if (type_ === "heading") {
|
||||||
if (subTrack.type) {
|
if (subTrack.type) {
|
||||||
|
@ -102,7 +65,6 @@ if (typeof item !== "undefined") {
|
||||||
position,
|
position,
|
||||||
duration,
|
duration,
|
||||||
extraartists,
|
extraartists,
|
||||||
artists,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -142,10 +104,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
|
||||||
) {
|
) {
|
||||||
|
@ -162,20 +124,6 @@ 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();
|
||||||
|
@ -197,7 +145,7 @@ if (typeof item !== "undefined") {
|
||||||
updateItem() {
|
updateItem() {
|
||||||
showToastr("Mise à jour en cours…", true);
|
showToastr("Mise à jour en cours…", true);
|
||||||
axios
|
axios
|
||||||
.patch(`/api/v1/${action}/${this.item._id}`)
|
.patch(`/api/v1/albums/${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;
|
||||||
|
@ -216,12 +164,9 @@ if (typeof item !== "undefined") {
|
||||||
},
|
},
|
||||||
deleteItem() {
|
deleteItem() {
|
||||||
axios
|
axios
|
||||||
.delete(`/api/v1/${action}/${this.item._id}`)
|
.delete(`/api/v1/albums/${this.item._id}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.location.href =
|
window.location.href = "/ma-collection";
|
||||||
action === "albums"
|
|
||||||
? "/ma-collection"
|
|
||||||
: "/ma-liste-de-souhaits";
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
showToastr(
|
showToastr(
|
||||||
|
@ -236,33 +181,6 @@ if (typeof item !== "undefined") {
|
||||||
goToArtist() {
|
goToArtist() {
|
||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
shareAlbum() {
|
|
||||||
if (this.shareSubmiting) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.shareSubmiting = true;
|
|
||||||
axios
|
|
||||||
.post(`/api/v1/${action}/${this.item._id}/share`, {
|
|
||||||
message: this.shareMessageTransformed,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
showToastr("Album partagé", true);
|
|
||||||
this.shareMessage = "";
|
|
||||||
this.showModalShare = false;
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
showToastr(
|
|
||||||
err.response?.data?.message ||
|
|
||||||
"Impossible de partager cet album",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.shareSubmiting = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}).mount("#ma-collection-details");
|
}).mount("#ma-collection-details");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/* eslint-disable no-undef */
|
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -11,10 +10,7 @@ Vue.createApp({
|
||||||
exportCollection(event) {
|
exportCollection(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
window.open(
|
window.open(`/api/v1/albums?exportFormat=${this.format}`, "_blank");
|
||||||
`/api/v1/${action}?exportFormat=${this.format}`,
|
|
||||||
"_blank"
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).mount("#exporter");
|
}).mount("#exporter");
|
||||||
|
|
|
@ -1,107 +0,0 @@
|
||||||
/* eslint-disable no-undef */
|
|
||||||
Vue.createApp({
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
file: "",
|
|
||||||
content: [],
|
|
||||||
parsed: false,
|
|
||||||
imported: 0,
|
|
||||||
disabled: true,
|
|
||||||
state: "default",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {},
|
|
||||||
destroyed() {},
|
|
||||||
methods: {
|
|
||||||
handleFileUpload(event) {
|
|
||||||
const { files } = event.target;
|
|
||||||
const [csv] = files;
|
|
||||||
|
|
||||||
this.file = csv;
|
|
||||||
|
|
||||||
this.file = csv;
|
|
||||||
// this.parseFile();
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (content) => {
|
|
||||||
this.content = [];
|
|
||||||
this.state = "parse";
|
|
||||||
const lines = content.target.result.split(/\r\n|\n/);
|
|
||||||
for (let line = 1; line < lines.length - 1; line += 1) {
|
|
||||||
this.parseLine(lines[0], lines[line]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = "default";
|
|
||||||
this.disabled = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsText(csv);
|
|
||||||
},
|
|
||||||
parseLine(header, line) {
|
|
||||||
const row = {};
|
|
||||||
let currentHeaderIndex = 0;
|
|
||||||
|
|
||||||
let separant = ",";
|
|
||||||
let value = "";
|
|
||||||
for (let i = 0; i < line.length; i += 1) {
|
|
||||||
const char = line[i];
|
|
||||||
|
|
||||||
if (char !== separant) {
|
|
||||||
if (char === '"') {
|
|
||||||
separant = '"';
|
|
||||||
} else {
|
|
||||||
value += char;
|
|
||||||
}
|
|
||||||
} else if (char === '"') {
|
|
||||||
separant = ",";
|
|
||||||
} else {
|
|
||||||
row[header.split(",")[currentHeaderIndex]] = value;
|
|
||||||
currentHeaderIndex += 1;
|
|
||||||
value = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.content.push(row);
|
|
||||||
},
|
|
||||||
async addOne(index) {
|
|
||||||
const { Artist, Title, release_id } = this.content[index];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await axios.get(
|
|
||||||
`/api/v1/${action}?discogsId=${release_id}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res.status === 204) {
|
|
||||||
await axios.post(`/api/v1/${action}`, {
|
|
||||||
discogsId: release_id,
|
|
||||||
share: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.imported += 1;
|
|
||||||
if (this.content.length > index + 1) {
|
|
||||||
await this.addOne(index + 1);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
showToastr(
|
|
||||||
`Impossible d'ajouter l'album ${Title} de ${Artist}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
async importCollection(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
this.disabled = true;
|
|
||||||
this.state = "submit";
|
|
||||||
this.imported = 0;
|
|
||||||
|
|
||||||
const imported = await this.addOne(0);
|
|
||||||
|
|
||||||
this.disabled = false;
|
|
||||||
this.state = imported ? "done" : "default";
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}).mount("#importer");
|
|
201
javascripts/mon-compte/ma-collection/index.js
Normal file
201
javascripts/mon-compte/ma-collection/index.js
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
if (typeof isPublicCollection !== "undefined") {
|
||||||
|
Vue.createApp({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
moreFilters: false,
|
||||||
|
items: [],
|
||||||
|
total: 0,
|
||||||
|
page: 1,
|
||||||
|
totalPages: 1,
|
||||||
|
limit: 16,
|
||||||
|
artist: "",
|
||||||
|
format: "",
|
||||||
|
year: "",
|
||||||
|
genre: "",
|
||||||
|
style: "",
|
||||||
|
sortOrder: "artists_sort-asc",
|
||||||
|
sort: "artists_sort",
|
||||||
|
order: "asc",
|
||||||
|
itemId: null,
|
||||||
|
showModalDelete: false,
|
||||||
|
showModalShare: false,
|
||||||
|
shareLink: `${protocol}//${host}/collection/<%= user._id %>`,
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
isPublicCollection,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetch();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetch() {
|
||||||
|
this.loading = true;
|
||||||
|
this.total = 0;
|
||||||
|
|
||||||
|
const queryString = window.location.search;
|
||||||
|
const urlParams = new URLSearchParams(queryString);
|
||||||
|
const entries = urlParams.entries();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const entry of entries) {
|
||||||
|
const [key, value] = entry;
|
||||||
|
switch (key) {
|
||||||
|
case "artists_sort":
|
||||||
|
this.artist = value;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||||
|
if (this.artist) {
|
||||||
|
url += `&artists_sort=${this.artist.replace("&", "%26")}`;
|
||||||
|
}
|
||||||
|
if (this.format) {
|
||||||
|
url += `&format=${this.format.replace("&", "%26")}`;
|
||||||
|
}
|
||||||
|
if (this.year) {
|
||||||
|
url += `&year=${this.year}`;
|
||||||
|
}
|
||||||
|
if (this.genre) {
|
||||||
|
url += `&genre=${this.genre.replace("&", "%26")}`;
|
||||||
|
}
|
||||||
|
if (this.style) {
|
||||||
|
url += `&style=${this.style.replace("&", "%26")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
axios
|
||||||
|
.get(url)
|
||||||
|
.then((response) => {
|
||||||
|
this.items = response.data.rows;
|
||||||
|
this.total = response.data.count || 0;
|
||||||
|
this.totalPages =
|
||||||
|
parseInt(response.data.count / this.limit, 10) +
|
||||||
|
(response.data.count % this.limit > 0 ? 1 : 0);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showToastr(
|
||||||
|
err.response?.data?.message ||
|
||||||
|
"Impossible de charger votre collection"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
changeUrl() {
|
||||||
|
let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
|
||||||
|
if (this.artist) {
|
||||||
|
url += `&artists_sort=${this.artist.replace("&", "%26")}`;
|
||||||
|
}
|
||||||
|
if (this.format) {
|
||||||
|
url += `&format=${this.format.replace("&", "%26")}`;
|
||||||
|
}
|
||||||
|
if (this.year) {
|
||||||
|
url += `&year=${this.year}`;
|
||||||
|
}
|
||||||
|
if (this.genre) {
|
||||||
|
url += `&genre=${this.genre.replace("&", "%26")}`;
|
||||||
|
}
|
||||||
|
if (this.style) {
|
||||||
|
url += `&style=${this.style.replace("&", "%26")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = url;
|
||||||
|
},
|
||||||
|
next(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.page += 1;
|
||||||
|
|
||||||
|
this.changeUrl();
|
||||||
|
},
|
||||||
|
previous(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
this.page -= 1;
|
||||||
|
|
||||||
|
this.changeUrl();
|
||||||
|
},
|
||||||
|
goTo(page) {
|
||||||
|
this.page = page;
|
||||||
|
|
||||||
|
this.changeUrl();
|
||||||
|
},
|
||||||
|
changeSort() {
|
||||||
|
const [sort, order] = this.sortOrder.split("-");
|
||||||
|
this.sort = sort;
|
||||||
|
this.order = order;
|
||||||
|
this.page = 1;
|
||||||
|
|
||||||
|
this.changeUrl();
|
||||||
|
},
|
||||||
|
changeFilter() {
|
||||||
|
this.page = 1;
|
||||||
|
|
||||||
|
this.changeUrl();
|
||||||
|
},
|
||||||
|
showMoreFilters() {
|
||||||
|
this.moreFilters = !this.moreFilters;
|
||||||
|
},
|
||||||
|
toggleModal() {
|
||||||
|
this.showModalDelete = !this.showModalDelete;
|
||||||
|
},
|
||||||
|
toggleModalShare() {
|
||||||
|
this.showModalShare = !this.showModalShare;
|
||||||
|
},
|
||||||
|
showConfirmDelete(itemId) {
|
||||||
|
this.itemId = itemId;
|
||||||
|
this.toggleModal();
|
||||||
|
},
|
||||||
|
deleteItem() {
|
||||||
|
axios
|
||||||
|
.delete(`/api/v1/albums/${this.itemId}`)
|
||||||
|
.then(() => {
|
||||||
|
this.fetch();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showToastr(
|
||||||
|
err.response?.data?.message ||
|
||||||
|
"Impossible de supprimer cet album"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.toggleModal();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
shareCollection() {
|
||||||
|
axios
|
||||||
|
.patch(`/api/v1/me`, {
|
||||||
|
isPublicCollection: !this.isPublicCollection,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
this.isPublicCollection = res.data.isPublicCollection;
|
||||||
|
|
||||||
|
if (this.isPublicCollection) {
|
||||||
|
showToastr(
|
||||||
|
"Votre collection est désormais publique",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showToastr(
|
||||||
|
"Votre collection n'est plus partagée",
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
showToastr(
|
||||||
|
err.response?.data?.message ||
|
||||||
|
"Impossible de supprimer cet album"
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.toggleModalShare();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).mount("#ma-collection");
|
||||||
|
}
|
|
@ -1,87 +0,0 @@
|
||||||
/**
|
|
||||||
* Fonction permettant de récupérer le thème du système
|
|
||||||
* @return {String}
|
|
||||||
*/
|
|
||||||
function getPreferredColorScheme() {
|
|
||||||
if (window.matchMedia) {
|
|
||||||
if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
|
||||||
return "dark";
|
|
||||||
}
|
|
||||||
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
|
|
||||||
const buttonsTheme = document.getElementsByClassName("theme");
|
|
||||||
// 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
|
|
||||||
*/
|
|
||||||
if (window.matchMedia) {
|
|
||||||
const colorSchemeQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
|
||||||
colorSchemeQuery.addEventListener("change", () => {
|
|
||||||
const selectedColorScheme = localStorage.getItem("theme") || "system";
|
|
||||||
|
|
||||||
if (selectedColorScheme === "system") {
|
|
||||||
const preferedColorScheme = getPreferredColorScheme();
|
|
||||||
setColorScheme(preferedColorScheme);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// INFO: Au chargement de la page on détecte le thème à charger
|
|
||||||
setColorScheme(currentTheme);
|
|
||||||
|
|
||||||
// INFO: On place un event au click sur chacun des boutons du menu
|
|
||||||
for (let i = 0; i < buttonsTheme.length; i += 1) {
|
|
||||||
buttonsTheme[i].addEventListener("click", changeTheme, false);
|
|
||||||
}
|
|
18192
package-lock.json
generated
18192
package-lock.json
generated
File diff suppressed because it is too large
Load diff
17
package.json
17
package.json
|
@ -5,16 +5,16 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node ./dist/bin/www",
|
"start": "node ./dist/bin/www",
|
||||||
"run:all": "npm-run-all build sass uglify start",
|
"run:all": "npm-run-all build sass uglify start",
|
||||||
"watch": "npx nodemon -e js,scss",
|
"watch": "nodemon -e js,scss",
|
||||||
"sass": "npx sass sass/index.scss public/css/main.css -s compressed --color",
|
"sass": "npx sass sass/index.scss public/css/main.css -s compressed --color",
|
||||||
"uglify": "npx gulp",
|
"uglify": "npx gulp",
|
||||||
"prebuild": "rimraf dist",
|
"prebuild": "rimraf dist",
|
||||||
"build": "npx babel ./src --out-dir dist --copy-files",
|
"build": "babel ./src --out-dir dist --copy-files",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"prepare": "npx husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "16.x || 18.x",
|
"node": "16.x",
|
||||||
"yarn": "1.x"
|
"yarn": "1.x"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Damien Broqua",
|
"name": "Damien Broqua",
|
||||||
"email": "contact@darkou.fr",
|
"email": "contact@darkou.fr",
|
||||||
"url": "https://www.darkou.link"
|
"url": "https://www.darkou.fr"
|
||||||
},
|
},
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -39,13 +39,11 @@
|
||||||
"prettier": "^2.5.1"
|
"prettier": "^2.5.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.490.0",
|
|
||||||
"@aws-sdk/lib-storage": "^3.490.0",
|
|
||||||
"@babel/cli": "^7.17.0",
|
"@babel/cli": "^7.17.0",
|
||||||
"@babel/core": "^7.17.2",
|
"@babel/core": "^7.17.2",
|
||||||
"@babel/preset-env": "^7.16.11",
|
"@babel/preset-env": "^7.16.11",
|
||||||
|
"aws-sdk": "^2.1110.0",
|
||||||
"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",
|
||||||
|
@ -65,7 +63,6 @@
|
||||||
"gulp-uglify": "^3.0.2",
|
"gulp-uglify": "^3.0.2",
|
||||||
"joi": "^17.6.0",
|
"joi": "^17.6.0",
|
||||||
"knacss": "^8.0.4",
|
"knacss": "^8.0.4",
|
||||||
"mastodon": "^1.2.2",
|
|
||||||
"mongoose": "^6.2.1",
|
"mongoose": "^6.2.1",
|
||||||
"mongoose-unique-validator": "^3.0.0",
|
"mongoose-unique-validator": "^3.0.0",
|
||||||
"nodemailer": "^6.7.8",
|
"nodemailer": "^6.7.8",
|
||||||
|
@ -81,7 +78,7 @@
|
||||||
"vue": "^3.2.31"
|
"vue": "^3.2.31"
|
||||||
},
|
},
|
||||||
"nodemonConfig": {
|
"nodemonConfig": {
|
||||||
"exec": "npm run run:all",
|
"exec": "yarn run:all",
|
||||||
"watch": [
|
"watch": [
|
||||||
"src/*",
|
"src/*",
|
||||||
"sass/*",
|
"sass/*",
|
||||||
|
|
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) 2024 by original authors @ fontello.com</metadata>
|
<metadata>Copyright (C) 2022 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,8 +28,6 @@
|
||||||
|
|
||||||
<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.3 KiB After Width: | Height: | Size: 8 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -11,14 +11,6 @@
|
||||||
img {
|
img {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.in-collection {
|
|
||||||
opacity: 0.6;
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,12 +5,9 @@
|
||||||
color: var(--font-color);
|
color: var(--font-color);
|
||||||
display: block;
|
display: block;
|
||||||
padding: 1.25rem;
|
padding: 1.25rem;
|
||||||
|
|
||||||
@include transition() {}
|
|
||||||
|
|
||||||
&.mini {
|
|
||||||
margin: auto;
|
|
||||||
width: calc(100% - 2rem);
|
width: calc(100% - 2rem);
|
||||||
|
margin: auto;
|
||||||
|
@include transition() {}
|
||||||
|
|
||||||
@include respond-to("small-up") {
|
@include respond-to("small-up") {
|
||||||
width: 65%;
|
width: 65%;
|
||||||
|
@ -18,7 +15,6 @@
|
||||||
@include respond-to("medium-up") {
|
@include respond-to("medium-up") {
|
||||||
width: 35%;
|
width: 35%;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
|
|
@ -22,12 +22,10 @@ $nord15: #b48ead;
|
||||||
|
|
||||||
$primary-color: $nord8;
|
$primary-color: $nord8;
|
||||||
$danger-color: $nord11;
|
$danger-color: $nord11;
|
||||||
$error-color: $nord12;
|
|
||||||
$warning-color: $nord13;
|
$warning-color: $nord13;
|
||||||
$success-color: $nord14;
|
$success-color: $nord14;
|
||||||
$primary-color-hl: darken($primary-color, $hoverAmount);
|
$primary-color-hl: darken($primary-color, $hoverAmount);
|
||||||
$danger-color-hl: darken($danger-color, $hoverAmount);
|
$danger-color-hl: darken($danger-color, $hoverAmount);
|
||||||
$error-color-hl: darken($error-color, $hoverAmount);
|
|
||||||
$warning-color-hl: darken($warning-color, $hoverAmount);
|
$warning-color-hl: darken($warning-color, $hoverAmount);
|
||||||
$success-color-hl: darken($success-color, $hoverAmount);
|
$success-color-hl: darken($success-color, $hoverAmount);
|
||||||
|
|
||||||
|
@ -37,15 +35,11 @@ $button-alternate-color: #01103C;
|
||||||
$pagination-border-color: $nord3;
|
$pagination-border-color: $nord3;
|
||||||
$pagination-hover-color: rgb(115, 151, 186);
|
$pagination-hover-color: rgb(115, 151, 186);
|
||||||
|
|
||||||
$close-background: rgba(10,10,10,.6);
|
|
||||||
$close-background-dark: rgba(240,240,240,.6);
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--default-color: #{$white};
|
--default-color: #{$white};
|
||||||
--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};
|
||||||
|
|
||||||
|
@ -62,8 +56,6 @@ $close-background-dark: rgba(240,240,240,.6);
|
||||||
|
|
||||||
--button-link-text-color: #2C364A;
|
--button-link-text-color: #2C364A;
|
||||||
|
|
||||||
--close-background: #{$close-background};
|
|
||||||
|
|
||||||
--loader-img: url('/img/loading-light.gif');
|
--loader-img: url('/img/loading-light.gif');
|
||||||
|
|
||||||
--nord0: #{$nord0};
|
--nord0: #{$nord0};
|
||||||
|
@ -89,7 +81,6 @@ $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};
|
||||||
|
|
||||||
|
@ -106,7 +97,5 @@ $close-background-dark: rgba(240,240,240,.6);
|
||||||
|
|
||||||
--button-link-text-color: #{$white};
|
--button-link-text-color: #{$white};
|
||||||
|
|
||||||
--close-background: #{$nord3};
|
|
||||||
|
|
||||||
--loader-img: url('/img/loading-dark.gif');
|
--loader-img: url('/img/loading-dark.gif');
|
||||||
}
|
}
|
|
@ -1,6 +1,4 @@
|
||||||
main {
|
.error {
|
||||||
&.error {
|
|
||||||
min-height: calc(100vh - 3.25rem - 100px);
|
min-height: calc(100vh - 3.25rem - 100px);
|
||||||
padding-top: 4rem;
|
padding-top: 4rem;
|
||||||
}
|
}
|
||||||
}
|
|
|
@ -9,7 +9,7 @@
|
||||||
margin: 2rem auto;
|
margin: 2rem auto;
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.info {
|
&.info {
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -24,6 +24,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
input,
|
input,
|
||||||
textarea,
|
textarea,
|
||||||
select {
|
select {
|
||||||
|
@ -31,12 +34,13 @@
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--input-color);
|
background-color: var(--input-color);
|
||||||
border: 1px solid var(--input-active-color) !important;
|
border: 1px solid transparent !important;
|
||||||
color: var(--input-font-color);
|
color: var(--input-font-color);
|
||||||
@include transition() {}
|
@include transition() {}
|
||||||
|
|
||||||
&:focus-visible {
|
&:focus-visible {
|
||||||
outline: unset;
|
outline: unset;
|
||||||
|
border-color: var(--input-active-color) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,3 +75,77 @@
|
||||||
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%;
|
||||||
|
}
|
||||||
|
|
|
@ -7,10 +7,19 @@ html {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-top: 3.5rem;
|
padding-top: 3.5rem;
|
||||||
font-family: 'lucioleregular';
|
font-family: 'open_sansregular';
|
||||||
|
font-weight: 400;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
color: var(--font-color);
|
color: var(--font-color);
|
||||||
@include transition();
|
@include transition() {}
|
||||||
|
|
||||||
|
&.is-accessible {
|
||||||
|
font-family: 'lucioleregular';
|
||||||
|
|
||||||
|
.text-justify {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
footer.footer {
|
footer.footer {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
|
|
|
@ -1,57 +1,35 @@
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'icon';
|
font-family: 'icon';
|
||||||
src: url('/font/icon.eot?15219908');
|
src: url('/font/icon.eot?41426785');
|
||||||
src: url('/font/icon.eot?15219908#iefix') format('embedded-opentype'),
|
src: url('/font/icon.eot?41426785#iefix') format('embedded-opentype'),
|
||||||
url('/font/icon.woff2?15219908') format('woff2'),
|
url('/font/icon.woff2?41426785') format('woff2'),
|
||||||
url('/font/icon.woff?15219908') format('woff'),
|
url('/font/icon.woff?41426785') format('woff'),
|
||||||
url('/font/icon.ttf?15219908') format('truetype'),
|
url('/font/icon.ttf?41426785') format('truetype'),
|
||||||
url('/font/icon.svg?15219908#icon') format('svg');
|
url('/font/icon.svg?41426785#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. */
|
|
||||||
/* 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 {
|
[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;
|
||||||
|
|
||||||
/* Uncomment for 3D effect */
|
|
||||||
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-plus:before { content: '\e800'; } /* '' */
|
.icon-plus:before { content: '\e800'; } /* '' */
|
||||||
|
@ -65,7 +43,6 @@
|
||||||
.icon-right-open:before { content: '\e808'; } /* '' */
|
.icon-right-open:before { content: '\e808'; } /* '' */
|
||||||
.icon-export:before { content: '\e809'; } /* '' */
|
.icon-export:before { content: '\e809'; } /* '' */
|
||||||
.icon-refresh:before { content: '\e80a'; } /* '' */
|
.icon-refresh:before { content: '\e80a'; } /* '' */
|
||||||
.icon-adjust:before { content: '\e810'; } /* '' */
|
|
||||||
.icon-spin:before { content: '\e839'; } /* '' */
|
.icon-spin:before { content: '\e839'; } /* '' */
|
||||||
.icon-link-ext:before { content: '\f08e'; } /* '' */
|
.icon-link-ext:before { content: '\f08e'; } /* '' */
|
||||||
.icon-sun:before { content: '\f185'; } /* '' */
|
.icon-sun:before { content: '\f185'; } /* '' */
|
||||||
|
@ -74,3 +51,16 @@
|
||||||
.icon-trash:before { content: '\f1f8'; } /* '' */
|
.icon-trash:before { content: '\f1f8'; } /* '' */
|
||||||
.icon-blind:before { content: '\f29d'; } /* '' */
|
.icon-blind:before { content: '\f29d'; } /* '' */
|
||||||
|
|
||||||
|
.animate-spin {
|
||||||
|
animation: spin 2s infinite linear;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
@keyframes spin {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(359deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,28 @@
|
||||||
@import '../node_modules/knacss/sass/knacss.scss';
|
// @use '../node_modules/knacss/sass/knacss.scss';
|
||||||
|
|
||||||
|
// NOYAU
|
||||||
|
@import "../node_modules/knacss/sass/abstracts/variables-sass";
|
||||||
|
@import "../node_modules/knacss/sass/abstracts/mixins-sass";
|
||||||
|
|
||||||
|
@import "../node_modules/knacss/sass/base/reset-base";
|
||||||
|
@import "../node_modules/knacss/sass/base/reset-accessibility";
|
||||||
|
@import "../node_modules/knacss/sass/base/reset-forms";
|
||||||
|
@import "../node_modules/knacss/sass/base/reset-print";
|
||||||
|
@import "../node_modules/knacss/sass/base/layout";
|
||||||
|
|
||||||
|
// UTILITAIRES
|
||||||
|
@import "../node_modules/knacss/sass/utils/utils-global";
|
||||||
|
@import "../node_modules/knacss/sass/utils/utils-font-sizes";
|
||||||
|
@import "../node_modules/knacss/sass/utils/utils-spacers";
|
||||||
|
@import "../node_modules/knacss/sass/utils/grillade";
|
||||||
|
|
||||||
|
// COMPOSANTS (à ajouter au besoin)
|
||||||
|
// @import "../node_modules/knacss/sass/components/button";
|
||||||
|
// @import "components/burger";
|
||||||
|
// @import "../node_modules/knacss/sass/components/checkbox";
|
||||||
|
@import "../node_modules/knacss/sass/components/radio";
|
||||||
|
// @import "../node_modules/knacss/sass/components/select";
|
||||||
|
// @import "components/quote";
|
||||||
|
|
||||||
// SPÉCIFIQUE AU SITE
|
// SPÉCIFIQUE AU SITE
|
||||||
@import './fonts';
|
@import './fonts';
|
||||||
|
@ -16,10 +40,8 @@
|
||||||
@import './list';
|
@import './list';
|
||||||
@import './box';
|
@import './box';
|
||||||
@import './loader';
|
@import './loader';
|
||||||
@import './table';
|
|
||||||
|
|
||||||
@import './error';
|
@import './error';
|
||||||
@import './messages.scss';
|
|
||||||
@import './500';
|
@import './500';
|
||||||
@import './home';
|
@import './home';
|
||||||
@import './ajouter-un-album';
|
@import './ajouter-un-album';
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
font-weight: 800;
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,44 +45,33 @@
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
button.close {
|
button.close {
|
||||||
height: 42px;
|
height: 36px;
|
||||||
max-height: 42px;
|
max-height: 36px;
|
||||||
max-width: 42px;
|
max-width: 36px;
|
||||||
min-height: 42px;
|
min-height: 36px;
|
||||||
min-width: 42px;
|
min-width: 36px;
|
||||||
width: 42px;
|
width: 36px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: var(--close-background);
|
background-color: rgba(10,10,10,.6);
|
||||||
right: 12px;
|
right: 12px;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
|
|
||||||
&::before,
|
|
||||||
&::after {
|
|
||||||
background-color: $white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.carousel {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto 80vw auto;
|
|
||||||
z-index: 1;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 80vh;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigation {
|
.navigation {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
i {
|
z-index: 10;
|
||||||
font-size: 1rem;
|
|
||||||
color: $nord4;
|
|
||||||
|
|
||||||
@include respond-to("small-up") {
|
&.previous {
|
||||||
|
left: 12px;
|
||||||
|
}
|
||||||
|
&.next {
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
i {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
}
|
color: $nord4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
.message {
|
|
||||||
margin: 8px 0;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
|
|
||||||
&.error {
|
|
||||||
color: $error-color-hl;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,7 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 2;
|
z-index: 40;
|
||||||
|
|
||||||
&.is-visible {
|
&.is-visible {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -84,11 +84,6 @@
|
||||||
width: 1200;
|
width: 1200;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.for-image {
|
|
||||||
display: initial;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
header,
|
header,
|
||||||
footer {
|
footer {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -121,25 +116,10 @@
|
||||||
border-bottom-left-radius: 6px;
|
border-bottom-left-radius: 6px;
|
||||||
border-bottom-right-radius: 6px;
|
border-bottom-right-radius: 6px;
|
||||||
border-top: 1px solid var(--border-color);
|
border-top: 1px solid var(--border-color);
|
||||||
justify-content: end;
|
|
||||||
align-items: baseline;
|
|
||||||
|
|
||||||
.field {
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
padding: 6px;
|
|
||||||
span {
|
|
||||||
padding-left: 6px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:not(:last-child) {
|
.button:not(:last-child) {
|
||||||
margin-right: .5em;
|
margin-right: .5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 80vh;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,25 +1,21 @@
|
||||||
.navbar {
|
.navbar {
|
||||||
min-height: 3.5rem;
|
min-height: 3.25rem;
|
||||||
background-color: var(--navbar-color);
|
background-color: var(--navbar-color);
|
||||||
box-shadow: rgba(216, 222, 233, 0.15) 0px 5px 10px 0px;
|
box-shadow: rgba(216, 222, 233, 0.15) 0px 5px 10px 0px;
|
||||||
color: rgba(0,0,0,.7);
|
color: rgba(0,0,0,.7);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1;
|
z-index: 30;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@include transition() {}
|
@include transition() {}
|
||||||
|
|
||||||
@include respond-to("medium-up") {
|
@include respond-to("medium-up") {
|
||||||
|
min-height: 3.25rem;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.container {
|
|
||||||
max-width: 1330px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -37,6 +33,7 @@
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
color: var(--font-color);
|
color: var(--font-color);
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
line-height: 1.125;
|
line-height: 1.125;
|
||||||
margin-left: .5rem !important;
|
margin-left: .5rem !important;
|
||||||
@include transition() {}
|
@include transition() {}
|
||||||
|
@ -103,7 +100,7 @@
|
||||||
|
|
||||||
.navbar-item {
|
.navbar-item {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
padding: .5rem;
|
padding: .5rem .75rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
@ -117,6 +114,7 @@
|
||||||
@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);
|
||||||
|
@ -126,9 +124,11 @@
|
||||||
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;
|
||||||
min-width: 280px;
|
left: 0;
|
||||||
|
min-width: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -136,7 +136,7 @@
|
||||||
|
|
||||||
.navbar-link {
|
.navbar-link {
|
||||||
background-color: var(--default-hl-color);
|
background-color: var(--default-hl-color);
|
||||||
color: var(--hover-font-color);
|
color: rgba(0,0,0,.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-dropdown {
|
.navbar-dropdown {
|
||||||
|
@ -165,6 +165,15 @@
|
||||||
@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;
|
||||||
|
@ -185,22 +194,11 @@
|
||||||
right: 1.125em;
|
right: 1.125em;
|
||||||
|
|
||||||
@include respond-to("medium-up") {
|
@include respond-to("medium-up") {
|
||||||
border-color: var(--font-color);
|
border-color: rgba(0,0,0,.7);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.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,7 +258,13 @@
|
||||||
background-color: var(--font-color);
|
background-color: var(--font-color);
|
||||||
border: none;
|
border: none;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
margin: .5rem 0 0 1.5rem;
|
margin: .5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-item {
|
||||||
|
cursor: pointer;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
padding-right: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include respond-to("medium-up") {
|
@include respond-to("medium-up") {
|
||||||
|
@ -271,19 +275,17 @@
|
||||||
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;
|
||||||
right: 0;
|
left: 0;
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
|
z-index: 20;
|
||||||
|
|
||||||
hr {
|
.navbar-item {
|
||||||
margin: 0.5rem 0;
|
white-space: nowrap;
|
||||||
|
padding: .375rem 1rem;
|
||||||
|
padding-right: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .navbar-item {
|
|
||||||
// white-space: nowrap;
|
|
||||||
// padding-block: .375rem;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,9 +320,3 @@
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
|
||||||
ul {
|
|
||||||
padding-inline: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,18 +3,15 @@
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 10;
|
z-index: 31;
|
||||||
right: 30px;
|
right: 30px;
|
||||||
top: 30px;
|
top: 30px;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
|
|
||||||
padding: 1.25rem 2.5rem 1.25rem 1.5rem;
|
padding: 1.25rem 2.5rem 1.25rem 1.5rem;
|
||||||
border-radius: 6px;
|
|
||||||
|
|
||||||
&.error {
|
|
||||||
background-color: $danger-color;
|
background-color: $danger-color;
|
||||||
color: $button-alternate-color;
|
color: $button-alternate-color;
|
||||||
}
|
border-radius: 6px;
|
||||||
|
|
||||||
&.success {
|
&.success {
|
||||||
background-color: $success-color;
|
background-color: $success-color;
|
||||||
|
|
13
src/app.js
13
src/app.js
|
@ -9,28 +9,24 @@ import MongoStore from "connect-mongo";
|
||||||
|
|
||||||
import passportConfig from "./libs/passport";
|
import passportConfig from "./libs/passport";
|
||||||
|
|
||||||
import config, { env, mongoDbUri, port, secret } from "./config";
|
import config, { env, mongoDbUri, 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 importMeRouterApiV1 from "./routes/api/v1/me";
|
import importMeRouterApiV1 from "./routes/api/v1/me";
|
||||||
import importContactRouterApiV1 from "./routes/api/v1/contact";
|
import importContactRouterApiV1 from "./routes/api/v1/contact";
|
||||||
|
|
||||||
passportConfig(passport);
|
passportConfig(passport);
|
||||||
|
|
||||||
mongoose.set("strictQuery", false);
|
|
||||||
mongoose
|
mongoose
|
||||||
.connect(mongoDbUri, { useNewUrlParser: true, useUnifiedTopology: true })
|
.connect(mongoDbUri, { useNewUrlParser: true, useUnifiedTopology: true })
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
@ -39,7 +35,7 @@ mongoose
|
||||||
|
|
||||||
const sess = {
|
const sess = {
|
||||||
cookie: {
|
cookie: {
|
||||||
maxAge: 604800000, // INFO: 7 jours
|
maxAge: 86400000,
|
||||||
},
|
},
|
||||||
secret,
|
secret,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
|
@ -83,13 +79,10 @@ 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/me", importMeRouterApiV1);
|
app.use("/api/v1/me", importMeRouterApiV1);
|
||||||
app.use("/api/v1/contact", importContactRouterApiV1);
|
app.use("/api/v1/contact", importContactRouterApiV1);
|
||||||
|
|
||||||
|
@ -154,6 +147,4 @@ app.use((error, req, res, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Server listening on port ${port}!`);
|
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|
|
@ -33,11 +33,6 @@ export const getAlbumDetails = async (id) => {
|
||||||
|
|
||||||
const res = await dis.getRelease(id);
|
const res = await dis.getRelease(id);
|
||||||
|
|
||||||
if (res.released && res.released.includes("-00")) {
|
|
||||||
const [year, month] = res.released.split("-");
|
|
||||||
res.released = new Date(year, parseInt(month, 10) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,28 +48,3 @@ 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;
|
|
||||||
};
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { S3Client } from "@aws-sdk/client-s3";
|
import AWS from "aws-sdk";
|
||||||
import { Upload } from "@aws-sdk/lib-storage";
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
@ -11,9 +10,13 @@ import {
|
||||||
s3BaseFolder,
|
s3BaseFolder,
|
||||||
s3Endpoint,
|
s3Endpoint,
|
||||||
s3Bucket,
|
s3Bucket,
|
||||||
// s3Signature,
|
s3Signature,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
|
|
||||||
|
AWS.config.update({
|
||||||
|
accessKeyId: awsAccessKeyId,
|
||||||
|
secretAccessKey: awsSecretAccessKey,
|
||||||
|
});
|
||||||
/**
|
/**
|
||||||
* Fonction permettant de stocker un fichier local sur S3
|
* Fonction permettant de stocker un fichier local sur S3
|
||||||
* @param {String} filename
|
* @param {String} filename
|
||||||
|
@ -24,28 +27,23 @@ import {
|
||||||
*/
|
*/
|
||||||
export const uploadFromFile = async (filename, file, deleteFile = false) => {
|
export const uploadFromFile = async (filename, file, deleteFile = false) => {
|
||||||
const data = await fs.readFileSync(file);
|
const data = await fs.readFileSync(file);
|
||||||
|
|
||||||
const base64data = Buffer.from(data, "binary");
|
const base64data = Buffer.from(data, "binary");
|
||||||
const dest = path.join(s3BaseFolder, filename);
|
const dest = path.join(s3BaseFolder, filename);
|
||||||
|
|
||||||
const multipartUpload = new Upload({
|
const s3 = new AWS.S3({
|
||||||
client: new S3Client({
|
endpoint: s3Endpoint,
|
||||||
region: "fr-par",
|
signatureVersion: s3Signature,
|
||||||
endpoint: `https://${s3Endpoint}`,
|
});
|
||||||
credentials: {
|
|
||||||
accessKeyId: awsAccessKeyId,
|
await s3
|
||||||
secretAccessKey: awsSecretAccessKey,
|
.putObject({
|
||||||
},
|
|
||||||
}),
|
|
||||||
params: {
|
|
||||||
Bucket: s3Bucket,
|
Bucket: s3Bucket,
|
||||||
Key: dest,
|
Key: dest,
|
||||||
Body: base64data,
|
Body: base64data,
|
||||||
ACL: "public-read",
|
ACL: "public-read",
|
||||||
endpoint: s3Endpoint,
|
})
|
||||||
},
|
.promise();
|
||||||
});
|
|
||||||
|
|
||||||
await multipartUpload.done();
|
|
||||||
|
|
||||||
if (deleteFile) {
|
if (deleteFile) {
|
||||||
fs.unlinkSync(file);
|
fs.unlinkSync(file);
|
||||||
|
@ -64,15 +62,11 @@ export const uploadFromUrl = async (url) => {
|
||||||
const filename = `${uuid()}.jpg`;
|
const filename = `${uuid()}.jpg`;
|
||||||
const file = `/tmp/${filename}`;
|
const file = `/tmp/${filename}`;
|
||||||
|
|
||||||
const { data } = await axios.get(url, {
|
const { data } = await axios.get(url, { responseType: "arraybuffer" });
|
||||||
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, data);
|
fs.writeFileSync(file, data);
|
||||||
|
|
||||||
return uploadFromFile(filename, file, true);
|
return uploadFromFile(filename, file, true);
|
||||||
|
|
||||||
|
// return s3Object;
|
||||||
};
|
};
|
||||||
|
|
|
@ -34,23 +34,6 @@ 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,9 +1,5 @@
|
||||||
import { format as formatDate } from "date-fns";
|
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 Pages from "./Pages";
|
||||||
import Export from "./Export";
|
import Export from "./Export";
|
||||||
|
|
||||||
|
@ -12,8 +8,7 @@ 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, getAllDistincts } from "../helpers";
|
import { getAlbumDetails } 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
|
||||||
|
@ -26,28 +21,13 @@ class Albums extends Pages {
|
||||||
*/
|
*/
|
||||||
static async postAddOne(req) {
|
static async postAddOne(req) {
|
||||||
const { body, user } = 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 = {
|
const data = {
|
||||||
...albumDetails,
|
...body,
|
||||||
discogsId: albumDetails.id,
|
discogsId: body.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;
|
||||||
|
|
||||||
|
@ -59,110 +39,34 @@ class Albums extends Pages {
|
||||||
model: "Albums",
|
model: "Albums",
|
||||||
id: album._id,
|
id: album._id,
|
||||||
};
|
};
|
||||||
|
|
||||||
const job = new JobsModel(jobData);
|
const job = new JobsModel(jobData);
|
||||||
|
|
||||||
job.save();
|
job.save();
|
||||||
|
|
||||||
try {
|
|
||||||
const User = await UsersModel.findOne({ _id: user._id });
|
|
||||||
|
|
||||||
const { mastodon: mastodonConfig } = User;
|
|
||||||
|
|
||||||
const { publish, token, url, message } = 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 = `${(
|
|
||||||
message ||
|
|
||||||
"Je viens d'ajouter {artist} - {album} à ma collection !"
|
|
||||||
)
|
|
||||||
.replaceAll("{artist}", data.artists[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("{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;
|
return album;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(req, viewname) {
|
/**
|
||||||
super(req, viewname);
|
* Méthode permettant de récupérer les éléments distincts d'une collection
|
||||||
|
* @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);
|
||||||
|
|
||||||
this.colors = [
|
return distincts;
|
||||||
"#2e3440",
|
|
||||||
"#d8dee9",
|
|
||||||
"#8fbcbb",
|
|
||||||
"#5e81ac",
|
|
||||||
"#d08770",
|
|
||||||
"#bf616a",
|
|
||||||
"#ebcb8b",
|
|
||||||
"#a3be8c",
|
|
||||||
"#b48ead",
|
|
||||||
];
|
|
||||||
|
|
||||||
this.setPageContent("action", "albums");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -172,27 +76,24 @@ 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",
|
||||||
artist,
|
artists_sort,
|
||||||
format,
|
format,
|
||||||
year,
|
year,
|
||||||
genre,
|
genre,
|
||||||
style,
|
style,
|
||||||
userId: collectionUserId,
|
userId: collectionUserId,
|
||||||
discogsIds,
|
|
||||||
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 = {};
|
||||||
|
|
||||||
if (artist) {
|
if (artists_sort) {
|
||||||
where["artists.name"] = artist;
|
where.artists_sort = artists_sort;
|
||||||
}
|
}
|
||||||
if (format) {
|
if (format) {
|
||||||
where["formats.name"] = format;
|
where["formats.name"] = format;
|
||||||
|
@ -234,13 +135,6 @@ Publié automatiquement via #musictopus`;
|
||||||
userId = userIsSharingCollection._id;
|
userId = userIsSharingCollection._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (discogsIds) {
|
|
||||||
where.discogsId = { $in: discogsIds };
|
|
||||||
}
|
|
||||||
if (discogsId) {
|
|
||||||
where.discogsId = Number(discogsId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const count = await AlbumsModel.count({
|
const count = await AlbumsModel.count({
|
||||||
User: userId,
|
User: userId,
|
||||||
...where,
|
...where,
|
||||||
|
@ -252,7 +146,7 @@ Publié automatiquement via #musictopus`;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (exportFormat === "json" && page && limit) {
|
if (page && limit) {
|
||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
|
@ -284,33 +178,11 @@ Publié automatiquement via #musictopus`;
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
rows,
|
rows,
|
||||||
limit,
|
|
||||||
count,
|
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 AlbumsModel.findOne({
|
|
||||||
_id,
|
|
||||||
User,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...album.toJSON(),
|
|
||||||
released: album.released
|
|
||||||
? formatDate(album.released, "MM/dd/yyyy")
|
|
||||||
: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Méthode permettant de mettre à jour un album
|
* Méthode permettant de mettre à jour un album
|
||||||
*
|
*
|
||||||
|
@ -335,9 +207,7 @@ Publié automatiquement via #musictopus`;
|
||||||
|
|
||||||
const values = await getAlbumDetails(album.discogsId);
|
const values = await getAlbumDetails(album.discogsId);
|
||||||
|
|
||||||
await AlbumsModel.findOneAndUpdate(query, values, { new: true });
|
return AlbumsModel.findOneAndUpdate(query, values, { new: true });
|
||||||
|
|
||||||
return this.getOne();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -346,7 +216,7 @@ Publié automatiquement via #musictopus`;
|
||||||
*/
|
*/
|
||||||
async deleteOne() {
|
async deleteOne() {
|
||||||
const res = await AlbumsModel.findOneAndDelete({
|
const res = await AlbumsModel.findOneAndDelete({
|
||||||
User: this.req.user._id,
|
user: this.req.user._id,
|
||||||
_id: this.req.params.itemId,
|
_id: this.req.params.itemId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -361,109 +231,24 @@ Publié automatiquement via #musictopus`;
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 AlbumsModel.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"
|
* Méthode permettant de créer la page "ma-collection"
|
||||||
*/
|
*/
|
||||||
async loadMyCollection() {
|
async loadMyCollection() {
|
||||||
const artists = await getAllDistincts(
|
const artists = await Albums.getAllDistincts(
|
||||||
AlbumsModel,
|
"artists_sort",
|
||||||
"artists.name",
|
|
||||||
this.req.user._id
|
this.req.user._id
|
||||||
);
|
);
|
||||||
const formats = await getAllDistincts(
|
const formats = await Albums.getAllDistincts(
|
||||||
AlbumsModel,
|
|
||||||
"formats.name",
|
"formats.name",
|
||||||
this.req.user._id
|
this.req.user._id
|
||||||
);
|
);
|
||||||
const years = await getAllDistincts(
|
const years = await Albums.getAllDistincts("year", this.req.user._id);
|
||||||
AlbumsModel,
|
const genres = await Albums.getAllDistincts(
|
||||||
"year",
|
|
||||||
this.req.user._id
|
|
||||||
);
|
|
||||||
const genres = await getAllDistincts(
|
|
||||||
AlbumsModel,
|
|
||||||
"genres",
|
"genres",
|
||||||
this.req.user._id
|
this.req.user._id
|
||||||
);
|
);
|
||||||
const styles = await getAllDistincts(
|
const styles = await Albums.getAllDistincts(
|
||||||
AlbumsModel,
|
|
||||||
"styles",
|
"styles",
|
||||||
this.req.user._id
|
this.req.user._id
|
||||||
);
|
);
|
||||||
|
@ -480,7 +265,19 @@ Publié automatiquement via #musictopus`;
|
||||||
* Méthode permettant d'afficher le détails d'un album
|
* Méthode permettant d'afficher le détails d'un album
|
||||||
*/
|
*/
|
||||||
async loadItem() {
|
async loadItem() {
|
||||||
const item = await this.getOne();
|
const { itemId: _id } = this.req.params;
|
||||||
|
const { _id: User } = this.req.user;
|
||||||
|
const album = await AlbumsModel.findOne({
|
||||||
|
_id,
|
||||||
|
User,
|
||||||
|
});
|
||||||
|
|
||||||
|
const item = {
|
||||||
|
...album.toJSON(),
|
||||||
|
released: album.released
|
||||||
|
? formatDate(album.released, "MM/dd/yyyy")
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
|
||||||
this.setPageContent("item", item);
|
this.setPageContent("item", item);
|
||||||
this.setPageTitle(
|
this.setPageTitle(
|
||||||
|
@ -488,164 +285,6 @@ Publié automatiquement via #musictopus`;
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 AlbumsModel.count({
|
|
||||||
User,
|
|
||||||
});
|
|
||||||
|
|
||||||
const items = await AlbumsModel.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 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 } = 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"
|
* Méthode permettant de créer la page "collection/:userId"
|
||||||
*/
|
*/
|
||||||
|
@ -662,22 +301,14 @@ Publié automatiquement via #musictopus`;
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const artists = await getAllDistincts(
|
const artists = await Albums.getAllDistincts("artists_sort", userId);
|
||||||
AlbumsModel,
|
const formats = await Albums.getAllDistincts("formats.name", userId);
|
||||||
"artists.name",
|
const years = await Albums.getAllDistincts("year", userId);
|
||||||
userId
|
const genres = await Albums.getAllDistincts("genres", 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.setPageTitle(`Collection publique de ${user.username}`);
|
|
||||||
this.setPageContent("username", user.username);
|
this.setPageContent("username", user.username);
|
||||||
|
this.setPageTitle(`Collection publique de ${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,7 +5,6 @@ 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 {
|
||||||
/**
|
/**
|
||||||
|
@ -52,50 +51,6 @@ 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
|
||||||
|
@ -123,9 +78,6 @@ 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,8 +1,6 @@
|
||||||
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";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,53 +12,21 @@ class Me extends Pages {
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
async patchMe() {
|
async patchMe() {
|
||||||
const { body } = this.req;
|
const { body, user } = this.req;
|
||||||
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(),
|
|
||||||
password: Joi.string(),
|
|
||||||
passwordConfirm: Joi.ref("password"),
|
|
||||||
mastodon: {
|
|
||||||
publish: Joi.boolean(),
|
|
||||||
url: Joi.string().uri().allow(null, ""),
|
|
||||||
token: Joi.string().allow(null, ""),
|
|
||||||
message: Joi.string().allow(null, ""),
|
|
||||||
wantlist: Joi.string().allow(null, ""),
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const value = await schema.validateAsync(body);
|
const value = await schema.validateAsync(body);
|
||||||
const user = await UsersModel.findById(_id);
|
const update = await UsersModel.findByIdAndUpdate(
|
||||||
|
user._id,
|
||||||
if (value.oldPassword) {
|
{ $set: value },
|
||||||
if (!user.validPassword(value.oldPassword)) {
|
{ new: true }
|
||||||
throw new Error("Votre ancien mot de passe n'est pas valide");
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.mastodon !== undefined) {
|
|
||||||
user.mastodon = value.mastodon;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.password) {
|
|
||||||
user.salt = value.password;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.pagination) {
|
|
||||||
user.pagination = value.pagination;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.isPublicCollection !== undefined) {
|
|
||||||
user.isPublicCollection = value.isPublicCollection;
|
|
||||||
}
|
|
||||||
|
|
||||||
user.save();
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
this.req.login(user, (err) => {
|
this.req.login(update, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
|
@ -69,24 +35,34 @@ class Me extends Pages {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return user;
|
return update;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Méthode permettant de supprimer un utilisateur et toutes ses données
|
* Méthode permettant de modifier le mot de passe d'un utilisateur
|
||||||
*
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
*/
|
||||||
async deleteMe() {
|
async updatePassword() {
|
||||||
|
const { body } = this.req;
|
||||||
const { _id } = this.req.user;
|
const { _id } = this.req.user;
|
||||||
|
|
||||||
await AlbumsModel.deleteMany({ User: _id });
|
const schema = Joi.object({
|
||||||
await WantlistModel.deleteMany({ User: _id });
|
oldPassword: Joi.string().required(),
|
||||||
await UsersModel.deleteOne({ _id });
|
password: Joi.string().required(),
|
||||||
|
passwordConfirm: Joi.ref("password"),
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
const value = await schema.validateAsync(body);
|
||||||
deleted: true,
|
const user = await UsersModel.findById(_id);
|
||||||
};
|
|
||||||
|
if (!user.validPassword(value.oldPassword)) {
|
||||||
|
throw new Error("Votre ancien mot de passe n'est pas valide");
|
||||||
|
}
|
||||||
|
|
||||||
|
user.salt = value.password;
|
||||||
|
|
||||||
|
await user.save();
|
||||||
|
|
||||||
|
this.req.flash("success", "Profil correctement mis à jour");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,690 +0,0 @@
|
||||||
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,21 +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,
|
||||||
},
|
},
|
||||||
mastodon: {
|
|
||||||
publish: Boolean,
|
|
||||||
token: String,
|
|
||||||
url: String,
|
|
||||||
message: String,
|
|
||||||
wantlist: String,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
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);
|
|
|
@ -68,17 +68,4 @@ router
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router
|
|
||||||
.route("/:itemId/share")
|
|
||||||
.post(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const albums = new Albums(req);
|
|
||||||
const data = await albums.shareOne();
|
|
||||||
|
|
||||||
sendResponse(req, res, data);
|
|
||||||
} catch (err) {
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
import express from "express";
|
|
||||||
import { ensureLoggedIn } from "connect-ensure-login";
|
|
||||||
|
|
||||||
import Mastodon from "mastodon";
|
|
||||||
import { sendResponse } from "../../../libs/format";
|
|
||||||
|
|
||||||
// eslint-disable-next-line new-cap
|
|
||||||
const router = express.Router();
|
|
||||||
|
|
||||||
router.route("/").post(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const { url, token } = req.body;
|
|
||||||
|
|
||||||
const M = new Mastodon({
|
|
||||||
access_token: token,
|
|
||||||
api_url: url,
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await M.post("statuses", {
|
|
||||||
status: "Test d'intégration de Mastodon sur mon compte #musictopus 👌",
|
|
||||||
});
|
|
||||||
return sendResponse(req, res, data);
|
|
||||||
} catch (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default router;
|
|
|
@ -15,16 +15,6 @@ 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);
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { ensureLoggedIn } from "connect-ensure-login";
|
||||||
|
|
||||||
import { sendResponse } from "../../../libs/format";
|
import { sendResponse } from "../../../libs/format";
|
||||||
import { searchSong, getAlbumDetails } from "../../../helpers";
|
import { searchSong, getAlbumDetails } from "../../../helpers";
|
||||||
import Albums from "../../../middleware/Albums";
|
|
||||||
|
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
@ -17,30 +16,6 @@ router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
req.query.country || null
|
req.query.country || null
|
||||||
);
|
);
|
||||||
|
|
||||||
const discogsIds = [];
|
|
||||||
const foundIds = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < data.results.length; i += 1) {
|
|
||||||
discogsIds.push(data.results[i].id);
|
|
||||||
}
|
|
||||||
|
|
||||||
req.query.discogsIds = discogsIds;
|
|
||||||
|
|
||||||
const albums = new Albums(req);
|
|
||||||
const myAlbums = await albums.getAll();
|
|
||||||
|
|
||||||
if (myAlbums.rows) {
|
|
||||||
for (let i = 0; i < myAlbums.rows.length; i += 1) {
|
|
||||||
foundIds.push(myAlbums.rows[i].discogsId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < data.results.length; i += 1) {
|
|
||||||
data.results[i].inCollection = foundIds.includes(
|
|
||||||
data.results[i].id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendResponse(req, res, data);
|
sendResponse(req, res, data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
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,23 +104,8 @@ 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);
|
||||||
|
|
|
@ -10,7 +10,7 @@ const router = express.Router();
|
||||||
|
|
||||||
router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const page = new Albums(req, "collection");
|
const page = new Albums(req, "mon-compte/ma-collection/index");
|
||||||
|
|
||||||
await page.loadMyCollection();
|
await page.loadMyCollection();
|
||||||
|
|
||||||
|
@ -24,37 +24,6 @@ router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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
|
router
|
||||||
.route("/exporter")
|
.route("/exporter")
|
||||||
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
@ -63,19 +32,6 @@ router
|
||||||
|
|
||||||
page.setPageTitle("Exporter ma collection");
|
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);
|
render(res, page);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
|
|
|
@ -8,7 +8,9 @@ import render from "../libs/format";
|
||||||
// eslint-disable-next-line new-cap
|
// eslint-disable-next-line new-cap
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
router
|
||||||
|
.route("/")
|
||||||
|
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const page = new Me(req, "mon-compte/index");
|
const page = new Me(req, "mon-compte/index");
|
||||||
|
|
||||||
|
@ -18,6 +20,16 @@ router.route("/").get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.post(ensureLoggedIn("/connexion"), async (req, res) => {
|
||||||
|
try {
|
||||||
|
const page = new Me(req, "mon-compte/index");
|
||||||
|
|
||||||
|
await page.updatePassword();
|
||||||
|
} catch (err) {
|
||||||
|
req.flash("error", err.toString());
|
||||||
|
}
|
||||||
|
res.redirect("/mon-compte");
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
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;
|
|
|
@ -1,133 +0,0 @@
|
||||||
<div class="grid md:grid-cols-3 gap-16">
|
|
||||||
<div>
|
|
||||||
<template v-for="album in tracklist">
|
|
||||||
<strong v-if="album.title">{{album.title}}</strong>
|
|
||||||
<ul>
|
|
||||||
<li v-for="(track, index) in album.tracks" class="ml-4">
|
|
||||||
{{track.position || (index+1)}} - {{ track.title }} <template v-if="track.duration">({{track.duration}})</template>
|
|
||||||
<ul v-if="track.artists && track.artists.length > 0" class="sm-hidden">
|
|
||||||
<li v-for="extra in track.artists" class=" ml-4">
|
|
||||||
<small>{{extra.name}}</small>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul v-if="track.extraartists && track.extraartists.length > 0" class="sm-hidden">
|
|
||||||
<li v-for="extra in track.extraartists" class=" ml-4">
|
|
||||||
<small>{{extra.role}} : <a :href="`/ma-collection?page=1&limit=16&sort=year&order=asc&artist=${extra.name}`">{{extra.name}}</a></small>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class="md:col-span-2">
|
|
||||||
<div class="grid grid-cols-2 gap-10">
|
|
||||||
<div>
|
|
||||||
<strong>Genres</strong>
|
|
||||||
<br />
|
|
||||||
<template v-for="(genre, index) in item.genres">
|
|
||||||
{{genre}}<template v-if="index < item.genres.length - 1">, </template>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Styles</strong>
|
|
||||||
<br />
|
|
||||||
<span v-for="(style, index) in item.styles">
|
|
||||||
{{style}}<template v-if="index < item.styles.length - 1">, </template>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div class="grid grid-cols-3 gap-10">
|
|
||||||
<div>
|
|
||||||
<strong>Pays</strong>
|
|
||||||
<br />
|
|
||||||
<span>{{item.country}}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Année</strong>
|
|
||||||
<br />
|
|
||||||
<span>{{item.year}}</span>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Date de sortie</strong>
|
|
||||||
<br />
|
|
||||||
<span>{{item.released}}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div class="grid gap-10">
|
|
||||||
<div>
|
|
||||||
<strong>Format<template v-if="item.formats.length > 1">s</template></strong>
|
|
||||||
<ul class="ml-4">
|
|
||||||
<li v-for="(format) in item.formats">
|
|
||||||
{{format.name}}
|
|
||||||
<template v-if="format.text">
|
|
||||||
- <i>{{format.text}}</i>
|
|
||||||
</template>
|
|
||||||
<template v-if="format.descriptions && format.descriptions.length > 0">
|
|
||||||
(<span v-for="(description, index) in format.descriptions">
|
|
||||||
{{description}}<template v-if="index < format.descriptions.length - 1">, </template>
|
|
||||||
</span>)
|
|
||||||
</template>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
|
||||||
<div>
|
|
||||||
<strong id="identifiers">Code<template v-if="item.identifiers.length > 1">s</template> barre<template v-if="item.identifiers.length > 1">s</template></strong>
|
|
||||||
<ol class="ml-4">
|
|
||||||
<li v-for="identifier in identifiers">
|
|
||||||
{{identifier.value}} ({{identifier.type}})
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<template v-if="item.identifiers.length > identifiersPreviewLength">
|
|
||||||
<button type="button" class="button is-link" v-if="identifiersMode === 'preview'" @click="showAllIdentifiers">
|
|
||||||
Voir la suite
|
|
||||||
</button>
|
|
||||||
<button type="button" class="button is-link" v-if="identifiersMode === 'all'" @click="showLessIdentifiers">
|
|
||||||
Voir moins
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Label<template v-if="item.labels.length > 1">s</template></strong>
|
|
||||||
<ol class="ml-4">
|
|
||||||
<li v-for="label in item.labels">
|
|
||||||
{{label.name}} {{label.catno}}
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<strong>Société<template v-if="item.companies.length > 1">s</template></strong>
|
|
||||||
<ol class="ml-4">
|
|
||||||
<li v-for="company in item.companies">
|
|
||||||
<strong>{{company.entity_type_name}}</strong> {{company.name}}
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div class="grid gap-10">
|
|
||||||
<div>
|
|
||||||
<strong>Note</strong>
|
|
||||||
<div v-html="(item.notes || '').replaceAll('\n', '<br />')"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<div class="grid gap-10">
|
|
||||||
<div>
|
|
||||||
<strong>Vidéos</strong>
|
|
||||||
<dl>
|
|
||||||
<template v-for="video in item.videos">
|
|
||||||
<dt>
|
|
||||||
<a :href="video.uri" target="_blank" rel="noopener noreferrer">{{video.title}}</a>
|
|
||||||
</dt>
|
|
||||||
<dd>
|
|
||||||
{{video.description}}
|
|
||||||
</dd>
|
|
||||||
</template>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<div class="field">
|
|
||||||
<label for="artist">Artiste</label>
|
|
||||||
<select id="artist" v-model="artist" @change="changeFilter">
|
|
||||||
<option value="">Tous</option>
|
|
||||||
<%
|
|
||||||
for (let i = 0; i < page.artists.length; i += 1 ) {
|
|
||||||
__append(`<option value="${page.artists[i].replaceAll('"','%22')}">${page.artists[i]}</option>`);
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<div class="field">
|
|
||||||
<label for="format">Format</label>
|
|
||||||
<select id="format" v-model="format" @change="changeFilter">
|
|
||||||
<option value="">Tous</option>
|
|
||||||
<%
|
|
||||||
for (let i = 0; i < page.formats.length; i += 1 ) {
|
|
||||||
__append(`<option value="${page.formats[i]}">${page.formats[i]}</option>`);
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label for="genre">Genre</label>
|
|
||||||
<select id="genre" v-model="genre" @change="changeFilter">
|
|
||||||
<option value="">Tous</option>
|
|
||||||
<%
|
|
||||||
for (let i = 0; i < page.genres.length; i += 1 ) {
|
|
||||||
__append(`<option value="${page.genres[i]}">${page.genres[i]}</option>`);
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
|
@ -1,18 +0,0 @@
|
||||||
<div class="filters">
|
|
||||||
<%- include('./artist') %>
|
|
||||||
<%- include('./format') %>
|
|
||||||
<%- include('./sort') %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filters" v-if="moreFilters">
|
|
||||||
<%- include('./year') %>
|
|
||||||
<%- include('./genre') %>
|
|
||||||
<%- include('./style') %>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<span @click="showMoreFilters" class="showMoreFilters">
|
|
||||||
<template v-if="!moreFilters">Voir plus 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 up" v-if="moreFilters"></i>
|
|
||||||
</span>
|
|
|
@ -1,15 +0,0 @@
|
||||||
<div class="field">
|
|
||||||
<label for="sortOrder">Trier par</label>
|
|
||||||
<select id="sortOrder" v-model="sortOrder" @change="changeSort">
|
|
||||||
<option value="artists_sort-asc">Artiste (A-Z)</option>
|
|
||||||
<option value="artists_sort-desc">Artiste (Z-A)</option>
|
|
||||||
<option value="year-asc">Année (1-9)</option>
|
|
||||||
<option value="year-desc">Année (9-1)</option>
|
|
||||||
<option value="country-asc">Pays (A-Z)</option>
|
|
||||||
<option value="country-desc">Pays (Z-A)</option>
|
|
||||||
<option value="formats.name-asc">Format (A-Z)</option>
|
|
||||||
<option value="formats.name-desc">Format (Z-A)</option>
|
|
||||||
<option value="createdAt-asc">Date d'ajout (1-9)</option>
|
|
||||||
<option value="createdAt-desc">Date d'ajout (9-1)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<div class="field">
|
|
||||||
<label for="style">Style</label>
|
|
||||||
<select id="style" v-model="style" @change="changeFilter">
|
|
||||||
<option value="">Tous</option>
|
|
||||||
<%
|
|
||||||
for (let i = 0; i < page.styles.length; i += 1 ) {
|
|
||||||
__append(`<option value="${page.styles[i]}">${page.styles[i]}</option>`);
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<div class="field">
|
|
||||||
<label for="format">Année</label>
|
|
||||||
<select id="format" v-model="year" @change="changeFilter">
|
|
||||||
<option value="">Toutes</option>
|
|
||||||
<%
|
|
||||||
for (let i = 0; i < page.years.length; i += 1 ) {
|
|
||||||
__append(`<option value="${page.years[i]}">${page.years[i]}</option>`);
|
|
||||||
}
|
|
||||||
%>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
142
views/index.ejs
142
views/index.ejs
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
<link href="/css/main.css" rel="stylesheet" />
|
<link href="/css/main.css" rel="stylesheet" />
|
||||||
|
|
||||||
<script src="/js/libs.js"></script>
|
<script defer src="/js/libs.js"></script>
|
||||||
<script defer src="/js/main.js"></script>
|
<script defer src="/js/main.js"></script>
|
||||||
|
|
||||||
<% if ( config.matomoUrl ) { %>
|
<% if ( config.matomoUrl ) { %>
|
||||||
|
@ -38,8 +38,7 @@
|
||||||
<% } %>
|
<% } %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar">
|
<nav class="navbar" aria-label="Navigation principale">
|
||||||
<nav class="navbar container" aria-label="Navigation principale">
|
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item" href="/">
|
<a class="navbar-item" href="/">
|
||||||
<img src="/img/logo.png" alt="Logo MusicTopus">
|
<img src="/img/logo.png" alt="Logo MusicTopus">
|
||||||
|
@ -69,51 +68,12 @@
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<ul class="navbar-end">
|
<div class="navbar-end">
|
||||||
<li class="navbar-item">
|
<a class="navbar-item" href="/nous-contacter">
|
||||||
<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 ) { %>
|
||||||
<li class="navbar-item has-dropdown">
|
<div 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>
|
||||||
|
@ -121,82 +81,46 @@
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<ul class="navbar-dropdown">
|
<div class="navbar-dropdown">
|
||||||
<li>
|
|
||||||
<a class="navbar-item" href="/mon-compte">
|
<a class="navbar-item" href="/mon-compte">
|
||||||
Mon compte
|
Mon compte
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<hr />
|
||||||
<li><hr /></li>
|
<a class="navbar-item" href="/ma-collection">
|
||||||
<li>
|
Ma collection
|
||||||
<a class="navbar-item" href="/ma-collection">Ma collection</a>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a class="navbar-item" href="/ma-collection/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
|
Exporter ma collection
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</div>
|
||||||
<li>
|
</div>
|
||||||
<a class="navbar-item" href="/ma-collection/importer">
|
|
||||||
Importer
|
|
||||||
</a>
|
|
||||||
</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">
|
|
||||||
Déconnexion
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if ( !user ) { %>
|
<div class="navbar-item apparence">
|
||||||
<li class="navbar-item">
|
<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="buttons">
|
<div class="buttons">
|
||||||
|
<button type="button" class="button is-primary" id="switchAriaTheme" aria-label="Renforcer la visibilité de ce site" title="Renforcer la visibilité de ce site">
|
||||||
|
<i class="icon-eye"></i>
|
||||||
|
</button>
|
||||||
|
<% if ( !user ) { %>
|
||||||
<a class="button is-primary" href="/connexion">
|
<a class="button is-primary" href="/connexion">
|
||||||
<strong>Connexion</strong>
|
<strong>Connexion</strong>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
<% } else { %>
|
||||||
</li>
|
<a class="button is-danger" href="/se-deconnecter">
|
||||||
|
Déconnexion
|
||||||
|
</a>
|
||||||
<% } %>
|
<% } %>
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div id="toastr">
|
<div id="toastr">
|
||||||
|
@ -253,7 +177,7 @@
|
||||||
|
|
||||||
<footer class="footer layout-hero">
|
<footer class="footer layout-hero">
|
||||||
<p>
|
<p>
|
||||||
<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.fr" 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://git.darkou.fr/dbroqua/MusicTopus" target="_blank">git.darkou.fr <i class="icon-link"></i></a>.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<main class="layout-maxed ajouter-un-album" id="ajouter-album">
|
<main class="layout-maxed ajouter-un-album" id="ajouter-album">
|
||||||
<h1>Ajouter un album</h1>
|
<h1>Ajouter un album</h1>
|
||||||
<form @submit="search">
|
<form @submit="search">
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2">
|
<div class="grid sm:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label for="q">Nom de l'album ou code barre</label>
|
<label for="q">Nom de l'album ou code barre</label>
|
||||||
<div class="field has-addons">
|
<div class="field has-addons">
|
||||||
|
@ -34,9 +34,8 @@
|
||||||
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 list hover">
|
<div class="grid grid-cols-1 md:grid-cols-2 list hover">
|
||||||
<div v-if="!loading" v-for="item in items" class="item" :class="{'in-collection': item.inCollection}">
|
<div class="item" v-if="!loading" v-for="item in items">
|
||||||
<a @click="loadDetails(item.id)" class="title">{{ item.artists_sort }} {{ item.title }}</a>
|
<a @click="loadDetails(item.id)" class="title">{{ item.artists_sort }} {{ item.title }}</a>
|
||||||
<small v-if="item.inCollection"> (Dans ma collection)</small>
|
|
||||||
<div class="grid grid-cols-2 md:grid-cols-4">
|
<div class="grid grid-cols-2 md:grid-cols-4">
|
||||||
<div>
|
<div>
|
||||||
<img :src="item.thumb" :alt="item.title" @click="loadDetails(item.id)"/>
|
<img :src="item.thumb" :alt="item.title" @click="loadDetails(item.id)"/>
|
||||||
|
@ -79,7 +78,7 @@
|
||||||
<button aria-label="Fermer" class="close" @click="toggleModal"></button>
|
<button aria-label="Fermer" class="close" @click="toggleModal"></button>
|
||||||
</header>
|
</header>
|
||||||
<section>
|
<section>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-16">
|
<div class="grid grid-cols-2 gap-16">
|
||||||
<div>
|
<div>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<img :src="details.thumb %>" :alt="`Miniature pour l'album ${details.title}`" />
|
<img :src="details.thumb %>" :alt="`Miniature pour l'album ${details.title}`" />
|
||||||
|
@ -87,93 +86,75 @@
|
||||||
<img v-for="image in details.images" :src="image.uri150" :alt="`Miniature de type ${image.type}`" style="max-width: 60px;" />
|
<img v-for="image in details.images" :src="image.uri150" :alt="`Miniature de type ${image.type}`" style="max-width: 60px;" />
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
<ul class="is-unstyled">
|
<ol class="ml-4">
|
||||||
<li v-for="track in details.tracklist" :class="{'ml-4': track.type_ === 'track'}">
|
<li v-for="track in details.tracklist">{{ track.title }} ({{track.duration}})</li>
|
||||||
<strong v-if="track.type_ === 'heading'">
|
</ol>
|
||||||
{{track.title}}
|
|
||||||
</strong>
|
|
||||||
<template v-else>
|
|
||||||
{{ track.position }}
|
|
||||||
{{ track.title }} <span v-if="track.duration">({{track.duration}})</span>
|
|
||||||
<ul v-if="track.artists && track.artists.length > 0" class="sm-hidden">
|
|
||||||
<li v-for="extra in track.artists" class=" ml-4">
|
|
||||||
<small>{{extra.role}} : {{extra.name}}</small>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="md:col-span-2">
|
<div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
<div class="grid grid-cols-2 gap-10">
|
||||||
<div class="grid grid-cols-1">
|
<div>
|
||||||
<strong>Genres</strong>
|
<strong>Genres</strong>
|
||||||
|
<br />
|
||||||
<template v-for="(genre, index) in details.genres">
|
<template v-for="(genre, index) in details.genres">
|
||||||
{{genre}}<template v-if="index < details.genres.length - 1">, </template>
|
{{genre}}<template v-if="index < details.genres.length - 1">, </template>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1">
|
<div>
|
||||||
<strong>Styles</strong>
|
<strong>Styles</strong>
|
||||||
|
<br />
|
||||||
<span v-for="(style, index) in details.styles">
|
<span v-for="(style, index) in details.styles">
|
||||||
{{style}}<template v-if="index < details.styles.length - 1">, </template>
|
{{style}}<template v-if="index < details.styles.length - 1">, </template>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-10">
|
<div class="grid grid-cols-3 gap-10">
|
||||||
<div class="grid grid-cols-2 md:grid-cols-1">
|
<div>
|
||||||
<strong>Pays</strong>
|
<strong>Pays</strong>
|
||||||
|
<br />
|
||||||
<span>{{details.country}}</span>
|
<span>{{details.country}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 md:grid-cols-1">
|
<div>
|
||||||
<strong>Année</strong>
|
<strong>Année</strong>
|
||||||
|
<br />
|
||||||
<span>{{details.year}}</span>
|
<span>{{details.year}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2 md:grid-cols-1">
|
<div>
|
||||||
<strong>Date de sortie</strong>
|
<strong>Date de sortie</strong>
|
||||||
|
<br />
|
||||||
<span>{{details.released}}</span>
|
<span>{{details.released}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="grid grid-cols-1 gap-10">
|
<div class="grid grid-cols-2 gap-10">
|
||||||
<div>
|
<div>
|
||||||
<strong>Format<template v-if="details?.formats?.length > 1">s</template></strong>
|
<strong>Format</strong>
|
||||||
<ul class="ml-4">
|
<br />
|
||||||
<li v-for="(format) in details.formats">
|
<span v-for="(format, index) in details.formats">
|
||||||
{{format.name}}
|
{{format.name}}<template v-if="index < details.formats.length - 1">, </template>
|
||||||
<template v-if="format.text">
|
</span>
|
||||||
- <i>{{format.text}}</i>
|
|
||||||
</template>
|
|
||||||
<template v-if="format.descriptions && format.descriptions.length > 0">
|
|
||||||
(<span v-for="(description, index) in format.descriptions">
|
|
||||||
{{description}}<template v-if="index < format.descriptions.length - 1">, </template>
|
|
||||||
</span>)
|
|
||||||
</template>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
<div class="grid grid-cols-2 gap-10">
|
||||||
<div>
|
<div>
|
||||||
<strong>Code<template v-if="details?.identifiers?.length > 1">s</template> barre<template v-if="details?.identifiers?.length > 1">s</template></strong>
|
<strong>Codes barres</strong>
|
||||||
<ol class="ml-4">
|
<ol>
|
||||||
<li v-for="identifier in details.identifiers">
|
<li v-for="identifier in details.identifiers">
|
||||||
{{identifier.value}} ({{identifier.type}})
|
{{identifier.value}} ({{identifier.type}})
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Label<template v-if="details?.labels?.length > 1">s</template></strong>
|
<strong>Label</strong>
|
||||||
<ol class="ml-4">
|
<ol>
|
||||||
<li v-for="label in details.labels">
|
<li v-for="label in details.labels">
|
||||||
{{label.name}}
|
{{label.name}}
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
<strong>Société<template v-if="details?.companies?.length > 1">s</template></strong>
|
<strong>Société</strong>
|
||||||
<ol class="ml-4">
|
<ol>
|
||||||
<li v-for="company in details.companies">
|
<li v-for="company in details.companie">
|
||||||
<strong>{{company.entity_type_name}}</strong>
|
|
||||||
{{company.name}}
|
{{company.name}}
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
@ -183,22 +164,9 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<footer>
|
<footer>
|
||||||
<% if ( user.mastodon && user.mastodon.publish ) { %>
|
<button class="button is-primary" @click="add">Ajouter</button>
|
||||||
<div class="field">
|
|
||||||
<label for="share">Partager sur le fédiverse</label>
|
|
||||||
<span>
|
|
||||||
<input type="checkbox" id="share" name="share" v-model="share">
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<% } %>
|
|
||||||
<button :class="['button is-primary', submitting ? 'is-disabled' : '']" @click="add">Ajouter</button>
|
|
||||||
<button class="button" @click="toggleModal">Annuler</button>
|
<button class="button" @click="toggleModal">Annuler</button>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
|
||||||
const canPublish = <%- (user.mastodon && user.mastodon.publish) || false %>;
|
|
||||||
const action = "<%- page.action %>";
|
|
||||||
</script>
|
|
|
@ -1,30 +1,90 @@
|
||||||
<%
|
<main class="layout-maxed collection" id="collection-publique">
|
||||||
const pageType = page.username ? 'public' : 'private';
|
|
||||||
%>
|
|
||||||
|
|
||||||
<main class="layout-maxed collection" id="collection">
|
|
||||||
<% if (page.action === 'albums') { %>
|
|
||||||
<h1>
|
<h1>
|
||||||
<% if ( pageType === 'private' ) {
|
Collection de <%= page.username %>
|
||||||
__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>');
|
|
||||||
} else {
|
|
||||||
__append(`Collection de ${page.username}`);
|
|
||||||
} %>
|
|
||||||
</h1>
|
</h1>
|
||||||
<% if ( pageType === 'private' ) { %>
|
|
||||||
<div>
|
<div class="filters">
|
||||||
<a :href="shareLink" v-if="isPublicCollection" target="_blank">
|
<div class="field">
|
||||||
<i class="icon-share"></i> Voir ma collection partagée
|
<label for="artist">Artiste</label>
|
||||||
</a>
|
<select id="artist" v-model="artist" @change="changeFilter">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.artists.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.artists[i]}">${page.artists[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="format">Format</label>
|
||||||
|
<select id="format" v-model="format" @change="changeFilter">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.formats.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.formats[i]}">${page.formats[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="sortOrder">Trier par</label>
|
||||||
|
<select id="sortOrder" v-model="sortOrder" @change="changeSort">
|
||||||
|
<option value="artists_sort-asc">Artiste (A-Z)</option>
|
||||||
|
<option value="artists_sort-desc">Artiste (Z-A)</option>
|
||||||
|
<option value="year-asc">Année (A-Z)</option>
|
||||||
|
<option value="year-desc">Année (Z-A)</option>
|
||||||
|
<option value="country-asc">Pays (A-Z)</option>
|
||||||
|
<option value="country-desc">Pays (Z-A)</option>
|
||||||
|
<option value="formats.name-asc">Format (A-Z)</option>
|
||||||
|
<option value="formats.name-desc">Format (Z-A)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
|
||||||
<% } else { %>
|
|
||||||
<h1>Ma liste de souhaits</h1>
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<%- include('../components/filters/index') %>
|
<div class="filters" v-if="moreFilters">
|
||||||
|
<div class="field">
|
||||||
|
<label for="format">Année</label>
|
||||||
|
<select id="format" v-model="year" @change="changeFilter">
|
||||||
|
<option value="">Toutes</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.years.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.years[i]}">${page.years[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="genre">Genre</label>
|
||||||
|
<select id="genre" v-model="genre" @change="changeFilter">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.genres.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.genres[i]}">${page.genres[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="style">Style</label>
|
||||||
|
<select id="style" v-model="style" @change="changeFilter">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.styles.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.styles[i]}">${page.styles[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 list hover">
|
<span @click="showMoreFilters" class="showMoreFilters">
|
||||||
|
<template v-if="!moreFilters">Voir plus 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 up" v-if="moreFilters"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 list">
|
||||||
<div class="loader" v-if="loading">
|
<div class="loader" v-if="loading">
|
||||||
<div class="animation"></div>
|
<div class="animation"></div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -33,20 +93,11 @@
|
||||||
</div>
|
</div>
|
||||||
<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' ) { %>
|
|
||||||
<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>
|
|
||||||
<% } else { %>
|
|
||||||
{{ item.artists_sort}} - {{ item.title }}
|
{{ item.artists_sort}} - {{ item.title }}
|
||||||
<% } %>
|
|
||||||
</span>
|
</span>
|
||||||
<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' ) { %>
|
|
||||||
<a :href="'/<%= page.action === 'albums' ? 'ma-collection' : 'ma-liste-de-souhaits' %>/' + item._id"><img :src="item.thumb" :alt="item.title" /></a>
|
|
||||||
<% } else { %>
|
|
||||||
<img :src="item.thumb" :alt="item.title" />
|
<img :src="item.thumb" :alt="item.title" />
|
||||||
<% } %>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="md:col-span-3">
|
<div class="md:col-span-3">
|
||||||
<span><strong>Année :</strong> {{ item.year }}</span>
|
<span><strong>Année :</strong> {{ item.year }}</span>
|
||||||
|
@ -78,85 +129,22 @@
|
||||||
</div>
|
</div>
|
||||||
<nav class="pagination" role="navigation" aria-label="Pagination">
|
<nav class="pagination" role="navigation" aria-label="Pagination">
|
||||||
<ul class="pagination-list">
|
<ul class="pagination-list">
|
||||||
<li v-if="page > 1">
|
|
||||||
<a class="pagination-link" @click="goTo(1)" aria-label="Aller à la première page">«</a>
|
|
||||||
</li>
|
|
||||||
<template v-for="p in Array.from({length: totalPages}, (v, i) => (i+1))">
|
<template v-for="p in Array.from({length: totalPages}, (v, i) => (i+1))">
|
||||||
<template v-if="p < 2 || p > (totalPages - 1) || (Number(page) - 1) <= p && Number(page) + 1 >= p">
|
<template v-if="p < 2 || p > (totalPages - 1) || (page - 1) <= p && page + 1 >= p">
|
||||||
<li>
|
<li>
|
||||||
<a class="pagination-link" :class="{'is-current': p === Number(page)}" @click="goTo(p)" :aria-label="'Aller à la page '+p">{{ p }}</a>
|
<a class="pagination-link" :class="{'is-current': p === page}" @click="goTo(p)" aria-label="Aller à la page {{p}}">{{ p }}</a>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="(Number(page) - 3 === p && Number(page) - 2 > 1) || (Number(page) + 2 === p && Number(page) + 2 < totalPages - 1)">
|
<template v-if="(page - 3 === p && page - 2 > 1) || (page + 2 === p && page + 2 < totalPages - 1)">
|
||||||
<li>
|
<li>
|
||||||
<a class="pagination-link is-disabled">…</a>
|
<a class="pagination-link is-disabled">…</a>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<li v-if="page < totalPages">
|
|
||||||
<a class="pagination-link" @click="goTo(totalPages)" aria-label="Aller à la derière page">»</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<% if ( pageType === 'private' ) { %>
|
|
||||||
<div class="modal" :class="{'is-visible': showModalDelete}">
|
|
||||||
<div class="modal-background"></div>
|
|
||||||
<div class="modal-card">
|
|
||||||
<header></header>
|
|
||||||
<section>
|
|
||||||
Êtes-vous sûr de vouloir supprimer cet album ?
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<button class="button is-primary" @click="deleteItem">Supprimer</button>
|
|
||||||
<button class="button" @click="toggleModal">Annuler</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal" :class="{'is-visible': showModalShare}">
|
|
||||||
<div class="modal-background"></div>
|
|
||||||
<div class="modal-card">
|
|
||||||
<header>
|
|
||||||
Partager ma collection
|
|
||||||
</header>
|
|
||||||
<section>
|
|
||||||
<template v-if="!isPublicCollection">
|
|
||||||
Votre collection sera visible de toute personne disposant du lien suivant :
|
|
||||||
<br />
|
|
||||||
<a :href="shareLink" target="_blank">{{shareLink}}</a>
|
|
||||||
<br />
|
|
||||||
Ce lien permet uniquement de visualiser l'ensemble de votre collection mais ne perment <strong class="is-danger">en aucun cas</strong> de la modifier.
|
|
||||||
<br />
|
|
||||||
Vous pourrez à tout moment supprimer le lien de partage en cliquant à nouveau sur l'icône <i class="icon-share"></i> sur votre collection.
|
|
||||||
</template>
|
|
||||||
<template v-if="isPublicCollection">
|
|
||||||
Vous êtes sur le point de rendre votre collection privée.
|
|
||||||
<br />
|
|
||||||
Toute les personnes ayant le lien partagé ne pourront plus accéder à votre collection.
|
|
||||||
<br />
|
|
||||||
Vous pourrez à tout moment rendre à nouveau votre collection publique en cliquant sur l'icône <i class="icon-share"></i>.
|
|
||||||
</template>
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<button v-if="!isPublicCollection" class="button is-primary" @click="shareCollection">Partager</button>
|
|
||||||
<button v-if="isPublicCollection" class="button is-danger" @click="shareCollection">Supprimer</button>
|
|
||||||
<button class="button" @click="toggleModalShare">Annuler</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const vueType = "<%= pageType %>";
|
|
||||||
const query = <%- JSON.stringify(query) %>;
|
|
||||||
const action = "<%- page.action %>";
|
|
||||||
<% if ( pageType === 'private' ) { %>
|
|
||||||
const isPublicCollection = <%= user.isPublicCollection ? 'true' : 'false' %>;
|
|
||||||
const userId = "<%= user._id %>";
|
|
||||||
<% } else { %>
|
|
||||||
const userId = "<%= params.userId %>";
|
const userId = "<%= params.userId %>";
|
||||||
const isPublicCollection = false;
|
|
||||||
<% } %>
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -238,7 +238,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 id="boites">Les boites</h2>
|
<h2 id="boites">Les boites</h2>
|
||||||
<div class="box mini">
|
<div class="box">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<h1>
|
<h1>
|
||||||
Connexion
|
Connexion
|
||||||
|
@ -487,8 +487,19 @@
|
||||||
</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">
|
||||||
|
<button type="button" class="button is-primary" id="switchAriaTheme" aria-label="Renforcer la visibilité de ce site" title="Renforcer la visibilité de ce site">
|
||||||
|
<i class="icon-eye"></i>
|
||||||
|
</button>
|
||||||
<a class="button is-danger" href="/se-deconnecter">
|
<a class="button is-danger" href="/se-deconnecter">
|
||||||
Déconnexion
|
Déconnexion
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="box mini">
|
<div class="box">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<h1>
|
<h1>
|
||||||
Connexion
|
Connexion
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
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 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.
|
<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.
|
||||||
<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>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class="box mini">
|
<div class="box">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<h1>
|
<h1>
|
||||||
Inscription
|
Inscription
|
||||||
|
|
|
@ -3,11 +3,9 @@
|
||||||
Mon compte
|
Mon compte
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<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 class="box">
|
<form method="POST" action="/mon-compte" @submit="updateProfil">
|
||||||
<h2>Mes données personnelles</h2>
|
|
||||||
<div>
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="email">Adresse e-mail</label>
|
<label for="email">Adresse e-mail</label>
|
||||||
<input
|
<input
|
||||||
|
@ -16,7 +14,7 @@
|
||||||
disabled
|
disabled
|
||||||
name="email"
|
name="email"
|
||||||
id="email"
|
id="email"
|
||||||
v-model="formData.email"
|
v-model="email"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -27,7 +25,7 @@
|
||||||
disabled
|
disabled
|
||||||
name="username"
|
name="username"
|
||||||
id="username"
|
id="username"
|
||||||
v-model="formData.username"
|
v-model="username"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -36,21 +34,21 @@
|
||||||
type="password"
|
type="password"
|
||||||
name="oldPassword"
|
name="oldPassword"
|
||||||
id="oldPassword"
|
id="oldPassword"
|
||||||
|
required
|
||||||
placeholder="Saisisssez votre mot de passe actuel"
|
placeholder="Saisisssez votre mot de passe actuel"
|
||||||
v-model="formData.oldPassword"
|
v-model="oldPassword"
|
||||||
/>
|
/>
|
||||||
<div class="message error" v-if="errors.includes('emptyPassword')">
|
|
||||||
Pour changer votre mot de passe vous devez saisir votre mot de passe actuel
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div></div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="password">Nouveau mot de passe</label>
|
<label for="password">Nouveau mot de passe</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
id="password"
|
id="password"
|
||||||
|
required
|
||||||
placeholder="Saisisssez votre nouveau mot de passe"
|
placeholder="Saisisssez votre nouveau mot de passe"
|
||||||
v-model="formData.password"
|
v-model="password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
@ -59,144 +57,21 @@
|
||||||
type="password"
|
type="password"
|
||||||
name="passwordConfirm"
|
name="passwordConfirm"
|
||||||
id="passwordConfirm"
|
id="passwordConfirm"
|
||||||
|
required
|
||||||
placeholder="Confirmez votre nouveau mot de passe"
|
placeholder="Confirmez votre nouveau mot de passe"
|
||||||
v-model="formData.passwordConfirm"
|
v-model="passwordConfirm"
|
||||||
/>
|
/>
|
||||||
<div class="message error" v-if="errors.includes('passwordsDiffer')">
|
|
||||||
La confirmation ne correspond pas avec votre nouveau mot de passe
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="box">
|
|
||||||
<h2>Mon activité</h2>
|
|
||||||
<div>
|
|
||||||
<div class="field">
|
|
||||||
<label for="mastodon.publish">Publier sur le fédiverse lorsque j'ajoute un album</label>
|
|
||||||
<select id="format" v-model="formData.mastodon.publish">
|
|
||||||
<option value="true">Oui</option>
|
|
||||||
<option value="false">Non</option>
|
|
||||||
</select>
|
|
||||||
<!-- <input
|
|
||||||
type="checkbox"
|
|
||||||
name="mastodon.publish"
|
|
||||||
id="mastodon.publish"
|
|
||||||
v-model="mastodon.publish"
|
|
||||||
/> -->
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label for="mastodon.url">Url de l'API de votre instance</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="mastodon.url"
|
|
||||||
id="mastodon.url"
|
|
||||||
v-model="formData.mastodon.url"
|
|
||||||
placeholder="https://mastodon.social/api/v1/"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label for="mastodon.token">Jeton d'accès (droits nécessaires : write:media et write:statuses)</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="mastodon.token"
|
|
||||||
id="mastodon.token"
|
|
||||||
v-model="formData.mastodon.token"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label for="mastodon.message">Message</label>
|
|
||||||
<textarea
|
|
||||||
name="mastodon.message"
|
|
||||||
id="mastodon.message"
|
|
||||||
v-model="formData.mastodon.message"
|
|
||||||
></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>
|
|
||||||
Variables possibles :
|
|
||||||
<ul>
|
|
||||||
<li>{artist}, exemple : Iron Maiden</li>
|
|
||||||
<li>{album}, exemple : Powerslave</li>
|
|
||||||
<li>{format}, exemple : Cassette</li>
|
|
||||||
<li>{year}, exemple: 1984</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>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<button type="button" class="button is-secondary mt-10" :disabled="loading" @click="testMastodon">
|
|
||||||
<span>Tester</span>
|
|
||||||
</button>
|
|
||||||
</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>
|
</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>
|
||||||
<i class="icon-spin animate-spin" v-if="loading"></i>
|
<i class="icon-spin animate-spin" v-if="loading"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<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: ''}) %>;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
<main class="layout-maxed ma-collection-details" id="ma-collection-details" v-cloak @keyup="changeImage">
|
<main class="layout-maxed ma-collection-details" id="ma-collection-details" v-cloak @keyup="changeImage">
|
||||||
|
|
||||||
<h1>
|
<h1>
|
||||||
<template v-for="artist in item.artists">
|
<a :href="`/ma-collection?page=1&limit=16&sort=year&order=asc&artists_sort=${item.artists_sort}`">{{item.artists_sort}}</a> - {{item.title}}
|
||||||
<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>
|
|
||||||
- {{item.title}}
|
|
||||||
<i class="icon-trash" title="Supprimer cette fiche" @click="showConfirmDelete()"></i>
|
<i class="icon-trash" title="Supprimer cette fiche" @click="showConfirmDelete()"></i>
|
||||||
<i class="icon-refresh" title="Mettre à jour les données de cette fiche" @click="updateItem()"></i>
|
<i class="icon-refresh" title="Mettre à jour les données de cette fiche" @click="updateItem()"></i>
|
||||||
<i class="icon-share" title="Partager cet album sur le fédiverse" @click="showModalShare = true" v-if="canShareItem"></i>
|
|
||||||
</h1>
|
</h1>
|
||||||
<div class="grid sm:grid-cols-3 gap-16">
|
<div class="grid sm:grid-cols-3 gap-16">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
@ -21,22 +16,147 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<%- include('../../../components/album.ejs') %>
|
<div class="grid md:grid-cols-3 gap-16">
|
||||||
|
<div>
|
||||||
|
<template v-for="album in tracklist">
|
||||||
|
<strong v-if="album.title">{{album.title}}</strong>
|
||||||
|
<ol class="ml-4">
|
||||||
|
<li v-for="track in album.tracks">
|
||||||
|
{{ track.title }} <template v-if="track.duration">({{track.duration}})</template>
|
||||||
|
<ul v-if="track.extraartists && track.extraartists.length > 0" class="sm-hidden ml-4">
|
||||||
|
<li v-for="extra in track.extraartists">
|
||||||
|
<small>{{extra.role}} : {{extra.name}}</small>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div class="md:col-span-2">
|
||||||
|
<div class="grid grid-cols-2 gap-10">
|
||||||
|
<div>
|
||||||
|
<strong>Genres</strong>
|
||||||
|
<br />
|
||||||
|
<template v-for="(genre, index) in item.genres">
|
||||||
|
{{genre}}<template v-if="index < item.genres.length - 1">, </template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Styles</strong>
|
||||||
|
<br />
|
||||||
|
<span v-for="(style, index) in item.styles">
|
||||||
|
{{style}}<template v-if="index < item.styles.length - 1">, </template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="grid grid-cols-3 gap-10">
|
||||||
|
<div>
|
||||||
|
<strong>Pays</strong>
|
||||||
|
<br />
|
||||||
|
<span>{{item.country}}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Année</strong>
|
||||||
|
<br />
|
||||||
|
<span>{{item.year}}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Date de sortie</strong>
|
||||||
|
<br />
|
||||||
|
<span>{{item.released}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="grid gap-10">
|
||||||
|
<div>
|
||||||
|
<strong>Format</strong>
|
||||||
|
<ul class="ml-4">
|
||||||
|
<li v-for="(format) in item.formats">
|
||||||
|
{{format.name}}
|
||||||
|
<template v-if="format.text">
|
||||||
|
- <i>{{format.text}}</i>
|
||||||
|
</template>
|
||||||
|
<template v-if="format.descriptions && format.descriptions.length > 0">
|
||||||
|
(<span v-for="(description, index) in format.descriptions">
|
||||||
|
{{description}}<template v-if="index < format.descriptions.length - 1">, </template>
|
||||||
|
</span>)
|
||||||
|
</template>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="grid grid-cols-2 gap-10">
|
||||||
|
<div>
|
||||||
|
<strong id="identifiers">Codes barres</strong>
|
||||||
|
<ol class="ml-4">
|
||||||
|
<li v-for="identifier in identifiers">
|
||||||
|
{{identifier.value}} ({{identifier.type}})
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
<template v-if="item.identifiers.length > identifiersPreviewLength">
|
||||||
|
<button type="button" class="button is-link" v-if="identifiersMode === 'preview'" @click="showAllIdentifiers">
|
||||||
|
Voir la suite
|
||||||
|
</button>
|
||||||
|
<button type="button" class="button is-link" v-if="identifiersMode === 'all'" @click="showLessIdentifiers">
|
||||||
|
Voir moins
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Label</strong>
|
||||||
|
<br />
|
||||||
|
<template v-for="label in item.labels">
|
||||||
|
{{label.name}} {{label.catno}}
|
||||||
|
<br />
|
||||||
|
</template>
|
||||||
|
<hr />
|
||||||
|
<strong>Sociétés</strong>
|
||||||
|
<br />
|
||||||
|
<template v-for="company in item.companies">
|
||||||
|
<strong>{{company.entity_type_name}}</strong> : {{company.name}}
|
||||||
|
<br />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="grid gap-10">
|
||||||
|
<div>
|
||||||
|
<strong>Note</strong>
|
||||||
|
<div v-html="(item.notes || '').replaceAll('\n', '<br />')"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<div class="grid gap-10">
|
||||||
|
<div>
|
||||||
|
<strong>Vidéos</strong>
|
||||||
|
<dl>
|
||||||
|
<template v-for="video in item.videos">
|
||||||
|
<dt>
|
||||||
|
<a :href="video.uri" target="_blank" rel="noopener noreferrer">{{video.title}}</a>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{{video.description}}
|
||||||
|
</dd>
|
||||||
|
</template>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal" :class="{'is-visible': modalIsVisible}">
|
<div class="modal" :class="{'is-visible': modalIsVisible}">
|
||||||
<div class="modal-background"></div>
|
<div class="modal-background"></div>
|
||||||
<button type="button" aria-label="Fermer" class="close" @click="toggleModal"></button>
|
<button type="button" aria-label="Fermer" class="close" @click="toggleModal"></button>
|
||||||
<div class="carousel">
|
<button type="button" aria-label="Image précédente" class="navigation previous" @click="previous" v-if="index > 0">
|
||||||
<button type="button" aria-label="Image précédente" class="navigation previous" @click="previous">
|
|
||||||
<i class="icon-left-open"></i>
|
<i class="icon-left-open"></i>
|
||||||
</button>
|
</button>
|
||||||
<div class="text-center">
|
<button type="button" aria-label="Image suivante" class="navigation next" @click="next" v-if="index + 1 < item.images.length">
|
||||||
<img :src="preview" />
|
|
||||||
</div>
|
|
||||||
<button type="button" aria-label="Image suivante" class="navigation next" @click="next">
|
|
||||||
<i class="icon-right-open"></i>
|
<i class="icon-right-open"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<div class="modal-card">
|
||||||
|
<img :src="preview" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -53,51 +173,8 @@
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal" :class="{'is-visible': showModalShare}">
|
|
||||||
<div class="modal-background"></div>
|
|
||||||
<div class="modal-card">
|
|
||||||
<header>Partager un album sur le fédiverse</header>
|
|
||||||
<section>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
|
||||||
<div class="field">
|
|
||||||
<label for="shareMessage">Message</label>
|
|
||||||
<textarea
|
|
||||||
name="shareMessage"
|
|
||||||
id="shareMessage"
|
|
||||||
v-model="shareMessage"
|
|
||||||
rows="6"
|
|
||||||
></textarea>
|
|
||||||
Caractères utilisés : {{ shareMessageLength }}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<small>
|
|
||||||
Variables possibles :
|
|
||||||
<ul>
|
|
||||||
<li>{artist}, exemple : Iron Maiden</li>
|
|
||||||
<li>{album}, exemple : Powerslave</li>
|
|
||||||
<li>{format}, exemple : Cassette</li>
|
|
||||||
<li>{year}, exemple: 1984</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>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<button :class="['button is-primary', shareSubmiting ? 'is-disabled' : '']" @click="shareAlbum">Partager</button>
|
|
||||||
<button class="button" @click="showModalShare=!showModalShare">Annuler</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const item = <%- JSON.stringify(page.item) %>;
|
const item = <%- JSON.stringify(page.item) %>;
|
||||||
const action = "<%- page.action %>";
|
|
||||||
const canShareItem = <%= user.mastodon?.publish || false %>;
|
|
||||||
</script>
|
</script>
|
|
@ -1,9 +1,5 @@
|
||||||
<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>
|
||||||
|
@ -49,7 +45,3 @@
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<script>
|
|
||||||
const action = "<%- page.action %>";
|
|
||||||
</script>
|
|
|
@ -1,51 +0,0 @@
|
||||||
<main class="layout-maxed" id="importer">
|
|
||||||
<% if (page.action === 'albums') { %>
|
|
||||||
<h1>Importer une collection</h1>
|
|
||||||
<% } else { %>
|
|
||||||
<h1>Importer une liste de souhaits</h1>
|
|
||||||
<% } %>
|
|
||||||
<p>
|
|
||||||
Il est actuellement possible d'importer <%= page.action === 'albums' ? "une collection" : "une liste de souhaits" %> provenant de discogs.
|
|
||||||
<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.
|
|
||||||
<br />
|
|
||||||
Une fois exporter vous recevrez un mail de Discogs avec un lien de téléchargement. Une fois le fichier .zip téléchargé vous devez en extraire le fichier .csv afin de l'importer dans MusicTopus.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
D'autres formats d'imports seront ajoutés par la suite, comme l'import entre 2 instances MusicTopus.
|
|
||||||
</p>
|
|
||||||
<div class="flash info">
|
|
||||||
<div class="header">
|
|
||||||
Information
|
|
||||||
</div>
|
|
||||||
<div class="body">
|
|
||||||
Si un album est déjà présent en base celui-ci sera ignoré.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form @submit="importCollection">
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label for="file">Fichier .csv</label>
|
|
||||||
<input type="file" name="file" id="file" @change="handleFileUpload( $event )" accept=".csv">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<span>
|
|
||||||
Albums à impoter : <strong>{{content.length}}</strong>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="button is-primary my-16" :disabled="disabled">
|
|
||||||
<i v-if="['parse', 'submit'].includes(state)" class="icon-spin animate-spin"></i>
|
|
||||||
<span v-if="state === 'default'">Importer</span>
|
|
||||||
<span v-if="state === 'parse'">Analyse en cours...</span>
|
|
||||||
<span v-if="state === 'submit'">Importation en cours... ({{imported}}/{{content.length}})</span>
|
|
||||||
<span v-if="state === 'done'">Importatation terminée</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const action = "<%- page.action %>";
|
|
||||||
</script>
|
|
200
views/pages/mon-compte/ma-collection/index.ejs
Normal file
200
views/pages/mon-compte/ma-collection/index.ejs
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
<main class="layout-maxed collection" id="ma-collection">
|
||||||
|
<h1>
|
||||||
|
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>
|
||||||
|
</h1>
|
||||||
|
<a :href="shareLink" v-if="isPublicCollection" target="_blank">
|
||||||
|
<i class="icon-share"></i> Voir ma collection partagée
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="filters">
|
||||||
|
<div class="field">
|
||||||
|
<label for="artist">Artiste</label>
|
||||||
|
<select id="artist" v-model="artist" @change="changeFilter">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.artists.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.artists[i]}">${page.artists[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="format">Format</label>
|
||||||
|
<select id="format" v-model="format" @change="changeFilter">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.formats.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.formats[i]}">${page.formats[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="sortOrder">Trier par</label>
|
||||||
|
<select id="sortOrder" v-model="sortOrder" @change="changeSort">
|
||||||
|
<option value="artists_sort-asc">Artiste (A-Z)</option>
|
||||||
|
<option value="artists_sort-desc">Artiste (Z-A)</option>
|
||||||
|
<option value="year-asc">Année (A-Z)</option>
|
||||||
|
<option value="year-desc">Année (Z-A)</option>
|
||||||
|
<option value="country-asc">Pays (A-Z)</option>
|
||||||
|
<option value="country-desc">Pays (Z-A)</option>
|
||||||
|
<option value="formats.name-asc">Format (A-Z)</option>
|
||||||
|
<option value="formats.name-desc">Format (Z-A)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filters" v-if="moreFilters">
|
||||||
|
<div class="field">
|
||||||
|
<label for="format">Année</label>
|
||||||
|
<select id="format" v-model="year" @change="changeFilter">
|
||||||
|
<option value="">Toutes</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.years.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.years[i]}">${page.years[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="genre">Genre</label>
|
||||||
|
<select id="genre" v-model="genre" @change="changeFilter">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.genres.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.genres[i]}">${page.genres[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label for="style">Style</label>
|
||||||
|
<select id="style" v-model="style" @change="changeFilter">
|
||||||
|
<option value="">Tous</option>
|
||||||
|
<%
|
||||||
|
for (let i = 0; i < page.styles.length; i += 1 ) {
|
||||||
|
__append(`<option value="${page.styles[i]}">${page.styles[i]}</option>`);
|
||||||
|
}
|
||||||
|
%>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span @click="showMoreFilters" class="showMoreFilters">
|
||||||
|
<template v-if="!moreFilters">Voir plus 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 up" v-if="moreFilters"></i>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 list hover">
|
||||||
|
<div class="loader" v-if="loading">
|
||||||
|
<div class="animation"></div>
|
||||||
|
<div>
|
||||||
|
Chargement des données en cours…
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item" v-if="!loading" v-for="item in items">
|
||||||
|
<span class="title">
|
||||||
|
<a :href="'/ma-collection/' + item._id">{{ item.artists_sort}} - {{ item.title }}</a>
|
||||||
|
<i class="icon-trash" @click="showConfirmDelete(item._id)"></i>
|
||||||
|
</span>
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-4">
|
||||||
|
<div>
|
||||||
|
<a :href="'/ma-collection/' + item._id"><img :src="item.thumb" :alt="item.title" /></a>
|
||||||
|
</div>
|
||||||
|
<div class="md:col-span-3">
|
||||||
|
<span><strong>Année :</strong> {{ item.year }}</span>
|
||||||
|
<br />
|
||||||
|
<span><strong>Pays :</strong> {{ item.country }}</span>
|
||||||
|
<br />
|
||||||
|
<span>
|
||||||
|
<strong>Format : </strong>
|
||||||
|
<span v-for="(format, index) in item.formats">
|
||||||
|
{{ format.name }}
|
||||||
|
<template v-if="format.descriptions">
|
||||||
|
(<template v-for="(description, j) in format.descriptions">
|
||||||
|
{{description}}<template v-if="j < format.descriptions.length - 1">, </template>
|
||||||
|
</template>)
|
||||||
|
</template>
|
||||||
|
<template v-if="index < item.formats.length - 1">, </template>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<span><strong>Genre :</strong> <template v-for="(genre, index) in item.genres">{{ genre }}<template v-if="index < item.genres.length - 1">, </template></template></span>
|
||||||
|
<br />
|
||||||
|
<span><strong>Style :</strong> <template v-for="(style, index) in item.styles">{{ style }}<template v-if="index < item.styles.length - 1">, </template></template></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="total">
|
||||||
|
<strong>Nombre total d'éléments : </strong>{{total}}
|
||||||
|
</div>
|
||||||
|
<nav class="pagination" role="navigation" aria-label="Pagination">
|
||||||
|
<ul class="pagination-list">
|
||||||
|
<template v-for="p in Array.from({length: totalPages}, (v, i) => (i+1))">
|
||||||
|
<template v-if="p < 2 || p > (totalPages - 1) || (page - 1) <= p && page + 1 >= p">
|
||||||
|
<li>
|
||||||
|
<a class="pagination-link" :class="{'is-current': p === page}" @click="goTo(p)" aria-label="Aller à la page {{p}}">{{ p }}</a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
<template v-if="(page - 3 === p && page - 2 > 1) || (page + 2 === p && page + 2 < totalPages - 1)">
|
||||||
|
<li>
|
||||||
|
<a class="pagination-link is-disabled">…</a>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="modal" :class="{'is-visible': showModalDelete}">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header></header>
|
||||||
|
<section>
|
||||||
|
Êtes-vous sûr de vouloir supprimer cet album ?
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
<button class="button is-primary" @click="deleteItem">Supprimer</button>
|
||||||
|
<button class="button" @click="toggleModal">Annuler</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal" :class="{'is-visible': showModalShare}">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header>
|
||||||
|
Partager ma collection
|
||||||
|
</header>
|
||||||
|
<section>
|
||||||
|
<template v-if="!isPublicCollection">
|
||||||
|
Votre collection sera visible de toute personne disposant du lien suivant :
|
||||||
|
<br />
|
||||||
|
<a :href="shareLink" target="_blank">{{shareLink}}</a>
|
||||||
|
<br />
|
||||||
|
Ce lien permet uniquement de visualiser l'ensemble de votre collection mais ne perment <strong class="is-danger">en aucun cas</strong> de la modifier.
|
||||||
|
<br />
|
||||||
|
Vous pourrez à tout moment supprimer le lien de partage en cliquant à nouveau sur l'icône <i class="icon-share"></i> sur votre collection.
|
||||||
|
</template>
|
||||||
|
<template v-if="isPublicCollection">
|
||||||
|
Vous êtes sur le point de rendre votre collection privée.
|
||||||
|
<br />
|
||||||
|
Toute les personnes ayant le lien partagé ne pourront plus accéder à votre collection.
|
||||||
|
<br />
|
||||||
|
Vous pourrez à tout moment rendre à nouveau votre collection publique en cliquant sur l'icône <i class="icon-share"></i>.
|
||||||
|
</template>
|
||||||
|
</section>
|
||||||
|
<footer>
|
||||||
|
<button v-if="!isPublicCollection" class="button is-primary" @click="shareCollection">Partager</button>
|
||||||
|
<button v-if="isPublicCollection" class="button is-danger" @click="shareCollection">Supprimer</button>
|
||||||
|
<button class="button" @click="toggleModalShare">Annuler</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const isPublicCollection = <%= user.isPublicCollection ? 'true' : 'false' %>;
|
||||||
|
</script>
|
|
@ -1,122 +0,0 @@
|
||||||
<main class="layout-maxed ma-collection-details" id="ma-collection-statistiques">
|
|
||||||
<h1>
|
|
||||||
Mes statistiques
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="grid gap-10 grid-cols-1 md:grid-cols-2 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>
|
|
||||||
<div class="grid gap-10 grid-cols-1 md:grid-cols-2 mb-10">
|
|
||||||
<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>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const byGenres = <%- JSON.stringify(page.byGenres) %>;
|
|
||||||
const byStyles = <%- JSON.stringify(page.byStyles) %>;
|
|
||||||
const byFormats = <%- JSON.stringify(page.byFormats) %>;
|
|
||||||
|
|
||||||
const ctxGenres= document.getElementById('byGenres');
|
|
||||||
const ctxStyles = document.getElementById('byStyles');
|
|
||||||
const ctxFormats = document.getElementById('byFormats');
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
responsive: true,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
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(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,
|
|
||||||
});
|
|
||||||
</script>
|
|
|
@ -1,4 +1,4 @@
|
||||||
<section class="box mini" id="contact">
|
<section class="box" 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…
Reference in a new issue