Compare commits
No commits in common. "5a7d9d707fa5742d8564f1b2cd72296e5af572e2" and "7b525d3e432e5be2582deee0a0d79d5b433a1129" have entirely different histories.
5a7d9d707f
...
7b525d3e43
24 changed files with 335 additions and 18814 deletions
|
@ -22,13 +22,7 @@ module.exports = {
|
|||
camelcase: [
|
||||
"error",
|
||||
{
|
||||
allow: [
|
||||
"artists_sort",
|
||||
"access_token",
|
||||
"api_url",
|
||||
"media_ids",
|
||||
"release_id",
|
||||
],
|
||||
allow: ["artists_sort", "access_token", "api_url", "media_ids"],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
Vue.createApp({
|
||||
data() {
|
||||
return {
|
||||
// eslint-disable-next-line no-undef
|
||||
share: canPublish,
|
||||
q: "",
|
||||
year: "",
|
||||
country: "",
|
||||
|
@ -171,10 +169,7 @@ Vue.createApp({
|
|||
this.submitting = true;
|
||||
|
||||
return axios
|
||||
.post("/api/v1/albums", {
|
||||
album: this.details,
|
||||
share: this.share,
|
||||
})
|
||||
.post("/api/v1/albums", this.details)
|
||||
.then(() => {
|
||||
window.location.href = "/ma-collection";
|
||||
})
|
||||
|
|
|
@ -25,10 +25,6 @@ Vue.createApp({
|
|||
// eslint-disable-next-line no-undef
|
||||
isPublicCollection,
|
||||
// eslint-disable-next-line no-undef
|
||||
userId,
|
||||
// eslint-disable-next-line no-undef
|
||||
vueType,
|
||||
// eslint-disable-next-line no-undef
|
||||
query,
|
||||
};
|
||||
},
|
||||
|
@ -85,10 +81,6 @@ Vue.createApp({
|
|||
if (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
|
||||
.get(url)
|
||||
|
|
|
@ -1,106 +0,0 @@
|
|||
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
18175
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -24,7 +24,7 @@
|
|||
"author": {
|
||||
"name": "Damien Broqua",
|
||||
"email": "contact@darkou.fr",
|
||||
"url": "https://www.darkou.link"
|
||||
"url": "https://www.darkou.fr"
|
||||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
|
@ -39,11 +39,10 @@
|
|||
"prettier": "^2.5.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.490.0",
|
||||
"@aws-sdk/lib-storage": "^3.490.0",
|
||||
"@babel/cli": "^7.17.0",
|
||||
"@babel/core": "^7.17.2",
|
||||
"@babel/preset-env": "^7.16.11",
|
||||
"aws-sdk": "^2.1110.0",
|
||||
"axios": "^0.26.0",
|
||||
"connect-ensure-login": "^0.1.1",
|
||||
"connect-flash": "^0.1.1",
|
||||
|
|
|
@ -37,9 +37,6 @@ $button-alternate-color: #01103C;
|
|||
$pagination-border-color: $nord3;
|
||||
$pagination-hover-color: rgb(115, 151, 186);
|
||||
|
||||
$close-background: rgba(10,10,10,.6);
|
||||
$close-background-dark: rgba(240,240,240,.6);
|
||||
|
||||
:root {
|
||||
--default-color: #{$white};
|
||||
--bg-color: #{darken($white, 5%)};
|
||||
|
@ -61,8 +58,6 @@ $close-background-dark: rgba(240,240,240,.6);
|
|||
|
||||
--button-link-text-color: #2C364A;
|
||||
|
||||
--close-background: #{$close-background};
|
||||
|
||||
--loader-img: url('/img/loading-light.gif');
|
||||
|
||||
--nord0: #{$nord0};
|
||||
|
@ -104,7 +99,5 @@ $close-background-dark: rgba(240,240,240,.6);
|
|||
|
||||
--button-link-text-color: #{$white};
|
||||
|
||||
--close-background: #{$nord3};
|
||||
|
||||
--loader-img: url('/img/loading-dark.gif');
|
||||
}
|
|
@ -8,10 +8,6 @@
|
|||
width: calc(100% - 6rem);
|
||||
margin: 2rem auto;
|
||||
|
||||
.header {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
&.info {
|
||||
background-color: $warning-color;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
@import './fonts';
|
||||
|
|
|
@ -45,44 +45,33 @@
|
|||
|
||||
.modal {
|
||||
button.close {
|
||||
height: 42px;
|
||||
max-height: 42px;
|
||||
max-width: 42px;
|
||||
min-height: 42px;
|
||||
min-width: 42px;
|
||||
width: 42px;
|
||||
height: 36px;
|
||||
max-height: 36px;
|
||||
max-width: 36px;
|
||||
min-height: 36px;
|
||||
min-width: 36px;
|
||||
width: 36px;
|
||||
position: absolute;
|
||||
background-color: var(--close-background);
|
||||
background-color: rgba(10,10,10,.6);
|
||||
right: 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 {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
cursor: pointer;
|
||||
i {
|
||||
font-size: 1rem;
|
||||
color: $nord4;
|
||||
z-index: 10;
|
||||
|
||||
@include respond-to("small-up") {
|
||||
font-size: 2rem;
|
||||
&.previous {
|
||||
left: 12px;
|
||||
}
|
||||
&.next {
|
||||
right: 12px;
|
||||
}
|
||||
i {
|
||||
font-size: 2rem;
|
||||
color: $nord4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
justify-content: center;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
z-index: 2;
|
||||
z-index: 40;
|
||||
|
||||
&.is-visible {
|
||||
display: flex;
|
||||
|
@ -84,11 +84,6 @@
|
|||
width: 1200;
|
||||
}
|
||||
|
||||
&.for-image {
|
||||
display: initial;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
header,
|
||||
footer {
|
||||
align-items: center;
|
||||
|
@ -121,25 +116,10 @@
|
|||
border-bottom-left-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
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) {
|
||||
margin-right: .5em;
|
||||
}
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 80vh;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +1,21 @@
|
|||
.navbar {
|
||||
min-height: 3.5rem;
|
||||
min-height: 3.25rem;
|
||||
background-color: var(--navbar-color);
|
||||
box-shadow: rgba(216, 222, 233, 0.15) 0px 5px 10px 0px;
|
||||
color: rgba(0,0,0,.7);
|
||||
position: fixed;
|
||||
z-index: 1;
|
||||
z-index: 30;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
@include transition() {}
|
||||
|
||||
@include respond-to("medium-up") {
|
||||
min-height: 3.25rem;
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&.container {
|
||||
max-width: 1330px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
|
@ -131,6 +127,7 @@
|
|||
min-width: 100%;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
@ -281,6 +278,7 @@
|
|||
min-width: 100%;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
z-index: 20;
|
||||
|
||||
.navbar-item {
|
||||
white-space: nowrap;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
min-width: 250px;
|
||||
max-width: 360px;
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
z-index: 66;
|
||||
right: 30px;
|
||||
top: 30px;
|
||||
font-size: 17px;
|
||||
|
|
|
@ -37,7 +37,7 @@ mongoose
|
|||
|
||||
const sess = {
|
||||
cookie: {
|
||||
maxAge: 604800000, // INFO: 7 jours
|
||||
maxAge: 86400000,
|
||||
},
|
||||
secret,
|
||||
saveUninitialized: false,
|
||||
|
|
|
@ -33,11 +33,6 @@ export const getAlbumDetails = async (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;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { S3Client } from "@aws-sdk/client-s3";
|
||||
import { Upload } from "@aws-sdk/lib-storage";
|
||||
import AWS from "aws-sdk";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import axios from "axios";
|
||||
|
@ -11,9 +10,13 @@ import {
|
|||
s3BaseFolder,
|
||||
s3Endpoint,
|
||||
s3Bucket,
|
||||
// s3Signature,
|
||||
s3Signature,
|
||||
} from "../config";
|
||||
|
||||
AWS.config.update({
|
||||
accessKeyId: awsAccessKeyId,
|
||||
secretAccessKey: awsSecretAccessKey,
|
||||
});
|
||||
/**
|
||||
* Fonction permettant de stocker un fichier local sur S3
|
||||
* @param {String} filename
|
||||
|
@ -24,28 +27,23 @@ import {
|
|||
*/
|
||||
export const uploadFromFile = async (filename, file, deleteFile = false) => {
|
||||
const data = await fs.readFileSync(file);
|
||||
|
||||
const base64data = Buffer.from(data, "binary");
|
||||
const dest = path.join(s3BaseFolder, filename);
|
||||
|
||||
const multipartUpload = new Upload({
|
||||
client: new S3Client({
|
||||
region: "fr-par",
|
||||
endpoint: `https://${s3Endpoint}`,
|
||||
credentials: {
|
||||
accessKeyId: awsAccessKeyId,
|
||||
secretAccessKey: awsSecretAccessKey,
|
||||
},
|
||||
}),
|
||||
params: {
|
||||
const s3 = new AWS.S3({
|
||||
endpoint: s3Endpoint,
|
||||
signatureVersion: s3Signature,
|
||||
});
|
||||
|
||||
await s3
|
||||
.putObject({
|
||||
Bucket: s3Bucket,
|
||||
Key: dest,
|
||||
Body: base64data,
|
||||
ACL: "public-read",
|
||||
endpoint: s3Endpoint,
|
||||
},
|
||||
});
|
||||
|
||||
await multipartUpload.done();
|
||||
})
|
||||
.promise();
|
||||
|
||||
if (deleteFile) {
|
||||
fs.unlinkSync(file);
|
||||
|
|
|
@ -25,21 +25,9 @@ class Albums extends Pages {
|
|||
*/
|
||||
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,
|
||||
...body,
|
||||
discogsId: body.id,
|
||||
User: user._id,
|
||||
};
|
||||
data.released = data.released
|
||||
|
@ -66,7 +54,7 @@ class Albums extends Pages {
|
|||
|
||||
const { publish, token, url, message } = mastodonConfig;
|
||||
|
||||
if (share && publish && url && token) {
|
||||
if (publish && url && token) {
|
||||
const M = new Mastodon({
|
||||
access_token: token,
|
||||
api_url: url,
|
||||
|
@ -180,7 +168,6 @@ Publié automatiquement via #musictopus`;
|
|||
style,
|
||||
userId: collectionUserId,
|
||||
discogsIds,
|
||||
discogsId,
|
||||
} = this.req.query;
|
||||
|
||||
let userId = this.req.user?._id;
|
||||
|
@ -233,9 +220,6 @@ Publié automatiquement via #musictopus`;
|
|||
if (discogsIds) {
|
||||
where.discogsId = { $in: discogsIds };
|
||||
}
|
||||
if (discogsId) {
|
||||
where.discogsId = Number(discogsId);
|
||||
}
|
||||
|
||||
const count = await AlbumsModel.count({
|
||||
User: userId,
|
||||
|
@ -285,27 +269,6 @@ 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
|
||||
*
|
||||
|
@ -330,9 +293,7 @@ Publié automatiquement via #musictopus`;
|
|||
|
||||
const values = await getAlbumDetails(album.discogsId);
|
||||
|
||||
await AlbumsModel.findOneAndUpdate(query, values, { new: true });
|
||||
|
||||
return this.getOne();
|
||||
return AlbumsModel.findOneAndUpdate(query, values, { new: true });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -467,7 +428,19 @@ Publié automatiquement via #musictopus`;
|
|||
* Méthode permettant d'afficher le détails d'un album
|
||||
*/
|
||||
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.setPageTitle(
|
||||
|
@ -475,31 +448,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 de créer la page "collection/:userId"
|
||||
*/
|
||||
|
|
|
@ -43,10 +43,6 @@ class Me extends Pages {
|
|||
user.salt = value.password;
|
||||
}
|
||||
|
||||
if (value.isPublicCollection !== undefined) {
|
||||
user.isPublicCollection = value.isPublicCollection;
|
||||
}
|
||||
|
||||
user.save();
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
|
|
|
@ -24,20 +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("/exporter")
|
||||
.get(ensureLoggedIn("/connexion"), async (req, res, next) => {
|
||||
|
@ -46,19 +32,6 @@ router
|
|||
|
||||
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);
|
||||
|
|
|
@ -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}} : {{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>
|
|
@ -35,8 +35,7 @@
|
|||
<% } %>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar">
|
||||
<nav class="navbar container" aria-label="Navigation principale">
|
||||
<nav class="navbar" aria-label="Navigation principale">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item" href="/">
|
||||
<img src="/img/logo.png" alt="Logo MusicTopus">
|
||||
|
@ -87,19 +86,9 @@
|
|||
<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>
|
||||
<% } %>
|
||||
|
@ -111,18 +100,21 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<% if ( !user ) { %>
|
||||
<div class="navbar-item">
|
||||
<div class="buttons">
|
||||
<% if ( !user ) { %>
|
||||
<a class="button is-primary" href="/connexion">
|
||||
<strong>Connexion</strong>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<a class="button is-danger" href="/se-deconnecter">
|
||||
Déconnexion
|
||||
</a>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div id="toastr">
|
||||
|
@ -179,7 +171,7 @@
|
|||
|
||||
<footer class="footer layout-hero">
|
||||
<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>.
|
||||
<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>.
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
<button aria-label="Fermer" class="close" @click="toggleModal"></button>
|
||||
</header>
|
||||
<section>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-16">
|
||||
<div class="grid grid-cols-2 gap-16">
|
||||
<div>
|
||||
<div class="text-center">
|
||||
<img :src="details.thumb %>" :alt="`Miniature pour l'album ${details.title}`" />
|
||||
|
@ -87,57 +87,56 @@
|
|||
<img v-for="image in details.images" :src="image.uri150" :alt="`Miniature de type ${image.type}`" style="max-width: 60px;" />
|
||||
<hr />
|
||||
</div>
|
||||
<ul class="is-unstyled">
|
||||
<li v-for="track in details.tracklist" :class="{'ml-4': track.type_ === 'track'}">
|
||||
<strong v-if="track.type_ === 'heading'">
|
||||
{{track.title}}
|
||||
</strong>
|
||||
<template v-else>
|
||||
{{ track.position }}
|
||||
{{ track.title }} <span v-if="track.duration">({{track.duration}})</span>
|
||||
<ol class="ml-4">
|
||||
<li v-for="track in details.tracklist">
|
||||
{{ track.title }} ({{track.duration}})
|
||||
<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>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="md:col-span-2">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||
<div class="grid grid-cols-1">
|
||||
<div>
|
||||
<div class="grid grid-cols-2 gap-10">
|
||||
<div>
|
||||
<strong>Genres</strong>
|
||||
<br />
|
||||
<template v-for="(genre, index) in details.genres">
|
||||
{{genre}}<template v-if="index < details.genres.length - 1">, </template>
|
||||
</template>
|
||||
</div>
|
||||
<div class="grid grid-cols-1">
|
||||
<div>
|
||||
<strong>Styles</strong>
|
||||
<br />
|
||||
<span v-for="(style, index) in details.styles">
|
||||
{{style}}<template v-if="index < details.styles.length - 1">, </template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-10">
|
||||
<div class="grid grid-cols-2 md:grid-cols-1">
|
||||
<div class="grid grid-cols-3 gap-10">
|
||||
<div>
|
||||
<strong>Pays</strong>
|
||||
<br />
|
||||
<span>{{details.country}}</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 md:grid-cols-1">
|
||||
<div>
|
||||
<strong>Année</strong>
|
||||
<br />
|
||||
<span>{{details.year}}</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 md:grid-cols-1">
|
||||
<div>
|
||||
<strong>Date de sortie</strong>
|
||||
<br />
|
||||
<span>{{details.released}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="grid grid-cols-1 gap-10">
|
||||
<div class="grid grid-cols-2 gap-10">
|
||||
<div>
|
||||
<strong>Format<template v-if="details?.formats?.length > 1">s</template></strong>
|
||||
<strong>Format</strong>
|
||||
<ul class="ml-4">
|
||||
<li v-for="(format) in details.formats">
|
||||
{{format.name}}
|
||||
|
@ -154,26 +153,25 @@
|
|||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-10">
|
||||
<div class="grid grid-cols-2 gap-10">
|
||||
<div>
|
||||
<strong>Code<template v-if="details?.identifiers?.length > 1">s</template> barre<template v-if="details?.identifiers?.length > 1">s</template></strong>
|
||||
<ol class="ml-4">
|
||||
<strong>Codes barres</strong>
|
||||
<ol>
|
||||
<li v-for="identifier in details.identifiers">
|
||||
{{identifier.value}} ({{identifier.type}})
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Label<template v-if="details?.labels?.length > 1">s</template></strong>
|
||||
<ol class="ml-4">
|
||||
<strong>Label</strong>
|
||||
<ol>
|
||||
<li v-for="label in details.labels">
|
||||
{{label.name}}
|
||||
</li>
|
||||
</ol>
|
||||
<strong>Société<template v-if="details?.companies?.length > 1">s</template></strong>
|
||||
<ol class="ml-4">
|
||||
<li v-for="company in details.companies">
|
||||
<strong>{{company.entity_type_name}}</strong>
|
||||
<strong>Société</strong>
|
||||
<ol>
|
||||
<li v-for="company in details.companie">
|
||||
{{company.name}}
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -183,21 +181,9 @@
|
|||
</div>
|
||||
</section>
|
||||
<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" @click="toggleModal">Annuler</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
const canPublish = <%- (user.mastodon && user.mastodon.publish) || false %>;
|
||||
</script>
|
|
@ -21,22 +21,152 @@
|
|||
</div>
|
||||
</div>
|
||||
<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>
|
||||
<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-background"></div>
|
||||
<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">
|
||||
<button type="button" aria-label="Image précédente" class="navigation previous" @click="previous" v-if="index > 0">
|
||||
<i class="icon-left-open"></i>
|
||||
</button>
|
||||
<div class="text-center">
|
||||
<img :src="preview" />
|
||||
</div>
|
||||
<button type="button" aria-label="Image suivante" class="navigation next" @click="next">
|
||||
<button type="button" aria-label="Image suivante" class="navigation next" @click="next" v-if="index + 1 < item.images.length">
|
||||
<i class="icon-right-open"></i>
|
||||
</button>
|
||||
|
||||
<div class="modal-card">
|
||||
<img :src="preview" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
<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