494 lines
13 KiB
Vue
494 lines
13 KiB
Vue
|
<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>
|