1.4.4 #84

Merged
dbroqua merged 21 commits from develop into master 2023-03-23 14:34:19 +01:00
20 changed files with 809 additions and 762 deletions
Showing only changes of commit 8f9e902587 - Show all commits

2
.gitignore vendored
View file

@ -121,6 +121,6 @@ dist
dist dist
yarn.lock yarn.lock
public/css public/css
public/css public/js
docker-compose.yml docker-compose.yml
dump dump

46
gulpfile.js Normal file
View file

@ -0,0 +1,46 @@
const { parallel, src, dest } = require("gulp");
const sourcemaps = require("gulp-sourcemaps");
const concat = require("gulp-concat");
const gulp = require("gulp");
const uglify = require("gulp-uglify");
const babel = require("gulp-babel");
const sourceJs = "javascripts/**/*.js";
const sourceRemoteJS = [
"./node_modules/vue/dist/vue.global.prod.js",
"./node_modules/axios/dist/axios.min.js",
];
const destination = "public/js";
// TASKS ----------------------------------------------------------------------
const compileJs = function () {
return gulp
.src(sourceJs)
.pipe(sourcemaps.init())
.pipe(concat("main.js"))
.pipe(
babel({
presets: ["@babel/env"],
})
)
.pipe(uglify())
.pipe(sourcemaps.write("."))
.pipe(gulp.dest(destination));
};
const compileRemoteJs = function () {
return gulp
.src(sourceRemoteJS)
.pipe(sourcemaps.init())
.pipe(concat("libs.js"))
.pipe(sourcemaps.write("."))
.pipe(gulp.dest(destination));
};
// ----------------------------------------------------------------------------
// COMMANDS -------------------------------------------------------------------
exports.default = parallel(compileJs, compileRemoteJs);
// ----------------------------------------------------------------------------

View file

@ -0,0 +1,171 @@
Vue.createApp({
data() {
return {
q: '',
year: '',
country: '',
format: '',
loading: false,
items: [],
details: {},
modalIsVisible: false,
formats: [
'Vinyl',
'Acetate',
'Flexi-disc',
'Lathe Cut',
'Mighty Tiny',
'Shellac',
'Sopic',
'Pathé Disc',
'Edison Disc',
'Cylinder',
'CD',
'CDr',
'CDV',
'DVD',
'DVDr',
'HD DVD',
'HD DVD-R',
'Blu-ray',
'Blu-ray-R',
'Ultra HD Blu-ray',
'SACD',
'4-Track Cartridge',
'8-Track Cartridge',
'Cassette',
'DC-International',
'Elcaset',
'PlayTape',
'RCA Tape Cartridge',
'DAT',
'DCC',
'Microcassette',
'NT Cassette',
'Pocket Rocker',
'Revere Magnetic Stereo Tape Ca',
'Tefifon',
'Reel-To-Reel',
'Sabamobil',
'Betacam',
'Betacam SP',
'Betamax',
'Cartrivision',
'MiniDV',
'Super VHS',
'U-matic',
'VHS',
'Video 2000',
'Video8',
'Film Reel',
'HitClips',
'Laserdisc',
'SelectaVision',
'VHD',
'Wire Recording',
'Minidisc',
'MVD',
'UMD',
'Floppy Disk',
'File',
'Memory Stick',
'Hybrid',
'All Media',
'Box Set',
]
}
},
methods: {
search(event) {
event.preventDefault();
if ( this.loading ) {
return false;
}
this.loading = true;
let url = `/api/v1/search?q=${this.q}`;
if ( this.year ) {
url += `&year=${this.year}`;
}
if ( this.country ) {
url += `&country=${this.country}`;
}
if ( this.format ) {
url += `&format=${this.format}`;
}
axios.get(url)
.then( response => {
const {
results,
} = response.data;
let items = [];
for (let i = 0 ; i < results.length ; i += 1 ) {
const {
id,
title,
thumb,
year,
country,
format,
genre,
style,
} = results[i];
items.push({
id,
title,
thumb,
year,
country,
format,
genre,
style,
});
}
this.items = items;
})
.catch((err) => {
showToastr(err.response?.data?.message || "Aucun résultat trouvé :/");
})
.finally(() => {
this.loading = false;
});
},
toggleModal() {
this.modalIsVisible = !this.modalIsVisible;
},
loadDetails(discogsId) {
axios.get(`/api/v1/search/${discogsId}`)
.then( response => {
const {
data,
} = response;
this.details = data;
this.toggleModal();
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de charger les détails de cet album");
})
.finally(() => {
this.loading = false;
});
},
add() {
axios.post('/api/v1/albums', this.details)
.then(() => {
window.location.href = '/ma-collection';
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…");
});
},
orderedItems(items) {
return items.sort();
}
}
}).mount('#ajouter-album');

133
javascripts/collection.js Normal file
View file

@ -0,0 +1,133 @@
if ( typeof userId !== 'undefined' ) {
Vue.createApp({
data() {
return {
loading: false,
moreFilters: false,
items: [],
total: 0,
page: 1,
totalPages: 1,
limit: 16,
artist: '',
format: '',
year: '',
genre: '',
style: '',
sortOrder: 'artists_sort-asc',
sort: 'artists_sort',
order: 'asc',
userId,
}
},
created() {
this.fetch();
},
methods: {
fetch() {
this.loading = true;
this.total = 0;
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const entries = urlParams.entries();
for(const entry of entries) {
switch(entry[0]) {
case 'artists_sort':
this.artist = entry[1];
break;
default:
this[entry[0]] = entry[1];
}
}
let url = `/api/v1/albums?userId=${this.userId}&page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
if ( this.artist ) {
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
}
if ( this.format ) {
url += `&format=${this.format.replace('&', '%26')}`;
}
if ( this.year ) {
url += `&year=${this.year}`;
}
if ( this.genre ) {
url += `&genre=${this.genre.replace('&', '%26')}`;
}
if ( this.style ) {
url += `&style=${this.style.replace('&', '%26')}`;
}
axios.get(url)
.then( response => {
this.items = response.data.rows;
this.total = response.data.count || 0;
this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0);
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de charger cette collection");
})
.finally(() => {
this.loading = false;
});
},
changeUrl() {
let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
if ( this.artist ) {
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
}
if ( this.format ) {
url += `&format=${this.format.replace('&', '%26')}`;
}
if ( this.year ) {
url += `&year=${this.year}`;
}
if ( this.genre ) {
url += `&genre=${this.genre.replace('&', '%26')}`;
}
if ( this.style ) {
url += `&style=${this.style.replace('&', '%26')}`;
}
location.href = url;
},
next(event) {
event.preventDefault();
this.page += 1;
this.changeUrl();
},
previous(event) {
event.preventDefault();
this.page -= 1;
this.changeUrl();
},
goTo(page) {
this.page = page;
this.changeUrl();
},
changeSort() {
const [sort,order] = this.sortOrder.split('-');
this.sort = sort;
this.order = order;
this.page = 1;
this.changeUrl();
},
changeFilter() {
this.page = 1;
this.changeUrl();
},
showMoreFilters() {
this.moreFilters = !this.moreFilters;
}
}
}).mount('#collection-publique');
}

42
javascripts/conctact.js Normal file
View file

@ -0,0 +1,42 @@
if ( typeof contactMethod !== 'undefined' && contactMethod === 'smtp' ) {
Vue.createApp({
data() {
return {
email: '',
name: '',
message: '',
captcha: '',
loading: false,
}
},
methods: {
send(event) {
event.preventDefault();
if ( this.loading ) {
return false;
}
this.loading = true;
const {
email,
message,
name,
captcha,
} = this;
axios.post('/api/v1/contact', {email, name, message, captcha})
.then( () => {
showToastr("Message correctement envoyé", true);
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible d'envoyer votre message", false);
})
.finally(() => {
this.loading = false;
})
},
},
}).mount('#contact');
}

View file

@ -1,8 +1,13 @@
/** const {
protocol,
host
} = window.location;
/**
* Fonction permettant d'afficher un message dans un toastr * Fonction permettant d'afficher un message dans un toastr
* @param {String} message * @param {String} message
*/ */
function showToastr(message, success = false) { function showToastr(message, success = false) {
let x = document.getElementById("toastr"); let x = document.getElementById("toastr");
if ( message ) { if ( message ) {
x.getElementsByTagName("SPAN")[0].innerHTML = message; x.getElementsByTagName("SPAN")[0].innerHTML = message;

View file

@ -0,0 +1,26 @@
if ( typeof email !== 'undefined' && typeof username !== 'undefined' ) {
Vue.createApp({
data() {
return {
email,
username,
oldPassword: '',
password: '',
passwordConfirm: '',
loading: false,
}
},
methods: {
async updateProfil(event) {
// try {
// if ( this.password !== this.passwordConfirm ) {
// throw "La confirnation du mot de passe ne correspond pas";
// }
// } catch(err) {
// event.preventDefault();
// showToastr(err);
// }
},
}
}).mount('#mon-compte');
}

View file

@ -0,0 +1,158 @@
if ( typeof item !== 'undefined' ) {
Vue.createApp({
data() {
return {
item,
tracklist: [],
identifiers: [],
modalIsVisible: false,
identifiersMode: 'preview',
identifiersPreviewLength: 16,
preview: null,
index: null,
showModalDelete: false,
}
},
created() {
this.setTrackList();
this.setIdentifiers();
window.addEventListener("keydown", this.changeImage);
},
destroyed() {
window.removeEventListener('keydown', this.changeImage);
},
methods: {
setIdentifiers() {
this.identifiers = [];
let max = this.identifiersMode == 'preview' && this.item.identifiers.length > this.identifiersPreviewLength ? this.identifiersPreviewLength : this.item.identifiers.length;
for ( let i = 0 ; i < max ; i += 1 ) {
this.identifiers.push(this.item.identifiers[i]);
}
},
setTrackList() {
let subTrack = {
type: null,
title: null,
tracks: [],
};
for (let i = 0 ; i < this.item.tracklist.length ; i += 1 ) {
const {
type_,
title,
position,
duration,
extraartists,
} = this.item.tracklist[i];
if ( type_ === 'heading' ) {
if ( subTrack.type ) {
this.tracklist.push(subTrack);
subTrack = {
type: null,
title: null,
tracks: [],
};
}
subTrack.type = type_;
subTrack.title = title;
} else {
subTrack.tracks.push({
title,
position,
duration,
extraartists
});
}
}
this.tracklist.push(subTrack);
},
setImage() {
this.preview = this.item.images[this.index].uri;
},
showGallery(event) {
const item = event.target.tagName === 'IMG' ? event.target.parentElement : event.target;
const {
index,
} = item.dataset;
this.index = Number(index);
this.modalIsVisible = true;
this.setImage();
},
toggleModal() {
this.modalIsVisible = !this.modalIsVisible;
},
previous() {
this.index = this.index > 0 ? this.index - 1 : this.item.images.length -1;
this.setImage();
},
next() {
this.index = (this.index +1) === this.item.images.length ? 0 : this.index + 1;
this.setImage();
},
changeImage(event) {
const direction = event.code;
if ( this.modalIsVisible && ['ArrowRight', 'ArrowLeft', 'Escape'].indexOf(direction) !== -1 ) {
switch (direction) {
case 'ArrowRight':
return this.next();
case 'ArrowLeft':
return this.previous();
default:
this.modalIsVisible = false;
return true;
}
}
},
showAllIdentifiers() {
this.identifiersMode = 'all';
this.setIdentifiers();
},
showLessIdentifiers() {
this.identifiersMode = 'preview';
this.setIdentifiers();
document.querySelector('#identifiers').scrollIntoView({ behavior: 'smooth' });
},
showConfirmDelete() {
this.toggleModal();
},
toggleModal() {
this.showModalDelete = !this.showModalDelete;
},
updateItem() {
showToastr("Mise à jour en cours…", true);
axios.patch(`/api/v1/albums/${this.item._id}`)
.then( (res) => {
showToastr("Mise à jour réalisée avec succès", true);
this.item = res.data;
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de mettre à jour cet album", false);
});
},
deleteItem() {
axios.delete(`/api/v1/albums/${this.item._id}`)
.then( () => {
window.location.href = "/ma-collection";
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de supprimer cet album");
})
.finally(() => {
this.toggleModal();
});
},
goToArtist() {
return "";
},
},
}).mount('#ma-collection-details');
}

View file

@ -0,0 +1,18 @@
Vue.createApp({
data() {
return {
format: 'xml',
}
},
created() {
},
destroyed() {
},
methods: {
exportCollection(event) {
event.preventDefault();
window.open(`/api/v1/albums?exportFormat=${this.format}`, '_blank');
}
},
}).mount('#exporter');

View file

@ -0,0 +1,178 @@
if ( typeof isPublicCollection !== 'undefined' ) {
Vue.createApp({
data() {
return {
loading: false,
moreFilters: false,
items: [],
total: 0,
page: 1,
totalPages: 1,
limit: 16,
artist: '',
format: '',
year: '',
genre: '',
style: '',
sortOrder: 'artists_sort-asc',
sort: 'artists_sort',
order: 'asc',
itemId: null,
showModalDelete: false,
showModalShare: false,
shareLink: `${protocol}//${host}/collection/<%= user._id %>`,
isPublicCollection,
}
},
created() {
this.fetch();
},
methods: {
fetch() {
this.loading = true;
this.total = 0;
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const entries = urlParams.entries();
for(const entry of entries) {
switch(entry[0]) {
case 'artists_sort':
this.artist = entry[1];
break;
default:
this[entry[0]] = entry[1];
}
}
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
if ( this.artist ) {
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
}
if ( this.format ) {
url += `&format=${this.format.replace('&', '%26')}`;
}
if ( this.year ) {
url += `&year=${this.year}`;
}
if ( this.genre ) {
url += `&genre=${this.genre.replace('&', '%26')}`;
}
if ( this.style ) {
url += `&style=${this.style.replace('&', '%26')}`;
}
axios.get(url)
.then( response => {
this.items = response.data.rows;
this.total = response.data.count || 0;
this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0);
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de charger votre collection");
})
.finally(() => {
this.loading = false;
});
},
changeUrl() {
let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
if ( this.artist ) {
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
}
if ( this.format ) {
url += `&format=${this.format.replace('&', '%26')}`;
}
if ( this.year ) {
url += `&year=${this.year}`;
}
if ( this.genre ) {
url += `&genre=${this.genre.replace('&', '%26')}`;
}
if ( this.style ) {
url += `&style=${this.style.replace('&', '%26')}`;
}
location.href = url;
},
next(event) {
event.preventDefault();
this.page += 1;
this.changeUrl();
},
previous(event) {
event.preventDefault();
this.page -= 1;
this.changeUrl();
},
goTo(page) {
this.page = page;
this.changeUrl();
},
changeSort() {
const [sort,order] = this.sortOrder.split('-');
this.sort = sort;
this.order = order;
this.page = 1;
this.changeUrl();
},
changeFilter() {
this.page = 1;
this.changeUrl();
},
showMoreFilters() {
this.moreFilters = !this.moreFilters;
},
toggleModal() {
this.showModalDelete = !this.showModalDelete;
},
toggleModalShare() {
this.showModalShare = !this.showModalShare;
},
showConfirmDelete(itemId) {
this.itemId = itemId;
this.toggleModal();
},
deleteItem() {
axios.delete(`/api/v1/albums/${this.itemId}`)
.then( () => {
this.fetch();
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de supprimer cet album");
})
.finally(() => {
this.toggleModal();
});
},
shareCollection() {
axios.patch(`/api/v1/me`, {
isPublicCollection: !this.isPublicCollection,
})
.then( (res) => {
this.isPublicCollection = res.data.isPublicCollection;
if ( this.isPublicCollection ) {
showToastr("Votre collection est désormais publique", true);
} else {
showToastr("Votre collection n'est plus partagée", true);
}
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de supprimer cet album");
})
.finally(() => {
this.toggleModalShare();
});
},
}
}).mount('#ma-collection');
}

View file

@ -4,9 +4,10 @@
"description": "Simple application to manage your CD/Vinyl collection", "description": "Simple application to manage your CD/Vinyl collection",
"scripts": { "scripts": {
"start": "node ./dist/bin/www", "start": "node ./dist/bin/www",
"run:all": "npm-run-all build sass start", "run:all": "npm-run-all build sass uglify start",
"watch": "nodemon -e js,scss", "watch": "nodemon -e js,scss",
"sass": "npx sass sass/index.scss public/css/main.css -s compressed --color", "sass": "npx sass sass/index.scss public/css/main.css -s compressed --color",
"uglify": "npx gulp",
"prebuild": "rimraf dist", "prebuild": "rimraf dist",
"build": "babel ./src --out-dir dist --copy-files", "build": "babel ./src --out-dir dist --copy-files",
"test": "jest", "test": "jest",
@ -55,6 +56,11 @@
"excel4node": "^1.7.2", "excel4node": "^1.7.2",
"express": "^4.17.2", "express": "^4.17.2",
"express-session": "^1.17.2", "express-session": "^1.17.2",
"gulp": "^4.0.2",
"gulp-babel": "^8.0.0",
"gulp-concat": "^2.6.1",
"gulp-sourcemaps": "^3.0.0",
"gulp-uglify": "^3.0.2",
"joi": "^17.6.0", "joi": "^17.6.0",
"knacss": "^8.0.4", "knacss": "^8.0.4",
"mongoose": "^6.2.1", "mongoose": "^6.2.1",
@ -75,7 +81,8 @@
"exec": "yarn run:all", "exec": "yarn run:all",
"watch": [ "watch": [
"src/*", "src/*",
"sass/*" "sass/*",
"javascripts/*"
], ],
"ignore": [ "ignore": [
"**/__tests__/**", "**/__tests__/**",

View file

@ -75,14 +75,6 @@ app.set("views", path.join(__dirname, "../views"));
app.set("view engine", "ejs"); app.set("view engine", "ejs");
app.use(express.static(path.join(__dirname, "../public"))); app.use(express.static(path.join(__dirname, "../public")));
app.use(
"/libs/vue",
express.static(path.join(__dirname, "../node_modules/vue/dist"))
);
app.use(
"/libs/axios",
express.static(path.join(__dirname, "../node_modules/axios/dist"))
);
app.use("/", indexRouter); app.use("/", indexRouter);
app.use("/mon-compte", monCompteRouter); app.use("/mon-compte", monCompteRouter);

View file

@ -16,9 +16,8 @@
<link href="/css/main.css" rel="stylesheet" /> <link href="/css/main.css" rel="stylesheet" />
<script src="/libs/axios/axios.min.js"></script> <script defer src="/js/libs.js"></script>
<script src="/libs/vue/vue.global.prod.js"></script> <script defer src="/js/main.js"></script>
<script src="/js/main.js"></script>
<% if ( config.matomoUrl ) { %> <% if ( config.matomoUrl ) { %>
<!-- Matomo --> <!-- Matomo -->

View file

@ -1,4 +1,4 @@
<main class="layout-maxed ajouter-un-album" id="app"> <main class="layout-maxed ajouter-un-album" id="ajouter-album">
<h1>Ajouter un album</h1> <h1>Ajouter un album</h1>
<form @submit="search"> <form @submit="search">
<div class="grid sm:grid-cols-2"> <div class="grid sm:grid-cols-2">
@ -169,178 +169,4 @@
</footer> </footer>
</div> </div>
</div> </div>
</main> </main>
<script>
Vue.createApp({
data() {
return {
q: '',
year: '',
country: '',
format: '',
loading: false,
items: [],
details: {},
modalIsVisible: false,
formats: [
'Vinyl',
'Acetate',
'Flexi-disc',
'Lathe Cut',
'Mighty Tiny',
'Shellac',
'Sopic',
'Pathé Disc',
'Edison Disc',
'Cylinder',
'CD',
'CDr',
'CDV',
'DVD',
'DVDr',
'HD DVD',
'HD DVD-R',
'Blu-ray',
'Blu-ray-R',
'Ultra HD Blu-ray',
'SACD',
'4-Track Cartridge',
'8-Track Cartridge',
'Cassette',
'DC-International',
'Elcaset',
'PlayTape',
'RCA Tape Cartridge',
'DAT',
'DCC',
'Microcassette',
'NT Cassette',
'Pocket Rocker',
'Revere Magnetic Stereo Tape Ca',
'Tefifon',
'Reel-To-Reel',
'Sabamobil',
'Betacam',
'Betacam SP',
'Betamax',
'Cartrivision',
'MiniDV',
'Super VHS',
'U-matic',
'VHS',
'Video 2000',
'Video8',
'Film Reel',
'HitClips',
'Laserdisc',
'SelectaVision',
'VHD',
'Wire Recording',
'Minidisc',
'MVD',
'UMD',
'Floppy Disk',
'File',
'Memory Stick',
'Hybrid',
'All Media',
'Box Set',
]
}
},
methods: {
search(event) {
event.preventDefault();
if ( this.loading ) {
return false;
}
this.loading = true;
let url = `/api/v1/search?q=${this.q}`;
if ( this.year ) {
url += `&year=${this.year}`;
}
if ( this.country ) {
url += `&country=${this.country}`;
}
if ( this.format ) {
url += `&format=${this.format}`;
}
axios.get(url)
.then( response => {
const {
results,
} = response.data;
let items = [];
for (let i = 0 ; i < results.length ; i += 1 ) {
const {
id,
title,
thumb,
year,
country,
format,
genre,
style,
} = results[i];
items.push({
id,
title,
thumb,
year,
country,
format,
genre,
style,
});
}
this.items = items;
})
.catch((err) => {
showToastr(err.response?.data?.message || "Aucun résultat trouvé :/");
})
.finally(() => {
this.loading = false;
});
},
toggleModal() {
this.modalIsVisible = !this.modalIsVisible;
},
loadDetails(discogsId) {
axios.get(`/api/v1/search/${discogsId}`)
.then( response => {
const {
data,
} = response;
this.details = data;
this.toggleModal();
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de charger les détails de cet album");
})
.finally(() => {
this.loading = false;
});
},
add() {
axios.post('/api/v1/albums', this.details)
.then(() => {
window.location.href = '/ma-collection';
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible d'ajouter cet album pour le moment…");
});
},
orderedItems(items) {
return items.sort();
}
}
}).mount('#app');
</script>

View file

@ -1,4 +1,4 @@
<main class="layout-maxed collection" id="app"> <main class="layout-maxed collection" id="collection-publique">
<h1> <h1>
Collection de <%= page.username %> Collection de <%= page.username %>
</h1> </h1>
@ -146,140 +146,5 @@
</main> </main>
<script> <script>
const { const userId = "<%= params.userId %>";
protocol,
host
} = window.location;
Vue.createApp({
data() {
return {
loading: false,
moreFilters: false,
items: [],
total: 0,
page: 1,
totalPages: 1,
limit: 16,
artist: '',
format: '',
year: '',
genre: '',
style: '',
sortOrder: 'artists_sort-asc',
sort: 'artists_sort',
order: 'asc',
userId: "<%= params.userId %>",
}
},
created() {
this.fetch();
},
methods: {
fetch() {
this.loading = true;
this.total = 0;
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const entries = urlParams.entries();
for(const entry of entries) {
switch(entry[0]) {
case 'artists_sort':
this.artist = entry[1];
break;
default:
this[entry[0]] = entry[1];
}
}
let url = `/api/v1/albums?userId=${this.userId}&page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
if ( this.artist ) {
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
}
if ( this.format ) {
url += `&format=${this.format.replace('&', '%26')}`;
}
if ( this.year ) {
url += `&year=${this.year}`;
}
if ( this.genre ) {
url += `&genre=${this.genre.replace('&', '%26')}`;
}
if ( this.style ) {
url += `&style=${this.style.replace('&', '%26')}`;
}
axios.get(url)
.then( response => {
this.items = response.data.rows;
this.total = response.data.count || 0;
this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0);
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de charger cette collection");
})
.finally(() => {
this.loading = false;
});
},
changeUrl() {
let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
if ( this.artist ) {
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
}
if ( this.format ) {
url += `&format=${this.format.replace('&', '%26')}`;
}
if ( this.year ) {
url += `&year=${this.year}`;
}
if ( this.genre ) {
url += `&genre=${this.genre.replace('&', '%26')}`;
}
if ( this.style ) {
url += `&style=${this.style.replace('&', '%26')}`;
}
location.href = url;
},
next(event) {
event.preventDefault();
this.page += 1;
this.changeUrl();
},
previous(event) {
event.preventDefault();
this.page -= 1;
this.changeUrl();
},
goTo(page) {
this.page = page;
this.changeUrl();
},
changeSort() {
const [sort,order] = this.sortOrder.split('-');
this.sort = sort;
this.order = order;
this.page = 1;
this.changeUrl();
},
changeFilter() {
this.page = 1;
this.changeUrl();
},
showMoreFilters() {
this.moreFilters = !this.moreFilters;
}
}
}).mount('#app');
</script> </script>

View file

@ -1,4 +1,4 @@
<main class="layout-maxed collection" id="app"> <main class="layout-maxed collection" id="mon-compte">
<h1> <h1>
Mon compte Mon compte
</h1> </h1>
@ -72,28 +72,6 @@
</main> </main>
<script> <script>
Vue.createApp({ const email = '<%= user.email %>';
data() { const username = '<%= user.username %>';
return {
email: '<%= user.email %>',
username: '<%= user.username %>',
oldPassword: '',
password: '',
passwordConfirm: '',
loading: false,
}
},
methods: {
async updateProfil(event) {
// try {
// if ( this.password !== this.passwordConfirm ) {
// throw "La confirnation du mot de passe ne correspond pas";
// }
// } catch(err) {
// event.preventDefault();
// showToastr(err);
// }
},
}
}).mount('#app');
</script> </script>

View file

@ -1,4 +1,4 @@
<main class="layout-maxed ma-collection-details" id="app" v-cloak @keyup="changeImage"> <main class="layout-maxed ma-collection-details" id="ma-collection-details" v-cloak @keyup="changeImage">
<h1> <h1>
<a :href="`/ma-collection?page=1&limit=16&sort=year&order=asc&artists_sort=${item.artists_sort}`">{{item.artists_sort}}</a> - {{item.title}} <a :href="`/ma-collection?page=1&limit=16&sort=year&order=asc&artists_sort=${item.artists_sort}`">{{item.artists_sort}}</a> - {{item.title}}
@ -176,160 +176,5 @@
</main> </main>
<script> <script>
Vue.createApp({ const item = <%- JSON.stringify(page.item) %>;
data() {
return {
item: <%- JSON.stringify(page.item) %>,
tracklist: [],
identifiers: [],
modalIsVisible: false,
identifiersMode: 'preview',
identifiersPreviewLength: 16,
preview: null,
index: null,
showModalDelete: false,
}
},
created() {
this.setTrackList();
this.setIdentifiers();
window.addEventListener("keydown", this.changeImage);
},
destroyed() {
window.removeEventListener('keydown', this.changeImage);
},
methods: {
setIdentifiers() {
this.identifiers = [];
let max = this.identifiersMode == 'preview' && this.item.identifiers.length > this.identifiersPreviewLength ? this.identifiersPreviewLength : this.item.identifiers.length;
for ( let i = 0 ; i < max ; i += 1 ) {
this.identifiers.push(this.item.identifiers[i]);
}
},
setTrackList() {
let subTrack = {
type: null,
title: null,
tracks: [],
};
for (let i = 0 ; i < this.item.tracklist.length ; i += 1 ) {
const {
type_,
title,
position,
duration,
extraartists,
} = this.item.tracklist[i];
if ( type_ === 'heading' ) {
if ( subTrack.type ) {
this.tracklist.push(subTrack);
subTrack = {
type: null,
title: null,
tracks: [],
};
}
subTrack.type = type_;
subTrack.title = title;
} else {
subTrack.tracks.push({
title,
position,
duration,
extraartists
});
}
}
this.tracklist.push(subTrack);
},
setImage() {
this.preview = this.item.images[this.index].uri;
},
showGallery(event) {
const item = event.target.tagName === 'IMG' ? event.target.parentElement : event.target;
const {
index,
} = item.dataset;
this.index = Number(index);
this.modalIsVisible = true;
this.setImage();
},
toggleModal() {
this.modalIsVisible = !this.modalIsVisible;
},
previous() {
this.index = this.index > 0 ? this.index - 1 : this.item.images.length -1;
this.setImage();
},
next() {
this.index = (this.index +1) === this.item.images.length ? 0 : this.index + 1;
this.setImage();
},
changeImage(event) {
const direction = event.code;
if ( this.modalIsVisible && ['ArrowRight', 'ArrowLeft', 'Escape'].indexOf(direction) !== -1 ) {
switch (direction) {
case 'ArrowRight':
return this.next();
case 'ArrowLeft':
return this.previous();
default:
this.modalIsVisible = false;
return true;
}
}
},
showAllIdentifiers() {
this.identifiersMode = 'all';
this.setIdentifiers();
},
showLessIdentifiers() {
this.identifiersMode = 'preview';
this.setIdentifiers();
document.querySelector('#identifiers').scrollIntoView({ behavior: 'smooth' });
},
showConfirmDelete() {
this.toggleModal();
},
toggleModal() {
this.showModalDelete = !this.showModalDelete;
},
updateItem() {
showToastr("Mise à jour en cours…", true);
axios.patch(`/api/v1/albums/${this.item._id}`)
.then( (res) => {
showToastr("Mise à jour réalisée avec succès", true);
this.item = res.data;
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de mettre à jour cet album", false);
});
},
deleteItem() {
axios.delete(`/api/v1/albums/${this.item._id}`)
.then( () => {
window.location.href = "/ma-collection";
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de supprimer cet album");
})
.finally(() => {
this.toggleModal();
});
},
goToArtist() {
return "";
},
},
}).mount('#app');
</script> </script>

View file

@ -1,4 +1,4 @@
<main class="layout-maxed" id="app"> <main class="layout-maxed" id="exporter">
<h1>Exporter ma collection</h1> <h1>Exporter ma collection</h1>
<p> <p>
Les formats CSV et Excel sont facilement lisiblent par un humain. Dans ces 2 formats vous trouverez seulement les informations principales de vos albums, à savoir : Les formats CSV et Excel sont facilement lisiblent par un humain. Dans ces 2 formats vous trouverez seulement les informations principales de vos albums, à savoir :
@ -44,25 +44,4 @@
Exporter Exporter
</button> </button>
</form> </form>
</main> </main>
<script>
Vue.createApp({
data() {
return {
format: 'xml',
}
},
created() {
},
destroyed() {
},
methods: {
exportCollection(event) {
event.preventDefault();
window.open(`/api/v1/albums?exportFormat=${this.format}`, '_blank');
}
},
}).mount('#app');
</script>

View file

@ -1,4 +1,4 @@
<main class="layout-maxed collection" id="app"> <main class="layout-maxed collection" id="ma-collection">
<h1> <h1>
Ma collection Ma collection
<i class="icon-share" @click="toggleModalShare" aria-label="Partager ma collection" title="Votre collection sera visible en lecture aux personnes ayant le lien de partage"></i> <i class="icon-share" @click="toggleModalShare" aria-label="Partager ma collection" title="Votre collection sera visible en lecture aux personnes ayant le lien de partage"></i>
@ -196,185 +196,5 @@
</main> </main>
<script> <script>
const { const isPublicCollection = <%= user.isPublicCollection ? 'true' : 'false' %>;
protocol, </script>
host
} = window.location;
Vue.createApp({
data() {
return {
loading: false,
moreFilters: false,
items: [],
total: 0,
page: 1,
totalPages: 1,
limit: 16,
artist: '',
format: '',
year: '',
genre: '',
style: '',
sortOrder: 'artists_sort-asc',
sort: 'artists_sort',
order: 'asc',
itemId: null,
showModalDelete: false,
showModalShare: false,
shareLink: `${protocol}//${host}/collection/<%= user._id %>`,
isPublicCollection: <%= user.isPublicCollection ? 'true' : 'false' %>,
}
},
created() {
this.fetch();
},
methods: {
fetch() {
this.loading = true;
this.total = 0;
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const entries = urlParams.entries();
for(const entry of entries) {
switch(entry[0]) {
case 'artists_sort':
this.artist = entry[1];
break;
default:
this[entry[0]] = entry[1];
}
}
let url = `/api/v1/albums?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
if ( this.artist ) {
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
}
if ( this.format ) {
url += `&format=${this.format.replace('&', '%26')}`;
}
if ( this.year ) {
url += `&year=${this.year}`;
}
if ( this.genre ) {
url += `&genre=${this.genre.replace('&', '%26')}`;
}
if ( this.style ) {
url += `&style=${this.style.replace('&', '%26')}`;
}
axios.get(url)
.then( response => {
this.items = response.data.rows;
this.total = response.data.count || 0;
this.totalPages = parseInt(response.data.count / this.limit) + (response.data.count % this.limit > 0 ? 1 : 0);
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de charger votre collection");
})
.finally(() => {
this.loading = false;
});
},
changeUrl() {
let url = `?page=${this.page}&limit=${this.limit}&sort=${this.sort}&order=${this.order}`;
if ( this.artist ) {
url += `&artists_sort=${this.artist.replace('&', '%26')}`;
}
if ( this.format ) {
url += `&format=${this.format.replace('&', '%26')}`;
}
if ( this.year ) {
url += `&year=${this.year}`;
}
if ( this.genre ) {
url += `&genre=${this.genre.replace('&', '%26')}`;
}
if ( this.style ) {
url += `&style=${this.style.replace('&', '%26')}`;
}
location.href = url;
},
next(event) {
event.preventDefault();
this.page += 1;
this.changeUrl();
},
previous(event) {
event.preventDefault();
this.page -= 1;
this.changeUrl();
},
goTo(page) {
this.page = page;
this.changeUrl();
},
changeSort() {
const [sort,order] = this.sortOrder.split('-');
this.sort = sort;
this.order = order;
this.page = 1;
this.changeUrl();
},
changeFilter() {
this.page = 1;
this.changeUrl();
},
showMoreFilters() {
this.moreFilters = !this.moreFilters;
},
toggleModal() {
this.showModalDelete = !this.showModalDelete;
},
toggleModalShare() {
this.showModalShare = !this.showModalShare;
},
showConfirmDelete(itemId) {
this.itemId = itemId;
this.toggleModal();
},
deleteItem() {
axios.delete(`/api/v1/albums/${this.itemId}`)
.then( () => {
this.fetch();
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de supprimer cet album");
})
.finally(() => {
this.toggleModal();
});
},
shareCollection() {
axios.patch(`/api/v1/me`, {
isPublicCollection: !this.isPublicCollection,
})
.then( (res) => {
this.isPublicCollection = res.data.isPublicCollection;
if ( this.isPublicCollection ) {
showToastr("Votre collection est désormais publique", true);
} else {
showToastr("Votre collection n'est plus partagée", true);
}
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible de supprimer cet album");
})
.finally(() => {
this.toggleModalShare();
});
},
}
}).mount('#app');
</script>

View file

@ -1,4 +1,4 @@
<section class="box" id="app"> <section class="box" id="contact">
<h1>Nous contacter</h1> <h1>Nous contacter</h1>
<form @submit="send" <% if (config.mailMethod === 'formspree' ) { %> id="contact" method="POST" action="https://formspree.io/f/<%= config.formspreeId %>" <% } %>> <form @submit="send" <% if (config.mailMethod === 'formspree' ) { %> id="contact" method="POST" action="https://formspree.io/f/<%= config.formspreeId %>" <% } %>>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-16"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-16">
@ -34,47 +34,6 @@
</form> </form>
</section> </section>
<% if (config.mailMethod === 'smtp' ) { %> <script>
<script> const contactMethod = '<%= config.mailMethod %>';
Vue.createApp({ </script>
data() {
return {
email: '',
name: '',
message: '',
captcha: '',
loading: false,
}
},
methods: {
send(event) {
event.preventDefault();
if ( this.loading ) {
return false;
}
this.loading = true;
const {
email,
message,
name,
captcha,
} = this;
axios.post('/api/v1/contact', {email, name, message, captcha})
.then( () => {
showToastr("Message correctement envoyé", true);
})
.catch((err) => {
showToastr(err.response?.data?.message || "Impossible d'envoyer votre message", false);
})
.finally(() => {
this.loading = false;
})
},
},
}).mount('#app');
</script>
<% } %>