client/src/components/dialogs/AlbumViewer.vue

494 lines
13 KiB
Vue
Raw Normal View History

2023-02-08 12:37:55 +01:00
<template>
<DialogBase
ref="dialogWindow"
:title="album_title"
@canceled="closed"
@opened="opened"
:showFooter="false"
:disableXscroll="true"
:disableYscroll="true"
>
<div id="albumViewer">
<div id="header" class="flex-column">
<div id="background" :style="coverBackground" />
<div id="albumList" class="flex-row z1" @scroll="loadingAlbums()">
<div class="dummyAlbum" />
<div id="loadPrevAlbums" />
<AlbumItem
v-for="album in albums"
:key="album._id"
:item="album"
class="ma"
:class="{ focus: album._id == selectedAlbum._id }"
:id="album._id"
@touchend="albumAutoSelect"
@click="selectAlbum(album)"
@dblclick="dblclick"
/>
<div id="loadNextAlbums" />
<div class="dummyAlbum" />
</div>
<awesome-icon
icon="star"
size="2x"
class="favourite ma4"
:class="{ active: isFavourite }"
@click="toggleFavourite"
/>
<div id="stats" class="flex-row grow z1 pa4-bottom">
<DropDown v-if="$store.getters['user/isAdministrator']">
<button class="flat pa8-left pa8-right" :title="visibility_text">
<awesome-icon :icon="visibility_icon" />
</button>
<template v-slot:dropdown-content>
<div>
<button
v-for="(item, i) in $store.state.system.lists.visibility"
:key="i"
@click="setVisibility(item)"
>
<awesome-icon :icon="getVisibilityIcon(item)" />{{
getVisibilityText(item)
}}
</button>
</div>
</template>
</DropDown>
<span class="grow center" @click="scrollIntoCenter(selectedAlbum)">
<b>{{ album_title }}</b> by
<b @click="gotoArtist" class="pointer">{{
selectedAlbum.artist_name
}}</b>
<br />
<span v-if="album_year">
from year <b>{{ album_year }}</b> </span
><br />
<b>{{ album_tracks.length }}</b> Tracks with a duration of
<b>{{ album_duration }}</b>
</span>
<DropDown v-if="$store.getters['user/isAdministrator']">
<button class="flat pa8-left pa8-right">
<awesome-icon icon="ellipsis-v" />
</button>
<template v-slot:dropdown-content>
<div>
<button @click="uploadNewCover">
<awesome-icon icon="image" />Set new Cover...
</button>
<button @click="resetCover">
<awesome-icon icon="eraser" />Reset Cover
</button>
<hr />
<button @click="mergeAlbum">
<awesome-icon icon="compress-alt" />Merge Albums...
</button>
</div>
</template>
</DropDown>
</div>
</div>
<ul id="trackList" class="tracks">
<li v-for="track in selectedAlbum.tracks" :key="track._id">
<TrackItem :track="track" :showCover="false" />
</li>
</ul>
</div>
<AlbumMerge ref="mergeDialog" />
</DialogBase>
</template>
<script>
import AlbumMerge from "./AlbumMerge.vue";
import TrackItem from "../Track";
import { mapGetters } from "vuex";
export default {
data() {
return {
move: 152,
albums: [],
scrollTimer: 0,
loadingPrev: false,
loadingNext: false,
elements: {},
};
},
mounted() {
if (window.innerWidth <= 480 || window.innerHeight <= 480) {
this.move = 120;
}
},
methods: {
albumAutoSelect() {
this.albums.forEach((album) => {
let center_client = document.documentElement.clientWidth / 2;
let e = document.getElementById(album._id);
let r = e.getBoundingClientRect();
let center_element = center_client - r.left - r.width / 2;
if (center_element < this.move / 2 && center_element > -this.move / 2) {
if (album._id == this.selectedAlbum._id) {
this.scrollIntoCenter(album, "smooth");
} else {
this.selectAlbum(album);
}
}
});
},
readElements() {
if (document.getElementById("header") == undefined) {
return false;
}
this.elements.prev = document
.getElementById("loadPrevAlbums")
.getBoundingClientRect();
this.elements.next = document
.getElementById("loadNextAlbums")
.getBoundingClientRect();
this.elements.header = document
.getElementById("header")
.getBoundingClientRect();
this.elements.albums = document.getElementById("albumList");
return true;
},
dblclick() {
this.$store.commit("tracks/resetSelectedTrack");
this.$store.commit("radios/resetSelectedRadio");
this.$store.dispatch("tracks/playContainer", this.selectedAlbum);
},
gotoArtist() {
let artist = this.$store.getters["artists/collection"].find(
(f) => f._id == this.selectedAlbum.artist_id
);
if (artist) {
this.$store.dispatch("artists/selectArtist", artist);
} else {
this.$store
.dispatch("artists/loadArtist", this.selectedAlbum.artist_id)
.then((artist) => {
this.$store.dispatch("artists/selectArtist", artist);
});
}
},
gotoNextAlbum() {
let i = this.albums.indexOf(this.selectedAlbum);
if (i < this.albums.length - 1) {
this.selectAlbum(this.albums[++i]);
}
},
gotoPrevAlbum() {
let i = this.albums.indexOf(this.selectedAlbum);
if (i > 0) {
this.selectAlbum(this.albums[--i]);
}
},
gotoTrack() {
if (this.$route.query.play) {
let track = this.selectedAlbum.tracks.find(
(f) => f._id == this.$route.query.play
);
if (track) {
this.$store.dispatch("tracks/play", track);
}
}
},
closed() {
if (
(window.history.state.back &&
window.history.state.back.indexOf("?") == -1) ||
window.history.state.back.startsWith("/search")
) {
this.$router.back();
} else {
this.$store.dispatch("albums/resetSelectedAlbum");
}
this.albums = [];
},
keydownListener(e) {
if (e.key == "ArrowLeft") {
e.preventDefault();
this.elements.albums.scrollLeft -= 0;
this.gotoPrevAlbum();
}
if (e.key == "ArrowRight") {
e.preventDefault();
this.elements.albums.scrollLeft += 0;
this.gotoNextAlbum();
}
},
loadingAlbums() {
clearTimeout(this.scrollTimer);
if (!this.readElements()) {
return;
}
let posPrev = this.elements.prev.left - this.elements.header.left;
let posNext = this.elements.header.right - this.elements.next.right;
if (posPrev >= -this.move && !this.loadingPrev) {
this.loadingPrev = true;
this.$store
.dispatch("albums/getPrevTo", this.albums[0])
.then((album) => {
if (album) {
this.albums.unshift(album);
this.elements.albums.scrollLeft += this.move;
}
this.loadingPrev = false;
});
} else if (posPrev < -this.move * 3 && !this.loadingPrev) {
this.loadingPrev = true;
this.elements.albums.scrollLeft -= this.move;
this.albums.shift();
this.loadingPrev = false;
}
if (posNext >= -this.move && !this.loadingNext) {
this.loadingNext = true;
this.$store
.dispatch("albums/getNextTo", this.albums[this.albums.length - 1])
.then((album) => {
if (album) {
this.albums.push(album);
}
this.loadingNext = false;
});
} else if (posNext < -this.move * 3 && !this.loadingNext) {
this.loadingNext = true;
this.albums.pop();
this.loadingNext = false;
}
},
mergeAlbum() {
this.$refs.mergeDialog.open(this.selectedAlbum);
},
opened() {
this.$nextTick(() => {
this.scrollIntoCenter(this.selectedAlbum);
});
},
setVisibility(visibility) {
this.selectedAlbum.visibility = visibility;
this.$store.dispatch("albums/updateAlbum", this.selectedAlbum);
},
toggleFavourite() {
this.$store.dispatch("user/toggleFavourite", {
itemId: this.selectedAlbum._id,
type: "album",
});
},
uploadNewCover() {
this.$store.dispatch("albums/uploadNewCover", this.selectedAlbum);
},
resetCover() {
this.$store.dispatch("albums/resetCover", this.selectedAlbum);
},
getVisibilityIcon(visibility) {
return visibility == "global"
? "globe"
: visibility == "instance"
? "server"
: visibility == "hidden"
? "eye-slash"
: "user";
},
getVisibilityText(visibility) {
return visibility == "global"
? "Global"
: visibility == "instance"
? "On this server"
: visibility == "hidden"
? "Hide this Album"
: "Only for me";
},
scrollIntoCenter(album, behavior = "auto") {
let e = document.getElementById(album._id);
if (e) {
e.scrollIntoView({ behavior: behavior, inline: "center" });
}
},
selectAlbum(album) {
this.$store.dispatch("albums/selectAlbum", album);
this.scrollIntoCenter(album);
},
},
computed: {
...mapGetters({
prevAlbum: ["albums/prevAlbum"],
nextAlbum: ["albums/nextAlbum"],
selectedAlbum: ["albums/selectedAlbum"],
selectedTrack: ["tracks/selectedTrack"],
favourites: ["user/favourites"],
}),
album_title() {
return this.selectedAlbum.title;
},
album_year() {
return this.selectedAlbum.year;
},
album_tracks() {
return this.selectedAlbum.tracks || [];
},
album_duration() {
if (!this.selectedAlbum.tracks) {
return 0;
}
let duration = 0;
let hours = 0;
let minutes = 0;
let seconds = 0;
this.selectedAlbum.tracks.forEach((track) => {
duration += track.duration;
});
if (duration >= 3600) {
hours = parseInt(duration / 3600);
duration -= hours * 3600;
}
minutes = parseInt(duration / 60);
seconds = parseInt(duration - minutes * 60);
return (
(hours > 0 ? hours + ":" : "") +
(minutes < 10 ? "0" : "") +
minutes +
":" +
(seconds < 10 ? "0" : "") +
seconds
);
},
coverBackground() {
return "background-image: url('" + this.cover + "')";
},
cover() {
return (
this.selectedAlbum.covers.cover256 || "/static/icons/dummy/album.svg"
);
},
visibility_icon() {
return this.selectedAlbum.visibility == "global"
? "globe"
: this.selectedAlbum.visibility == "instance"
? "server"
: this.selectedAlbum.visibility == "hidden"
? "eye-slash"
: "user";
},
visibility_text() {
return this.selectedAlbum.visibility == "global"
? "Visible for the whole world"
: this.selectedAlbum.visibility == "instance"
? "Visible on this instance"
: this.selectedAlbum.visibility == "hidden"
? "Hidden for all users"
: "Visible only for me";
},
isFavourite() {
return (
this.favourites.find((f) => f.itemId == this.selectedAlbum._id) !=
undefined
);
},
},
watch: {
selectedAlbum(newVal) {
if (newVal._id) {
if (this.albums.length == 0) {
this.albums.push(newVal);
}
if (!this.$refs.dialogWindow.visible) {
this.$refs.dialogWindow.open();
window.addEventListener("keydown", this.keydownListener);
}
this.gotoTrack();
} else {
if (this.$refs.dialogWindow.visible) {
this.$refs.dialogWindow.close();
}
window.removeEventListener("keydown", this.keydownListener);
}
},
},
components: {
AlbumMerge,
TrackItem,
},
};
</script>
<style scoped>
#albumViewer {
display: flex;
flex-direction: column;
height: 100%;
}
#albumList {
overflow-x: overlay;
max-width: 100%;
align-self: center;
}
#albumList::-webkit-scrollbar {
display: none;
}
#header {
position: relative;
min-width: 280px;
width: 456px;
background: black;
}
#header img {
align-self: center;
}
#navigation {
width: 456px;
overflow: auto;
}
#stats {
z-index: 2;
color: var(--white);
text-shadow: 0 1px 2px black;
line-height: 1.4;
background-color: #ffffff40;
border-top: 1px solid #ffffff20;
border-bottom: 1px solid #00000020;
}
#trackList {
height: 360px;
width: 456px;
background-color: var(--white);
z-index: 1;
}
.album {
transition: transform 0.25s;
}
.album.focus {
transform: scale(1.1);
}
.dummyAlbum {
min-width: 160px;
}
@media (max-width: 480px), (max-height: 480px) {
#header {
width: 100%;
}
#trackList {
height: initial;
width: 100%;
}
.dummyAlbum {
min-width: 128px;
}
}
</style>