mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
↩️ Merge pull request #59 from geoffrey45/fuzzy-search
Implement Fuzzy search using rapidfuzz
This commit is contained in:
@@ -9,7 +9,10 @@
|
||||
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^8.5.0",
|
||||
"@vueuse/motion": "^2.0.0-beta.18",
|
||||
"axios": "^0.26.1",
|
||||
"defu": "^6.0.0",
|
||||
"mitt": "^3.0.0",
|
||||
"node-vibrant": "^3.2.1-alpha.1",
|
||||
"pinia": "^2.0.11",
|
||||
|
||||
+88
-31
@@ -1,11 +1,10 @@
|
||||
"""
|
||||
Contains all the search routes.
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request
|
||||
|
||||
from app.lib import searchlib
|
||||
from app import helpers
|
||||
from app.lib import searchlib
|
||||
from flask import Blueprint
|
||||
from flask import request
|
||||
|
||||
search_bp = Blueprint("search", __name__, url_prefix="/")
|
||||
|
||||
@@ -16,6 +15,63 @@ SEARCH_RESULTS = {
|
||||
}
|
||||
|
||||
|
||||
@search_bp.route("/search/tracks", methods=["GET"])
|
||||
def search_tracks():
|
||||
"""
|
||||
Searches for tracks.
|
||||
"""
|
||||
|
||||
query = request.args.get("q")
|
||||
if not query:
|
||||
return {"error": "No query provided"}, 400
|
||||
|
||||
results = searchlib.SearchTracks(query)()
|
||||
SEARCH_RESULTS["tracks"] = results
|
||||
|
||||
return {
|
||||
"tracks": results[:5],
|
||||
"more": len(results) > 5,
|
||||
}, 200
|
||||
|
||||
|
||||
@search_bp.route("/search/albums", methods=["GET"])
|
||||
def search_albums():
|
||||
"""
|
||||
Searches for albums.
|
||||
"""
|
||||
|
||||
query = request.args.get("q")
|
||||
if not query:
|
||||
return {"error": "No query provided"}, 400
|
||||
|
||||
results = searchlib.SearchAlbums(query)()
|
||||
SEARCH_RESULTS["albums"] = results
|
||||
|
||||
return {
|
||||
"albums": results[:6],
|
||||
"more": len(results) > 6,
|
||||
}, 200
|
||||
|
||||
|
||||
@search_bp.route("/search/artists", methods=["GET"])
|
||||
def search_artists():
|
||||
"""
|
||||
Searches for artists.
|
||||
"""
|
||||
|
||||
query = request.args.get("q")
|
||||
if not query:
|
||||
return {"error": "No query provided"}, 400
|
||||
|
||||
results = searchlib.SearchArtists(query)()
|
||||
SEARCH_RESULTS["artists"] = results
|
||||
|
||||
return {
|
||||
"artists": results[:6],
|
||||
"more": len(results) > 6,
|
||||
}, 200
|
||||
|
||||
|
||||
@search_bp.route("/search")
|
||||
def search():
|
||||
"""
|
||||
@@ -23,25 +79,14 @@ def search():
|
||||
"""
|
||||
query = request.args.get("q") or "Mexican girl"
|
||||
|
||||
albums = searchlib.get_search_albums(query)
|
||||
artists_dicts = []
|
||||
albums = searchlib.SearchAlbums(query)()
|
||||
artists_dicts = searchlib.SearchArtists(query)()
|
||||
|
||||
artist_tracks = searchlib.get_artists(query)
|
||||
tracks = searchlib.SearchTracks(query)()
|
||||
top_artist = artists_dicts[0]["name"]
|
||||
|
||||
for song in artist_tracks:
|
||||
for artist in song.artists:
|
||||
if query.lower() in artist.lower():
|
||||
|
||||
artist_obj = {
|
||||
"name": artist,
|
||||
"image": helpers.check_artist_image(artist),
|
||||
}
|
||||
|
||||
if artist_obj not in artists_dicts:
|
||||
artists_dicts.append(artist_obj)
|
||||
|
||||
_tracks = searchlib.get_tracks(query)
|
||||
tracks = [*_tracks, *artist_tracks]
|
||||
_tracks = searchlib.GetTopArtistTracks(top_artist)()
|
||||
tracks = [*tracks, *[t for t in _tracks if t not in tracks]]
|
||||
|
||||
SEARCH_RESULTS.clear()
|
||||
SEARCH_RESULTS["tracks"] = tracks
|
||||
@@ -50,9 +95,18 @@ def search():
|
||||
|
||||
return {
|
||||
"data": [
|
||||
{"tracks": tracks[:5], "more": len(tracks) > 5},
|
||||
{"albums": albums[:6], "more": len(albums) > 6},
|
||||
{"artists": artists_dicts[:6], "more": len(artists_dicts) > 6},
|
||||
{
|
||||
"tracks": tracks[:5],
|
||||
"more": len(tracks) > 5
|
||||
},
|
||||
{
|
||||
"albums": albums[:6],
|
||||
"more": len(albums) > 6
|
||||
},
|
||||
{
|
||||
"artists": artists_dicts[:6],
|
||||
"more": len(artists_dicts) > 6
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -63,22 +117,25 @@ def search_load_more():
|
||||
Returns more songs, albums or artists from a search query.
|
||||
"""
|
||||
type = request.args.get("type")
|
||||
start = int(request.args.get("start"))
|
||||
index = int(request.args.get("index"))
|
||||
|
||||
print(type, index)
|
||||
print(len(SEARCH_RESULTS["tracks"]))
|
||||
|
||||
if type == "tracks":
|
||||
return {
|
||||
"tracks": SEARCH_RESULTS["tracks"][start : start + 5],
|
||||
"more": len(SEARCH_RESULTS["tracks"]) > start + 5,
|
||||
"tracks": SEARCH_RESULTS["tracks"][index:index + 5],
|
||||
"more": len(SEARCH_RESULTS["tracks"]) > index + 5,
|
||||
}
|
||||
|
||||
elif type == "albums":
|
||||
return {
|
||||
"albums": SEARCH_RESULTS["albums"][start : start + 6],
|
||||
"more": len(SEARCH_RESULTS["albums"]) > start + 6,
|
||||
"albums": SEARCH_RESULTS["albums"][index:index + 6],
|
||||
"more": len(SEARCH_RESULTS["albums"]) > index + 6,
|
||||
}
|
||||
|
||||
elif type == "artists":
|
||||
return {
|
||||
"artists": SEARCH_RESULTS["artists"][start : start + 6],
|
||||
"more": len(SEARCH_RESULTS["artists"]) > start + 6,
|
||||
"artists": SEARCH_RESULTS["artists"][index:index + 6],
|
||||
"more": len(SEARCH_RESULTS["artists"]) > index + 6,
|
||||
}
|
||||
|
||||
+19
-10
@@ -1,11 +1,11 @@
|
||||
"""
|
||||
This module contains mini functions for the server.
|
||||
"""
|
||||
from datetime import datetime
|
||||
import os
|
||||
import random
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
||||
@@ -27,7 +27,9 @@ def background(func):
|
||||
return background_func
|
||||
|
||||
|
||||
def run_fast_scandir(__dir: str, ext: list, full=False) -> Dict[List[str], List[str]]:
|
||||
def run_fast_scandir(__dir: str,
|
||||
ext: list,
|
||||
full=False) -> Dict[List[str], List[str]]:
|
||||
"""
|
||||
Scans a directory for files with a specific extension. Returns a list of files and folders in the directory.
|
||||
"""
|
||||
@@ -60,12 +62,10 @@ def remove_duplicates(tracklist: List[models.Track]) -> List[models.Track]:
|
||||
|
||||
while song_num < len(tracklist) - 1:
|
||||
for index, song in enumerate(tracklist):
|
||||
if (
|
||||
tracklist[song_num].title == song.title
|
||||
and tracklist[song_num].album == song.album
|
||||
and tracklist[song_num].artists == song.artists
|
||||
and index != song_num
|
||||
):
|
||||
if (tracklist[song_num].title == song.title
|
||||
and tracklist[song_num].album == song.album
|
||||
and tracklist[song_num].artists == song.artists
|
||||
and index != song_num):
|
||||
tracklist.remove(song)
|
||||
|
||||
song_num += 1
|
||||
@@ -108,7 +108,8 @@ def check_artist_image(image: str) -> str:
|
||||
"""
|
||||
img_name = image.replace("/", "::") + ".webp"
|
||||
|
||||
if not os.path.exists(os.path.join(app_dir, "images", "artists", img_name)):
|
||||
if not os.path.exists(os.path.join(app_dir, "images", "artists",
|
||||
img_name)):
|
||||
return use_memoji()
|
||||
else:
|
||||
return img_name
|
||||
@@ -120,7 +121,15 @@ def create_album_hash(title: str, artist: str) -> str:
|
||||
"""
|
||||
return (title + artist).replace(" ", "").lower()
|
||||
|
||||
|
||||
def create_new_date():
|
||||
now = datetime.now()
|
||||
str = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
return str
|
||||
return str
|
||||
|
||||
|
||||
def create_safe_name(name: str) -> str:
|
||||
"""
|
||||
Creates a url-safe name from a name.
|
||||
"""
|
||||
return "".join([i for i in name if i not in '/\\:*?"<>|'])
|
||||
|
||||
@@ -6,13 +6,13 @@ import urllib
|
||||
from typing import List
|
||||
|
||||
from app import api
|
||||
from app import helpers
|
||||
from app import instances
|
||||
from app import models
|
||||
from app.lib import taglib
|
||||
from app.lib import trackslib
|
||||
from progress.bar import Bar
|
||||
from tqdm import tqdm
|
||||
from app import helpers
|
||||
|
||||
|
||||
def get_all_albums() -> List[models.Album]:
|
||||
@@ -52,6 +52,10 @@ def create_everything() -> List[models.Track]:
|
||||
def find_album(albums: List[models.Album], hash: str) -> int | None:
|
||||
"""
|
||||
Finds an album by album title and artist.
|
||||
|
||||
:param `albums`: List of album objects.
|
||||
:param `hash`: Hash of album.
|
||||
:return: Index of album in list.
|
||||
"""
|
||||
|
||||
left = 0
|
||||
@@ -62,8 +66,11 @@ def find_album(albums: List[models.Album], hash: str) -> int | None:
|
||||
iter += 1
|
||||
mid = (left + right) // 2
|
||||
|
||||
if albums[mid].hash == hash:
|
||||
return mid
|
||||
try:
|
||||
if albums[mid].hash == hash:
|
||||
return mid
|
||||
except AttributeError:
|
||||
print(albums)
|
||||
|
||||
if albums[mid].hash < hash:
|
||||
left = mid + 1
|
||||
@@ -151,7 +158,7 @@ def get_album_tracks(album: str, artist: str) -> List:
|
||||
return GetAlbumTracks(album, artist).find_tracks()
|
||||
|
||||
|
||||
def create_album(track: dict, tracklist: list) -> models.Album:
|
||||
def create_album(track: dict, tracklist: list) -> dict:
|
||||
"""
|
||||
Generates and returns an album object from a track object.
|
||||
"""
|
||||
|
||||
+38
-20
@@ -1,25 +1,26 @@
|
||||
import os
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from copy import deepcopy
|
||||
|
||||
from multiprocessing import Pool
|
||||
from os import path
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
from app import api
|
||||
from app import settings
|
||||
from app.helpers import create_album_hash, run_fast_scandir
|
||||
from app.helpers import create_album_hash
|
||||
from app.helpers import run_fast_scandir
|
||||
from app.instances import album_instance
|
||||
from app.instances import tracks_instance
|
||||
from app.lib import folderslib
|
||||
from app.lib.albumslib import create_album
|
||||
from app.lib.albumslib import find_album
|
||||
from app.lib.taglib import get_tags
|
||||
from app.logger import Log
|
||||
from app.models import Album, Track
|
||||
|
||||
from app.lib.trackslib import find_track
|
||||
from app.logger import Log
|
||||
from app.models import Album
|
||||
from app.models import Track
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
class Populate:
|
||||
@@ -46,7 +47,7 @@ class Populate:
|
||||
|
||||
def run(self):
|
||||
self.check_untagged()
|
||||
self.tag_all_files()
|
||||
self.get_all_tags()
|
||||
|
||||
if len(self.tagged_tracks) == 0:
|
||||
return
|
||||
@@ -76,6 +77,17 @@ class Populate:
|
||||
|
||||
Log(f"Found {len(self.files)} untagged tracks")
|
||||
|
||||
def process_tags(self, tags: dict):
|
||||
for t in tags:
|
||||
if t is None:
|
||||
continue
|
||||
|
||||
t["albumhash"] = create_album_hash(t["album"], t["albumartist"])
|
||||
self.tagged_tracks.append(t)
|
||||
api.DB_TRACKS.append(t)
|
||||
|
||||
self.folders.add(t["folder"])
|
||||
|
||||
def get_tags(self, file: str):
|
||||
tags = get_tags(file)
|
||||
|
||||
@@ -83,19 +95,27 @@ class Populate:
|
||||
folder = tags["folder"]
|
||||
self.folders.add(folder)
|
||||
|
||||
tags["albumhash"] = create_album_hash(tags["album"], tags["albumartist"])
|
||||
tags["albumhash"] = create_album_hash(tags["album"],
|
||||
tags["albumartist"])
|
||||
self.tagged_tracks.append(tags)
|
||||
api.DB_TRACKS.append(tags)
|
||||
|
||||
def tag_all_files(self):
|
||||
def get_all_tags(self):
|
||||
"""
|
||||
Loops through all the untagged files and tags them.
|
||||
"""
|
||||
|
||||
s = time.time()
|
||||
print(f"Started tagging files")
|
||||
with ThreadPoolExecutor() as executor:
|
||||
executor.map(self.get_tags, self.files)
|
||||
# print(f"Started tagging files")
|
||||
# with ThreadPoolExecutor() as executor:
|
||||
# executor.map(self.get_tags, self.files)
|
||||
|
||||
with Pool(maxtasksperchild=10) as p:
|
||||
tags = p.map(get_tags, tqdm(self.files))
|
||||
self.process_tags(tags)
|
||||
|
||||
# for t in tqdm(self.files):
|
||||
# self.get_tags(t)
|
||||
|
||||
d = time.time() - s
|
||||
Log(f"Tagged {len(self.tagged_tracks)} files in {d} seconds")
|
||||
@@ -149,9 +169,8 @@ class Populate:
|
||||
for album in tqdm(self.pre_albums, desc="Building albums"):
|
||||
self.create_album(album)
|
||||
|
||||
Log(
|
||||
f"{self.exist_count} of {len(self.pre_albums)} albums were already in the database"
|
||||
)
|
||||
Log(f"{self.exist_count} of {len(self.pre_albums)} albums were already in the database"
|
||||
)
|
||||
|
||||
def create_track(self, track: dict):
|
||||
"""
|
||||
@@ -186,9 +205,8 @@ class Populate:
|
||||
with ThreadPoolExecutor() as executor:
|
||||
executor.map(self.create_track, self.tagged_tracks)
|
||||
|
||||
Log(
|
||||
f"Added {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums"
|
||||
)
|
||||
Log(f"Added {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums"
|
||||
)
|
||||
|
||||
def save_albums(self):
|
||||
"""
|
||||
|
||||
+148
-8
@@ -1,19 +1,156 @@
|
||||
"""
|
||||
This library contains all the functions related to the search functionality.
|
||||
"""
|
||||
|
||||
from typing import List
|
||||
from app import models, helpers
|
||||
from app.lib import albumslib
|
||||
|
||||
from app import api
|
||||
from app import helpers
|
||||
from app import models
|
||||
from app.lib import albumslib
|
||||
from rapidfuzz import fuzz
|
||||
from rapidfuzz import process
|
||||
|
||||
ratio = fuzz.ratio
|
||||
wratio = fuzz.WRatio
|
||||
|
||||
|
||||
def get_tracks(query: str) -> List[models.Track]:
|
||||
class Cutoff:
|
||||
"""
|
||||
Gets all songs with a given title.
|
||||
Holds all the default cutoff values.
|
||||
"""
|
||||
tracks = [track for track in api.TRACKS if query.lower() in track.title.lower()]
|
||||
return helpers.remove_duplicates(tracks)
|
||||
|
||||
tracks: int = 70
|
||||
albums: int = 70
|
||||
artists: int = 70
|
||||
|
||||
|
||||
class Limit:
|
||||
"""
|
||||
Holds all the default limit values.
|
||||
"""
|
||||
|
||||
tracks: int = 50
|
||||
albums: int = 50
|
||||
artists: int = 50
|
||||
|
||||
|
||||
class SearchTracks:
|
||||
|
||||
def __init__(self, query) -> None:
|
||||
self.query = query
|
||||
|
||||
def __call__(self) -> List[models.Track]:
|
||||
"""
|
||||
Gets all songs with a given title.
|
||||
"""
|
||||
|
||||
tracks = [track.title for track in api.TRACKS]
|
||||
results = process.extract(
|
||||
self.query,
|
||||
tracks,
|
||||
scorer=fuzz.WRatio,
|
||||
score_cutoff=Cutoff.tracks,
|
||||
limit=Limit.tracks,
|
||||
)
|
||||
|
||||
return [api.TRACKS[i[2]] for i in results]
|
||||
|
||||
|
||||
class SearchArtists:
|
||||
|
||||
def __init__(self, query) -> None:
|
||||
self.query = query
|
||||
|
||||
@staticmethod
|
||||
def get_all_artist_names() -> List[str]:
|
||||
"""
|
||||
Gets all artist names.
|
||||
"""
|
||||
|
||||
artists = [track.artists for track in api.TRACKS]
|
||||
|
||||
f_artists = set()
|
||||
|
||||
for artist in artists:
|
||||
for a in artist:
|
||||
f_artists.add(a)
|
||||
|
||||
return f_artists
|
||||
|
||||
def __call__(self) -> list:
|
||||
"""
|
||||
Gets all artists with a given name.
|
||||
"""
|
||||
|
||||
artists = self.get_all_artist_names()
|
||||
results = process.extract(
|
||||
self.query,
|
||||
artists,
|
||||
scorer=fuzz.WRatio,
|
||||
score_cutoff=Cutoff.artists,
|
||||
limit=Limit.artists,
|
||||
)
|
||||
|
||||
f_artists = []
|
||||
for artist in results:
|
||||
aa = {
|
||||
"name": artist[0],
|
||||
"image": helpers.create_safe_name(artist[0]) + ".webp",
|
||||
}
|
||||
f_artists.append(aa)
|
||||
|
||||
return f_artists
|
||||
|
||||
|
||||
class SearchAlbums:
|
||||
|
||||
def __init__(self, query) -> None:
|
||||
self.query = query
|
||||
|
||||
def get_albums_by_name(self) -> List[models.Album]:
|
||||
"""
|
||||
Gets all albums with a given title.
|
||||
"""
|
||||
|
||||
albums = [album.title for album in api.ALBUMS]
|
||||
results = process.extract(self.query, albums)
|
||||
return [api.ALBUMS[i[2]] for i in results]
|
||||
|
||||
def __call__(self) -> List[models.Album]:
|
||||
"""
|
||||
Gets all albums with a given title.
|
||||
"""
|
||||
|
||||
albums = [a.title for a in api.ALBUMS]
|
||||
results = process.extract(
|
||||
self.query,
|
||||
albums,
|
||||
scorer=fuzz.WRatio,
|
||||
score_cutoff=Cutoff.albums,
|
||||
limit=Limit.albums,
|
||||
)
|
||||
|
||||
return [api.ALBUMS[i[2]] for i in results]
|
||||
|
||||
# get all artists that matched the query
|
||||
# for get all albums from the artists
|
||||
# get all albums that matched the query
|
||||
# return [**artist_albums **albums]
|
||||
|
||||
# recheck next and previous artist on play next or add to playlist
|
||||
|
||||
|
||||
class GetTopArtistTracks:
|
||||
|
||||
def __init__(self, artist: str) -> None:
|
||||
self.artist = artist
|
||||
|
||||
def __call__(self) -> List[models.Track]:
|
||||
"""
|
||||
Gets all tracks from a given artist.
|
||||
"""
|
||||
|
||||
return [track for track in api.TRACKS if self.artist in track.artists]
|
||||
|
||||
|
||||
def get_search_albums(query: str) -> List[models.Album]:
|
||||
@@ -27,4 +164,7 @@ def get_artists(artist: str) -> List[models.Track]:
|
||||
"""
|
||||
Gets all songs with a given artist.
|
||||
"""
|
||||
return [track for track in api.TRACKS if artist.lower() in str(track.artists).lower()]
|
||||
return [
|
||||
track for track in api.TRACKS
|
||||
if artist.lower() in str(track.artists).lower()
|
||||
]
|
||||
|
||||
@@ -154,7 +154,7 @@ def parse_disk_number(audio):
|
||||
return disk_number
|
||||
|
||||
|
||||
def get_tags(fullpath: str) -> dict:
|
||||
def get_tags(fullpath: str) -> dict | None:
|
||||
"""
|
||||
Returns a dictionary of tags for a given file.
|
||||
"""
|
||||
|
||||
@@ -7,14 +7,14 @@ import time
|
||||
from app import api
|
||||
from app import instances
|
||||
from app import models
|
||||
from app.helpers import create_album_hash
|
||||
from app.lib import folderslib
|
||||
from app.lib.albumslib import create_album, find_album
|
||||
from app.lib.albumslib import create_album
|
||||
from app.lib.albumslib import find_album
|
||||
from app.lib.taglib import get_tags
|
||||
from watchdog.events import PatternMatchingEventHandler
|
||||
from watchdog.observers import Observer
|
||||
|
||||
from app.helpers import create_album_hash
|
||||
|
||||
|
||||
class OnMyWatch:
|
||||
"""
|
||||
@@ -50,18 +50,19 @@ def add_track(filepath: str) -> None:
|
||||
tags = get_tags(filepath)
|
||||
|
||||
if tags is not None:
|
||||
tags["albumhash"] = create_album_hash(tags["album"], tags["albumartist"])
|
||||
hash = create_album_hash(tags["album"], tags["albumartist"])
|
||||
tags["albumhash"] = hash
|
||||
api.DB_TRACKS.append(tags)
|
||||
|
||||
albumindex = find_album(tags["album"], tags["albumartist"])
|
||||
albumindex = find_album(api.ALBUMS, hash)
|
||||
|
||||
if albumindex is not None:
|
||||
album = api.ALBUMS[albumindex]
|
||||
else:
|
||||
album_data = create_album(tags, api.DB_TRACKS)
|
||||
instances.album_instance.insert_album(album_data)
|
||||
|
||||
album = models.Album(album_data)
|
||||
|
||||
instances.album_instance.insert_album(album)
|
||||
api.ALBUMS.append(album)
|
||||
|
||||
tags["image"] = album.image
|
||||
@@ -86,7 +87,8 @@ def remove_track(filepath: str) -> None:
|
||||
fpath = filepath.replace(fname, "")
|
||||
|
||||
try:
|
||||
trackid = instances.tracks_instance.get_song_by_path(filepath)["_id"]["$oid"]
|
||||
trackid = instances.tracks_instance.get_song_by_path(
|
||||
filepath)["_id"]["$oid"]
|
||||
except TypeError:
|
||||
print(f"💙 Watchdog Error: Error removing track {filepath} TypeError")
|
||||
return
|
||||
@@ -152,7 +154,14 @@ class Handler(PatternMatchingEventHandler):
|
||||
Fired when a created file is closed.
|
||||
"""
|
||||
print("⚫ closed ~~~")
|
||||
self.files_to_process.remove(event.src_path)
|
||||
try:
|
||||
self.files_to_process.remove(event.src_path)
|
||||
except ValueError:
|
||||
"""
|
||||
The file was already removed from the list, or it was not in the list to begin with.
|
||||
"""
|
||||
pass
|
||||
|
||||
add_track(event.src_path)
|
||||
|
||||
|
||||
|
||||
+25
-8
@@ -6,8 +6,8 @@ from dataclasses import field
|
||||
from typing import List
|
||||
|
||||
from app import api
|
||||
from app.exceptions import TrackExistsInPlaylist
|
||||
from app import helpers
|
||||
from app.exceptions import TrackExistsInPlaylist
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@@ -18,7 +18,7 @@ class Track:
|
||||
|
||||
trackid: str
|
||||
title: str
|
||||
artists: str
|
||||
artists: list
|
||||
albumartist: str
|
||||
album: str
|
||||
folder: str
|
||||
@@ -32,8 +32,11 @@ class Track:
|
||||
albumhash: str
|
||||
|
||||
def __init__(self, tags):
|
||||
try:
|
||||
self.trackid = tags["_id"]["$oid"]
|
||||
except KeyError:
|
||||
print(tags)
|
||||
|
||||
self.trackid = tags["_id"]["$oid"]
|
||||
self.title = tags["title"]
|
||||
self.artists = tags["artists"].split(", ")
|
||||
self.albumartist = tags["albumartist"]
|
||||
@@ -49,6 +52,22 @@ class Track:
|
||||
self.albumhash = tags["albumhash"]
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class Artist:
|
||||
"""
|
||||
Artist class
|
||||
"""
|
||||
|
||||
artistid: str
|
||||
name: str
|
||||
image: str
|
||||
|
||||
def __init__(self, tags):
|
||||
self.artistid = tags["_id"]["$oid"]
|
||||
self.name = tags["name"]
|
||||
self.image = tags["image"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class Album:
|
||||
"""
|
||||
@@ -78,11 +97,9 @@ class Album:
|
||||
|
||||
def get_p_track(ptrack):
|
||||
for track in api.TRACKS:
|
||||
if (
|
||||
track.title == ptrack["title"]
|
||||
and track.artists == ptrack["artists"]
|
||||
and ptrack["album"] == track.album
|
||||
):
|
||||
if (track.title == ptrack["title"]
|
||||
and track.artists == ptrack["artists"]
|
||||
and ptrack["album"] == track.album):
|
||||
return track
|
||||
|
||||
|
||||
|
||||
Generated
+153
-1
@@ -118,6 +118,14 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "jarowinkler"
|
||||
version = "1.0.2"
|
||||
description = "library for fast approximate string matching using Jaro and Jaro-Winkler similarity"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.0.3"
|
||||
@@ -181,6 +189,20 @@ snappy = ["python-snappy"]
|
||||
srv = ["dnspython (>=1.16.0,<3.0.0)"]
|
||||
zstd = ["zstandard"]
|
||||
|
||||
[[package]]
|
||||
name = "rapidfuzz"
|
||||
version = "2.0.11"
|
||||
description = "rapid fuzzy string matching"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
jarowinkler = ">=1.0.2,<1.1.0"
|
||||
|
||||
[package.extras]
|
||||
full = ["numpy"]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.27.1"
|
||||
@@ -262,7 +284,7 @@ watchdog = ["watchdog"]
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "a2a0355e78fe2881e226dabda21b6d972a98aa4f1d60bf9f7f74957bb8ba6bea"
|
||||
content-hash = "143a7a7f2158b3c3abfcfec207443a90f1a3d31a6935dfdf5e5e7452e226ca58"
|
||||
|
||||
[metadata.files]
|
||||
certifi = [
|
||||
@@ -309,6 +331,88 @@ itsdangerous = [
|
||||
{file = "itsdangerous-2.0.1-py3-none-any.whl", hash = "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c"},
|
||||
{file = "itsdangerous-2.0.1.tar.gz", hash = "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0"},
|
||||
]
|
||||
jarowinkler = [
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:71772fcd787e0286b779de0f1bef1e0a25deb4578328c0fc633bc345f13ffd20"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:912ee0a465822a8d659413cebc1ab9937ac5850c9cd1e80be478ba209e7c8095"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0320f7187dced1ad413bf2c3631ec47567e65dfdea92c523aafb2c085ae15035"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58bc6a8f01b0dfdf3721f9a4954060addeccf8bbe5e72a71cf23a88ce0d30440"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:679ec7a42f70baa61f3a214d1b59cec90fc036021c759722075efcc8697e7b1f"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dde57d47962d6a4436d8a3b477bcc8233c6da28e675027eb3a490b0d6dc325be"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:657f50204970fac8f120c293e52a3451b742c9b26125010405ec7365cb6e2a49"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04f18a7398766b36ffbe4bcd26d34fcd6ed01f4f2f7eea13e316e6cca0e10c98"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:33a24b380e2c076eabf2d3e12eee56b6bf10b1f326444e18c36a495387dbf0de"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e1d7d6e6c98fb785026584373240cc4076ad21033f508973faae05e846206e8c"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e50c750a45c800d91134200d8cbf746258ed357a663e97cc0348ee42a948386a"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:5b380afce6cdc25a4dafd86874f07a393800577c05335c6ad67ccda41db95c60"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e73712747ac5d2218af3ed3c1600377f18a0a45af95f22c39576165aea2908b4"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-win32.whl", hash = "sha256:9511f4e1f00c822e08dbffeb69e15c75eb294a5f24729815a97807ecf03d22eb"},
|
||||
{file = "jarowinkler-1.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a5c44f92e9ac6088286292ecb69e970adc2b98e139b8923bce9bbb9d484e6a0f"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:02b0bf34ffc2995b695d9b10d2f18c1c447fbbdb7c913a84a0a48c186ccca3b8"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df7a8e45176298a1210c06f8b2328030cc3c93a45dab068ac1fbc9cf075cd95b"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da27a9c206249a50701bfa5cfbbb3a04236e1145b2b0967e825438acb14269bf"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:43ea0155379df92021af0f4a32253be3953dfa0f050ec3515f314b8f48a96674"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f33b6b1687db1be1abba60850628ee71547501592fcf3504e021274bc5ccb7a"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff304de32ee6acd5387103a0ad584060d8d419aa19cbbeca95204de9c4f01171"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:662dd6f59cca536640be0cda32c901989504d95316b192e6aa41d098fa08c795"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:01f85abb75fa43e98db34853d35570d98495ee2fcbbf45a93838e0289c162f19"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:5b9332dcc8130af4101c9752a03e977c54b8c12982a2a3ca4c2e4cc542accc00"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:af765b037404a536c372e33ddd4c430aea28f1d82a8ef51a2955442b8b690577"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:aea2c7d66b57c56d00f9c45ae7862d86e3ae84368ecea17f3552c0052a7f3bcf"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-win32.whl", hash = "sha256:8b1288a09a8d100e9bf7cf9ce1329433db73a0d0350d74c2c6f5c31ac69096cf"},
|
||||
{file = "jarowinkler-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ed39199b0e806902347473c65e5c05933549cf7e55ba628c6812782f2c310b19"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:473b057d7e5a0f5e5b8c0e0f7960d3ca2f2954c3c93fd7a9fb2cc4bc3cc940fb"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdb892dbbbd77b3789a10b2ce5e8acfe5821cc6423e835bae2b489159f3c2211"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:012a8333328ce061cba1ff081843c8d80eb1afe8fa2889ad29d767ea3fdc7562"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3421120c07ee6d3f59c5adde32eb9a050cfd1b3666b0e2d8c337d934a9d091f9"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dad57327cc90f8daa3afb98e2d274d7dd1b60651f32717449be95d3b3366d61a"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4fd1757eff43df97227fd63d9c8078582267a0b25cefef6f6a64d3e46e80ba2"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:32269ebbcb860f01c055d9bb145b4cc91990f62c7644a85b21458b4868621113"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3b5a0839e84f5ff914b01b5b94d0273954affce9cc2b2ee2c31fe2fcb9c8ae76"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:6c9d3a9ef008428b5dce2855eebe2b6127ea7a7e433aedf240653fad4bd4baa6"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:a3d7759d8a66ee05595bde012f93da8a63499f38205e2bb47022c52bd6c47108"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2ba1b1b0bf45042a9bbb95d272fd8b0c559fe8f6806f088ec0372899e1bc6224"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-win32.whl", hash = "sha256:4cb33f4343774d69abf8cf65ad57919e7a171c44ba6ad57b08147c3f0f06b073"},
|
||||
{file = "jarowinkler-1.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:0392b72ddb5ab5d6c1d5df94dbdac7bf229670e5e64b2b9a382d02d6158755e5"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:94f663ad85bc7a89d7e8b6048f93a46d2848a0570ab07fc895a239b9a5d97b93"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:895a10766ff3db15e7cf2b735e4277bee051eaafb437aaaef2c5de64a5c3f05c"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0c1a84e770b3ec7385a4f40efb30bdc96f96844564f91f8d3937d54a8969d82c"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27defe81d76e02b3929322baea999f5232837e7f308c2dc5b37de7568c2bc583"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:158f117481388f8d23fe4bd2567f37be0ccae0f4631c34e4b0345803147da207"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:427c675b4f3e83c79a4b6af7441f29e30a173c7a0ae72a54f51090eee7a8ae02"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90a7f3fd173339bc62e52c02f43d50c947cb3af9cda41646e218aea13547e0c2"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3975cbe8b6ae13fc63d74bcbed8dac1577078d8cd8728e60621fe75885d2a8c5"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:141840f33345b00abd611839080edc99d4d31abd2dcf701a3e50c90f9bfb2383"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f592f9f6179e347a5f518ca7feb9bf3ac068f2fad60ece5a0eef5e5e580d4c8b"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:30565d70396eb9d1eb622e1e707ddc2f3b7a9692558b8bf4ea49415a5ca2f854"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:35fc430c11b80a43ed826879c78c4197ec665d5150745b3668bec961acf8a757"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e4cf4b7090f0c4075bec1638717f54b22c3b0fe733dc87146a19574346ed3161"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-win32.whl", hash = "sha256:199f4f7edbc49439a97440caa1e244d2e33da3e16d7b0afce4e4dfd307e555c7"},
|
||||
{file = "jarowinkler-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:b587e8fdd96cc470d6bdf428129c65264731b09b5db442e2d092e983feec4aab"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4b233180b3e2f2d7967aa570d36984e9d2ec5a9067c0d1c44cd3b805d9da9363"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2220665a1f52262ae8b76e3baf474ebcd209bfcb6a7cada346ffd62818f5aa3e"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08c98387e04e749c84cc967db628e5047843f19f87bf515a35b72f7050bc28ad"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d710921657442ad3c942de684aba0bdf16b7de5feed3223b12f3b2517cf17f7c"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:401c02ac7245103826f54c816324274f53d50b638ab0f8b359a13055a7a6e793"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a1929a0029f208cc9244499dc93b4d52ee8e80d2849177d425cf6e0be1ea781"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab25d147be9b04e7de2d28a18e72fadc152698c3e51683c6c61f73ffbae2f9e"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:465cfdff355ec9c55f65fd1e1315260ec20c8cff0eb90d9f1a0ad8d503dc002b"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:29ef1113697cc74c2f04bc15008abbd726cb2d5b01c040ba87c6cb7abd1d0e0d"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:61b57c8b36361ec889f99f761441bb0fa21b850a5eb3305dea25fef68f6a797b"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ee9d9af1bbf194d78f4b69c2139807c23451068b27a053a1400d683d6f36c61d"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a9b33b0ceb472bbc65683467189bd032c162256b2a137586ee3448a9f8f886ec"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:582f6e213a6744883ced44482a51efcc21ae632defac27f12f6430a8e99b1070"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-win32.whl", hash = "sha256:4d1c8f403016d5c0262de7a8588eee370c37a609e1f529f8407e99a70d020af7"},
|
||||
{file = "jarowinkler-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:ab50ffa66aa201616871c1b90ac0790f56666118db3c8a8fcb3a7a6e03971510"},
|
||||
{file = "jarowinkler-1.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8e59a289dcf93504ab92795666c39b2dbe98ac18655201992a7e6247de676bf4"},
|
||||
{file = "jarowinkler-1.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c36eccdc866f06a7b35da701bd8f91e0dfc83b35c07aba75ce8c906cbafaf184"},
|
||||
{file = "jarowinkler-1.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123163f01a5c43f12e4294e7ce567607d859e1446b1a43bd6cd404b3403ffa07"},
|
||||
{file = "jarowinkler-1.0.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d41fdecd907189e47c7d478e558ad417da38bf3eb34cc20527035cb3fca3e2b8"},
|
||||
{file = "jarowinkler-1.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e7829368fc91de225f37f6325f8d8ec7ad831dc5b0e9547f1977e2fdc85eccc1"},
|
||||
{file = "jarowinkler-1.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278595417974553a8fdf3c8cce5c2b4f859335344075b870ecb55cc416eb76cf"},
|
||||
{file = "jarowinkler-1.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:208fc49741db5d3e6bbd4a2f7b32d32644b462bf205e7510eca4e2d530225f03"},
|
||||
{file = "jarowinkler-1.0.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:924afcab6739c453f1c3492701d185d71dc0e5ba15692bd0bfa6d482c7e8f79e"},
|
||||
{file = "jarowinkler-1.0.2.tar.gz", hash = "sha256:788ac33e6ffdbd78fd913b481e37cfa149288575f087a1aae1a4ce219cb1c654"},
|
||||
]
|
||||
jinja2 = [
|
||||
{file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"},
|
||||
{file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"},
|
||||
@@ -513,6 +617,54 @@ pymongo = [
|
||||
{file = "pymongo-4.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:65f8a93816dcb2202710839907759aca9eece94d9f13215686f224fcc8966f9e"},
|
||||
{file = "pymongo-4.0.1.tar.gz", hash = "sha256:13d0624c13a91da71fa0d960205d93b3d98344481be865ee7cc238c972d41d73"},
|
||||
]
|
||||
rapidfuzz = [
|
||||
{file = "rapidfuzz-2.0.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eb54edd0fa8620d37a7c0762895260bc75a6cc083d161b14d40a562b6f303975"},
|
||||
{file = "rapidfuzz-2.0.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8093b5f234be618bb8cfe34d65c072fee362fbd13f6c1b37f80eac0f30c24cfa"},
|
||||
{file = "rapidfuzz-2.0.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aea72586c08ba8ce08c37c21bb7c383df740dc7d6e921423e1881570be62ed15"},
|
||||
{file = "rapidfuzz-2.0.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ee9a057b7638e91377b217df3724d4adefec3936617180b3df1f64fa64cd995"},
|
||||
{file = "rapidfuzz-2.0.11-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bad453a76f6832a99251beb89c352a4f436f4e7687a5843b080c294dba68d8d6"},
|
||||
{file = "rapidfuzz-2.0.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a545627a7d45ea4ad1cb66fb6ad7b951825b0e97053056cda9d2f5fbb30abe3e"},
|
||||
{file = "rapidfuzz-2.0.11-cp310-cp310-win32.whl", hash = "sha256:8e583595efe5afdd68a7b5423cbd5fff0d1870d60cee16af17897f701f39d933"},
|
||||
{file = "rapidfuzz-2.0.11-cp310-cp310-win_amd64.whl", hash = "sha256:09efd5a02a33dfb18ec6f28b85f102b51cbac080e624924f3a4f36d3b08962ef"},
|
||||
{file = "rapidfuzz-2.0.11-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c81216ecff325765bb441caf7f50a1f55aa66192aca12ec6d448b509c9387a39"},
|
||||
{file = "rapidfuzz-2.0.11-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b555dbebb413ab66c2cd394338c860094a5464f9b63faeb40ebec44271c460b"},
|
||||
{file = "rapidfuzz-2.0.11-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a007fe85dfff7a961daba13884629dcd9ab45197a2fc40749a7e8f750e7715a4"},
|
||||
{file = "rapidfuzz-2.0.11-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1031420c083681b41346267e26f9f76ef2c1544a0129fa67b07239d7a9ab9fd6"},
|
||||
{file = "rapidfuzz-2.0.11-cp36-cp36m-win32.whl", hash = "sha256:4b16147122ec4c5e4a31131b8530e674ba1b3e74e2b43b73aedc6bd0021fcae6"},
|
||||
{file = "rapidfuzz-2.0.11-cp36-cp36m-win_amd64.whl", hash = "sha256:de7559765e1da54d8d42495368e0a9852041cf8d4e077fb27811d6f009611a4b"},
|
||||
{file = "rapidfuzz-2.0.11-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ace59b7857e5d5b252564dd60d840667c19c00d357c7ba32e9671b68615dc49a"},
|
||||
{file = "rapidfuzz-2.0.11-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09d83e5ab57fb61a6003a3607494b1f443978e8d6b199fed3094e92f466f3bba"},
|
||||
{file = "rapidfuzz-2.0.11-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3a53797613b53e93adbf9c410260aecde5ab1d7cd1b07792be1ee4800716598"},
|
||||
{file = "rapidfuzz-2.0.11-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9084a550719aff3752e5a63e32d381d64b09264cabf35d5e21e6a9f0e91baca"},
|
||||
{file = "rapidfuzz-2.0.11-cp37-cp37m-win32.whl", hash = "sha256:35c8f2cae3e2079616fdf90c6b6bcf850d3810c9184c6e89a4826b6d0af88974"},
|
||||
{file = "rapidfuzz-2.0.11-cp37-cp37m-win_amd64.whl", hash = "sha256:1da580130b37a007684ab9dc6f85125e3c0d06c9f9df349e7bd52e312811c436"},
|
||||
{file = "rapidfuzz-2.0.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:757ae64598a93d0f8a21007c1abd6800f38c04e4b89167ca7b833ce30f54aef3"},
|
||||
{file = "rapidfuzz-2.0.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f5b0fd8f6bde8d89c07b76643c9f3a01e2e089b246a97b721e7fe97fdaa41820"},
|
||||
{file = "rapidfuzz-2.0.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a13cea3303b444af49417352cd11830ea2245d4e5a82bb06b6895638b81c6029"},
|
||||
{file = "rapidfuzz-2.0.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96dba6ef863cba7efd22077ba28e19a8829b523c7c7e41304c568a6ab91fc4d"},
|
||||
{file = "rapidfuzz-2.0.11-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcecc662df808ad051d9524608a3682fd80d882c93664adbaab4c7b0796e385"},
|
||||
{file = "rapidfuzz-2.0.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff74f3abd0ed473f81ba67d3207ca6db74b8b50ebae1a6734ba199a8d90d67b4"},
|
||||
{file = "rapidfuzz-2.0.11-cp38-cp38-win32.whl", hash = "sha256:b3e7eea1dd304bac4ac74a1af71da35bb68bf6060f5d6b4ad8a3e4e2c84d5110"},
|
||||
{file = "rapidfuzz-2.0.11-cp38-cp38-win_amd64.whl", hash = "sha256:5b203e83adc10dbe961a3000fa09cc47f5672d2c98c3fd2f6bb7b0df225805ee"},
|
||||
{file = "rapidfuzz-2.0.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fe837f8b305c59549e2eec6fe8dbdd7344eeef0033fa4ee90af65f72b32c25f"},
|
||||
{file = "rapidfuzz-2.0.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc2c8aa23de4a0bef2162440f5095f606052c289059fbeb03180740783e25e6b"},
|
||||
{file = "rapidfuzz-2.0.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ebb03a6a5171233958b6adcada00c7521186ea4b78b6652b99d94d5dbf59c809"},
|
||||
{file = "rapidfuzz-2.0.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f21d8754ee49ee8da73ad5e746495a27fd29ab769f5e45ede4d8232955e0237"},
|
||||
{file = "rapidfuzz-2.0.11-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23c79ca555f188445f2e054b40a89c82e5d21b22d34f00da6e7491a6d70feff5"},
|
||||
{file = "rapidfuzz-2.0.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b10de1ea834be1f26d1f35f3e1d1f8c003c7951a7475ab9b28b5a62e9f6f0c0"},
|
||||
{file = "rapidfuzz-2.0.11-cp39-cp39-win32.whl", hash = "sha256:1bda150aca38c4d4739780c3a99c190c05101839adc10ab7804ed86000440267"},
|
||||
{file = "rapidfuzz-2.0.11-cp39-cp39-win_amd64.whl", hash = "sha256:f4ea654ef221a57b47523fe70d7423254dd285f73948b9d8c1215610d2a38e9e"},
|
||||
{file = "rapidfuzz-2.0.11-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2542f0a3c4079b15c0485ece589e5a248633de84326e4a3ca63ea024a0b59775"},
|
||||
{file = "rapidfuzz-2.0.11-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dca3c02511d23a58ef14f2fbcd7b311eae1bc40e3d36be493ef22b9572ebed1"},
|
||||
{file = "rapidfuzz-2.0.11-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbb649a978fab0232cefdeb67321d853c676b3ebb7481b8b80030905a42d799"},
|
||||
{file = "rapidfuzz-2.0.11-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:081415323e94e0016109715438d4ccb233ab038b09ba3cf79038e50601a410e9"},
|
||||
{file = "rapidfuzz-2.0.11-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a80efb64f1b38c64f04f4ca6881c9684d7912dc9124ecbf953c9b541f935b33c"},
|
||||
{file = "rapidfuzz-2.0.11-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ac8ab8106bc7b0ffab539baa5279a850c61b71ccad86dc11503bc084f6ac1af"},
|
||||
{file = "rapidfuzz-2.0.11-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3933a1cfdf6ab4e059d8eb68460fa430a4f6be06431d1a8b05f7fecdd63e586e"},
|
||||
{file = "rapidfuzz-2.0.11-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:537b72d954ff395cefc210fc7e41810a26e84ed7f1e93d0dffe3669277d6ea23"},
|
||||
{file = "rapidfuzz-2.0.11-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2d167b1bf92a60eefbaea3abf646baa2a3aa7125595e129c8890072706a80ac"},
|
||||
{file = "rapidfuzz-2.0.11.tar.gz", hash = "sha256:934b65fea75e3bd310d74903ec69ff3df061b3058ab5b7f49ab772958109bca8"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
|
||||
{file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
|
||||
|
||||
@@ -18,6 +18,7 @@ Pillow = "^9.0.1"
|
||||
Flask-Caching = "^1.10.1"
|
||||
"colorgram.py" = "^1.2.0"
|
||||
tqdm = "^4.64.0"
|
||||
rapidfuzz = "^2.0.11"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
|
||||
+15
-5
@@ -5,8 +5,18 @@
|
||||
#python manage.py
|
||||
|
||||
gpath=$(poetry run which gunicorn)
|
||||
cd app
|
||||
"$gpath" -b 0.0.0.0:9877 -w 4 --threads=2 "imgserver:app" &
|
||||
echo "Booted image server"
|
||||
cd ../
|
||||
"$gpath" -b 0.0.0.0:9876 -w 1 --threads=4 "manage:create_app()" #--log-level=debug
|
||||
while getopts ':s' opt; do
|
||||
case $opt in
|
||||
s)
|
||||
echo "🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴🔴"
|
||||
cd "./app"
|
||||
"$gpath" -b 0.0.0.0:9877 -w 4 --threads=2 "imgserver:app" &
|
||||
cd ../
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
"$gpath" -b 0.0.0.0:9876 -w 1 --threads=4 "manage:create_app()"
|
||||
|
||||
+11
-30
@@ -2,13 +2,9 @@
|
||||
<ContextMenu />
|
||||
<Modal />
|
||||
<Notification />
|
||||
<div class="l-container" :class="{ collapsed: collapsed }">
|
||||
<div class="l-sidebar">
|
||||
<div id="logo-container">
|
||||
<router-link :to="{ name: 'Home' }" v-if="!collapsed"
|
||||
><div class="logo"></div
|
||||
></router-link>
|
||||
</div>
|
||||
<div class="l-container">
|
||||
<div class="l-sidebar rounded">
|
||||
<Logo />
|
||||
<Navigation />
|
||||
<div class="l-album-art">
|
||||
<nowPlaying />
|
||||
@@ -24,7 +20,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import Navigation from "./components/LeftSidebar/Navigation.vue";
|
||||
|
||||
import Main from "./components/RightSideBar/Main.vue";
|
||||
@@ -38,13 +34,14 @@ import Modal from "./components/modal.vue";
|
||||
import Notification from "./components/Notification.vue";
|
||||
import useQStore from "./stores/queue";
|
||||
import listenForKeyboardEvents from "./composables/keyboard";
|
||||
import Logo from "./components/Logo.vue";
|
||||
|
||||
const RightSideBar = Main;
|
||||
const context_store = useContextStore();
|
||||
const queue = useQStore();
|
||||
const app_dom = document.getElementById("app");
|
||||
|
||||
queue.readQueueFromLocalStorage();
|
||||
queue.readQueue();
|
||||
listenForKeyboardEvents(queue);
|
||||
|
||||
app_dom.addEventListener("click", (e) => {
|
||||
@@ -55,38 +52,22 @@ app_dom.addEventListener("click", (e) => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./assets/css/mixins.scss";
|
||||
|
||||
.l-sidebar {
|
||||
position: relative;
|
||||
|
||||
.l-album-art {
|
||||
width: calc(100% - 2rem);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
#logo-container {
|
||||
position: relative;
|
||||
height: 3.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
#toggle {
|
||||
position: absolute;
|
||||
width: 3rem;
|
||||
height: 100%;
|
||||
background: url(./assets/icons/menu.svg) no-repeat center;
|
||||
background-size: 2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 4.5rem;
|
||||
width: 15rem;
|
||||
background: url(./assets/icons/logo.svg) no-repeat 1rem;
|
||||
background-size: 9rem;
|
||||
}
|
||||
|
||||
|
||||
.r-sidebar {
|
||||
&::-webkit-scrollbar {
|
||||
|
||||
@@ -8,45 +8,15 @@
|
||||
height: 100%;
|
||||
padding-right: $small;
|
||||
|
||||
|
||||
|
||||
@include phone-only {
|
||||
grid-template-columns: 1fr 9.2rem;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
padding-top: $small;
|
||||
margin-left: $small;
|
||||
|
||||
.art {
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
background-image: url("../../images/null.webp");
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.desc {
|
||||
width: calc(100% - 5rem);
|
||||
margin-left: $small;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: -$small;
|
||||
|
||||
.artists {
|
||||
font-size: 0.8rem;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.controlsx {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 12rem 1fr 12rem;
|
||||
align-items: center;
|
||||
padding: $small;
|
||||
|
||||
@@ -55,10 +25,6 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.controls-bottom {
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
.progress-bottom {
|
||||
width: 100%;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@import "../css/ProgressBar.scss";
|
||||
@import "mixins.scss";
|
||||
|
||||
:root {
|
||||
--separator: #ffffff46;
|
||||
@@ -20,6 +21,8 @@ body {
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.heading {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
@@ -39,7 +42,6 @@ a {
|
||||
}
|
||||
|
||||
.border {
|
||||
// border: solid 1px $gray5;
|
||||
background-color: $black;
|
||||
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@@ -95,11 +97,11 @@ a {
|
||||
}
|
||||
|
||||
.l-sidebar {
|
||||
width: 15rem;
|
||||
width: 17rem;
|
||||
grid-area: l-sidebar;
|
||||
padding-top: 0.5rem;
|
||||
// border-right: solid 1px $gray3;
|
||||
background-color: $black;
|
||||
background-color: $gray;
|
||||
margin: $small;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
@@ -107,11 +109,6 @@ a {
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.collapsed .l-sidebar {
|
||||
width: 3rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.ellip {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
@mixin ximage {
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
@@ -3,17 +3,19 @@
|
||||
<div class="a-header rounded">
|
||||
<div class="art">
|
||||
<div
|
||||
class="image shadow-lg"
|
||||
class="image shadow-lg rounded"
|
||||
:style="{
|
||||
backgroundImage: `url("${imguri + props.album.image}")`,
|
||||
}"
|
||||
v-motion-slide-from-left
|
||||
></div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="top">
|
||||
<div class="top" v-motion-slide-from-top>
|
||||
<div
|
||||
class="h"
|
||||
v-if="props.album.artist.toLowerCase() == 'various artists'"
|
||||
|
||||
>
|
||||
Compilation
|
||||
</div>
|
||||
@@ -23,8 +25,9 @@
|
||||
<div class="bottom">
|
||||
<div class="stats">
|
||||
{{ props.album.count }} Tracks •
|
||||
{{ perks.formatSeconds(props.album.duration, "long") }} •
|
||||
{{ props.album.date }} • {{ props.album.artist }}
|
||||
{{ formatSeconds(props.album.duration, true) }} •
|
||||
{{ props.album.date }} •
|
||||
{{ props.album.artist }}
|
||||
</div>
|
||||
<PlayBtnRect :source="playSources.album" />
|
||||
</div>
|
||||
@@ -34,11 +37,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import perks from "../../composables/perks.js";
|
||||
import { AlbumInfo } from "../../interfaces.js";
|
||||
import PlayBtnRect from "../shared/PlayBtnRect.vue";
|
||||
import { playSources } from "../../composables/enums";
|
||||
import { formatSeconds } from "../../composables/perks";
|
||||
import { paths } from "../../config";
|
||||
import { AlbumInfo } from "../../interfaces";
|
||||
import PlayBtnRect from "../shared/PlayBtnRect.vue";
|
||||
|
||||
const imguri = paths.images.thumb;
|
||||
const props = defineProps<{
|
||||
@@ -89,7 +92,7 @@ extrackColors();
|
||||
|
||||
.title {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 1000;
|
||||
font-weight: 600;
|
||||
color: white;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
<!-- <template>
|
||||
<template>
|
||||
<div class="b-bar">
|
||||
<div class="grid rounded">
|
||||
<div class="controlsx rounded">
|
||||
<div class="controls controls-bottom">
|
||||
<div class="controls-bottom">
|
||||
<HotKeys />
|
||||
</div>
|
||||
<div class="progress progress-bottom">
|
||||
<span class="durationx">{{ formatSeconds(current_pos) }}</span>
|
||||
<span class="durationx">{{ formatSeconds(q.track.current_time) }}</span>
|
||||
<Progress />
|
||||
<span class="durationx">{{
|
||||
formatSeconds(state.current.value.length)
|
||||
}}</span>
|
||||
<span class="durationx">{{ formatSeconds(q.length) }}</span>
|
||||
</div>
|
||||
<div class="r-group">
|
||||
<div id="heart" class="image ctrl-btn"></div>
|
||||
<div id="add-to" class="image ctrl-btn"></div>
|
||||
<div id="repeat" class="image ctrl-btn"></div>
|
||||
</div>
|
||||
<div class="controls controls-bottom"></div>
|
||||
</div>
|
||||
<div class="volume-group"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import "../../assets/css/BottomBar/BottomBar.scss";
|
||||
import Progress from "../shared/Progress.vue";
|
||||
import HotKeys from "../shared/HotKeys.vue";
|
||||
import state from "../../composables/state";
|
||||
import perks from "../../composables/perks";
|
||||
import playAudio from "../../composables/playAudio";
|
||||
<script setup lang="ts">
|
||||
import "@/assets/css/BottomBar/BottomBar.scss";
|
||||
import Progress from "../LeftSidebar/NP/Progress.vue";
|
||||
import HotKeys from "../LeftSidebar/NP/HotKeys.vue";
|
||||
import { formatSeconds } from "@/composables/perks";
|
||||
|
||||
const current_pos = playAudio.current_time;
|
||||
const formatSeconds = perks.formatSeconds;
|
||||
</script> -->
|
||||
import useQStore from "@/stores/queue";
|
||||
|
||||
const q = useQStore();
|
||||
</script>
|
||||
|
||||
@@ -84,7 +84,7 @@ function updateQueue(track: Track) {
|
||||
.table {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-y: hidden;
|
||||
|
||||
.current {
|
||||
a {
|
||||
@@ -141,6 +141,10 @@ function updateQueue(track: Track) {
|
||||
}
|
||||
|
||||
.songlist {
|
||||
scrollbar-width: none;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.context-on {
|
||||
background-color: $gray4;
|
||||
color: $white !important;
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
<template>
|
||||
<div class="hotkeys">
|
||||
<div class="image ctrl-btn" id="previous" @click="props.prev"></div>
|
||||
<div class="image ctrl-btn" id="previous" @click="q.playPrev"></div>
|
||||
<div
|
||||
class="image ctrl-btn play-pause"
|
||||
@click="playPause"
|
||||
:class="{ isPlaying: props.playing }"
|
||||
@click="q.playPause"
|
||||
:class="{ isPlaying: q.playing }"
|
||||
></div>
|
||||
<div class="image ctrl-btn" id="next" @click="props.next"></div>
|
||||
<div class="image ctrl-btn" id="next" @click="q.playNext"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
playing: boolean;
|
||||
playPause: () => void;
|
||||
next: () => void;
|
||||
prev: () => void;
|
||||
}>();
|
||||
import useQStore from "@/stores/queue";
|
||||
|
||||
const q = useQStore();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.hotkeys {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
width: 100%;
|
||||
gap: $small;
|
||||
height: 3rem;
|
||||
height: 2.5rem;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
place-content: flex-end;
|
||||
width: 100%;
|
||||
|
||||
.ctrl-btn {
|
||||
height: 2.5rem;
|
||||
@@ -37,7 +35,7 @@ const props = defineProps<{
|
||||
border-radius: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
background-color: $red;
|
||||
background-color: $accent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<input
|
||||
id="progress"
|
||||
type="range"
|
||||
:value="props.pos"
|
||||
:value="q.track.current_time"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.1"
|
||||
@@ -11,13 +11,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const seek = () => {
|
||||
const value = Number(document.getElementById("progress").value);
|
||||
props.seek(value);
|
||||
};
|
||||
import useQStore from "../../../stores/queue";
|
||||
|
||||
const props = defineProps<{
|
||||
pos: number;
|
||||
seek: (time: number) => void;
|
||||
}>();
|
||||
const q = useQStore();
|
||||
const seek = () => {
|
||||
const elem = <HTMLFormElement>document.getElementById("progress");
|
||||
const value = elem.value;
|
||||
|
||||
q.seek(value);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<template>
|
||||
<div class="side-nav-container" :class="{ collapsed: props.collapsed }">
|
||||
<div class="side-nav-container">
|
||||
<router-link
|
||||
v-for="menu in menus"
|
||||
:key="menu.name"
|
||||
:to="{ name: menu.route_name, params: menu.params }"
|
||||
>
|
||||
<div class="nav-button" id="home-button">
|
||||
<div
|
||||
class="nav-button"
|
||||
id="home-button"
|
||||
v-motion-slide-from-left-100
|
||||
|
||||
>
|
||||
<div class="in">
|
||||
<div class="nav-icon image" :id="`${menu.name}-icon`"></div>
|
||||
<span>{{ menu.name }}</span>
|
||||
@@ -15,7 +20,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
const menus = [
|
||||
{
|
||||
name: "home",
|
||||
@@ -46,36 +51,12 @@ const menus = [
|
||||
route_name: "SettingsView",
|
||||
},
|
||||
];
|
||||
|
||||
const props = defineProps({
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.collapsed {
|
||||
.nav-button {
|
||||
margin-top: 5px;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.in {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.side-nav-container {
|
||||
color: #fff;
|
||||
margin-bottom: 1rem;
|
||||
padding: 10px $small $small;
|
||||
margin-top: 1rem;
|
||||
margin: 1rem 0;
|
||||
text-transform: capitalize;
|
||||
|
||||
.nav-button {
|
||||
@@ -83,11 +64,10 @@ const props = defineProps({
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
height: 100%;
|
||||
padding: 0.6rem 0 0.6rem 0;
|
||||
padding: 0.6rem 0;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray;
|
||||
background-color: $gray3;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<template>
|
||||
<div
|
||||
class="side-nav-container rounded hidden"
|
||||
:class="{ hidden: collapsed }"
|
||||
id="pinned-container"
|
||||
>
|
||||
<div class="side-nav-container rounded hidden" id="pinned-container">
|
||||
<div>
|
||||
<div class="nav-button" id="pinned-button">
|
||||
<span id="text">Quick access</span>
|
||||
@@ -53,13 +49,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["collapsed"],
|
||||
setup() {},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#pinned-container {
|
||||
background: #6e2c00;
|
||||
@@ -102,5 +91,4 @@ export default {
|
||||
#pinned-container #pinned-button:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -34,12 +34,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import perks from "../../composables/perks";
|
||||
import { putCommas } from "../../composables/perks";
|
||||
import { Track } from "../../interfaces";
|
||||
import { paths } from "../../config";
|
||||
const imguri = paths.images.thumb
|
||||
|
||||
const putCommas = perks.putCommas;
|
||||
const imguri = paths.images.thumb;
|
||||
|
||||
const props = defineProps<{
|
||||
track: Track;
|
||||
|
||||
@@ -5,18 +5,13 @@
|
||||
<div class="separator no-border"></div>
|
||||
<div>
|
||||
<SongCard :track="queue.current" />
|
||||
<Progress :seek="queue.seek" :pos="queue.current_time" />
|
||||
<HotKeys
|
||||
:playing="queue.playing"
|
||||
:playPause="queue.playPause"
|
||||
:next="queue.playNext"
|
||||
:prev="queue.playPrev"
|
||||
/>
|
||||
<Progress />
|
||||
<HotKeys />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import SongCard from "./SongCard.vue";
|
||||
import HotKeys from "./NP/HotKeys.vue";
|
||||
import Progress from "./NP/Progress.vue";
|
||||
@@ -28,9 +23,8 @@ const queue = useQStore();
|
||||
.l_ {
|
||||
padding: 1rem;
|
||||
background-color: $primary;
|
||||
margin: $small;
|
||||
text-align: center;
|
||||
width: 14rem;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
position: relative;
|
||||
text-transform: capitalize;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div id="logo-container"
|
||||
v-motion-slide-from-top
|
||||
>
|
||||
<router-link :to="{ name: 'Home' }">
|
||||
<div id="logo" class="rounded"></div
|
||||
></router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import "../assets/css/mixins.scss";
|
||||
|
||||
#logo-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#logo {
|
||||
height: 4.5rem !important;
|
||||
width: 15rem;
|
||||
background-image: url(./../assets/images/logo.webp);
|
||||
background-size: contain;
|
||||
@include ximage;
|
||||
}
|
||||
</style>
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="type">Playlist</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="last-updated">
|
||||
<div class="last-updated" v-motion-slide-from-right>
|
||||
<span class="status"
|
||||
>Last updated {{ props.info.lastUpdated }}  |  </span
|
||||
>
|
||||
@@ -46,8 +46,9 @@ import Option from "../shared/Option.vue";
|
||||
import pContext from "../../contexts/playlist";
|
||||
import useContextStore from "../../stores/context";
|
||||
import { paths } from "../../config";
|
||||
import { onBeforeUnmount } from "vue";
|
||||
|
||||
const imguri = paths.images.playlist
|
||||
const imguri = paths.images.playlist;
|
||||
const context = useContextStore();
|
||||
const modal = useModalStore();
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Search from "./Search.vue";
|
||||
<script setup lang="ts">
|
||||
import Search from "./Search/Main.vue";
|
||||
import UpNext from "./Queue.vue";
|
||||
import Main from "./Home/Main.vue";
|
||||
import useTabStore from "../../stores/tabs";
|
||||
|
||||
// import Search from "./Searchh.vue";
|
||||
const DashBoard = Main;
|
||||
const tabs = useTabStore();
|
||||
</script>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
<script>
|
||||
import playAudio from "@/composables/playAudio.js";
|
||||
import {ref} from "@vue/reactivity";
|
||||
import perks from "../../composables/perks.js";
|
||||
import {putCommas, formatSeconds} from "../../composables/perks.js";
|
||||
import HotKeys from "../shared/HotKeys.vue";
|
||||
import Progress from "../shared/Progress.vue";
|
||||
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
<template>
|
||||
<div class="right-search">
|
||||
<Options />
|
||||
<div class="scrollable rounded" ref="search_thing">
|
||||
<TracksGrid
|
||||
v-if="tracks.tracks.length"
|
||||
:more="tracks.more"
|
||||
:tracks="tracks.tracks"
|
||||
:query="search.query"
|
||||
@loadMore="loadMoreTracks"
|
||||
/>
|
||||
<div class="separator no-border" v-if="tracks.tracks.length"></div>
|
||||
|
||||
<AlbumGrid
|
||||
v-if="albums.albums.length"
|
||||
:albums="albums.albums"
|
||||
:more="albums.more"
|
||||
@loadMore="loadMoreAlbums"
|
||||
/>
|
||||
<div class="separator no-border" v-if="albums.albums.length"></div>
|
||||
<ArtistGrid
|
||||
v-if="artists.artists.length"
|
||||
:artists="artists.artists"
|
||||
:more="artists.more"
|
||||
@loadMore="loadMoreArtists"
|
||||
/>
|
||||
<div
|
||||
v-if="search.query.trim().length === 0"
|
||||
class="no-res border rounded"
|
||||
>
|
||||
<div class="no-res-text">🦋 Find your music</div>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="
|
||||
!artists.artists.length &&
|
||||
!tracks.tracks.length &&
|
||||
!albums.albums.length
|
||||
"
|
||||
class="no-res border rounded"
|
||||
>
|
||||
<div class="no-res-text">
|
||||
No results for
|
||||
<span class="highlight rounded">{{ search.query }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref } from "@vue/reactivity";
|
||||
import state from "../../composables/state";
|
||||
import searchMusic from "@/composables/searchMusic.js";
|
||||
import useDebouncedRef from "@/composables/useDebouncedRef";
|
||||
import AlbumGrid from "@/components/Search/AlbumGrid.vue";
|
||||
import ArtistGrid from "@/components/Search/ArtistGrid.vue";
|
||||
import TracksGrid from "@/components/Search/TracksGrid.vue";
|
||||
import Options from "@/components/Search/Options.vue";
|
||||
import loadMore from "../../composables/loadmore";
|
||||
import useSearchStore from "../../stores/gsearch";
|
||||
import useTabStore from "../../stores/tabs";
|
||||
|
||||
import "@/assets/css/Search/Search.scss";
|
||||
|
||||
const search = useSearchStore();
|
||||
const tabs = useTabStore();
|
||||
|
||||
const search_thing = ref(null);
|
||||
|
||||
const tracks = reactive({
|
||||
tracks: [],
|
||||
more: false,
|
||||
});
|
||||
|
||||
let albums = reactive({
|
||||
albums: [],
|
||||
more: false,
|
||||
});
|
||||
|
||||
const artists = reactive({
|
||||
artists: [],
|
||||
more: false,
|
||||
});
|
||||
|
||||
function scrollSearchThing() {
|
||||
search_thing.value.scroll({
|
||||
top: search_thing.value.scrollTop + 330,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
|
||||
function loadMoreTracks(start) {
|
||||
scrollSearchThing();
|
||||
loadMore.loadMoreTracks(start).then((response) => {
|
||||
tracks.tracks = [...tracks.tracks, ...response.tracks];
|
||||
tracks.more = response.more;
|
||||
});
|
||||
}
|
||||
|
||||
function loadMoreAlbums(start) {
|
||||
loadMore.loadMoreAlbums(start).then((response) => {
|
||||
albums.albums = [...albums.albums, ...response.albums];
|
||||
albums.more = response.more;
|
||||
});
|
||||
}
|
||||
|
||||
function loadMoreArtists(start) {
|
||||
scrollSearchThing();
|
||||
loadMore.loadMoreArtists(start).then((response) => {
|
||||
artists.artists = [...artists.artists, ...response.artists];
|
||||
artists.more = response.more;
|
||||
});
|
||||
}
|
||||
|
||||
search.$subscribe((mutation, state) => {
|
||||
if (state.query.trim() == "") {
|
||||
tracks.tracks = [];
|
||||
albums.albums = [];
|
||||
artists.artists = [];
|
||||
return;
|
||||
}
|
||||
|
||||
searchMusic(state.query).then((res) => {
|
||||
if (tabs.current !== tabs.tabs.search) {
|
||||
tabs.switchToSearch();
|
||||
}
|
||||
|
||||
albums.albums = res.albums.albums;
|
||||
albums.more = res.albums.more;
|
||||
|
||||
artists.artists = res.artists.artists;
|
||||
artists.more = res.artists.more;
|
||||
|
||||
tracks.tracks = res.tracks.tracks;
|
||||
tracks.more = res.tracks.more;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.right-search {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: min-content 1fr;
|
||||
overflow: hidden;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
padding: $small $small 0 0;
|
||||
|
||||
.no-res {
|
||||
text-align: center;
|
||||
display: grid;
|
||||
height: calc(100% - $small);
|
||||
place-items: center;
|
||||
font-size: 2rem;
|
||||
transition: all 0.3s ease;
|
||||
line-height: 4rem !important;
|
||||
|
||||
.highlight {
|
||||
padding: $small;
|
||||
background-color: rgb(29, 26, 26);
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
padding: $medium;
|
||||
border-radius: $small;
|
||||
margin-bottom: $small;
|
||||
font-size: 2rem;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.right-search .scrollable {
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+15
-22
@@ -1,36 +1,29 @@
|
||||
<template>
|
||||
<div class="albums-results border">
|
||||
<div class="heading">Albums</div>
|
||||
<div class="grid">
|
||||
<AlbumCard v-for="album in albums" :key="album" :album="album" />
|
||||
<AlbumCard
|
||||
v-for="album in search.albums.value"
|
||||
:key="album.image"
|
||||
:album="album"
|
||||
/>
|
||||
</div>
|
||||
<LoadMore v-if="more" @loadMore="loadMore" />
|
||||
<LoadMore v-if="search.albums.more" @loadMore="loadMore()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AlbumCard from "@/components/shared/AlbumCard.vue";
|
||||
<script setup lang="ts">
|
||||
import AlbumCard from "../../shared/AlbumCard.vue";
|
||||
import LoadMore from "./LoadMore.vue";
|
||||
import useSearchStore from "../../../stores/search";
|
||||
|
||||
export default {
|
||||
props: ["albums", "more"],
|
||||
components: {
|
||||
AlbumCard,
|
||||
LoadMore,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
let counter = 0;
|
||||
const search = useSearchStore();
|
||||
|
||||
function loadMore() {
|
||||
counter += 6;
|
||||
emit("loadMore", counter);
|
||||
}
|
||||
let counter = 0;
|
||||
|
||||
return {
|
||||
loadMore,
|
||||
};
|
||||
},
|
||||
};
|
||||
function loadMore() {
|
||||
counter += 6;
|
||||
search.loadAlbums(counter);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<div class="artists-results border">
|
||||
<div class="grid">
|
||||
<ArtistCard
|
||||
v-for="artist in search.artists.value"
|
||||
:key="artist.image"
|
||||
:artist="artist"
|
||||
/>
|
||||
</div>
|
||||
<LoadMore v-if="search.artists.more" @loadMore="loadMore" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ArtistCard from "../../shared/ArtistCard.vue";
|
||||
import LoadMore from "./LoadMore.vue";
|
||||
import useSearchStore from "../../../stores/search";
|
||||
const search = useSearchStore();
|
||||
|
||||
let counter = 0;
|
||||
|
||||
function loadMore() {
|
||||
counter += 6;
|
||||
search.loadArtists(counter);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.right-search .artists-results {
|
||||
border-radius: 0.5rem;
|
||||
padding: $small;
|
||||
margin-bottom: $small;
|
||||
|
||||
|
||||
.xartist {
|
||||
background-color: $gray;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<div class="right-search">
|
||||
<TabsWrapper>
|
||||
<Tab name="tracks">
|
||||
<TracksGrid />
|
||||
</Tab>
|
||||
<Tab name="albums">
|
||||
<AlbumGrid />
|
||||
</Tab>
|
||||
<Tab name="artists">
|
||||
<ArtistGrid />
|
||||
</Tab>
|
||||
</TabsWrapper>
|
||||
<component :is="s.currentTab" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TabsWrapper from "./TabsWrapper.vue";
|
||||
import Tab from "./Tab.vue";
|
||||
import TracksGrid from "./TracksGrid.vue";
|
||||
import AlbumGrid from "./AlbumGrid.vue";
|
||||
import ArtistGrid from "./ArtistGrid.vue";
|
||||
import "@/assets/css/Search/Search.scss";
|
||||
import useSearchStore from "@/stores/search";
|
||||
const s = useSearchStore();
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.right-search {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: auto;
|
||||
height: 100%;
|
||||
|
||||
.no-res {
|
||||
text-align: center;
|
||||
display: grid;
|
||||
height: calc(100% - $small);
|
||||
place-items: center;
|
||||
font-size: 2rem;
|
||||
transition: all 0.3s ease;
|
||||
line-height: 4rem !important;
|
||||
|
||||
.highlight {
|
||||
padding: $small;
|
||||
background-color: rgb(29, 26, 26);
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
padding: $medium;
|
||||
border-radius: $small;
|
||||
margin-bottom: $small;
|
||||
font-size: 2rem;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.right-search .scrollable {
|
||||
overflow-y: auto;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div v-show="name == s.currentTab" v-motion-slide-visible-top>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useSearchStore from "@/stores/search";
|
||||
const s = useSearchStore();
|
||||
defineProps<{
|
||||
name: string;
|
||||
}>();
|
||||
</script>
|
||||
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<div id="right-tabs">
|
||||
<div id="tabheaders">
|
||||
<div
|
||||
class="tab rounded"
|
||||
v-for="slot in $slots.default()"
|
||||
:key="slot.key"
|
||||
@click="s.changeTab(slot.props.name)"
|
||||
:class="{ activetab: slot.props.name === s.currentTab }"
|
||||
>
|
||||
{{ slot.props.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useSearchStore from "@/stores/search";
|
||||
|
||||
const s = useSearchStore();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#right-tabs {
|
||||
height: 100%;
|
||||
margin-right: $small;
|
||||
display: grid;
|
||||
grid-template-rows: min-content 1fr;
|
||||
|
||||
#tabheaders {
|
||||
display: flex;
|
||||
gap: $small;
|
||||
margin: $small 0;
|
||||
|
||||
.tab {
|
||||
background-color: $gray3;
|
||||
padding: $small;
|
||||
text-transform: capitalize;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
width: 4rem;
|
||||
}
|
||||
|
||||
.activetab {
|
||||
background-color: $accent;
|
||||
width: 6rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
#tab-content {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
border-radius: $small;
|
||||
background-color: $gray;
|
||||
// overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+14
-20
@@ -1,9 +1,8 @@
|
||||
<template>
|
||||
<div class="tracks-results border" v-if="tracks">
|
||||
<div class="heading">Tracks</div>
|
||||
<TransitionGroup class="items" name="list">
|
||||
<div id="tracks-results" v-if="search.tracks.value">
|
||||
<TransitionGroup name="list">
|
||||
<TrackItem
|
||||
v-for="track in tracks"
|
||||
v-for="track in search.tracks.value"
|
||||
:key="track.trackid"
|
||||
:track="track"
|
||||
:isPlaying="queue.playing"
|
||||
@@ -12,43 +11,38 @@
|
||||
@PlayThis="updateQueue"
|
||||
/>
|
||||
</TransitionGroup>
|
||||
<LoadMore v-if="more" @loadMore="loadMore" />
|
||||
<LoadMore v-if="search.tracks.more" @loadMore="loadMore" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import LoadMore from "./LoadMore.vue";
|
||||
import TrackItem from "../shared/TrackItem.vue";
|
||||
import useQStore from "../../stores/queue";
|
||||
import { Track } from "../../interfaces";
|
||||
import TrackItem from "../../shared/TrackItem.vue";
|
||||
import useQStore from "../../../stores/queue";
|
||||
import { Track } from "../../../interfaces";
|
||||
import useSearchStore from "../../../stores/search";
|
||||
|
||||
let counter = 0;
|
||||
const queue = useQStore();
|
||||
|
||||
const props = defineProps<{
|
||||
tracks: Track[];
|
||||
more: boolean;
|
||||
query: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits(["loadMore"]);
|
||||
const search = useSearchStore();
|
||||
|
||||
function loadMore() {
|
||||
counter += 5;
|
||||
emit("loadMore", counter);
|
||||
search.loadTracks(counter);
|
||||
}
|
||||
|
||||
function updateQueue(track: Track) {
|
||||
console.log(props.query);
|
||||
queue.playFromSearch(props.query, props.tracks);
|
||||
queue.playFromSearch(search.query, search.tracks.value);
|
||||
queue.play(track);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.right-search .tracks-results {
|
||||
.right-search #tracks-results {
|
||||
border-radius: 0.5rem;
|
||||
padding: $small;
|
||||
height: 100% !important;
|
||||
overflow: hidden;
|
||||
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
@@ -1,47 +1,36 @@
|
||||
<template>
|
||||
<div class="gsearch-input">
|
||||
<Filters :filters="search.filters" @removeFilter="removeFilter" />
|
||||
<div class="input-loader">
|
||||
<div id="gsearch-input">
|
||||
<div id="ginner" tabindex="0">
|
||||
<div class="icon image"></div>
|
||||
<input
|
||||
id="search"
|
||||
class="rounded"
|
||||
v-model="search.query"
|
||||
placeholder="Search your library"
|
||||
type="text"
|
||||
@keyup.backspace="removeLastFilter"
|
||||
@focus="focusThis()"
|
||||
@blur="unfocusThis()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Filters from "../Search/Filters.vue";
|
||||
import Loader from "../shared/Loader.vue";
|
||||
import useSearchStore from "../../stores/gsearch";
|
||||
<script setup lang="ts">
|
||||
import useSearchStore from "../../stores/search";
|
||||
|
||||
const search = useSearchStore();
|
||||
|
||||
function removeFilter(filter) {
|
||||
search.removeFilter(filter);
|
||||
function focusThis() {
|
||||
document.getElementById("ginner").classList.add("focused");
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
|
||||
function removeLastFilter() {
|
||||
if (search.query === "") {
|
||||
counter++;
|
||||
|
||||
if (counter > 0) {
|
||||
search.removeLastFilter();
|
||||
}
|
||||
} else {
|
||||
counter = 0;
|
||||
}
|
||||
function unfocusThis() {
|
||||
document.getElementById("ginner").classList.remove("focused");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.gsearch-input {
|
||||
#gsearch-input {
|
||||
padding: $small;
|
||||
display: flex;
|
||||
|
||||
@@ -49,15 +38,20 @@ function removeLastFilter() {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.input-loader {
|
||||
#ginner {
|
||||
width: 100%;
|
||||
border-radius: 0.4rem;
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: $small;
|
||||
background-color: $gray4;
|
||||
height: 2.25rem;
|
||||
|
||||
._loader {
|
||||
position: absolute;
|
||||
top: -0.15rem;
|
||||
right: 2rem;
|
||||
.icon {
|
||||
width: 2rem;
|
||||
background-image: url("../../assets/icons/search.svg");
|
||||
background-size: 1.5rem;
|
||||
margin-left: $smaller;
|
||||
}
|
||||
|
||||
input {
|
||||
@@ -66,16 +60,15 @@ function removeLastFilter() {
|
||||
width: 100%;
|
||||
border: none;
|
||||
line-height: 2.25rem;
|
||||
background-color: $black;
|
||||
color: inherit;
|
||||
font-size: 1rem;
|
||||
padding-left: 0.75rem;
|
||||
background-color: transparent;
|
||||
outline: 2px solid transparent;
|
||||
|
||||
&:focus {
|
||||
outline: solid $accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.focused {
|
||||
outline: solid $accent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<p class="title ellip">{{ next.title }}</p>
|
||||
<hr />
|
||||
<p class="artist ellip">
|
||||
<span v-for="artist in perks.putCommas(next.artists)" :key="artist">{{
|
||||
<span v-for="artist in putCommas(next.artists)" :key="artist">{{
|
||||
artist
|
||||
}}</span>
|
||||
</p>
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Track } from "../../../interfaces";
|
||||
import perks from "../../../composables/perks";
|
||||
import {putCommas} from "../../../composables/perks";
|
||||
import { paths } from "../../../config";
|
||||
const imguri = paths.images.thumb;
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<div class="artists-results border">
|
||||
<div class="heading">Artists</div>
|
||||
<div class="grid">
|
||||
<ArtistCard v-for="artist in artists" :key="artist" :artist="artist" />
|
||||
</div>
|
||||
<LoadMore v-if="more" @loadMore="loadMore" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ArtistCard from "@/components/shared/ArtistCard.vue";
|
||||
import LoadMore from "./LoadMore.vue";
|
||||
|
||||
export default {
|
||||
props: ["artists", "more"],
|
||||
components: {
|
||||
ArtistCard,
|
||||
LoadMore,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
let counter = 0;
|
||||
|
||||
function loadMore() {
|
||||
counter += 6;
|
||||
emit("loadMore", counter);
|
||||
}
|
||||
|
||||
return {
|
||||
loadMore,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.right-search .artists-results {
|
||||
border-radius: 0.5rem;
|
||||
padding: $small;
|
||||
margin-bottom: $small;
|
||||
|
||||
.xartist {
|
||||
background-color: $gray;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<div class="filter">
|
||||
<div
|
||||
class="item"
|
||||
v-for="filter in filters"
|
||||
:key="filter"
|
||||
@click="removeFilter(filter)"
|
||||
>
|
||||
{{ filter }}<span class="cancel image"></span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ["filters"],
|
||||
setup(props, { emit }) {
|
||||
const removeFilter = (filter) => {
|
||||
emit("removeFilter", filter);
|
||||
};
|
||||
|
||||
return {
|
||||
removeFilter,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.gsearch-input .filter {
|
||||
display: flex;
|
||||
|
||||
.item {
|
||||
transition: all 0.2s ease-in-out;
|
||||
background-color: $gray3;
|
||||
height: 2.5rem;
|
||||
|
||||
&:hover {
|
||||
width: 4rem;
|
||||
|
||||
.cancel {
|
||||
position: absolute;
|
||||
right: 0.5rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
background-image: url(../../assets/icons/a.svg);
|
||||
background-size: 70%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<div class="options border rounded">
|
||||
<div class="item info header">Filter by:</div>
|
||||
<div
|
||||
class="item"
|
||||
v-for="option in options"
|
||||
:key="option"
|
||||
@click="search.addFilter(option.icon)"
|
||||
>
|
||||
<div>
|
||||
<span class="icon">{{ option.icon }}</span>
|
||||
<span class="title"> {{ option.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import useSearchStore from "../../stores/gsearch";
|
||||
|
||||
const search = useSearchStore();
|
||||
|
||||
const options = [
|
||||
{
|
||||
title: "Track",
|
||||
icon: "🎵",
|
||||
},
|
||||
{
|
||||
title: "Album",
|
||||
icon: "💿",
|
||||
},
|
||||
{
|
||||
title: "Artist",
|
||||
icon: "👤",
|
||||
},
|
||||
{
|
||||
title: "Playlist",
|
||||
icon: "🎧",
|
||||
},
|
||||
{
|
||||
title: "Folder",
|
||||
icon: "📁",
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.right-search .options {
|
||||
display: flex;
|
||||
margin-bottom: $small;
|
||||
|
||||
.item {
|
||||
margin: $small;
|
||||
width: 2.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease-in-out;
|
||||
position: relative;
|
||||
background-color: $gray3;
|
||||
|
||||
.title {
|
||||
position: absolute;
|
||||
left: 1.5rem;
|
||||
top: 0.5rem;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 0.75rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
width: 5.5rem;
|
||||
background-color: $gray5;
|
||||
|
||||
.title {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
width: 5.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -6,12 +6,45 @@
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
<div class="title" v-if="$route.name == 'Playlists'">Playlists</div>
|
||||
<div class="folder" v-else-if="$route.name == 'FolderView'">
|
||||
<div
|
||||
class="title"
|
||||
v-show="$route.name == 'Playlists'"
|
||||
v-motion
|
||||
:initial="{
|
||||
opacity: 0,
|
||||
x: -20,
|
||||
}"
|
||||
:visible="{
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: {
|
||||
delay: 100,
|
||||
},
|
||||
}"
|
||||
>
|
||||
Playlists
|
||||
</div>
|
||||
<div
|
||||
class="folder"
|
||||
v-show="$route.name == 'FolderView'"
|
||||
v-motion
|
||||
:initial="{
|
||||
opacity: 0,
|
||||
x: -20,
|
||||
}"
|
||||
:visible="{
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: {
|
||||
delay: 100,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<div class="fname">
|
||||
<div class="icon image"></div>
|
||||
<div class="ellip">
|
||||
{{ $route.params.path.split("/").splice(-1)[0] }}
|
||||
<!-- {{ $route.params.path.split("/").splice(-1)[0] }} -->
|
||||
{{ $route.params.path }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -28,7 +61,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup lang="ts">
|
||||
import NavButtons from "./NavButtons.vue";
|
||||
import Loader from "../shared/Loader.vue";
|
||||
import Search from "./Search.vue";
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
console.log();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#back-forward {
|
||||
display: grid;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
type="search"
|
||||
name=""
|
||||
id=""
|
||||
placeholder="Search this playlist"
|
||||
placeholder="Search here"
|
||||
class="rounded"
|
||||
/>
|
||||
</form>
|
||||
|
||||
@@ -17,7 +17,7 @@ const imguri = paths.images.artist;
|
||||
|
||||
defineProps<{
|
||||
artist: any;
|
||||
color: string;
|
||||
color?: string;
|
||||
}>();
|
||||
|
||||
</script>
|
||||
|
||||
@@ -30,7 +30,7 @@ function play() {
|
||||
queue.play(queue.tracks[0]);
|
||||
break;
|
||||
case playSources.album:
|
||||
queue.playFromAlbum(album.info.album, album.info.artist, album.tracks);
|
||||
queue.playFromAlbum(album.info.title, album.info.artist, album.tracks);
|
||||
queue.play(album.tracks[0]);
|
||||
break;
|
||||
case playSources.playlist:
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
<div
|
||||
class="album-art image rounded"
|
||||
:style="{
|
||||
backgroundImage: `url("${
|
||||
imguri + props.song.image
|
||||
}"`,
|
||||
backgroundImage: `url("${imguri + props.song.image}"`,
|
||||
}"
|
||||
@click="emitUpdate(props.song)"
|
||||
>
|
||||
@@ -30,7 +28,7 @@
|
||||
<div class="ellip" v-if="props.song.artists[0] !== ''">
|
||||
<span
|
||||
class="artist"
|
||||
v-for="artist in perks.putCommas(props.song.artists)"
|
||||
v-for="artist in putCommas(props.song.artists)"
|
||||
:key="artist"
|
||||
>{{ artist }}</span
|
||||
>
|
||||
@@ -54,13 +52,13 @@
|
||||
</div>
|
||||
</router-link>
|
||||
<div class="song-duration">
|
||||
{{ perks.formatSeconds(props.song.length) }}
|
||||
{{ formatSeconds(props.song.length) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import perks from "../../composables/perks.js";
|
||||
import { putCommas, formatSeconds } from "../../composables/perks";
|
||||
import useContextStore from "../../stores/context";
|
||||
import useModalStore from "../../stores/modal";
|
||||
import useQueueStore from "../../stores/queue";
|
||||
@@ -68,13 +66,13 @@ import { ContextSrc } from "../../composables/enums";
|
||||
|
||||
import { ref } from "vue";
|
||||
import trackContext from "../../contexts/track_context";
|
||||
import { Track } from "../../interfaces.js";
|
||||
import { Track } from "../../interfaces";
|
||||
import { paths } from "../../config";
|
||||
|
||||
const contextStore = useContextStore();
|
||||
|
||||
const context_on = ref(false);
|
||||
const imguri = paths.images.thumb
|
||||
const imguri = paths.images.thumb;
|
||||
|
||||
const showContextMenu = (e: Event) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import perks from "../../composables/perks";
|
||||
import { putCommas } from "../../composables/perks";
|
||||
import trackContext from "../../contexts/track_context";
|
||||
import { Track } from "../../interfaces";
|
||||
import { ContextSrc } from "../../composables/enums";
|
||||
@@ -46,9 +46,8 @@ import useModalStore from "../../stores/modal";
|
||||
import useQueueStore from "../../stores/queue";
|
||||
import { paths } from "../../config";
|
||||
|
||||
|
||||
const contextStore = useContextStore();
|
||||
const imguri = paths.images.thumb
|
||||
const imguri = paths.images.thumb;
|
||||
|
||||
const props = defineProps<{
|
||||
track: Track;
|
||||
@@ -78,9 +77,6 @@ const emit = defineEmits<{
|
||||
(e: "PlayThis", track: Track): void;
|
||||
}>();
|
||||
|
||||
const current = ref(perks.current);
|
||||
const putCommas = perks.putCommas;
|
||||
|
||||
const playThis = (track: Track) => {
|
||||
emit("PlayThis", track);
|
||||
};
|
||||
@@ -97,7 +93,6 @@ const playThis = (track: Track) => {
|
||||
}
|
||||
|
||||
.track-item {
|
||||
width: 26.55rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
@@ -64,11 +64,15 @@ export default function (queue: any) {
|
||||
if (!key_down_fired) {
|
||||
if (!ctrlKey) return;
|
||||
e.preventDefault();
|
||||
focusSearchBox();
|
||||
|
||||
|
||||
key_down_fired = true;
|
||||
}
|
||||
}
|
||||
case "/": {{
|
||||
e.preventDefault();
|
||||
focusSearchBox();
|
||||
}}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import axios from "axios";
|
||||
|
||||
const url = "http://127.0.0.1:9876/search/loadmore";
|
||||
|
||||
async function loadMoreTracks(start) {
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
type: "tracks",
|
||||
start: start,
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function loadMoreAlbums(start) {
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
type: "albums",
|
||||
start: start,
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function loadMoreArtists(start) {
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
type: "artists",
|
||||
start: start,
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export default {
|
||||
loadMoreTracks,
|
||||
loadMoreAlbums,
|
||||
loadMoreArtists,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import perks from "./perks";
|
||||
import { getElem } from "./perks";
|
||||
|
||||
export default (mouseX, mouseY) => {
|
||||
const scope = perks.getElem("app", "id");
|
||||
const contextMenu = perks.getElem("context-menu", "class");
|
||||
// ? compute what is the mouse position relative to the container element (scope)
|
||||
const scope = getElem("app", "id");
|
||||
const contextMenu = getElem("context-menu", "class");
|
||||
// ? compute what is the mouse position relative to the container element
|
||||
// (scope)
|
||||
let { left: scopeOffsetX, top: scopeOffsetY } = scope.getBoundingClientRect();
|
||||
|
||||
scopeOffsetX = scopeOffsetX < 0 ? 0 : scopeOffsetX;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const putCommas = (artists) => {
|
||||
const putCommas = (artists: string[]) => {
|
||||
let result = [];
|
||||
|
||||
artists.forEach((i, index, artists) => {
|
||||
@@ -24,18 +24,18 @@ function focusCurrent() {
|
||||
}
|
||||
}
|
||||
|
||||
function getElem(identifier, type) {
|
||||
function getElem(id: string, type: string) {
|
||||
switch (type) {
|
||||
case "class": {
|
||||
return document.getElementsByClassName(identifier)[0];
|
||||
return document.getElementsByClassName(id)[0];
|
||||
}
|
||||
case "id": {
|
||||
return document.getElementById(identifier);
|
||||
return document.getElementById(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function formatSeconds(seconds) {
|
||||
function formatSeconds(seconds: number, long?: boolean) {
|
||||
// check if there are arguments
|
||||
|
||||
const date = new Date(seconds * 1000);
|
||||
@@ -48,7 +48,7 @@ function formatSeconds(seconds) {
|
||||
let _mm = mm < 10 ? `0${mm}` : mm;
|
||||
let _ss = ss < 10 ? `0${ss}` : ss;
|
||||
|
||||
if (arguments[1]) {
|
||||
if (long == true) {
|
||||
if (hh === 1) {
|
||||
_hh = hh + " Hour";
|
||||
} else {
|
||||
@@ -75,10 +75,4 @@ function formatSeconds(seconds) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
putCommas,
|
||||
focusCurrent,
|
||||
formatSeconds,
|
||||
getElem,
|
||||
};
|
||||
export { putCommas, focusCurrent, formatSeconds, getElem };
|
||||
@@ -1,27 +0,0 @@
|
||||
import state from "./state";
|
||||
|
||||
const base_url = `${state.settings.uri}/search?q=`;
|
||||
|
||||
async function search(query) {
|
||||
state.loading.value = true;
|
||||
const url = base_url + encodeURIComponent(query.trim());
|
||||
|
||||
const res = await fetch(url);
|
||||
|
||||
if (!res.ok) {
|
||||
const message = `An error has occured: ${res.status}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
state.loading.value = false;
|
||||
|
||||
return {
|
||||
tracks: data.data[0],
|
||||
albums: data.data[1],
|
||||
artists: data.data[2],
|
||||
};
|
||||
}
|
||||
|
||||
export default search;
|
||||
@@ -0,0 +1,106 @@
|
||||
import state from "./state";
|
||||
import axios from "axios";
|
||||
|
||||
const base_url = `${state.settings.uri}/search`;
|
||||
|
||||
const uris = {
|
||||
tracks: `${base_url}/tracks?q=`,
|
||||
albums: `${base_url}/albums?q=`,
|
||||
artists: `${base_url}/artists?q=`,
|
||||
};
|
||||
|
||||
async function search(query: string) {
|
||||
state.loading.value = true;
|
||||
|
||||
const url = base_url + encodeURIComponent(query.trim());
|
||||
|
||||
const res = await fetch(url);
|
||||
|
||||
if (!res.ok) {
|
||||
const message = `An error has occured: ${res.status}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
state.loading.value = false;
|
||||
|
||||
return {
|
||||
tracks: data.data[0],
|
||||
albums: data.data[1],
|
||||
artists: data.data[2],
|
||||
};
|
||||
}
|
||||
|
||||
async function searchTracks(query: string) {
|
||||
const url = uris.tracks + encodeURIComponent(query.trim());
|
||||
|
||||
const res = await fetch(url);
|
||||
|
||||
if (!res.ok) {
|
||||
const message = `An error has occured: ${res.status}`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function searchAlbums(query: string) {
|
||||
const url = uris.albums + encodeURIComponent(query.trim());
|
||||
|
||||
const res = await axios.get(url);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
async function searchArtists(query: string) {
|
||||
const url = uris.artists + encodeURIComponent(query.trim());
|
||||
|
||||
const res = await axios.get(url);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
const url = state.settings.uri + "/search/loadmore";
|
||||
|
||||
async function loadMoreTracks(index: number) {
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
type: "tracks",
|
||||
index: index,
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function loadMoreAlbums(index: number) {
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
type: "albums",
|
||||
index: index,
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function loadMoreArtists(index: number) {
|
||||
const response = await axios.get(url, {
|
||||
params: {
|
||||
type: "artists",
|
||||
index: index,
|
||||
},
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export {
|
||||
searchTracks,
|
||||
searchAlbums,
|
||||
searchArtists,
|
||||
loadMoreTracks,
|
||||
loadMoreAlbums,
|
||||
loadMoreArtists,
|
||||
};
|
||||
@@ -1,33 +1,49 @@
|
||||
import {customRef, ref} from 'vue'
|
||||
import { customRef, ref } from "vue";
|
||||
|
||||
/**
|
||||
* Debounces a function
|
||||
*
|
||||
* @param {*} fn The function to debounce
|
||||
* @param {*} delay The delay in milliseconds
|
||||
* @param {*} immediate whether to debounce immediately
|
||||
* @returns {Function} The debounced function
|
||||
*/
|
||||
const debounce = (fn, delay = 0, immediate = false) => {
|
||||
let timeout
|
||||
let timeout;
|
||||
return (...args) => {
|
||||
if (immediate && !timeout) fn(...args)
|
||||
clearTimeout(timeout)
|
||||
if (immediate && !timeout) fn(...args);
|
||||
clearTimeout(timeout);
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
fn(...args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
fn(...args);
|
||||
}, delay);
|
||||
};
|
||||
};
|
||||
|
||||
const useDebouncedRef = (initialValue, delay, immediate) => {
|
||||
const state = ref(initialValue)
|
||||
/**
|
||||
* Emits the ref updated value after the given delay.
|
||||
*
|
||||
* @param {*} initialValue The default value of the ref
|
||||
* @param {*} delay The delay in milliseconds
|
||||
* @param {*} immediate Whether to call the function immediately
|
||||
* @returns {Object} The ref and a function to call to update the ref
|
||||
*/
|
||||
const useDebouncedRef = (initialValue, delay, immediate = false) => {
|
||||
const state = ref(initialValue);
|
||||
return customRef((track, trigger) => ({
|
||||
get() {
|
||||
track()
|
||||
return state.value
|
||||
track();
|
||||
return state.value;
|
||||
},
|
||||
set: debounce(
|
||||
value => {
|
||||
state.value = value
|
||||
trigger()
|
||||
},
|
||||
delay,
|
||||
immediate
|
||||
(value) => {
|
||||
state.value = value;
|
||||
trigger();
|
||||
},
|
||||
delay,
|
||||
immediate
|
||||
),
|
||||
}))
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
export default useDebouncedRef
|
||||
export default useDebouncedRef;
|
||||
|
||||
@@ -137,14 +137,14 @@ export default async (
|
||||
add_to_playlist,
|
||||
play_next,
|
||||
add_to_q,
|
||||
add_to_fav,
|
||||
// add_to_fav,
|
||||
separator,
|
||||
go_to_folder,
|
||||
go_to_artist,
|
||||
go_to_alb_artist,
|
||||
// go_to_artist,
|
||||
// go_to_alb_artist,
|
||||
go_to_album,
|
||||
separator,
|
||||
del_track,
|
||||
// separator,
|
||||
// del_track,
|
||||
];
|
||||
|
||||
return options;
|
||||
|
||||
+13
-8
@@ -1,13 +1,18 @@
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
import "./registerServiceWorker";
|
||||
import router from "./router";
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
|
||||
import "../src/assets/css/global.scss";
|
||||
|
||||
import { MotionPlugin } from "@vueuse/motion";
|
||||
import { createPinia } from "pinia";
|
||||
import { createApp } from "vue";
|
||||
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import useCustomTransitions from "./transitions";
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(createPinia())
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
app.mount('#app');
|
||||
app.use(MotionPlugin, useCustomTransitions);
|
||||
|
||||
app.mount("#app");
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { defineStore } from "pinia";
|
||||
import useDebouncedRef from "../composables/useDebouncedRef";
|
||||
|
||||
export default defineStore("gsearch", {
|
||||
state: () => ({
|
||||
filters: [],
|
||||
query: useDebouncedRef("", 600),
|
||||
results: {
|
||||
tracks: {
|
||||
items: [],
|
||||
more: false,
|
||||
},
|
||||
albums: {
|
||||
items: [],
|
||||
more: false,
|
||||
},
|
||||
artists: {
|
||||
items: [],
|
||||
more: false,
|
||||
},
|
||||
},
|
||||
}),
|
||||
actions: {
|
||||
addFilter(filter) {
|
||||
if (this.filters.includes(filter)) {
|
||||
return;
|
||||
}
|
||||
this.filters.push(filter);
|
||||
},
|
||||
removeFilter(filter) {
|
||||
this.filters = this.filters.filter((f) => f !== filter);
|
||||
},
|
||||
removeLastFilter() {
|
||||
this.filters.pop();
|
||||
},
|
||||
updateQuery(query) {
|
||||
this.query = query;
|
||||
},
|
||||
updateTrackResults(results) {
|
||||
this.results.tracks = results;
|
||||
},
|
||||
addMoreTrackResults(results) {
|
||||
this.results.tracks.items = [
|
||||
...this.results.tracks.items,
|
||||
...results.items,
|
||||
];
|
||||
},
|
||||
updateAlbumResults(results) {
|
||||
this.results.albums = results;
|
||||
},
|
||||
addMoreAlbumResults(results) {
|
||||
this.results.albums.items = [
|
||||
...this.results.albums.items,
|
||||
...results.items,
|
||||
];
|
||||
},
|
||||
updateArtistResults(results) {
|
||||
this.results.artists = results;
|
||||
},
|
||||
addMoreArtistResults(results) {
|
||||
this.results.artists.items = [
|
||||
...this.results.artists.items,
|
||||
...results.items,
|
||||
];
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -12,7 +12,6 @@ export default defineStore("Loader", {
|
||||
},
|
||||
stopLoading() {
|
||||
const diff = new Date().getTime() - this.duration;
|
||||
console.log(diff);
|
||||
|
||||
if (diff <= 250) {
|
||||
setTimeout(() => {
|
||||
|
||||
+29
-17
@@ -12,7 +12,7 @@ import {
|
||||
import notif from "../composables/mediaNotification";
|
||||
import { FromOptions } from "../composables/enums";
|
||||
|
||||
function addQToLocalStorage(
|
||||
function writeQueue(
|
||||
from: fromFolder | fromAlbum | fromPlaylist,
|
||||
tracks: Track[]
|
||||
) {
|
||||
@@ -25,11 +25,11 @@ function addQToLocalStorage(
|
||||
);
|
||||
}
|
||||
|
||||
function addCurrentToLocalStorage(track: Track) {
|
||||
function writeCurrent(track: Track) {
|
||||
localStorage.setItem("current", JSON.stringify(track));
|
||||
}
|
||||
|
||||
function readCurrentFromLocalStorage(): Track {
|
||||
function readCurrent(): Track {
|
||||
const current = localStorage.getItem("current");
|
||||
if (current) {
|
||||
return JSON.parse(current);
|
||||
@@ -48,11 +48,14 @@ export default defineStore("Queue", {
|
||||
state: () => ({
|
||||
progressElem: HTMLElement,
|
||||
audio: new Audio(),
|
||||
track: {
|
||||
current_time: 0,
|
||||
duration: 0,
|
||||
},
|
||||
current: <Track>{},
|
||||
next: <Track>{},
|
||||
prev: <Track>{},
|
||||
playing: false,
|
||||
current_time: 0,
|
||||
from: <fromFolder>{} || <fromAlbum>{} || <fromPlaylist>{},
|
||||
tracks: <Track[]>[defaultTrack],
|
||||
}),
|
||||
@@ -68,14 +71,16 @@ export default defineStore("Queue", {
|
||||
this.audio.onerror = reject;
|
||||
})
|
||||
.then(() => {
|
||||
this.track.duration = this.audio.duration;
|
||||
|
||||
this.audio.play().then(() => {
|
||||
this.playing = true;
|
||||
notif(track, this.playPause, this.playNext, this.playPrev);
|
||||
|
||||
this.audio.ontimeupdate = () => {
|
||||
this.current_time =
|
||||
this.track.current_time =
|
||||
(this.audio.currentTime / this.audio.duration) * 100;
|
||||
elem.style.backgroundSize = `${this.current_time}% 100%`;
|
||||
elem.style.backgroundSize = `${this.track.current_time}% 100%`;
|
||||
};
|
||||
|
||||
this.audio.onended = () => {
|
||||
@@ -114,7 +119,7 @@ export default defineStore("Queue", {
|
||||
}
|
||||
}
|
||||
},
|
||||
readQueueFromLocalStorage() {
|
||||
readQueue() {
|
||||
const queue = localStorage.getItem("queue");
|
||||
|
||||
if (queue) {
|
||||
@@ -123,7 +128,7 @@ export default defineStore("Queue", {
|
||||
this.tracks = parsed.tracks;
|
||||
}
|
||||
|
||||
this.updateCurrent(readCurrentFromLocalStorage());
|
||||
this.updateCurrent(readCurrent());
|
||||
},
|
||||
updateCurrent(track: Track) {
|
||||
this.current = track;
|
||||
@@ -131,7 +136,7 @@ export default defineStore("Queue", {
|
||||
this.updateNext(this.current);
|
||||
this.updatePrev(this.current);
|
||||
|
||||
addCurrentToLocalStorage(track);
|
||||
writeCurrent(track);
|
||||
},
|
||||
updateNext(track: Track) {
|
||||
const index = this.tracks.findIndex(
|
||||
@@ -161,8 +166,9 @@ export default defineStore("Queue", {
|
||||
},
|
||||
setNewQueue(tracklist: Track[]) {
|
||||
if (this.tracks !== tracklist) {
|
||||
this.tracks = tracklist;
|
||||
addQToLocalStorage(this.from, this.tracks);
|
||||
this.tracks = [];
|
||||
this.tracks.push(...tracklist);
|
||||
writeQueue(this.from, this.tracks);
|
||||
}
|
||||
},
|
||||
playFromFolder(fpath: string, tracks: Track[]) {
|
||||
@@ -201,7 +207,8 @@ export default defineStore("Queue", {
|
||||
},
|
||||
addTrackToQueue(track: Track) {
|
||||
this.tracks.push(track);
|
||||
addQToLocalStorage(this.from, this.tracks);
|
||||
writeQueue(this.from, this.tracks);
|
||||
this.updateNext(this.current);
|
||||
},
|
||||
playTrackNext(track: Track) {
|
||||
const Toast = useNotifStore();
|
||||
@@ -209,19 +216,24 @@ export default defineStore("Queue", {
|
||||
(t: Track) => t.trackid === this.current.trackid
|
||||
);
|
||||
|
||||
const next: Track = this.tracks[currentid + 1];
|
||||
if (currentid == this.tracks.length - 1) {
|
||||
this.tracks.push(track);
|
||||
} else {
|
||||
const next: Track = this.tracks[currentid + 1];
|
||||
|
||||
if (next.trackid === track.trackid) {
|
||||
Toast.showNotification("Track is already queued", NotifType.Info);
|
||||
return;
|
||||
if (next.trackid === track.trackid) {
|
||||
Toast.showNotification("Track is already queued", NotifType.Info);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.tracks.splice(currentid + 1, 0, track);
|
||||
this.updateNext(this.current);
|
||||
Toast.showNotification(
|
||||
`Added ${track.title} to queue`,
|
||||
NotifType.Success
|
||||
);
|
||||
addQToLocalStorage(this.from, this.tracks);
|
||||
writeQueue(this.from, this.tracks);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
import { ref, reactive } from "@vue/reactivity";
|
||||
import { defineStore } from "pinia";
|
||||
import { AlbumInfo, Artist, Track } from "../interfaces";
|
||||
import {
|
||||
searchTracks,
|
||||
searchAlbums,
|
||||
searchArtists,
|
||||
loadMoreTracks,
|
||||
loadMoreAlbums,
|
||||
loadMoreArtists,
|
||||
} from "../composables/searchMusic";
|
||||
import { watch } from "vue";
|
||||
import useDebouncedRef from "../composables/useDebouncedRef";
|
||||
import useTabStore from "./tabs";
|
||||
/**
|
||||
*
|
||||
* @param id The id of the element of the div to scroll
|
||||
* Scrolls on clicking the loadmore button
|
||||
*/
|
||||
function scrollOnLoad(id: string) {
|
||||
const elem = document.getElementById(id);
|
||||
|
||||
elem.scroll({
|
||||
top: elem.scrollHeight,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
}
|
||||
|
||||
export default defineStore("search", () => {
|
||||
const query = useDebouncedRef(null, 600);
|
||||
const currentTab = ref("tracks");
|
||||
|
||||
const tracks = reactive({
|
||||
value: <Track[]>[],
|
||||
more: false,
|
||||
});
|
||||
|
||||
const albums = reactive({
|
||||
value: <AlbumInfo[]>[],
|
||||
more: false,
|
||||
});
|
||||
|
||||
const artists = reactive({
|
||||
value: <Artist[]>[],
|
||||
more: false,
|
||||
});
|
||||
|
||||
/**
|
||||
* Searches for tracks, albums and artists
|
||||
* @param query query to search for
|
||||
*/
|
||||
function fetchTracks(query: string) {
|
||||
searchTracks(query).then((res) => {
|
||||
tracks.value = res.tracks;
|
||||
tracks.more = res.more;
|
||||
});
|
||||
}
|
||||
|
||||
function fetchAlbums(query: string) {
|
||||
searchAlbums(query).then((res) => {
|
||||
albums.value = res.albums;
|
||||
albums.more = res.more;
|
||||
});
|
||||
}
|
||||
|
||||
function fetchArtists(query: string) {
|
||||
searchArtists(query).then((res) => {
|
||||
artists.value = res.artists;
|
||||
artists.more = res.more;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads more search tracks results
|
||||
*
|
||||
* @param index The starting index of the tracks to load
|
||||
*/
|
||||
function loadTracks(index: number) {
|
||||
loadMoreTracks(index)
|
||||
.then((res) => {
|
||||
tracks.value = [...tracks.value, ...res.tracks];
|
||||
tracks.more = res.more;
|
||||
})
|
||||
.then(() => {
|
||||
scrollOnLoad("tab-content");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads more search albums results
|
||||
*
|
||||
* @param index The starting index of the albums to load
|
||||
*/
|
||||
function loadAlbums(index: number) {
|
||||
loadMoreAlbums(index)
|
||||
.then((res) => {
|
||||
albums.value = [...albums.value, ...res.albums];
|
||||
albums.more = res.more;
|
||||
})
|
||||
.then(() => {
|
||||
scrollOnLoad("tab-content");
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads more search artists results
|
||||
*
|
||||
* @param index The starting index of the artists to load
|
||||
*/
|
||||
function loadArtists(index: number) {
|
||||
loadMoreArtists(index)
|
||||
.then((res) => {
|
||||
artists.value = [...artists.value, ...res.artists];
|
||||
artists.more = res.more;
|
||||
})
|
||||
.then(() => {
|
||||
scrollOnLoad("tab-content");
|
||||
});
|
||||
}
|
||||
|
||||
watch(
|
||||
() => query.value,
|
||||
(newQuery) => {
|
||||
const tabs = useTabStore();
|
||||
|
||||
if (tabs.current !== "search") {
|
||||
tabs.switchToSearch();
|
||||
}
|
||||
|
||||
switch (currentTab.value) {
|
||||
case "tracks":
|
||||
fetchTracks(newQuery);
|
||||
break;
|
||||
case "albums":
|
||||
fetchAlbums(newQuery);
|
||||
break;
|
||||
case "artists":
|
||||
fetchArtists(newQuery);
|
||||
break;
|
||||
default:
|
||||
fetchTracks(newQuery);
|
||||
break;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => currentTab.value,
|
||||
(newTab) => {
|
||||
switch (newTab) {
|
||||
case "tracks":
|
||||
fetchTracks(query.value);
|
||||
break;
|
||||
case "albums":
|
||||
fetchAlbums(query.value);
|
||||
break;
|
||||
case "artists":
|
||||
fetchArtists(query.value);
|
||||
break;
|
||||
default:
|
||||
fetchTracks(query.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function changeTab(tab: string) {
|
||||
currentTab.value = tab;
|
||||
}
|
||||
|
||||
setTimeout(() => {}, 3000);
|
||||
|
||||
return {
|
||||
tracks,
|
||||
albums,
|
||||
artists,
|
||||
query,
|
||||
currentTab,
|
||||
loadTracks,
|
||||
loadAlbums,
|
||||
loadArtists,
|
||||
changeTab,
|
||||
};
|
||||
});
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { defineStore } from "pinia";
|
||||
import perks from "../composables/perks";
|
||||
import { focusCurrent } from "../composables/perks";
|
||||
|
||||
const tablist = {
|
||||
home: "home",
|
||||
@@ -16,7 +16,7 @@ export default defineStore("tabs", {
|
||||
changeTab(tab: string) {
|
||||
if (tab === this.tabs.queue) {
|
||||
setTimeout(() => {
|
||||
perks.focusCurrent();
|
||||
focusCurrent();
|
||||
}, 500);
|
||||
}
|
||||
this.current = tab;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
export default {
|
||||
directives: {
|
||||
"slide-from-left": {
|
||||
initial: {
|
||||
opacity: 0,
|
||||
x: 0,
|
||||
y: 20
|
||||
},
|
||||
enter: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 100,
|
||||
ease: "circInOut",
|
||||
},
|
||||
},
|
||||
},
|
||||
"slide-from-left-100": {
|
||||
initial: {
|
||||
opacity: 0,
|
||||
x: -20,
|
||||
},
|
||||
enter: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
transition: {
|
||||
delay: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
"slide-from-top": {
|
||||
initial: {
|
||||
y: -20,
|
||||
opacity: 0,
|
||||
},
|
||||
enter: {
|
||||
y: 0,
|
||||
opacity: 1,
|
||||
transition: {
|
||||
delay: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
"slide-from-right": {
|
||||
initial: {
|
||||
x: 20,
|
||||
opacity: 0,
|
||||
},
|
||||
enter: {
|
||||
x: 0,
|
||||
opacity: 1,
|
||||
transition: {
|
||||
delay: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
scale: {
|
||||
initial: {
|
||||
scale: 0.8,
|
||||
},
|
||||
enter: {
|
||||
scale: 1,
|
||||
transition: {
|
||||
duration: 200,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
+8
-16
@@ -1,23 +1,15 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<AlbumOfTheDay />
|
||||
<home />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AlbumOfTheDay from "@/components/AlbumOfTheDay.vue";
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
components: {
|
||||
AlbumOfTheDay,
|
||||
},
|
||||
};
|
||||
<script setup lang="ts">
|
||||
import Home from "@/components/Home.vue";
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.home {
|
||||
padding-left: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
.home {
|
||||
padding-left: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "preserve",
|
||||
"paths": {
|
||||
"baseUrl": ["./"],
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,6 +376,40 @@
|
||||
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.29.tgz#07dac7051117236431d2f737d16932aa38bbb925"
|
||||
integrity sha512-BjNpU8OK6Z0LVzGUppEk0CMYm/hKDnZfYdjSmPOs0N+TR1cLKJAkDwW8ASZUvaaSLEi6d3hVM7jnWnX+6yWnHw==
|
||||
|
||||
"@vueuse/core@^8.1.2", "@vueuse/core@^8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-8.5.0.tgz#2b7548e52165c88e1463756c36188e105d806543"
|
||||
integrity sha512-VEJ6sGNsPlUp0o9BGda2YISvDZbhWJSOJu5zlp2TufRGVrLcYUKr31jyFEOj6RXzG3k/H4aCYeZyjpItfU8glw==
|
||||
dependencies:
|
||||
"@vueuse/metadata" "8.5.0"
|
||||
"@vueuse/shared" "8.5.0"
|
||||
vue-demi "*"
|
||||
|
||||
"@vueuse/metadata@8.5.0":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-8.5.0.tgz#1aaa3787922cfda0f38243aaa7779366a669b4db"
|
||||
integrity sha512-WxsD+Cd+bn+HcjpY6Dl9FJ8ywTRTT9pTwk3bCQpzEhXVYAyNczKDSahk50fCfIJKeWHhyI4B2+/ZEOxQAkUr0g==
|
||||
|
||||
"@vueuse/motion@^2.0.0-beta.18":
|
||||
version "2.0.0-beta.18"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/motion/-/motion-2.0.0-beta.18.tgz#e98e9a4c34da2ca456a10639dca3b36409e97b5f"
|
||||
integrity sha512-mPeXxuqZp13lqpcb+345TnEP7tEOjC/wTkwf8be1Obzt3913lPpZPXgwKafMoocKRNOnMZye8Y6PqQOEKztk9A==
|
||||
dependencies:
|
||||
"@vueuse/core" "^8.1.2"
|
||||
"@vueuse/shared" "^8.1.2"
|
||||
csstype "^3.0.11"
|
||||
framesync "^6.1.0"
|
||||
popmotion "^11.0.3"
|
||||
style-value-types "^5.1.0"
|
||||
vue-demi "*"
|
||||
|
||||
"@vueuse/shared@8.5.0", "@vueuse/shared@^8.1.2":
|
||||
version "8.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-8.5.0.tgz#fa01ecd3161933f521dd6428b9ef8015ded1bbd3"
|
||||
integrity sha512-qKG+SZb44VvGD4dU5cQ63z4JE2Yk39hQUecR0a9sEdJA01cx+XrxAvFKJfPooxwoiqalAVw/ktWK6xbyc/jS3g==
|
||||
dependencies:
|
||||
vue-demi "*"
|
||||
|
||||
"@webassemblyjs/ast@1.11.1":
|
||||
version "1.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
|
||||
@@ -717,6 +751,11 @@ csstype@^2.6.8:
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.19.tgz#feeb5aae89020bb389e1f63669a5ed490e391caa"
|
||||
integrity sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ==
|
||||
|
||||
csstype@^3.0.11:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2"
|
||||
integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==
|
||||
|
||||
debug@^4.1.1, debug@^4.3.2:
|
||||
version "4.3.3"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||
@@ -729,6 +768,11 @@ deep-is@^0.1.3:
|
||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||
|
||||
defu@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/defu/-/defu-6.0.0.tgz#b397a6709a2f3202747a3d9daf9446e41ad0c5fc"
|
||||
integrity sha512-t2MZGLf1V2rV4VBZbWIaXKdX/mUcYW0n2znQZoADBkGGxYL8EWqCuCZBmJPJ/Yy9fofJkyuuSuo5GSwo0XdEgw==
|
||||
|
||||
doctrine@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
|
||||
@@ -1071,6 +1115,20 @@ follow-redirects@^1.14.8:
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
|
||||
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
|
||||
|
||||
framesync@6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/framesync/-/framesync-6.0.1.tgz#5e32fc01f1c42b39c654c35b16440e07a25d6f20"
|
||||
integrity sha512-fUY88kXvGiIItgNC7wcTOl0SNRCVXMKSWW2Yzfmn7EKNc+MpCzcz9DhdHcdjbrtN3c6R4H5dTY2jiCpPdysEjA==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
framesync@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/framesync/-/framesync-6.1.0.tgz#b22cf9afba52a9a895668b09e033b6a61e901c41"
|
||||
integrity sha512-aBX+hdWAvwiJYeQlFLY2533VxeL6OEu71CAgV4GGKksrj6+dE6i7K86WSSiRBEARCoJn5bFqffhg4l07eA27tg==
|
||||
dependencies:
|
||||
tslib "^2.3.1"
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
@@ -1162,6 +1220,11 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hey-listen@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/hey-listen/-/hey-listen-1.0.8.tgz#8e59561ff724908de1aa924ed6ecc84a56a9aa68"
|
||||
integrity sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==
|
||||
|
||||
ieee754@^1.1.13:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
@@ -1569,6 +1632,16 @@ pngjs@^3.0.0, pngjs@^3.3.3:
|
||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f"
|
||||
integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==
|
||||
|
||||
popmotion@^11.0.3:
|
||||
version "11.0.3"
|
||||
resolved "https://registry.yarnpkg.com/popmotion/-/popmotion-11.0.3.tgz#565c5f6590bbcddab7a33a074bb2ba97e24b0cc9"
|
||||
integrity sha512-Y55FLdj3UxkR7Vl3s7Qr4e9m0onSnP8W7d/xQLsoJM40vs6UKHFdygs6SWryasTZYqugMjm3BepCF4CWXDiHgA==
|
||||
dependencies:
|
||||
framesync "6.0.1"
|
||||
hey-listen "^1.0.8"
|
||||
style-value-types "5.0.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
postcss@^8.1.10, postcss@^8.4.5:
|
||||
version "8.4.6"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1"
|
||||
@@ -1765,6 +1838,22 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||
|
||||
style-value-types@5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-5.0.0.tgz#76c35f0e579843d523187989da866729411fc8ad"
|
||||
integrity sha512-08yq36Ikn4kx4YU6RD7jWEv27v4V+PUsOGa4n/as8Et3CuODMJQ00ENeAVXAeydX4Z2j1XHZF1K2sX4mGl18fA==
|
||||
dependencies:
|
||||
hey-listen "^1.0.8"
|
||||
tslib "^2.1.0"
|
||||
|
||||
style-value-types@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-5.1.0.tgz#228b02bd9418c57db46c1f450b85577e634a877f"
|
||||
integrity sha512-DRIfBtjxQ4ztBZpexkFcI+UR7pODC5qLMf2Syt+bH98PAHHRH2tQnzxBuDQlqcAoYar6GzWnj8iAfqfwnEzCiQ==
|
||||
dependencies:
|
||||
hey-listen "^1.0.8"
|
||||
tslib "^2.3.1"
|
||||
|
||||
supports-color@^7.1.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
|
||||
@@ -1831,6 +1920,11 @@ to-regex-range@^5.0.1:
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
tslib@^2.1.0, tslib@^2.3.1:
|
||||
version "2.4.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3"
|
||||
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
|
||||
|
||||
type-check@^0.4.0, type-check@~0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
|
||||
|
||||
Reference in New Issue
Block a user