mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
🔷 add PlayingFrom component to right sidebar
🔷 move upNext card into separate component 🔷 a lot of refactors
This commit is contained in:
+15
-19
@@ -8,6 +8,7 @@ from app import instances, api
|
|||||||
from app.lib import playlistlib
|
from app.lib import playlistlib
|
||||||
from app import models
|
from app import models
|
||||||
from app import exceptions
|
from app import exceptions
|
||||||
|
from app import serializer
|
||||||
|
|
||||||
playlist_bp = Blueprint("playlist", __name__, url_prefix="/")
|
playlist_bp = Blueprint("playlist", __name__, url_prefix="/")
|
||||||
|
|
||||||
@@ -17,15 +18,7 @@ TrackExistsInPlaylist = exceptions.TrackExistsInPlaylist
|
|||||||
|
|
||||||
@playlist_bp.route("/playlists", methods=["GET"])
|
@playlist_bp.route("/playlists", methods=["GET"])
|
||||||
def get_all_playlists():
|
def get_all_playlists():
|
||||||
ppp = deepcopy(api.PLAYLISTS)
|
return {"data": [serializer.Playlist(p) for p in api.PLAYLISTS]}
|
||||||
playlists = []
|
|
||||||
|
|
||||||
for pl in ppp:
|
|
||||||
pl.count = len(pl.tracks)
|
|
||||||
pl.tracks = []
|
|
||||||
playlists.append(pl)
|
|
||||||
|
|
||||||
return {"data": playlists}
|
|
||||||
|
|
||||||
|
|
||||||
@playlist_bp.route("/playlist/new", methods=["POST"])
|
@playlist_bp.route("/playlist/new", methods=["POST"])
|
||||||
@@ -35,16 +28,16 @@ def create_playlist():
|
|||||||
playlist = {
|
playlist = {
|
||||||
"name": data["name"],
|
"name": data["name"],
|
||||||
"description": "",
|
"description": "",
|
||||||
"tracks": [],
|
"pre_tracks": [],
|
||||||
"count": 0,
|
|
||||||
"lastUpdated": 0,
|
"lastUpdated": 0,
|
||||||
|
"image": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
p_in_db = instances.playlist_instance.get_playlist_by_name(playlist["name"])
|
for pl in api.PLAYLISTS:
|
||||||
|
if pl.name == playlist["name"]:
|
||||||
|
raise PlaylistExists("Playlist already exists.")
|
||||||
|
|
||||||
if p_in_db:
|
|
||||||
raise PlaylistExists("Playlist already exists.")
|
|
||||||
except PlaylistExists as e:
|
except PlaylistExists as e:
|
||||||
return {"error": str(e)}, 409
|
return {"error": str(e)}, 409
|
||||||
|
|
||||||
@@ -71,12 +64,15 @@ def add_track_to_playlist(playlist_id: str):
|
|||||||
return {"msg": "I think It's done"}, 200
|
return {"msg": "I think It's done"}, 200
|
||||||
|
|
||||||
|
|
||||||
@playlist_bp.route("/playlist/<playlist_id>")
|
@playlist_bp.route("/playlist/<playlistid>")
|
||||||
def get_single_p_info(playlist_id: str):
|
def get_single_p_info(playlistid: str):
|
||||||
for p in api.PLAYLISTS:
|
for p in api.PLAYLISTS:
|
||||||
if p.playlistid == playlist_id:
|
if p.playlistid == playlistid:
|
||||||
p.count = len(p.tracks)
|
print(p)
|
||||||
return {"data": p}
|
tracks = p.get_tracks()
|
||||||
|
return {"info": serializer.Playlist(p), "tracks": tracks}
|
||||||
|
|
||||||
|
return {"info": {}, "tracks": []}
|
||||||
|
|
||||||
|
|
||||||
# @playlist_bp.route("/playlist/<playlist_id>/info")
|
# @playlist_bp.route("/playlist/<playlist_id>/info")
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ convert_many = db.convert_many
|
|||||||
convert_one = db.convert_one
|
convert_one = db.convert_one
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Playlists(db.Mongo):
|
class Playlists(db.Mongo):
|
||||||
"""
|
"""
|
||||||
The class for all playlist-related database operations.
|
The class for all playlist-related database operations.
|
||||||
@@ -43,20 +42,15 @@ class Playlists(db.Mongo):
|
|||||||
playlist = self.collection.find_one({"_id": ObjectId(id)})
|
playlist = self.collection.find_one({"_id": ObjectId(id)})
|
||||||
return convert_one(playlist)
|
return convert_one(playlist)
|
||||||
|
|
||||||
def add_track_to_playlist(self, playlistid: str, track: models.Track):
|
def add_track_to_playlist(self, playlistid: str, track: dict) -> None:
|
||||||
"""
|
"""
|
||||||
Adds a track to a playlist.
|
Adds a track to a playlist.
|
||||||
"""
|
"""
|
||||||
track = {
|
|
||||||
"title": track.title,
|
|
||||||
"artists": track.artists,
|
|
||||||
"album": track.album,
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.collection.update_one(
|
return self.collection.update_one(
|
||||||
{"_id": ObjectId(playlistid)},
|
{"_id": ObjectId(playlistid)},
|
||||||
{"$push": {"tracks": track}},
|
{"$push": {"pre_tracks": track}},
|
||||||
).modified_count
|
)
|
||||||
|
|
||||||
def get_playlist_by_name(self, name: str) -> dict:
|
def get_playlist_by_name(self, name: str) -> dict:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ def populate():
|
|||||||
|
|
||||||
albumslib.create_everything()
|
albumslib.create_everything()
|
||||||
folderslib.run_scandir()
|
folderslib.run_scandir()
|
||||||
playlistlib.create_all_playlists()
|
|
||||||
|
|
||||||
end = time.time()
|
end = time.time()
|
||||||
|
|
||||||
|
|||||||
@@ -15,14 +15,20 @@ def add_track(playlistid: str, trackid: str):
|
|||||||
"""
|
"""
|
||||||
for playlist in api.PLAYLISTS:
|
for playlist in api.PLAYLISTS:
|
||||||
if playlist.playlistid == playlistid:
|
if playlist.playlistid == playlistid:
|
||||||
track = trackslib.get_track_by_id(trackid)
|
tt = trackslib.get_track_by_id(trackid)
|
||||||
|
|
||||||
if track not in playlist.tracks:
|
track = {
|
||||||
playlist.tracks.append(track)
|
"title": tt.title,
|
||||||
|
"artists": tt.artists,
|
||||||
|
"album": tt.album,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
playlist.add_track(track)
|
||||||
instances.playlist_instance.add_track_to_playlist(playlistid, track)
|
instances.playlist_instance.add_track_to_playlist(playlistid, track)
|
||||||
return
|
return
|
||||||
else:
|
except TrackExistsInPlaylist as e:
|
||||||
raise TrackExistsInPlaylist("Track already in playlist.")
|
return {"error": str(e)}, 409
|
||||||
|
|
||||||
|
|
||||||
def get_playlist_tracks(pid: str):
|
def get_playlist_tracks(pid: str):
|
||||||
@@ -31,7 +37,6 @@ def get_playlist_tracks(pid: str):
|
|||||||
return p.tracks
|
return p.tracks
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_all_playlists():
|
def create_all_playlists():
|
||||||
"""
|
"""
|
||||||
Gets all playlists from the database.
|
Gets all playlists from the database.
|
||||||
|
|||||||
+26
-3
@@ -2,11 +2,12 @@
|
|||||||
Contains all the models for objects generation and typing.
|
Contains all the models for objects generation and typing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass, field
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import List
|
from typing import List
|
||||||
from app import api
|
from app import api
|
||||||
from app import settings
|
from app import settings
|
||||||
|
from app.exceptions import TrackExistsInPlaylist
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -99,10 +100,11 @@ class Playlist:
|
|||||||
|
|
||||||
playlistid: str
|
playlistid: str
|
||||||
name: str
|
name: str
|
||||||
description: str
|
|
||||||
image: str
|
image: str
|
||||||
tracks: List[Track]
|
tracks: List[Track]
|
||||||
|
_pre_tracks: list = field(init=False, repr=False)
|
||||||
lastUpdated: int
|
lastUpdated: int
|
||||||
|
description: str = ""
|
||||||
count: int = 0
|
count: int = 0
|
||||||
"""A list of track objects in the playlist"""
|
"""A list of track objects in the playlist"""
|
||||||
|
|
||||||
@@ -111,8 +113,29 @@ class Playlist:
|
|||||||
self.name = data["name"]
|
self.name = data["name"]
|
||||||
self.description = data["description"]
|
self.description = data["description"]
|
||||||
self.image = ""
|
self.image = ""
|
||||||
self.tracks = create_playlist_tracks(data["tracks"])
|
self._pre_tracks = data["pre_tracks"]
|
||||||
|
self.tracks = []
|
||||||
self.lastUpdated = data["lastUpdated"]
|
self.lastUpdated = data["lastUpdated"]
|
||||||
|
self.count = len(self._pre_tracks)
|
||||||
|
|
||||||
|
def get_tracks(self) -> List[Track]:
|
||||||
|
"""
|
||||||
|
Generates and returns Track objects from pre_tracks
|
||||||
|
"""
|
||||||
|
return create_playlist_tracks(self._pre_tracks)
|
||||||
|
|
||||||
|
def update_count(self):
|
||||||
|
self.count = len(self.tracks)
|
||||||
|
|
||||||
|
def add_track(self, track):
|
||||||
|
if track not in self._pre_tracks:
|
||||||
|
self._pre_tracks.append(track)
|
||||||
|
self.update_count()
|
||||||
|
else:
|
||||||
|
raise TrackExistsInPlaylist("Track already exists in playlist")
|
||||||
|
|
||||||
|
def update_desc(self, desc):
|
||||||
|
self.description = desc
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -38,11 +38,7 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.border {
|
.border {
|
||||||
border: solid 1px $gray;
|
border: solid 1px $gray3;
|
||||||
}
|
|
||||||
|
|
||||||
.border-sm {
|
|
||||||
border: solid 1px #27262654;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.separator {
|
.separator {
|
||||||
|
|||||||
@@ -4,47 +4,38 @@
|
|||||||
<div
|
<div
|
||||||
class="image art shadow-lg rounded"
|
class="image art shadow-lg rounded"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: `url("${props.album_info.image}")`,
|
backgroundImage: `url("${props.album.image}")`,
|
||||||
}"
|
}"
|
||||||
></div>
|
></div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<div class="h">Album</div>
|
<div class="h">Album</div>
|
||||||
<div class="separator no-border"></div>
|
<div class="separator no-border"></div>
|
||||||
<div class="title">{{ props.album_info.album }}</div>
|
<div class="title">{{ props.album.album }}</div>
|
||||||
<div class="artist">{{ props.album_info.artist }}</div>
|
<div class="artist">{{ props.album.artist }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator no-border"></div>
|
<div class="separator no-border"></div>
|
||||||
<div class="bottom">
|
<div class="bottom">
|
||||||
<div class="stats shadow-sm">
|
<div class="stats shadow-sm">
|
||||||
{{ props.album_info.count }} Tracks •
|
{{ props.album.count }} Tracks •
|
||||||
{{ perks.formatSeconds(props.album_info.duration, "long") }} •
|
{{ perks.formatSeconds(props.album.duration, "long") }} •
|
||||||
{{ props.album_info.date }}
|
{{ props.album.date }}
|
||||||
</div>
|
|
||||||
<div class="play rounded" @click="playAlbum">
|
|
||||||
<div class="icon"></div>
|
|
||||||
<div>Play</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<PlayBtnRect :source="playSources.album" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import state from "@/composables/state";
|
import perks from "../../composables/perks.js";
|
||||||
import perks from "@/composables/perks.js";
|
import { AlbumInfo } from "../../interfaces.js";
|
||||||
|
import PlayBtnRect from "../shared/PlayBtnRect.vue";
|
||||||
const props = defineProps({
|
import { playSources } from "../../composables/enums";
|
||||||
album_info: {
|
const props = defineProps<{
|
||||||
type: Object,
|
album: AlbumInfo;
|
||||||
default: () => ({}),
|
}>();
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function playAlbum() {
|
|
||||||
perks.updateQueue(state.album.tracklist[0], "album");
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
v-for="artist in artists"
|
v-for="artist in artists"
|
||||||
:key="artist"
|
:key="artist"
|
||||||
:artist="artist"
|
:artist="artist"
|
||||||
:color="ffffff00"
|
:color="'ffffff00'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-header">
|
<div class="p-header">
|
||||||
<div class="carddd">
|
<div class="carddd">
|
||||||
<div class="art image"></div>
|
<div class="art image shadow-sm"></div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<PlayBtnRect :source="playSources.playlist" />
|
<PlayBtnRect :source="playSources.playlist" />
|
||||||
</div>
|
</div>
|
||||||
<div class="duration">4 Tracks • 3 Hours</div>
|
<div class="duration">{{props.info.count}} Tracks • 3 Hours</div>
|
||||||
<div class="desc">
|
<div class="desc">
|
||||||
{{ props.info.desc[0] }}
|
{{ props.info.description }}
|
||||||
</div>
|
</div>
|
||||||
<div class="title ellip">{{ props.info.name }}</div>
|
<div class="title ellip">{{ props.info.name }}</div>
|
||||||
<div class="type">Playlist</div>
|
<div class="type">Playlist</div>
|
||||||
@@ -25,16 +25,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { playSources } from "../../composables/enums";
|
import { playSources } from "../../composables/enums";
|
||||||
|
import { Playlist } from "../../interfaces";
|
||||||
|
|
||||||
import PlayBtnRect from "../shared/PlayBtnRect.vue";
|
import PlayBtnRect from "../shared/PlayBtnRect.vue";
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
info: {
|
info: Playlist;
|
||||||
name: string;
|
|
||||||
count: number;
|
|
||||||
duration: string;
|
|
||||||
desc: string;
|
|
||||||
lastUpdated: string;
|
|
||||||
};
|
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -43,9 +38,7 @@ const props = defineProps<{
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
height: 14rem;
|
height: 14rem;
|
||||||
// background-image: url("../../assets/images/eggs.jpg");
|
background-image: linear-gradient(37deg, $black 4%, $accent, $black);
|
||||||
|
|
||||||
background-image: linear-gradient(23deg, $black 40%, rgb(141, 11, 2), $black);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: $small;
|
margin-top: $small;
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="r-home">
|
<div class="r-home">
|
||||||
<Recommendations />
|
<UpNext :next="queue.next" :playNext="queue.playNext" />
|
||||||
|
<Recommendations />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.r-home {
|
.r-home {
|
||||||
height: calc(100% - 1rem);
|
height: calc(100% - 1rem);
|
||||||
// padding: 0 $small $small 0;
|
padding: 0 $small $small 0;
|
||||||
|
margin-top: $small;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import Recommendations from '../Recommendation.vue';
|
import Recommendations from "../Recommendation.vue";
|
||||||
|
import UpNext from "../queue/upNext.vue";
|
||||||
|
import useQStore from "../../../stores/queue";
|
||||||
|
const queue = useQStore();
|
||||||
</script>
|
</script>
|
||||||
@@ -1,28 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="up-next">
|
<div class="up-next">
|
||||||
<div class="r-grid">
|
<div class="r-grid">
|
||||||
<div class="main-item border">
|
<PlayingFrom :from="queue.from" />
|
||||||
<p class="heading">COMING UP NEXT</p>
|
<UpNext :next="queue.next" :playNext="queue.playNext" />
|
||||||
<div class="itemx" @click="queue.playNext">
|
|
||||||
<div
|
|
||||||
class="album-art image"
|
|
||||||
:style="{
|
|
||||||
backgroundImage: `url("${queue.next.image}")`,
|
|
||||||
}"
|
|
||||||
></div>
|
|
||||||
<div class="tags">
|
|
||||||
<p class="title ellip">{{ queue.next.title }}</p>
|
|
||||||
<hr />
|
|
||||||
<p class="artist ellip">
|
|
||||||
<span
|
|
||||||
v-for="artist in putCommas(queue.next.artists)"
|
|
||||||
:key="artist"
|
|
||||||
>{{ artist }}</span
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="scrollable-r border rounded">
|
<div class="scrollable-r border rounded">
|
||||||
<TrackItem
|
<TrackItem
|
||||||
v-for="t in queue.tracks"
|
v-for="t in queue.tracks"
|
||||||
@@ -38,16 +18,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import perks from "../../composables/perks.js";
|
|
||||||
import TrackItem from "../shared/TrackItem.vue";
|
import TrackItem from "../shared/TrackItem.vue";
|
||||||
import useQStore from "../../stores/queue";
|
import useQStore from "../../stores/queue";
|
||||||
import { Track } from "../../interfaces.js";
|
import { Track } from "../../interfaces.js";
|
||||||
import { onBeforeMount } from "vue";
|
import PlayingFrom from "./queue/playingFrom.vue";
|
||||||
|
import UpNext from "./queue/upNext.vue";
|
||||||
|
|
||||||
const queue = useQStore();
|
const queue = useQStore();
|
||||||
|
|
||||||
const putCommas = perks.putCommas;
|
|
||||||
|
|
||||||
function playThis(track: Track) {
|
function playThis(track: Track) {
|
||||||
queue.play(track);
|
queue.play(track);
|
||||||
}
|
}
|
||||||
@@ -64,53 +42,11 @@ function playThis(track: Track) {
|
|||||||
margin: 0.5rem 0 1rem 0;
|
margin: 0.5rem 0 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-item {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
|
|
||||||
.itemx {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $gray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.album-art {
|
|
||||||
width: 4.5rem;
|
|
||||||
height: 4.5rem;
|
|
||||||
background-image: url(../../assets/images/null.webp);
|
|
||||||
margin: 0 0.5rem 0 0;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags {
|
|
||||||
hr {
|
|
||||||
border: none;
|
|
||||||
margin: 0.3rem;
|
|
||||||
}
|
|
||||||
.title {
|
|
||||||
width: 20rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.artist {
|
|
||||||
width: 20rem;
|
|
||||||
margin: 0;
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.r-grid {
|
.r-grid {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: min-content;
|
grid-template-rows: max-content max-content 1fr;
|
||||||
|
|
||||||
.scrollable-r {
|
.scrollable-r {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="r-tracks rounded border">
|
<div class="r-tracks rounded border">
|
||||||
<p class="heading">SIMILAR TRACKS</p>
|
<div class="heading">Similar Tracks</div>
|
||||||
<div class="tracks">
|
<div class="tracks">
|
||||||
<div class="song-item" v-for="song in songs" :key="song">
|
<div class="song-item" v-for="song in songs" :key="song">
|
||||||
<div class="album-art image"></div>
|
<div class="album-art image"></div>
|
||||||
@@ -37,8 +37,13 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.r-tracks {
|
.r-tracks {
|
||||||
margin: 0.5rem 0.5rem 0.5rem 0;
|
margin: 0.5rem 0 0.5rem 0;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 0.5rem !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.r-tracks .tracks .song-item {
|
.r-tracks .tracks .song-item {
|
||||||
|
|||||||
@@ -170,7 +170,6 @@ search.$subscribe((mutation, state) => {
|
|||||||
padding: $medium;
|
padding: $medium;
|
||||||
border-radius: $small;
|
border-radius: $small;
|
||||||
margin-bottom: $small;
|
margin-bottom: $small;
|
||||||
text-align: center !important;
|
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
color: $white;
|
color: $white;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
<template>
|
||||||
|
<div id="playing-from" class="rounded" @click="goTo">
|
||||||
|
<div class="abs shadow-sm">Playing From</div>
|
||||||
|
<div class="h">
|
||||||
|
<div class="icon image" :class="from.type"></div>
|
||||||
|
{{ from.type }}
|
||||||
|
</div>
|
||||||
|
<div class="name">
|
||||||
|
<div id="to">
|
||||||
|
{{ from.name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { fromFolder, fromAlbum, fromPlaylist } from "../../../interfaces";
|
||||||
|
import { FromOptions } from "../../../composables/enums";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { computed } from "@vue/reactivity";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
from: fromFolder | fromAlbum | fromPlaylist;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
interface from {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const from = computed((): from => {
|
||||||
|
switch (props.from.type) {
|
||||||
|
case undefined:
|
||||||
|
return {
|
||||||
|
type: "album",
|
||||||
|
name: "Welcome to Alice",
|
||||||
|
};
|
||||||
|
case FromOptions.folder:
|
||||||
|
return {
|
||||||
|
type: "folder",
|
||||||
|
name: props.from.name,
|
||||||
|
};
|
||||||
|
case FromOptions.album:
|
||||||
|
return {
|
||||||
|
type: "album",
|
||||||
|
name: props.from.name,
|
||||||
|
};
|
||||||
|
case FromOptions.playlist:
|
||||||
|
return {
|
||||||
|
type: "playlist",
|
||||||
|
name: props.from.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
function goToAlbum() {
|
||||||
|
router.push({
|
||||||
|
name: "AlbumView",
|
||||||
|
params: {
|
||||||
|
album: props.from.name,
|
||||||
|
artist: props.from.albumartist,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToFolder() {
|
||||||
|
router.push({
|
||||||
|
name: "FolderView",
|
||||||
|
params: {
|
||||||
|
path: props.from.path,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToPlaylist() {
|
||||||
|
router.push({
|
||||||
|
name: "PlaylistView",
|
||||||
|
params: {
|
||||||
|
pid: props.from.playlistid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function goTo() {
|
||||||
|
switch (props.from.type) {
|
||||||
|
case FromOptions.folder:
|
||||||
|
goToFolder();
|
||||||
|
break;
|
||||||
|
case FromOptions.album:
|
||||||
|
goToAlbum();
|
||||||
|
break;
|
||||||
|
case FromOptions.playlist:
|
||||||
|
goToPlaylist();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#playing-from {
|
||||||
|
background: linear-gradient(-200deg, $gray4 40%, $red, $gray4);
|
||||||
|
background-size: 120%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-bottom: $small;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
transition: all .2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-position: -4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.abs {
|
||||||
|
position: absolute;
|
||||||
|
right: $small;
|
||||||
|
top: $small;
|
||||||
|
font-size: .9rem;
|
||||||
|
background-color: $gray;
|
||||||
|
padding: $smaller;
|
||||||
|
border-radius: .25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
.h {
|
||||||
|
font-size: .9rem;
|
||||||
|
margin-bottom: $small;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $small;
|
||||||
|
text-transform: capitalize;
|
||||||
|
color: rgba(255, 255, 255, 0.664);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 1.25rem;
|
||||||
|
width: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.folder {
|
||||||
|
background-image: url("../../../assets/icons/folder.fill.svg") !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album {
|
||||||
|
background-image: url("../../../assets/icons/album.svg") !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist {
|
||||||
|
background-image: url("../../../assets/icons/playlist.svg") !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<template>
|
||||||
|
<div class="main-item border" @click="playNext">
|
||||||
|
<div class="h">#Up_Next</div>
|
||||||
|
<div class="itemx shadow">
|
||||||
|
<div
|
||||||
|
class="album-art image"
|
||||||
|
:style="{
|
||||||
|
backgroundImage: `url("${next.image}")`,
|
||||||
|
}"
|
||||||
|
></div>
|
||||||
|
<div class="tags">
|
||||||
|
<p class="title ellip">{{ next.title }}</p>
|
||||||
|
<hr />
|
||||||
|
<p class="artist ellip">
|
||||||
|
<span v-for="artist in perks.putCommas(next.artists)" :key="artist">{{
|
||||||
|
artist
|
||||||
|
}}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Track } from "../../../interfaces";
|
||||||
|
import perks from "../../../composables/perks";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
next: Track;
|
||||||
|
playNext: () => void;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.main-item {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $accent;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
|
||||||
|
.h {
|
||||||
|
background-color: $black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.h {
|
||||||
|
position: absolute;
|
||||||
|
right: $small;
|
||||||
|
top: $small;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
background-color: $accent;
|
||||||
|
padding: $smaller;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemx {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album-art {
|
||||||
|
width: 4.5rem;
|
||||||
|
height: 4.5rem;
|
||||||
|
background-image: url(../../assets/images/null.webp);
|
||||||
|
margin: 0 0.5rem 0 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags {
|
||||||
|
hr {
|
||||||
|
border: none;
|
||||||
|
margin: 0.3rem;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
width: 20rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.artist {
|
||||||
|
width: 20rem;
|
||||||
|
margin: 0;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -89,6 +89,7 @@ const context = useContextStore();
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: -13rem;
|
right: -13rem;
|
||||||
width: 13rem;
|
width: 13rem;
|
||||||
|
top: -0.5rem;
|
||||||
max-height: 21.25rem;
|
max-height: 21.25rem;
|
||||||
|
|
||||||
padding: $small !important;
|
padding: $small !important;
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ function play() {
|
|||||||
break;
|
break;
|
||||||
case playSources.playlist:
|
case playSources.playlist:
|
||||||
queue.playFromPlaylist(
|
queue.playFromPlaylist(
|
||||||
playlist.playlist.name,
|
playlist.info.name,
|
||||||
playlist.playlist.playlistid,
|
playlist.info.playlistid,
|
||||||
playlist.playlist.tracks
|
playlist.tracks
|
||||||
);
|
);
|
||||||
queue.play(playlist.playlist.tracks[0]);
|
queue.play(playlist.tracks[0]);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,11 +53,7 @@ function play() {
|
|||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
padding-left: 0.75rem;
|
padding-left: 0.75rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: linear-gradient(
|
background: linear-gradient(34deg, $accent, $red);
|
||||||
34deg,
|
|
||||||
rgba(255, 166, 0, 0.644) 30%,
|
|
||||||
rgb(214, 188, 38)
|
|
||||||
);
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
transition: all 0.5s ease;
|
transition: all 0.5s ease;
|
||||||
|
|
||||||
|
|||||||
@@ -10,3 +10,10 @@ export enum NotifType {
|
|||||||
Info,
|
Info,
|
||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum FromOptions {
|
||||||
|
playlist = "playlist",
|
||||||
|
folder = "folder",
|
||||||
|
album = "album",
|
||||||
|
search = "search",
|
||||||
|
}
|
||||||
|
|||||||
@@ -95,12 +95,16 @@ async function getPTracks(playlistid: string) {
|
|||||||
async function getPlaylist(pid: string) {
|
async function getPlaylist(pid: string) {
|
||||||
const uri = state.settings.uri + "/playlist/" + pid;
|
const uri = state.settings.uri + "/playlist/" + pid;
|
||||||
|
|
||||||
let playlist: Playlist;
|
let playlist = {
|
||||||
|
info: {},
|
||||||
|
tracks: <Track[]>[],
|
||||||
|
};
|
||||||
|
|
||||||
await axios
|
await axios
|
||||||
.get(uri)
|
.get(uri)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
playlist = res.data.data;
|
playlist.info = res.data.info;
|
||||||
|
playlist.tracks = res.data.tracks;
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
new Notification("Something funny happened!", NotifType.Error);
|
new Notification("Something funny happened!", NotifType.Error);
|
||||||
@@ -110,4 +114,10 @@ async function getPlaylist(pid: string) {
|
|||||||
return playlist;
|
return playlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { createNewPlaylist, getAllPlaylists, addTrackToPlaylist, getPTracks, getPlaylist };
|
export {
|
||||||
|
createNewPlaylist,
|
||||||
|
getAllPlaylists,
|
||||||
|
addTrackToPlaylist,
|
||||||
|
getPTracks,
|
||||||
|
getPlaylist,
|
||||||
|
};
|
||||||
|
|||||||
+30
-2
@@ -1,4 +1,5 @@
|
|||||||
import { NotifType } from "./stores/enums";
|
import { FromOptions } from "./composables/enums";
|
||||||
|
import { NotifType } from "./composables/enums";
|
||||||
|
|
||||||
interface Track {
|
interface Track {
|
||||||
trackid: string;
|
trackid: string;
|
||||||
@@ -62,4 +63,31 @@ interface Notif {
|
|||||||
type: NotifType;
|
type: NotifType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Track, Folder, AlbumInfo, Artist, Option, Playlist, Notif };
|
interface fromFolder {
|
||||||
|
type: FromOptions;
|
||||||
|
path: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
interface fromAlbum {
|
||||||
|
type: FromOptions;
|
||||||
|
name: string;
|
||||||
|
albumartist: string;
|
||||||
|
}
|
||||||
|
interface fromPlaylist {
|
||||||
|
type: FromOptions;
|
||||||
|
name: string;
|
||||||
|
playlistid: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Track,
|
||||||
|
Folder,
|
||||||
|
AlbumInfo,
|
||||||
|
Artist,
|
||||||
|
Option,
|
||||||
|
Playlist,
|
||||||
|
Notif,
|
||||||
|
fromFolder,
|
||||||
|
fromAlbum,
|
||||||
|
fromPlaylist,
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { getPlaylist, getPTracks } from "../composables/playlists";
|
import { getPlaylist } from "../composables/playlists";
|
||||||
import { Track, Playlist } from "../interfaces";
|
import { Track, Playlist } from "../interfaces";
|
||||||
|
|
||||||
export default defineStore("playlist-tracks", {
|
export default defineStore("playlist-tracks", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
playlist: <Playlist>{},
|
info: <Playlist>{},
|
||||||
|
tracks: <Track[]>[],
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async fetchAll(playlistid: string) {
|
async fetchAll(playlistid: string) {
|
||||||
const playlist = await getPlaylist(playlistid);
|
const playlist = await getPlaylist(playlistid);
|
||||||
this.playlist = playlist;
|
this.info = playlist.info;
|
||||||
|
this.tracks = playlist.tracks;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+10
-32
@@ -1,31 +1,8 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import state from "../composables/state";
|
import state from "../composables/state";
|
||||||
import { Track } from "../interfaces";
|
import { Track, fromFolder, fromAlbum, fromPlaylist } from "../interfaces";
|
||||||
import notif from "../composables/mediaNotification";
|
import notif from "../composables/mediaNotification";
|
||||||
|
import { FromOptions } from "../composables/enums";
|
||||||
enum FromOptions {
|
|
||||||
playlist = "Playlist",
|
|
||||||
folder = "Folder",
|
|
||||||
album = "Album",
|
|
||||||
search = "Search",
|
|
||||||
}
|
|
||||||
|
|
||||||
interface fromFolder {
|
|
||||||
type: FromOptions.folder;
|
|
||||||
path: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface fromAlbum {
|
|
||||||
type: FromOptions.album;
|
|
||||||
name: string;
|
|
||||||
albumartist: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface fromPlaylist {
|
|
||||||
type: FromOptions.playlist;
|
|
||||||
name: string;
|
|
||||||
playlistid: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addQToLocalStorage(
|
function addQToLocalStorage(
|
||||||
from: fromFolder | fromAlbum | fromPlaylist,
|
from: fromFolder | fromAlbum | fromPlaylist,
|
||||||
@@ -84,7 +61,6 @@ export default defineStore("Queue", {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.audio.play().then(() => {
|
this.audio.play().then(() => {
|
||||||
|
|
||||||
this.playing = true;
|
this.playing = true;
|
||||||
notif(track, this.playPause, this.playNext, this.playPrev);
|
notif(track, this.playPause, this.playNext, this.playPrev);
|
||||||
|
|
||||||
@@ -108,6 +84,7 @@ export default defineStore("Queue", {
|
|||||||
this.play(this.current);
|
this.play(this.current);
|
||||||
} else if (this.audio.paused) {
|
} else if (this.audio.paused) {
|
||||||
this.audio.play();
|
this.audio.play();
|
||||||
|
this.playing = true;
|
||||||
} else {
|
} else {
|
||||||
this.audio.pause();
|
this.audio.pause();
|
||||||
this.playing = false;
|
this.playing = false;
|
||||||
@@ -131,6 +108,7 @@ export default defineStore("Queue", {
|
|||||||
},
|
},
|
||||||
readQueueFromLocalStorage() {
|
readQueueFromLocalStorage() {
|
||||||
const queue = localStorage.getItem("queue");
|
const queue = localStorage.getItem("queue");
|
||||||
|
|
||||||
if (queue) {
|
if (queue) {
|
||||||
const parsed = JSON.parse(queue);
|
const parsed = JSON.parse(queue);
|
||||||
this.from = parsed.from;
|
this.from = parsed.from;
|
||||||
@@ -180,30 +158,30 @@ export default defineStore("Queue", {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
playFromFolder(fpath: string, tracks: Track[]) {
|
playFromFolder(fpath: string, tracks: Track[]) {
|
||||||
this.setNewQueue(tracks);
|
|
||||||
|
|
||||||
this.from = <fromFolder>{
|
this.from = <fromFolder>{
|
||||||
type: FromOptions.folder,
|
type: FromOptions.folder,
|
||||||
path: fpath,
|
path: fpath,
|
||||||
|
name: fpath.split("/").splice(-1).join(""),
|
||||||
};
|
};
|
||||||
|
this.setNewQueue(tracks);
|
||||||
},
|
},
|
||||||
playFromAlbum(aname: string, albumartist: string, tracks: Track[]) {
|
playFromAlbum(aname: string, albumartist: string, tracks: Track[]) {
|
||||||
this.setNewQueue(tracks);
|
|
||||||
|
|
||||||
this.from = <fromAlbum>{
|
this.from = <fromAlbum>{
|
||||||
type: FromOptions.album,
|
type: FromOptions.album,
|
||||||
name: aname,
|
name: aname,
|
||||||
albumartist: albumartist,
|
albumartist: albumartist,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.setNewQueue(tracks);
|
||||||
},
|
},
|
||||||
playFromPlaylist(pname: string, pid: string, tracks: Track[]) {
|
playFromPlaylist(pname: string, pid: string, tracks: Track[]) {
|
||||||
this.setNewQueue(tracks);
|
|
||||||
|
|
||||||
this.from = <fromPlaylist>{
|
this.from = <fromPlaylist>{
|
||||||
type: FromOptions.playlist,
|
type: FromOptions.playlist,
|
||||||
name: pname,
|
name: pname,
|
||||||
playlistid: pid,
|
playlistid: pid,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.setNewQueue(tracks);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="al-view rounded">
|
<div class="al-view rounded">
|
||||||
<div>
|
<div>
|
||||||
<Header :album_info="album.info" />
|
<Header :album="album.info" />
|
||||||
</div>
|
</div>
|
||||||
<div class="separator" id="av-sep"></div>
|
<div class="separator" id="av-sep"></div>
|
||||||
<div class="songs rounded">
|
<div class="songs rounded">
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="playlist-view">
|
<div class="playlist-view">
|
||||||
<Header :info="info" />
|
<Header :info="playlist.info" />
|
||||||
<div class="separator no-border"></div>
|
<div class="separator no-border"></div>
|
||||||
|
|
||||||
<div class="songlist rounded">
|
<div class="songlist rounded">
|
||||||
<SongList
|
<SongList
|
||||||
:tracks="playlist.tracks"
|
:tracks="playlist.tracks"
|
||||||
:pname="info.name"
|
:pname="playlist.info.name"
|
||||||
:playlistid="playlist.playlistid"
|
:playlistid="playlist.info.playlistid"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator no-border"></div>
|
<div class="separator no-border"></div>
|
||||||
@@ -21,15 +21,7 @@ import SongList from "../components/FolderView/SongList.vue";
|
|||||||
import FeaturedArtists from "../components/PlaylistView/FeaturedArtists.vue";
|
import FeaturedArtists from "../components/PlaylistView/FeaturedArtists.vue";
|
||||||
import usePTrackStore from "../stores/p.ptracks";
|
import usePTrackStore from "../stores/p.ptracks";
|
||||||
|
|
||||||
const playlist = usePTrackStore().playlist;
|
const playlist = usePTrackStore();
|
||||||
|
|
||||||
const info = {
|
|
||||||
name: playlist.name,
|
|
||||||
count: playlist.tracks.length,
|
|
||||||
desc: playlist.description,
|
|
||||||
duration: "3 hours, 4 minutes",
|
|
||||||
lastUpdated: "yesterday",
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
Reference in New Issue
Block a user