mirror of
https://github.com/tcsenpai/swingmusic.git
synced 2025-07-28 13:41:42 +00:00
Merge pull request #30 from geoffrey45/integrate-topbar
- Adds back and forward buttons to topbar - Adds square images in playlists page - Adds thumbnail creation to playlists - Other minor refactors
This commit is contained in:
commit
68b474bbba
@ -40,6 +40,7 @@ def create_playlist():
|
||||
"pre_tracks": [],
|
||||
"lastUpdated": data["lastUpdated"],
|
||||
"image": "",
|
||||
"thumb": "",
|
||||
}
|
||||
|
||||
try:
|
||||
@ -100,15 +101,20 @@ def update_playlist(playlistid: str):
|
||||
"description": str(data.get("description").strip()),
|
||||
"lastUpdated": str(data.get("lastUpdated")),
|
||||
"image": None,
|
||||
"thumb": None,
|
||||
}
|
||||
|
||||
for p in api.PLAYLISTS:
|
||||
if p.playlistid == playlistid:
|
||||
|
||||
if image:
|
||||
playlist["image"] = playlistlib.save_p_image(image, playlistid)
|
||||
image_, thumb_ = playlistlib.save_p_image(image, playlistid)
|
||||
playlist["image"] = image_
|
||||
playlist["thumb"] = thumb_
|
||||
|
||||
else:
|
||||
playlist["image"] = p.image.split("/")[-1]
|
||||
playlist["thumb"] = p.thumb.split("/")[-1]
|
||||
|
||||
p.update_playlist(playlist)
|
||||
instances.playlist_instance.update_playlist(playlistid, playlist)
|
||||
|
@ -19,7 +19,7 @@ def send_track_file(trackid):
|
||||
file["filepath"] for file in api.PRE_TRACKS
|
||||
if file["_id"]["$oid"] == trackid
|
||||
][0]
|
||||
except (FileNotFoundError, IndexError):
|
||||
except (FileNotFoundError, IndexError) as e:
|
||||
return "File not found", 404
|
||||
|
||||
return send_file(filepath, mimetype="audio/mp3")
|
||||
|
@ -2,7 +2,6 @@
|
||||
This file contains the Album class for interacting with
|
||||
album documents in MongoDB.
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from bson import ObjectId
|
||||
|
||||
@ -16,16 +15,21 @@ class Albums(db.Mongo):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(Albums, self).__init__("ALBUMS")
|
||||
self.collection = self.db["ALBUMS"]
|
||||
super(Albums, self).__init__("ALICE_ALBUMS")
|
||||
self.collection = self.db["ALL_ALBUMS"]
|
||||
|
||||
def insert_album(self, album: dict) -> None:
|
||||
"""
|
||||
Inserts a new album object into the database.
|
||||
"""
|
||||
return self.collection.update_one(
|
||||
{"album": album["album"], "artist": album["artist"]},
|
||||
{"$set": album},
|
||||
{
|
||||
"album": album["album"],
|
||||
"artist": album["artist"]
|
||||
},
|
||||
{
|
||||
"$set": album
|
||||
},
|
||||
upsert=True,
|
||||
).upserted_id
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
This file contains the Artists class for interacting with artist documents in MongoDB.
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from bson import ObjectId
|
||||
|
||||
@ -12,16 +11,17 @@ class Artists(db.Mongo):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(Artists, self).__init__("ALL_ARTISTS")
|
||||
self.collection = self.db["THEM_ARTISTS"]
|
||||
super(Artists, self).__init__("ALICE_ARTISTS")
|
||||
self.collection = self.db["ALL_ARTISTS"]
|
||||
|
||||
def insert_artist(self, artist_obj: dict) -> None:
|
||||
"""
|
||||
Inserts an artist into the database.
|
||||
"""
|
||||
self.collection.update_one(
|
||||
artist_obj, {"$set": artist_obj}, upsert=True
|
||||
).upserted_id
|
||||
self.collection.update_one(artist_obj, {
|
||||
"$set": artist_obj
|
||||
},
|
||||
upsert=True).upserted_id
|
||||
|
||||
def get_all_artists(self) -> list:
|
||||
"""
|
||||
|
@ -15,8 +15,8 @@ class Playlists(db.Mongo):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(Playlists, self).__init__("PLAYLISTS")
|
||||
self.collection = self.db["PLAYLISTS"]
|
||||
super(Playlists, self).__init__("ALICE_PLAYLISTS")
|
||||
self.collection = self.db["ALL_PLAYLISTS"]
|
||||
|
||||
def insert_playlist(self, playlist: dict) -> None:
|
||||
"""
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
This file contains the TrackColors class for interacting with Track colors documents in MongoDB.
|
||||
"""
|
||||
|
||||
from app import db
|
||||
|
||||
|
||||
@ -11,7 +10,7 @@ class TrackColors(db.Mongo):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(TrackColors, self).__init__("TRACK_COLORS")
|
||||
super(TrackColors, self).__init__("ALICE_TRACK_COLORS")
|
||||
self.collection = self.db["TRACK_COLORS"]
|
||||
|
||||
def insert_track_color(self, track_color: dict) -> None:
|
||||
@ -19,8 +18,12 @@ class TrackColors(db.Mongo):
|
||||
Inserts a new track object into the database.
|
||||
"""
|
||||
return self.collection.update_one(
|
||||
{"filepath": track_color["filepath"]},
|
||||
{"$set": track_color},
|
||||
{
|
||||
"filepath": track_color["filepath"]
|
||||
},
|
||||
{
|
||||
"$set": track_color
|
||||
},
|
||||
upsert=True,
|
||||
).upserted_id
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""
|
||||
This file contains the AllSongs class for interacting with track documents in MongoDB.
|
||||
"""
|
||||
|
||||
from app import db
|
||||
from bson import ObjectId
|
||||
|
||||
@ -15,8 +14,8 @@ class AllSongs(db.Mongo):
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(AllSongs, self).__init__("ALL_SONGS")
|
||||
self.collection = self.db["ALL_SONGS"]
|
||||
super(AllSongs, self).__init__("ALICE_MUSIC_TRACKS")
|
||||
self.collection = self.db["ALL_TRACKS"]
|
||||
|
||||
# def drop_db(self):
|
||||
# self.collection.drop()
|
||||
@ -25,9 +24,12 @@ class AllSongs(db.Mongo):
|
||||
"""
|
||||
Inserts a new track object into the database.
|
||||
"""
|
||||
return self.collection.update_one(
|
||||
{"filepath": song_obj["filepath"]}, {"$set": song_obj}, upsert=True
|
||||
).upserted_id
|
||||
return self.collection.update_one({
|
||||
"filepath": song_obj["filepath"]
|
||||
}, {
|
||||
"$set": song_obj
|
||||
},
|
||||
upsert=True).upserted_id
|
||||
|
||||
def get_all_songs(self) -> list:
|
||||
"""
|
||||
@ -53,22 +55,33 @@ class AllSongs(db.Mongo):
|
||||
"""
|
||||
Returns all the songs matching the albums in the query params (using regex).
|
||||
"""
|
||||
songs = self.collection.find({"album": {"$regex": query, "$options": "i"}})
|
||||
songs = self.collection.find(
|
||||
{"album": {
|
||||
"$regex": query,
|
||||
"$options": "i"
|
||||
}})
|
||||
return convert_many(songs)
|
||||
|
||||
def search_songs_by_artist(self, query: str) -> list:
|
||||
"""
|
||||
Returns all the songs matching the artists in the query params.
|
||||
"""
|
||||
songs = self.collection.find({"artists": {"$regex": query, "$options": "i"}})
|
||||
songs = self.collection.find(
|
||||
{"artists": {
|
||||
"$regex": query,
|
||||
"$options": "i"
|
||||
}})
|
||||
return convert_many(songs)
|
||||
|
||||
def find_song_by_title(self, query: str) -> list:
|
||||
"""
|
||||
Finds all the tracks matching the title in the query params.
|
||||
"""
|
||||
self.collection.create_index([("title", db.pymongo.TEXT)])
|
||||
song = self.collection.find({"title": {"$regex": query, "$options": "i"}})
|
||||
song = self.collection.find(
|
||||
{"title": {
|
||||
"$regex": query,
|
||||
"$options": "i"
|
||||
}})
|
||||
return convert_many(song)
|
||||
|
||||
def find_songs_by_album(self, name: str, artist: str) -> list:
|
||||
@ -82,7 +95,9 @@ class AllSongs(db.Mongo):
|
||||
"""
|
||||
Returns a sorted list of all the tracks exactly matching the folder in the query params
|
||||
"""
|
||||
songs = self.collection.find({"folder": query}).sort("title", db.pymongo.ASCENDING)
|
||||
songs = self.collection.find({
|
||||
"folder": query
|
||||
}).sort("title", db.pymongo.ASCENDING)
|
||||
return convert_many(songs)
|
||||
|
||||
def find_songs_by_folder_og(self, query: str) -> list:
|
||||
@ -104,8 +119,10 @@ class AllSongs(db.Mongo):
|
||||
Returns a list of all the tracks containing the albumartist in the query params.
|
||||
"""
|
||||
songs = self.collection.find(
|
||||
{"albumartist": {"$regex": query, "$options": "i"}}
|
||||
)
|
||||
{"albumartist": {
|
||||
"$regex": query,
|
||||
"$options": "i"
|
||||
}})
|
||||
return convert_many(songs)
|
||||
|
||||
def get_song_by_path(self, path: str) -> dict:
|
||||
|
@ -1,6 +1,9 @@
|
||||
import time
|
||||
from typing import List
|
||||
from app import api, helpers, models
|
||||
|
||||
from app import api
|
||||
from app import helpers
|
||||
from app import models
|
||||
from progress.bar import Bar
|
||||
|
||||
|
||||
@ -58,7 +61,6 @@ def get_subdirs(foldername: str) -> List[models.Folder]:
|
||||
|
||||
if str1 is not None:
|
||||
subdirs.add(foldername + "/" + str1)
|
||||
|
||||
return [create_folder(dir) for dir in subdirs]
|
||||
|
||||
|
||||
|
@ -61,6 +61,26 @@ def create_all_playlists():
|
||||
_bar.next()
|
||||
_bar.finish()
|
||||
|
||||
validate_images()
|
||||
|
||||
|
||||
def create_thumbnail(image: any, img_path: str) -> str:
|
||||
"""
|
||||
Creates a 250 x 250 thumbnail from a playlist image
|
||||
"""
|
||||
thumb_path = "thumb_" + img_path
|
||||
full_thumb_path = os.path.join(settings.APP_DIR, "images", "playlists",
|
||||
thumb_path)
|
||||
|
||||
aspect_ratio = image.width / image.height
|
||||
|
||||
new_w = round(250 * aspect_ratio)
|
||||
|
||||
thumb = image.resize((new_w, 250), Image.ANTIALIAS)
|
||||
thumb.save(full_thumb_path, "webp")
|
||||
|
||||
return thumb_path
|
||||
|
||||
|
||||
def save_p_image(file: datastructures.FileStorage, pid: str):
|
||||
"""
|
||||
@ -72,7 +92,9 @@ def save_p_image(file: datastructures.FileStorage, pid: str):
|
||||
random.choices(string.ascii_letters + string.digits, k=5))
|
||||
|
||||
img_path = pid + str(random_str) + ".webp"
|
||||
full_path = os.path.join(settings.APP_DIR, "images", "playlists", img_path)
|
||||
|
||||
full_img_path = os.path.join(settings.APP_DIR, "images", "playlists",
|
||||
img_path)
|
||||
|
||||
if file.content_type == "image/gif":
|
||||
frames = []
|
||||
@ -80,9 +102,33 @@ def save_p_image(file: datastructures.FileStorage, pid: str):
|
||||
for frame in ImageSequence.Iterator(img):
|
||||
frames.append(frame.copy())
|
||||
|
||||
frames[0].save(full_path, save_all=True, append_images=frames[1:])
|
||||
return img_path
|
||||
frames[0].save(full_img_path, save_all=True, append_images=frames[1:])
|
||||
thumb_path = create_thumbnail(img, img_path=img_path)
|
||||
|
||||
img.save(full_path, "webp")
|
||||
return img_path, thumb_path
|
||||
|
||||
return img_path
|
||||
img.save(full_img_path, "webp")
|
||||
thumb_path = create_thumbnail(img, img_path=img_path)
|
||||
|
||||
return img_path, thumb_path
|
||||
|
||||
|
||||
def validate_images():
|
||||
"""
|
||||
Removes all unused images in the images/playlists folder.
|
||||
"""
|
||||
images = []
|
||||
|
||||
for playlist in api.PLAYLISTS:
|
||||
if playlist.image:
|
||||
img_path = playlist.image.split("/")[-1]
|
||||
thumb_path = playlist.thumb.split("/")[-1]
|
||||
|
||||
images.append(img_path)
|
||||
images.append(thumb_path)
|
||||
|
||||
p_path = os.path.join(settings.APP_DIR, "images", "playlists")
|
||||
|
||||
for image in os.listdir(p_path):
|
||||
if image not in images:
|
||||
os.remove(os.path.join(p_path, image))
|
||||
|
@ -103,6 +103,7 @@ class Playlist:
|
||||
_pre_tracks: list = field(init=False, repr=False)
|
||||
lastUpdated: int
|
||||
image: str
|
||||
thumb: str
|
||||
description: str = ""
|
||||
count: int = 0
|
||||
"""A list of track objects in the playlist"""
|
||||
@ -112,6 +113,7 @@ class Playlist:
|
||||
self.name = data["name"]
|
||||
self.description = data["description"]
|
||||
self.image = self.create_img_link(data["image"])
|
||||
self.thumb = self.create_img_link(data["thumb"])
|
||||
self._pre_tracks = data["pre_tracks"]
|
||||
self.tracks = []
|
||||
self.lastUpdated = data["lastUpdated"]
|
||||
@ -149,6 +151,7 @@ class Playlist:
|
||||
|
||||
if data["image"]:
|
||||
self.image = self.create_img_link(data["image"])
|
||||
self.thumb = self.create_img_link(data["thumb"])
|
||||
|
||||
|
||||
@dataclass
|
||||
|
3
server/app/patches.py
Normal file
3
server/app/patches.py
Normal file
@ -0,0 +1,3 @@
|
||||
"""
|
||||
This module contains patch functions to modify existing data in the database.
|
||||
"""
|
@ -58,6 +58,7 @@ class Playlist:
|
||||
playlistid: str
|
||||
name: str
|
||||
image: str
|
||||
thumb: str
|
||||
lastUpdated: int
|
||||
description: str
|
||||
count: int = 0
|
||||
@ -68,6 +69,7 @@ class Playlist:
|
||||
self.playlistid = p.playlistid
|
||||
self.name = p.name
|
||||
self.image = p.image
|
||||
self.thumb = p.thumb
|
||||
self.lastUpdated = p.lastUpdated
|
||||
self.description = p.description
|
||||
self.count = p.count
|
||||
|
@ -103,5 +103,6 @@ app_dom.addEventListener("click", (e) => {
|
||||
padding: 0 $small;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr;
|
||||
margin-top: $small;
|
||||
}
|
||||
</style>
|
||||
|
@ -44,7 +44,7 @@ const props = defineProps<{
|
||||
|
||||
.a-header {
|
||||
display: grid;
|
||||
grid-template-columns: 13rem 1fr;
|
||||
grid-template-columns: 15rem 1fr;
|
||||
padding: 1rem;
|
||||
height: 100%;
|
||||
background-color: $gray4;
|
||||
@ -58,8 +58,8 @@ const props = defineProps<{
|
||||
align-items: flex-end;
|
||||
|
||||
.image {
|
||||
width: 12rem;
|
||||
height: 12rem;
|
||||
width: 14rem;
|
||||
height: 14rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,59 +0,0 @@
|
||||
<template>
|
||||
<div class="folder-top flex">
|
||||
<div class="fname">
|
||||
<PlayBtnRect />
|
||||
<div class="ftext">
|
||||
<div class="icon image"></div>
|
||||
<div class="ellip">
|
||||
{{ folder.path.split("/").splice(-1).join("") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useFStore from "../../stores/folder";
|
||||
import PlayBtnRect from "../shared/PlayBtnRect.vue";
|
||||
|
||||
const folder = useFStore();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.folder-top {
|
||||
border-bottom: 1px solid $separator;
|
||||
width: calc(100% - 0.5rem);
|
||||
padding-bottom: $small;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.folder-top .fname {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: $small;
|
||||
align-items: center;
|
||||
|
||||
.ftext {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 2.5rem;
|
||||
border-radius: $small;
|
||||
background-color: $primary;
|
||||
padding: $small $small $small 2.25rem;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
left: $small;
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
background-image: url(../../assets/icons/folder.fill.svg);
|
||||
margin-right: $small;
|
||||
}
|
||||
|
||||
@include phone-only {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,9 +1,8 @@
|
||||
<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 }}
|
||||
Playing from
|
||||
</div>
|
||||
<div class="name">
|
||||
<div id="to">
|
||||
@ -100,27 +99,17 @@ function goTo() {
|
||||
|
||||
<style lang="scss">
|
||||
#playing-from {
|
||||
background: linear-gradient(-200deg, $gray4 40%, $red, $gray4);
|
||||
background-size: 120%;
|
||||
padding: 0.75rem;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all .2s ease;
|
||||
background-color: $accent;
|
||||
|
||||
&:hover {
|
||||
background-position: -4rem;
|
||||
}
|
||||
|
||||
.abs {
|
||||
position: absolute;
|
||||
right: $small;
|
||||
bottom: $small;
|
||||
font-size: .9rem;
|
||||
background-color: $gray;
|
||||
padding: $smaller;
|
||||
border-radius: .25rem;
|
||||
}
|
||||
|
||||
.name {
|
||||
text-transform: capitalize;
|
||||
font-weight: bolder;
|
||||
@ -133,7 +122,7 @@ function goTo() {
|
||||
align-items: center;
|
||||
gap: $small;
|
||||
text-transform: capitalize;
|
||||
color: rgba(255, 255, 255, 0.664);
|
||||
color: rgba(255, 255, 255, 0.849);
|
||||
|
||||
.icon {
|
||||
height: 1.25rem;
|
||||
|
@ -1,31 +1,50 @@
|
||||
<template>
|
||||
<div class="topnav rounded">
|
||||
<div class="topnav">
|
||||
<div class="left">
|
||||
<div class="btn">
|
||||
<PlayBtn />
|
||||
<NavButtons />
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<div class="title">Playlists</div>
|
||||
<div class="title" v-if="$route.name == 'Playlists'">Playlists</div>
|
||||
<div class="folder" v-else-if="$route.name == 'FolderView'">
|
||||
<div class="play">
|
||||
<PlayBtnRect />
|
||||
</div>
|
||||
<div class="fname">
|
||||
<div class="icon image"></div>
|
||||
<div class="ellip">
|
||||
{{ $route.params.path.split("/").splice(-1)[0] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="center rounded">
|
||||
<Loader />
|
||||
</div>
|
||||
<div class="right"></div>
|
||||
<div class="right">
|
||||
<Search />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import PlayBtn from "../shared/PlayBtn.vue";
|
||||
import NavButtons from "./NavButtons.vue";
|
||||
import Loader from "../shared/Loader.vue";
|
||||
import PlayBtnRect from "../shared/PlayBtnRect.vue";
|
||||
import Search from "./Search.vue";
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.topnav {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
padding: 0 $small;
|
||||
grid-template-columns: 1fr max-content max-content;
|
||||
padding-bottom: 1rem;
|
||||
margin: $small $small 0 $small;
|
||||
border-bottom: 1px solid $gray3;
|
||||
height: 3rem;
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
@ -37,12 +56,46 @@ import Loader from "../shared/Loader.vue";
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.folder {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
.playbtnrect {
|
||||
height: 2.25rem;
|
||||
}
|
||||
|
||||
.fname {
|
||||
position: relative;
|
||||
padding-left: 2.25rem;
|
||||
background-color: $gray4;
|
||||
border-radius: $small;
|
||||
height: 2.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: $small;
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
left: $small;
|
||||
top: $small;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
background-image: url("../../assets/icons/folder.fill.svg");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.right {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
44
src/components/nav/NavButtons.vue
Normal file
44
src/components/nav/NavButtons.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div id="back-forward">
|
||||
<div class="back image" @click="$router.back()"></div>
|
||||
<div class="forward image" @click="$router.forward()"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
console.log();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#back-forward {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
padding-right: 1rem;
|
||||
margin-right: $small;
|
||||
border-right: 1px solid $gray3;
|
||||
|
||||
& > div {
|
||||
background-color: $gray4;
|
||||
border-radius: $small;
|
||||
height: 2.25rem;
|
||||
width: 2.25rem;
|
||||
cursor: pointer;
|
||||
background-size: 2rem;
|
||||
transition: all .25s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: $accent;
|
||||
}
|
||||
}
|
||||
|
||||
.back {
|
||||
background-image: url("../../assets/icons/right-arrow.svg");
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.forward {
|
||||
background-image: url("../../assets/icons/right-arrow.svg");
|
||||
}
|
||||
}
|
||||
</style>
|
36
src/components/nav/Search.vue
Normal file
36
src/components/nav/Search.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div id="nav-search">
|
||||
<form>
|
||||
<input
|
||||
type="search"
|
||||
name=""
|
||||
id=""
|
||||
placeholder="Search this playlist"
|
||||
class="rounded"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#nav-search {
|
||||
form {
|
||||
display: flex;
|
||||
gap: $small;
|
||||
|
||||
input[type="search"] {
|
||||
background-color: $gray5;
|
||||
border: none;
|
||||
padding: $small;
|
||||
width: 100%;
|
||||
min-width: 24rem;
|
||||
color: $white;
|
||||
font-size: 1rem;
|
||||
|
||||
&:focus {
|
||||
outline: solid $accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -3,6 +3,7 @@
|
||||
<div class="gradient rounded"></div>
|
||||
<div class="plus image p-image"></div>
|
||||
<div>New Playlist</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -10,30 +11,31 @@
|
||||
#new-playlist-card {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background-color: $black;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
.gradient {
|
||||
position: absolute;
|
||||
width: calc(100% - 2rem);
|
||||
height: 10rem;
|
||||
top: 1rem;
|
||||
width: calc(100% - 1.5rem);
|
||||
top: 0.75rem;
|
||||
background-image: linear-gradient(37deg, $red, $blue);
|
||||
background-size: 100%;
|
||||
transition: all .5s ease-in-out;
|
||||
transition: all 0.5s ease-in-out;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.image {
|
||||
background-image: url("../../assets/icons/plus.svg");
|
||||
background-size: 5rem;
|
||||
z-index: 1;
|
||||
transition: all .5s ease-in-out;
|
||||
transition: all 0.5s ease-in-out;
|
||||
background-color: transparent;
|
||||
margin-bottom: $small;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.gradient {
|
||||
background-size: 30rem;
|
||||
background-size: 300rem;
|
||||
}
|
||||
.image {
|
||||
transform: rotate(270deg);
|
||||
|
@ -10,7 +10,7 @@
|
||||
<div
|
||||
class="image p-image rounded shadow-sm"
|
||||
:style="{
|
||||
backgroundImage: `url(${props.playlist.image})`,
|
||||
backgroundImage: `url(${props.playlist.thumb})`,
|
||||
}"
|
||||
></div>
|
||||
<div class="pbtn">
|
||||
@ -37,21 +37,21 @@ import Option from "../shared/Option.vue";
|
||||
const props = defineProps<{
|
||||
playlist: Playlist;
|
||||
}>();
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.p-card {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
transition: all 0.2s ease;
|
||||
background-image: linear-gradient(37deg, #000000e8, $gray);
|
||||
transition: all 0.25s ease;
|
||||
background-position: -10rem;
|
||||
position: relative;
|
||||
|
||||
.p-image {
|
||||
min-width: 100%;
|
||||
height: 10rem;
|
||||
transition: all 0.2s ease;
|
||||
background-color: $gray4;
|
||||
aspect-ratio: 1;
|
||||
}
|
||||
|
||||
.drop {
|
||||
@ -60,6 +60,7 @@ const props = defineProps<{
|
||||
right: 1.25rem;
|
||||
opacity: 0;
|
||||
transition: all 0.25s ease-in-out;
|
||||
display: none;
|
||||
|
||||
.drop-btn {
|
||||
background-color: $gray3;
|
||||
@ -67,6 +68,7 @@ const props = defineProps<{
|
||||
}
|
||||
|
||||
.pbtn {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 4.5rem;
|
||||
left: 1.25rem;
|
||||
@ -75,10 +77,12 @@ const props = defineProps<{
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $gray5;
|
||||
|
||||
.drop {
|
||||
transition-delay: .75s;
|
||||
transition-delay: 0.75s;
|
||||
opacity: 1;
|
||||
transform: translate(0, -.5rem);
|
||||
transform: translate(0, -0.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,20 +1,21 @@
|
||||
<template>
|
||||
<div class="loaderx" :class="{ loader: loading, not_loader: !loading }">
|
||||
<div v-if="!loading">😹</div>
|
||||
<div v-if="!loading">🦋</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import state from "@/composables/state";
|
||||
|
||||
const loading = state.loading
|
||||
const loading = state.loading;
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.loaderx {
|
||||
width: 1.5rem;
|
||||
height:1.5rem;
|
||||
height: 1.5rem;
|
||||
border-radius: 50%;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.loader {
|
||||
|
@ -56,7 +56,7 @@ interface Playlist {
|
||||
tracks?: Track[];
|
||||
count?: number;
|
||||
lastUpdated?: string;
|
||||
color?: string;
|
||||
thumb?: string;
|
||||
}
|
||||
|
||||
interface Notif {
|
||||
|
@ -21,6 +21,7 @@ export default defineStore("album", {
|
||||
this.tracks = tracks.tracks;
|
||||
this.info = tracks.info;
|
||||
this.artists = artists;
|
||||
this.bio = null;
|
||||
},
|
||||
fetchBio(title: string, albumartist: string) {
|
||||
getAlbumBio(title, albumartist).then((bio) => {
|
||||
|
@ -46,7 +46,7 @@ onBeforeRouteUpdate(async (to) => {
|
||||
|
||||
.songs {
|
||||
padding: $small;
|
||||
min-height: calc(100% - 30rem);
|
||||
min-height: calc(100% - 32rem);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
|
@ -1,8 +1,5 @@
|
||||
<template>
|
||||
<div id="f-view-parent" class="rounded">
|
||||
<div class="fixed">
|
||||
<Header :path="FStore.path" :first_song="FStore.tracks[0]" />
|
||||
</div>
|
||||
<div id="scrollable" ref="scrollable">
|
||||
<FolderList :folders="FStore.dirs" />
|
||||
<div
|
||||
@ -20,7 +17,6 @@ import { onBeforeRouteUpdate } from "vue-router";
|
||||
|
||||
import SongList from "@/components/FolderView/SongList.vue";
|
||||
import FolderList from "@/components/FolderView/FolderList.vue";
|
||||
import Header from "@/components/FolderView/Header.vue";
|
||||
|
||||
import useFStore from "../stores/folder";
|
||||
import state from "../composables/state";
|
||||
@ -44,9 +40,10 @@ onBeforeRouteUpdate((to) => {
|
||||
<style lang="scss">
|
||||
#f-view-parent {
|
||||
position: relative;
|
||||
padding: 4rem $small 0 $small;
|
||||
padding: 0 $small 0 $small;
|
||||
overflow: hidden;
|
||||
margin: $small;
|
||||
margin-top: $small;
|
||||
|
||||
.h {
|
||||
font-size: 2rem;
|
||||
@ -54,12 +51,12 @@ onBeforeRouteUpdate((to) => {
|
||||
}
|
||||
}
|
||||
|
||||
#f-view-parent .fixed {
|
||||
position: absolute;
|
||||
height: min-content;
|
||||
width: calc(100% - 1rem);
|
||||
top: 0.5rem;
|
||||
}
|
||||
// #f-view-parent .fixed {
|
||||
// position: absolute;
|
||||
// height: min-content;
|
||||
// width: calc(100% - 1rem);
|
||||
// top: 0.5rem;
|
||||
// }
|
||||
|
||||
#scrollable {
|
||||
overflow-y: auto;
|
||||
|
@ -49,7 +49,7 @@ const playlist = usePTrackStore();
|
||||
}
|
||||
.songlist {
|
||||
padding: $small;
|
||||
min-height: calc(100% - 30rem);
|
||||
min-height: calc(100% - 32rem);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -22,15 +22,15 @@ const pStore = usePStore();
|
||||
<style lang="scss">
|
||||
#p-view {
|
||||
margin: $small;
|
||||
margin-top: 0;
|
||||
padding: $small;
|
||||
overflow: auto;
|
||||
scrollbar-color: $gray2 transparent;
|
||||
border-top: 1px solid $gray3;
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
gap: $small;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user