Compare commits
36 commits
Author | SHA1 | Date | |
---|---|---|---|
|
30bd3ebdf9 | ||
|
5a7d9d707f | ||
|
041e24e26f | ||
|
71c120564a | ||
|
1a9728fce6 | ||
|
2eb22bb3d6 | ||
|
abcbd0f8f7 | ||
|
f73d4a3093 | ||
|
0a2d5029b5 | ||
|
fcb527aa5e | ||
|
c79f1c5a74 | ||
|
960f53ab54 | ||
|
6994170a04 | ||
|
8e0947ed4b | ||
|
736a0afa44 | ||
|
209ba0f5f0 | ||
77de7d54ca | |||
00bb8647e1 | |||
c32b182151 | |||
85752c537d | |||
3b3a4cf779 | |||
1931bd9eda | |||
7b525d3e43 | |||
81c61a0529 | |||
e01dbd5c31 | |||
205474a701 | |||
e28f382c6c | |||
3626b074bd | |||
4ea7b42d52 | |||
fd0a9df724 | |||
97b8bab2f4 | |||
2f988798df | |||
15eb2c2dad | |||
6862afda5c | |||
4109186a47 | |||
e0f227af08 |
31 changed files with 19081 additions and 371 deletions
|
@ -22,7 +22,13 @@ module.exports = {
|
||||||
camelcase: [
|
camelcase: [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
allow: ["artists_sort", "access_token", "api_url", "media_ids"],
|
allow: [
|
||||||
|
"artists_sort",
|
||||||
|
"access_token",
|
||||||
|
"api_url",
|
||||||
|
"media_ids",
|
||||||
|
"release_id",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
share: canPublish,
|
||||||
q: "",
|
q: "",
|
||||||
year: "",
|
year: "",
|
||||||
country: "",
|
country: "",
|
||||||
|
@ -113,6 +115,7 @@ Vue.createApp({
|
||||||
format,
|
format,
|
||||||
genre,
|
genre,
|
||||||
style,
|
style,
|
||||||
|
inCollection,
|
||||||
} = results[i];
|
} = results[i];
|
||||||
items.push({
|
items.push({
|
||||||
id,
|
id,
|
||||||
|
@ -123,6 +126,7 @@ Vue.createApp({
|
||||||
format,
|
format,
|
||||||
genre,
|
genre,
|
||||||
style,
|
style,
|
||||||
|
inCollection,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,7 +171,10 @@ Vue.createApp({
|
||||||
this.submitting = true;
|
this.submitting = true;
|
||||||
|
|
||||||
return axios
|
return axios
|
||||||
.post("/api/v1/albums", this.details)
|
.post("/api/v1/albums", {
|
||||||
|
album: this.details,
|
||||||
|
share: this.share,
|
||||||
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
window.location.href = "/ma-collection";
|
window.location.href = "/ma-collection";
|
||||||
})
|
})
|
||||||
|
|
|
@ -25,6 +25,10 @@ Vue.createApp({
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
isPublicCollection,
|
isPublicCollection,
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
|
userId,
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
vueType,
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
query,
|
query,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -81,6 +85,10 @@ Vue.createApp({
|
||||||
if (this.style) {
|
if (this.style) {
|
||||||
url += `&style=${this.formatParams(this.style)}`;
|
url += `&style=${this.formatParams(this.style)}`;
|
||||||
}
|
}
|
||||||
|
// INFO: Cas d'une collection partagée
|
||||||
|
if (this.vueType === "public" && this.userId) {
|
||||||
|
url += `&userId=${this.userId}`;
|
||||||
|
}
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.get(url)
|
.get(url)
|
||||||
|
@ -167,10 +175,11 @@ Vue.createApp({
|
||||||
this.toggleModal();
|
this.toggleModal();
|
||||||
},
|
},
|
||||||
deleteItem() {
|
deleteItem() {
|
||||||
if ( vueType === 'private' ) {
|
// eslint-disable-next-line no-undef
|
||||||
|
if (vueType !== "private") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
axios
|
return axios
|
||||||
.delete(`/api/v1/albums/${this.itemId}`)
|
.delete(`/api/v1/albums/${this.itemId}`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
|
@ -186,10 +195,11 @@ Vue.createApp({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
shareCollection() {
|
shareCollection() {
|
||||||
if ( vueType === 'private' ) {
|
// eslint-disable-next-line no-undef
|
||||||
|
if (vueType !== "private") {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
axios
|
return axios
|
||||||
.patch(`/api/v1/me`, {
|
.patch(`/api/v1/me`, {
|
||||||
isPublicCollection: !this.isPublicCollection,
|
isPublicCollection: !this.isPublicCollection,
|
||||||
})
|
})
|
||||||
|
@ -219,19 +229,16 @@ Vue.createApp({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
renderAlbumTitle(item) {
|
renderAlbumTitle(item) {
|
||||||
let render = '';
|
let render = "";
|
||||||
|
|
||||||
for ( let i = 0 ; i < item.artists.length ; i += 1 ) {
|
for (let i = 0; i < item.artists.length; i += 1) {
|
||||||
const {
|
const { name, join } = item.artists[i];
|
||||||
name,
|
render += `${name} ${join ? `${join} ` : ""}`;
|
||||||
join,
|
|
||||||
} = item.artists[i];
|
|
||||||
render += `${name} ${join ? `${join} ` : ''}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render += `- ${item.title}`;
|
render += `- ${item.title}`;
|
||||||
|
|
||||||
return render;
|
return render;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
}).mount("#collection");
|
}).mount("#collection");
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
if (typeof email !== "undefined" && typeof username !== "undefined") {
|
if (typeof email !== "undefined" && typeof username !== "undefined") {
|
||||||
Vue.createApp({
|
Vue.createApp({
|
||||||
data() {
|
data() {
|
||||||
|
|
|
@ -4,6 +4,8 @@ if (typeof item !== "undefined") {
|
||||||
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,
|
||||||
|
@ -12,6 +14,11 @@ 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() {
|
||||||
|
@ -23,6 +30,26 @@ if (typeof item !== "undefined") {
|
||||||
destroyed() {
|
destroyed() {
|
||||||
window.removeEventListener("keydown", this.changeImage);
|
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("{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() {
|
||||||
this.identifiers = [];
|
this.identifiers = [];
|
||||||
|
@ -189,6 +216,33 @@ if (typeof item !== "undefined") {
|
||||||
goToArtist() {
|
goToArtist() {
|
||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
|
shareAlbum() {
|
||||||
|
if (this.shareSubmiting) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.shareSubmiting = true;
|
||||||
|
axios
|
||||||
|
.post(`/api/v1/albums/${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");
|
||||||
}
|
}
|
||||||
|
|
106
javascripts/mon-compte/ma-collection/importer.js
Normal file
106
javascripts/mon-compte/ma-collection/importer.js
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
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/albums?discogsId=${release_id}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.status === 204) {
|
||||||
|
await axios.post("/api/v1/albums", {
|
||||||
|
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");
|
18175
package-lock.json
generated
Normal file
18175
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -24,7 +24,7 @@
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Damien Broqua",
|
"name": "Damien Broqua",
|
||||||
"email": "contact@darkou.fr",
|
"email": "contact@darkou.fr",
|
||||||
"url": "https://www.darkou.fr"
|
"url": "https://www.darkou.link"
|
||||||
},
|
},
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -39,10 +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",
|
||||||
"connect-ensure-login": "^0.1.1",
|
"connect-ensure-login": "^0.1.1",
|
||||||
"connect-flash": "^0.1.1",
|
"connect-flash": "^0.1.1",
|
||||||
|
|
|
@ -7,10 +7,18 @@
|
||||||
.list {
|
.list {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
|
|
||||||
.item{
|
.item {
|
||||||
img {
|
img {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.in-collection {
|
||||||
|
opacity: 0.6;
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -37,6 +37,9 @@ $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%)};
|
||||||
|
@ -58,6 +61,8 @@ $pagination-hover-color: rgb(115, 151, 186);
|
||||||
|
|
||||||
--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};
|
||||||
|
@ -99,5 +104,7 @@ $pagination-hover-color: rgb(115, 151, 186);
|
||||||
|
|
||||||
--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');
|
||||||
}
|
}
|
|
@ -8,6 +8,10 @@
|
||||||
width: calc(100% - 6rem);
|
width: calc(100% - 6rem);
|
||||||
margin: 2rem auto;
|
margin: 2rem auto;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
&.info {
|
&.info {
|
||||||
background-color: $warning-color;
|
background-color: $warning-color;
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,12 @@
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: var(--input-color);
|
background-color: var(--input-color);
|
||||||
border: 1px solid transparent !important;
|
border: 1px solid var(--input-active-color) !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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,4 @@
|
||||||
// @use '../node_modules/knacss/sass/knacss.scss';
|
@import '../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';
|
||||||
|
|
|
@ -45,33 +45,44 @@
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
button.close {
|
button.close {
|
||||||
height: 36px;
|
height: 42px;
|
||||||
max-height: 36px;
|
max-height: 42px;
|
||||||
max-width: 36px;
|
max-width: 42px;
|
||||||
min-height: 36px;
|
min-height: 42px;
|
||||||
min-width: 36px;
|
min-width: 42px;
|
||||||
width: 36px;
|
width: 42px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background-color: rgba(10,10,10,.6);
|
background-color: var(--close-background);
|
||||||
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;
|
||||||
z-index: 10;
|
|
||||||
|
|
||||||
&.previous {
|
|
||||||
left: 12px;
|
|
||||||
}
|
|
||||||
&.next {
|
|
||||||
right: 12px;
|
|
||||||
}
|
|
||||||
i {
|
i {
|
||||||
font-size: 2rem;
|
font-size: 1rem;
|
||||||
color: $nord4;
|
color: $nord4;
|
||||||
|
|
||||||
|
@include respond-to("small-up") {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 40;
|
z-index: 2;
|
||||||
|
|
||||||
&.is-visible {
|
&.is-visible {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -84,6 +84,11 @@
|
||||||
width: 1200;
|
width: 1200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.for-image {
|
||||||
|
display: initial;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
header,
|
header,
|
||||||
footer {
|
footer {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -116,10 +121,25 @@
|
||||||
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,21 +1,25 @@
|
||||||
.navbar {
|
.navbar {
|
||||||
min-height: 3.25rem;
|
min-height: 3.5rem;
|
||||||
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: 30;
|
z-index: 1;
|
||||||
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;
|
||||||
|
@ -127,7 +131,6 @@
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
z-index: 20;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -278,7 +281,6 @@
|
||||||
min-width: 100%;
|
min-width: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
z-index: 20;
|
|
||||||
|
|
||||||
.navbar-item {
|
.navbar-item {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
min-width: 250px;
|
min-width: 250px;
|
||||||
max-width: 360px;
|
max-width: 360px;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 66;
|
z-index: 10;
|
||||||
right: 30px;
|
right: 30px;
|
||||||
top: 30px;
|
top: 30px;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
|
|
|
@ -37,7 +37,7 @@ mongoose
|
||||||
|
|
||||||
const sess = {
|
const sess = {
|
||||||
cookie: {
|
cookie: {
|
||||||
maxAge: 86400000,
|
maxAge: 604800000, // INFO: 7 jours
|
||||||
},
|
},
|
||||||
secret,
|
secret,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
|
|
|
@ -33,6 +33,11 @@ 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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import AWS from "aws-sdk";
|
import { S3Client } from "@aws-sdk/client-s3";
|
||||||
|
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";
|
||||||
|
@ -10,13 +11,9 @@ 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
|
||||||
|
@ -27,23 +24,28 @@ AWS.config.update({
|
||||||
*/
|
*/
|
||||||
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 s3 = new AWS.S3({
|
const multipartUpload = new Upload({
|
||||||
endpoint: s3Endpoint,
|
client: new S3Client({
|
||||||
signatureVersion: s3Signature,
|
region: "fr-par",
|
||||||
});
|
endpoint: `https://${s3Endpoint}`,
|
||||||
|
credentials: {
|
||||||
await s3
|
accessKeyId: awsAccessKeyId,
|
||||||
.putObject({
|
secretAccessKey: awsSecretAccessKey,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
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);
|
||||||
|
@ -62,11 +64,15 @@ 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, { responseType: "arraybuffer" });
|
const { data } = await axios.get(url, {
|
||||||
|
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;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,9 +25,21 @@ 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 = {
|
||||||
...body,
|
...albumDetails,
|
||||||
discogsId: body.id,
|
discogsId: albumDetails.id,
|
||||||
User: user._id,
|
User: user._id,
|
||||||
};
|
};
|
||||||
data.released = data.released
|
data.released = data.released
|
||||||
|
@ -43,6 +55,9 @@ class Albums extends Pages {
|
||||||
model: "Albums",
|
model: "Albums",
|
||||||
id: album._id,
|
id: album._id,
|
||||||
};
|
};
|
||||||
|
const job = new JobsModel(jobData);
|
||||||
|
|
||||||
|
job.save();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const User = await UsersModel.findOne({ _id: user._id });
|
const User = await UsersModel.findOne({ _id: user._id });
|
||||||
|
@ -51,7 +66,7 @@ class Albums extends Pages {
|
||||||
|
|
||||||
const { publish, token, url, message } = mastodonConfig;
|
const { publish, token, url, message } = mastodonConfig;
|
||||||
|
|
||||||
if (publish && url && token) {
|
if (share && publish && url && token) {
|
||||||
const M = new Mastodon({
|
const M = new Mastodon({
|
||||||
access_token: token,
|
access_token: token,
|
||||||
api_url: url,
|
api_url: url,
|
||||||
|
@ -59,7 +74,7 @@ class Albums extends Pages {
|
||||||
|
|
||||||
const video =
|
const video =
|
||||||
data.videos && data.videos.length > 0
|
data.videos && data.videos.length > 0
|
||||||
? data.videso[0].uri
|
? data.videos[0].uri
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
const status = `${(
|
const status = `${(
|
||||||
|
@ -89,6 +104,10 @@ Publié automatiquement via #musictopus`;
|
||||||
const { data: buff } = await axios.get(
|
const { data: buff } = await axios.get(
|
||||||
data.images[i].uri,
|
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",
|
responseType: "arraybuffer",
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -110,10 +129,6 @@ Publié automatiquement via #musictopus`;
|
||||||
|
|
||||||
await M.post("statuses", { status, media_ids });
|
await M.post("statuses", { status, media_ids });
|
||||||
}
|
}
|
||||||
|
|
||||||
const job = new JobsModel(jobData);
|
|
||||||
|
|
||||||
job.save();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new ErrorEvent(
|
throw new ErrorEvent(
|
||||||
500,
|
500,
|
||||||
|
@ -164,6 +179,8 @@ Publié automatiquement via #musictopus`;
|
||||||
genre,
|
genre,
|
||||||
style,
|
style,
|
||||||
userId: collectionUserId,
|
userId: collectionUserId,
|
||||||
|
discogsIds,
|
||||||
|
discogsId,
|
||||||
} = this.req.query;
|
} = this.req.query;
|
||||||
|
|
||||||
let userId = this.req.user?._id;
|
let userId = this.req.user?._id;
|
||||||
|
@ -213,6 +230,13 @@ 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,
|
||||||
|
@ -261,6 +285,27 @@ Publié automatiquement via #musictopus`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
*
|
*
|
||||||
|
@ -285,7 +330,9 @@ Publié automatiquement via #musictopus`;
|
||||||
|
|
||||||
const values = await getAlbumDetails(album.discogsId);
|
const values = await getAlbumDetails(album.discogsId);
|
||||||
|
|
||||||
return AlbumsModel.findOneAndUpdate(query, values, { new: true });
|
await AlbumsModel.findOneAndUpdate(query, values, { new: true });
|
||||||
|
|
||||||
|
return this.getOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -294,7 +341,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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -309,6 +356,83 @@ 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"
|
||||||
*/
|
*/
|
||||||
|
@ -343,19 +467,7 @@ 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 { itemId: _id } = this.req.params;
|
const item = await this.getOne();
|
||||||
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(
|
||||||
|
@ -363,6 +475,31 @@ 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 de créer la page "collection/:userId"
|
* Méthode permettant de créer la page "collection/:userId"
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -37,12 +37,18 @@ class Me extends Pages {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user.mastodon = value.mastodon;
|
if (value.mastodon !== undefined) {
|
||||||
|
user.mastodon = value.mastodon;
|
||||||
|
}
|
||||||
|
|
||||||
if (value.password) {
|
if (value.password) {
|
||||||
user.salt = value.password;
|
user.salt = value.password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.isPublicCollection !== undefined) {
|
||||||
|
user.isPublicCollection = value.isPublicCollection;
|
||||||
|
}
|
||||||
|
|
||||||
user.save();
|
user.save();
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
await new Promise((resolve, reject) => {
|
||||||
|
|
|
@ -68,4 +68,17 @@ 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;
|
||||||
|
|
|
@ -3,6 +3,7 @@ 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();
|
||||||
|
@ -16,6 +17,30 @@ 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);
|
||||||
|
|
|
@ -24,6 +24,20 @@ 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
|
router
|
||||||
.route("/exporter")
|
.route("/exporter")
|
||||||
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||||
|
@ -32,6 +46,19 @@ 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);
|
||||||
|
|
133
views/components/album.ejs
Normal file
133
views/components/album.ejs
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
<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}} : {{extra.name}}</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>
|
158
views/index.ejs
158
views/index.ejs
|
@ -16,9 +16,6 @@
|
||||||
|
|
||||||
<link href="/css/main.css" rel="stylesheet" />
|
<link href="/css/main.css" rel="stylesheet" />
|
||||||
|
|
||||||
<script defer src="/js/libs.js"></script>
|
|
||||||
<script defer src="/js/main.js"></script>
|
|
||||||
|
|
||||||
<% if ( config.matomoUrl ) { %>
|
<% if ( config.matomoUrl ) { %>
|
||||||
<!-- Matomo -->
|
<!-- Matomo -->
|
||||||
<script>
|
<script>
|
||||||
|
@ -38,86 +35,94 @@
|
||||||
<% } %>
|
<% } %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<nav class="navbar" aria-label="Navigation principale">
|
<nav class="navbar">
|
||||||
<div class="navbar-brand">
|
<nav class="navbar container" aria-label="Navigation principale">
|
||||||
<a class="navbar-item" href="/">
|
<div class="navbar-brand">
|
||||||
<img src="/img/logo.png" alt="Logo MusicTopus">
|
<a class="navbar-item" href="/">
|
||||||
<span>MusicTopus</span>
|
<img src="/img/logo.png" alt="Logo MusicTopus">
|
||||||
</a>
|
<span>MusicTopus</span>
|
||||||
|
|
||||||
<a role="button" class="navbar-burger" aria-label="Afficher le menu" aria-expanded="false" data-target="navbar">
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
<span aria-hidden="true"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="navbar" class="navbar-menu">
|
|
||||||
<% if ( user ) { %>
|
|
||||||
<div class="navbar-start">
|
|
||||||
<div class="navbar-item">
|
|
||||||
<div class="buttons">
|
|
||||||
<a class="button is-primary" href="/ajouter-un-album">
|
|
||||||
<i class="icon-plus"></i>
|
|
||||||
<span>
|
|
||||||
Ajouter un album
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<% } %>
|
|
||||||
|
|
||||||
<div class="navbar-end">
|
|
||||||
<a class="navbar-item" href="/nous-contacter">
|
|
||||||
Nous contacter
|
|
||||||
</a>
|
</a>
|
||||||
<% if ( user ) { %>
|
|
||||||
<div class="navbar-item has-dropdown">
|
|
||||||
<a class="navbar-link">
|
|
||||||
<i class="icon-user"></i>
|
|
||||||
<span>
|
|
||||||
<%= user.username %>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="navbar-dropdown">
|
<a role="button" class="navbar-burger" aria-label="Afficher le menu" aria-expanded="false" data-target="navbar">
|
||||||
<a class="navbar-item" href="/mon-compte">
|
<span aria-hidden="true"></span>
|
||||||
Mon compte
|
<span aria-hidden="true"></span>
|
||||||
</a>
|
<span aria-hidden="true"></span>
|
||||||
<hr />
|
</a>
|
||||||
<a class="navbar-item" href="/ma-collection">
|
</div>
|
||||||
Ma collection
|
|
||||||
</a>
|
<div id="navbar" class="navbar-menu">
|
||||||
<a class="navbar-item" href="/ma-collection/exporter">
|
<% if ( user ) { %>
|
||||||
Exporter ma collection
|
<div class="navbar-start">
|
||||||
</a>
|
<div class="navbar-item">
|
||||||
|
<div class="buttons">
|
||||||
|
<a class="button is-primary" href="/ajouter-un-album">
|
||||||
|
<i class="icon-plus"></i>
|
||||||
|
<span>
|
||||||
|
Ajouter un album
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
<div class="navbar-item apparence">
|
|
||||||
<div class="theme-switch-wrapper">
|
<div class="navbar-end">
|
||||||
<label class="theme-switch" for="checkbox" aria-label="Passer du thème clair au thème sombre et inversement">
|
<a class="navbar-item" href="/nous-contacter">
|
||||||
<input type="checkbox" id="checkbox" />
|
Nous contacter
|
||||||
<div class="slider round"></div>
|
</a>
|
||||||
</label>
|
<% if ( user ) { %>
|
||||||
</div>
|
<div class="navbar-item has-dropdown">
|
||||||
</div>
|
<a class="navbar-link">
|
||||||
<div class="navbar-item">
|
<i class="icon-user"></i>
|
||||||
<div class="buttons">
|
<span>
|
||||||
<% if ( !user ) { %>
|
<%= user.username %>
|
||||||
<a class="button is-primary" href="/connexion">
|
</span>
|
||||||
<strong>Connexion</strong>
|
|
||||||
</a>
|
</a>
|
||||||
<% } else { %>
|
|
||||||
<a class="button is-danger" href="/se-deconnecter">
|
<div class="navbar-dropdown">
|
||||||
Déconnexion
|
<a class="navbar-item" href="/mon-compte">
|
||||||
</a>
|
Mon compte
|
||||||
<% } %>
|
</a>
|
||||||
|
<hr />
|
||||||
|
<a class="navbar-item" href="/ma-collection">
|
||||||
|
Ma collection
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/ma-collection/on-air">
|
||||||
|
On air
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/ma-collection/exporter">
|
||||||
|
Exporter ma collection
|
||||||
|
</a>
|
||||||
|
<a class="navbar-item" href="/ma-collection/importer">
|
||||||
|
Importer une collection
|
||||||
|
</a>
|
||||||
|
<hr />
|
||||||
|
<a class="navbar-item is-danger" href="/se-deconnecter">
|
||||||
|
Déconnexion
|
||||||
|
</a>
|
||||||
|
</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>
|
||||||
|
<% if ( !user ) { %>
|
||||||
|
<div class="navbar-item">
|
||||||
|
<div class="buttons">
|
||||||
|
<a class="button is-primary" href="/connexion">
|
||||||
|
<strong>Connexion</strong>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div id="toastr">
|
<div id="toastr">
|
||||||
|
@ -174,7 +179,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.fr" target="_blank" rel="noopener noreferrer">Damien Broqua <i class="icon-link"></i></a>.
|
<strong title="Merci Brunus ! 😜">MusicTopus</strong> par <a href="https://www.darkou.link" target="_blank" rel="noopener noreferrer">Damien Broqua <i class="icon-link"></i></a>.
|
||||||
Logo réalisé par Brunus avec <a href="https://inkscape.org/fr/" target="_blank" rel="noopener noreferrer">Inkscape <i class="icon-link"></i></a>.
|
Logo réalisé par Brunus avec <a href="https://inkscape.org/fr/" target="_blank" rel="noopener noreferrer">Inkscape <i class="icon-link"></i></a>.
|
||||||
<br />
|
<br />
|
||||||
Le code source est sous licence <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html" target="_blank" rel="noopener noreferrer">GNU GPL-3.0-or-later <i class="icon-link"></i></a> et disponible sur <a href="https://git.darkou.fr/dbroqua/MusicTopus" target="_blank">git.darkou.fr <i class="icon-link"></i></a>.
|
Le code source est sous licence <a href="https://www.gnu.org/licenses/gpl-3.0-standalone.html" target="_blank" rel="noopener noreferrer">GNU GPL-3.0-or-later <i class="icon-link"></i></a> et disponible sur <a href="https://git.darkou.fr/dbroqua/MusicTopus" target="_blank">git.darkou.fr <i class="icon-link"></i></a>.
|
||||||
|
@ -182,5 +187,8 @@
|
||||||
Fait avec ❤️ à Bordeaux.
|
Fait avec ❤️ à Bordeaux.
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<script defer src="/js/libs.js"></script>
|
||||||
|
<script defer src="/js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -34,8 +34,9 @@
|
||||||
|
|
||||||
|
|
||||||
<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 class="item" v-if="!loading" v-for="item in items">
|
<div v-if="!loading" v-for="item in items" class="item" :class="{'in-collection': item.inCollection}">
|
||||||
<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)"/>
|
||||||
|
@ -78,7 +79,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-2 gap-16">
|
<div class="grid grid-cols-1 md:grid-cols-3 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}`" />
|
||||||
|
@ -86,56 +87,57 @@
|
||||||
<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>
|
||||||
<ol class="ml-4">
|
<ul class="is-unstyled">
|
||||||
<li v-for="track in details.tracklist">
|
<li v-for="track in details.tracklist" :class="{'ml-4': track.type_ === 'track'}">
|
||||||
{{ track.title }} ({{track.duration}})
|
<strong v-if="track.type_ === 'heading'">
|
||||||
<ul v-if="track.artists && track.artists.length > 0" class="sm-hidden">
|
{{track.title}}
|
||||||
<li v-for="extra in track.artists" class=" ml-4">
|
</strong>
|
||||||
<small>{{extra.role}} : {{extra.name}}</small>
|
<template v-else>
|
||||||
</li>
|
{{ track.position }}
|
||||||
</ul>
|
{{ 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>
|
</li>
|
||||||
</ol>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="md:col-span-2">
|
||||||
<div class="grid grid-cols-2 gap-10">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||||
<div>
|
<div class="grid grid-cols-1">
|
||||||
<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>
|
<div class="grid grid-cols-1">
|
||||||
<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-3 gap-10">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-10">
|
||||||
<div>
|
<div class="grid grid-cols-2 md:grid-cols-1">
|
||||||
<strong>Pays</strong>
|
<strong>Pays</strong>
|
||||||
<br />
|
|
||||||
<span>{{details.country}}</span>
|
<span>{{details.country}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="grid grid-cols-2 md:grid-cols-1">
|
||||||
<strong>Année</strong>
|
<strong>Année</strong>
|
||||||
<br />
|
|
||||||
<span>{{details.year}}</span>
|
<span>{{details.year}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="grid grid-cols-2 md:grid-cols-1">
|
||||||
<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-2 gap-10">
|
<div class="grid grid-cols-1 gap-10">
|
||||||
<div>
|
<div>
|
||||||
<strong>Format</strong>
|
<strong>Format<template v-if="details?.formats?.length > 1">s</template></strong>
|
||||||
<ul class="ml-4">
|
<ul class="ml-4">
|
||||||
<li v-for="(format) in details.formats">
|
<li v-for="(format) in details.formats">
|
||||||
{{format.name}}
|
{{format.name}}
|
||||||
|
@ -152,25 +154,26 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="grid grid-cols-2 gap-10">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||||
<div>
|
<div>
|
||||||
<strong>Codes barres</strong>
|
<strong>Code<template v-if="details?.identifiers?.length > 1">s</template> barre<template v-if="details?.identifiers?.length > 1">s</template></strong>
|
||||||
<ol>
|
<ol class="ml-4">
|
||||||
<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</strong>
|
<strong>Label<template v-if="details?.labels?.length > 1">s</template></strong>
|
||||||
<ol>
|
<ol class="ml-4">
|
||||||
<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é</strong>
|
<strong>Société<template v-if="details?.companies?.length > 1">s</template></strong>
|
||||||
<ol>
|
<ol class="ml-4">
|
||||||
<li v-for="company in details.companie">
|
<li v-for="company in details.companies">
|
||||||
|
<strong>{{company.entity_type_name}}</strong>
|
||||||
{{company.name}}
|
{{company.name}}
|
||||||
</li>
|
</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
@ -180,9 +183,21 @@
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<footer>
|
<footer>
|
||||||
|
<% if ( user.mastodon && user.mastodon.publish ) { %>
|
||||||
|
<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 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 %>;
|
||||||
|
</script>
|
|
@ -105,12 +105,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="mastodon.message">Message</label>
|
<label for="mastodon.message">Message</label>
|
||||||
<input
|
<textarea
|
||||||
type="text"
|
|
||||||
name="mastodon.message"
|
name="mastodon.message"
|
||||||
id="mastodon.message"
|
id="mastodon.message"
|
||||||
v-model="formData.mastodon.message"
|
v-model="formData.mastodon.message"
|
||||||
/>
|
></textarea>
|
||||||
<small>
|
<small>
|
||||||
Variables possibles :
|
Variables possibles :
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -140,5 +139,5 @@
|
||||||
<script>
|
<script>
|
||||||
const email = '<%= user.email %>';
|
const email = '<%= user.email %>';
|
||||||
const username = '<%= user.username %>';
|
const username = '<%= user.username %>';
|
||||||
const mastodon = <%- JSON.stringify(user.mastodon) %>;
|
const mastodon = <%- JSON.stringify(user.mastodon || {publish: false, url: '', token: '', message: ''}) %>;
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
- {{item.title}}
|
- {{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">
|
||||||
|
@ -20,152 +21,22 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div class="grid md:grid-cols-3 gap-16">
|
<%- include('../../../components/album.ejs') %>
|
||||||
<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}} : {{extra.name}}</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</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>
|
||||||
<button type="button" aria-label="Image précédente" class="navigation previous" @click="previous" v-if="index > 0">
|
<div class="carousel">
|
||||||
<i class="icon-left-open"></i>
|
<button type="button" aria-label="Image précédente" class="navigation previous" @click="previous">
|
||||||
</button>
|
<i class="icon-left-open"></i>
|
||||||
<button type="button" aria-label="Image suivante" class="navigation next" @click="next" v-if="index + 1 < item.images.length">
|
</button>
|
||||||
<i class="icon-right-open"></i>
|
<div class="text-center">
|
||||||
</button>
|
<img :src="preview" />
|
||||||
<div class="modal-card">
|
</div>
|
||||||
<img :src="preview" />
|
<button type="button" aria-label="Image suivante" class="navigation next" @click="next">
|
||||||
|
<i class="icon-right-open"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -182,8 +53,46 @@
|
||||||
</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>
|
||||||
|
</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 canShareItem = <%= user.mastodon?.publish || false %>;
|
||||||
</script>
|
</script>
|
43
views/pages/mon-compte/ma-collection/importer.ejs
Normal file
43
views/pages/mon-compte/ma-collection/importer.ejs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
<main class="layout-maxed" id="importer">
|
||||||
|
<h1>Importer une collection</h1>
|
||||||
|
<p>
|
||||||
|
Il est actuellement possible d'importer une collection 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>
|
Loading…
Reference in a new issue