major refactors

This commit is contained in:
geoffrey45
2022-03-30 14:56:40 +03:00
parent 1a19fb61cd
commit 0c1e792839
21 changed files with 164 additions and 322 deletions
+8 -1
View File
@@ -30,7 +30,14 @@ def get_all_playlists():
@playlist_bp.route("/playlist/new", methods=["POST"]) @playlist_bp.route("/playlist/new", methods=["POST"])
def create_playlist(): def create_playlist():
data = request.get_json() data = request.get_json()
playlist = {"name": data["name"], "description": [], "tracks": []}
playlist = {
"name": data["name"],
"description": [],
"tracks": [],
"count": 0,
"lastUpdated": 0,
}
try: try:
p_in_db = instances.playlist_instance.get_playlist_by_name(playlist["name"]) p_in_db = instances.playlist_instance.get_playlist_by_name(playlist["name"])
+1 -1
View File
@@ -21,7 +21,7 @@ class AllSongs(db.Mongo):
# def drop_db(self): # def drop_db(self):
# self.collection.drop() # self.collection.drop()
def insert_song(self, song_obj: dict) -> None: def insert_song(self, song_obj: dict) -> str:
""" """
Inserts a new track object into the database. Inserts a new track object into the database.
""" """
+5 -4
View File
@@ -69,11 +69,12 @@ def populate():
for file in files: for file in files:
tags = get_tags(file) tags = get_tags(file)
if tags not in api.PRE_TRACKS:
api.PRE_TRACKS.append(tags)
if tags is not None: if tags is not None:
instances.songs_instance.insert_song(tags) upsert_id = instances.songs_instance.insert_song(tags)
if upsert_id is not None:
tags["_id"] = {"$oid": upsert_id}
api.PRE_TRACKS.append(tags)
_bar.next() _bar.next()
_bar.finish() _bar.finish()
+7 -3
View File
@@ -2,6 +2,7 @@
This library contains all the functions related to albums. This library contains all the functions related to albums.
""" """
from pprint import pprint
import urllib import urllib
from typing import List from typing import List
from app import models, functions, helpers from app import models, functions, helpers
@@ -52,9 +53,12 @@ def get_album_tracks(album: str, artist: str) -> List:
tracks = [] tracks = []
for track in api.PRE_TRACKS: for track in api.PRE_TRACKS:
if track["album"] == album and track["albumartist"] == artist: try:
tracks.append(track) if track["album"] == album and track["albumartist"] == artist:
tracks.append(track)
except TypeError:
pprint(track, indent=4)
print(album, artist)
return tracks return tracks
+6
View File
@@ -3,6 +3,7 @@ Contains all the models for objects generation and typing.
""" """
from dataclasses import dataclass from dataclasses import dataclass
from datetime import date
from typing import List from typing import List
from app import api from app import api
from app import settings from app import settings
@@ -95,6 +96,8 @@ class Playlist:
description: str description: str
image: str image: str
tracks: List[Track] tracks: List[Track]
count: int
lastUpdated: int
"""A list of track objects in the playlist""" """A list of track objects in the playlist"""
def __init__(self, data): def __init__(self, data):
@@ -103,6 +106,9 @@ class Playlist:
self.description = data["description"] self.description = data["description"]
self.image = "" self.image = ""
self.tracks = create_playlist_tracks(data["tracks"]) self.tracks = create_playlist_tracks(data["tracks"])
self.count = len(data["tracks"])
self.lastUpdated = data["lastUpdated"]
@dataclass @dataclass
+5 -1
View File
@@ -39,11 +39,15 @@ import useContextStore from "./stores/context";
import ContextMenu from "./components/contextMenu.vue"; import ContextMenu from "./components/contextMenu.vue";
import Modal from "./components/modal.vue"; import Modal from "./components/modal.vue";
import Notification from "./components/Notification.vue"; import Notification from "./components/Notification.vue";
import useQStore from "./stores/queue";
const context_store = useContextStore(); const context_store = useContextStore();
const queue = useQStore();
queue.readQueueFromLocalStorage();
const RightSideBar = Main; const RightSideBar = Main;
perks.readQueue();
const collapsed = ref(false); const collapsed = ref(false);
const app_dom = document.getElementById("app"); const app_dom = document.getElementById("app");
+18 -19
View File
@@ -1,23 +1,22 @@
<template> <template>
<div class="hotkeys"> <div class="hotkeys">
<div class="image ctrl-btn" id="previous" @click="playPrev"></div> <div class="image ctrl-btn" id="previous" @click="props.prev"></div>
<div <div
class="image ctrl-btn play-pause" class="image ctrl-btn play-pause"
@click="playPause" @click="playPause"
:class="{ isPlaying: isPlaying }" :class="{ isPlaying: props.playing }"
></div> ></div>
<div class="image ctrl-btn" id="next" @click="playNext"></div> <div class="image ctrl-btn" id="next" @click="props.next"></div>
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import playAudio from '../../../composables/playAudio'; const props = defineProps<{
playing: boolean;
const playPause = playAudio.playPause; playPause: () => void;
const playNext = playAudio.playNext; next: () => void;
const playPrev = playAudio.playPrev; prev: () => void;
}>();
const isPlaying = playAudio.playing;
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -31,16 +30,16 @@ const isPlaying = playAudio.playing;
place-content: flex-end; place-content: flex-end;
.ctrl-btn { .ctrl-btn {
height: 2.5rem; height: 2.5rem;
width: 100%; width: 100%;
background-size: 1.5rem !important; background-size: 1.5rem !important;
cursor: pointer; cursor: pointer;
border-radius: 0.5rem; border-radius: 0.5rem;
&:hover { &:hover {
background-color: $red; background-color: $red;
}
} }
}
#previous { #previous {
background-image: url(../../../assets/icons/previous.svg); background-image: url(../../../assets/icons/previous.svg);
+9 -7
View File
@@ -2,7 +2,7 @@
<input <input
id="progress" id="progress"
type="range" type="range"
:value="pos" :value="props.pos"
min="0" min="0"
max="100" max="100"
step="0.1" step="0.1"
@@ -10,12 +10,14 @@
/> />
</template> </template>
<script setup> <script setup lang="ts">
import { ref } from "vue";
import playAudio from "../../../composables/playAudio";
const pos = ref(playAudio.pos);
const seek = () => { const seek = () => {
playAudio.seek(document.getElementById("progress").value); const value = Number(document.getElementById("progress").value);
props.seek(value);
}; };
const props = defineProps<{
pos: number;
seek: (time: number) => void;
}>();
</script> </script>
+14 -23
View File
@@ -1,39 +1,30 @@
<template> <template>
<div class="info"> <div class="info">
<div
v-if="props.collapsed"
class="image art"
:style="{
backgroundImage: `url(&quot;${track.image}&quot;)`,
}"
></div>
<div class="desc"> <div class="desc">
<div> <div>
<div class="title ellip">{{ track.title }}</div> <div class="title ellip">{{ props.track.title }}</div>
<div class="separator no-border"></div> <div class="separator no-border"></div>
<div class="artists ellip" v-if="track.artists[0] !== ''"> <div class="artists ellip" v-if="props.track.artists[0] !== ''">
<span v-for="artist in putCommas(track.artists)" :key="artist">{{ <span
artist v-for="artist in putCommas(props.track.artists)"
}}</span> :key="artist"
>{{ artist }}</span
>
</div> </div>
<div class="artists" v-else> <div class="artists" v-else>
<span>{{ track.albumartist }}</span> <span>{{ props.track.albumartist }}</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import perks from "../../composables/perks"; import perks from "../../composables/perks";
import state from "../../composables/state"; import { Track } from "../../interfaces";
const track = state.current;
const props = defineProps({
collapsed: {
type: Boolean,
default: false,
},
});
const putCommas = perks.putCommas; const putCommas = perks.putCommas;
const props = defineProps<{
track: Track;
}>();
</script> </script>
+12 -14
View File
@@ -1,5 +1,5 @@
<template> <template>
<div class="l_ rounded" v-if="!props.collapsed"> <div class="l_ rounded">
<div class="headin">Now Playing</div> <div class="headin">Now Playing</div>
<div class="button menu image rounded"></div> <div class="button menu image rounded"></div>
<div class="separator no-border"></div> <div class="separator no-border"></div>
@@ -8,32 +8,30 @@
<div <div
class="l-image image rounded" class="l-image image rounded"
:style="{ :style="{
backgroundImage: `url(&quot;${current.image}&quot;)`, backgroundImage: `url(&quot;${queue.current.image}&quot;)`,
}" }"
></div> ></div>
</div> </div>
<div class="separator no-border"></div> <div class="separator no-border"></div>
<SongCard /> <SongCard :track="queue.current" />
<Progress /> <Progress :seek="queue.seek" :pos="queue.current_time" />
<HotKeys /> <HotKeys
:playing="queue.playing"
:playPause="queue.playPause"
:next="queue.playNext"
:prev="queue.playPrev"
/>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref } from "vue";
import state from "../../composables/state";
import SongCard from "./SongCard.vue"; import SongCard from "./SongCard.vue";
import HotKeys from "./NP/HotKeys.vue"; import HotKeys from "./NP/HotKeys.vue";
import Progress from "./NP/Progress.vue"; import Progress from "./NP/Progress.vue";
import useQStore from "../../stores/queue";
const current = ref(state.current); const queue = useQStore();
const props = defineProps({
collapsed: {
type: Boolean,
default: false,
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
.l_ { .l_ {
+1 -1
View File
@@ -28,7 +28,7 @@ const props = defineProps<{
.image { .image {
min-width: 100%; min-width: 100%;
height: 8.5rem; height: 10rem;
background-image: url("../../assets/images/eggs.jpg"); background-image: url("../../assets/images/eggs.jpg");
} }
+1 -3
View File
@@ -12,10 +12,8 @@ const loading = state.loading
<style lang="scss"> <style lang="scss">
.loaderx { .loaderx {
position: absolute;
top: 0.65rem;
width: 1.5rem; width: 1.5rem;
height: 1.5rem; height:1.5rem;
border-radius: 50%; border-radius: 50%;
} }
+1 -4
View File
@@ -79,10 +79,7 @@ const showContextMenu = (e: Event) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
contextStore.showContextMenu( contextStore.showContextMenu(e, trackContext(props.song, modalStore));
e,
trackContext(props.song, modalStore)
);
context_on.value = true; context_on.value = true;
contextStore.$subscribe((mutation, state) => { contextStore.$subscribe((mutation, state) => {
+24 -16
View File
@@ -4,7 +4,7 @@
@click="playThis(props.track)" @click="playThis(props.track)"
:class="[ :class="[
{ {
currentInQueue: current.trackid === props.track.trackid, currentInQueue: props.isCurrent,
}, },
{ 'context-on': context_on }, { 'context-on': context_on },
]" ]"
@@ -18,8 +18,8 @@
> >
<div <div
class="now-playing-track image" class="now-playing-track image"
v-if="current.trackid === props.track.trackid" v-if="props.isCurrent"
:class="{ active: is_playing, not_active: !is_playing }" :class="{ active: props.isPlaying, not_active: !props.isPlaying }"
></div> ></div>
</div> </div>
<div class="tags"> <div class="tags">
@@ -34,21 +34,31 @@
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import perks from "../../composables/perks"; import perks from "../../composables/perks";
import playAudio from "../../composables/playAudio";
import useContextStore from "@/stores/context";
import trackContext from "../../contexts/track_context"; import trackContext from "../../contexts/track_context";
import { Track } from "../../interfaces";
import useContextStore from "../../stores/context";
import useModalStore from "../../stores/modal";
const contextStore = useContextStore(); const contextStore = useContextStore();
const modalStore = useModalStore();
const props = defineProps<{
track: Track;
isCurrent: boolean;
isPlaying: boolean;
}>();
const context_on = ref(false); const context_on = ref(false);
const showContextMenu = (e) => { const showContextMenu = (e: Event) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
contextStore.showContextMenu(e, trackContext(props.track)); contextStore.showContextMenu(e, trackContext(props.track, modalStore));
context_on.value = true; context_on.value = true;
contextStore.$subscribe((mutation, state) => { contextStore.$subscribe((mutation, state) => {
@@ -57,18 +67,16 @@ const showContextMenu = (e) => {
} }
}); });
}; };
const props = defineProps({
track: Object, const emit = defineEmits<{
default: () => ({}), (e: "PlayThis", track: Track): void;
}); }>();
const current = ref(perks.current); const current = ref(perks.current);
const putCommas = perks.putCommas; const putCommas = perks.putCommas;
const is_playing = ref(playAudio.playing);
const playThis = (song) => { const playThis = (track: Track) => {
playAudio.playAudio(song.trackid); emit("PlayThis", track);
perks.current.value = song;
}; };
</script> </script>
@@ -1,41 +1,44 @@
import { Track } from "../interfaces.js";
import perks from "./perks.js"; import perks from "./perks.js";
import playAudio from "./playAudio.js";
let showMediaNotif = () => {
let current = perks.current.value;
export default (
track: Track,
playPause: () => void,
playNext: () => void,
playPrev: () => void
) => {
if ("mediaSession" in navigator) { if ("mediaSession" in navigator) {
navigator.mediaSession.metadata = new window.MediaMetadata({ navigator.mediaSession.metadata = new window.MediaMetadata({
title: current.title, title: track.title,
artist: current.artists, artist: track.artists.join(", "),
artwork: [ artwork: [
{ {
src: current.image, src: track.image,
sizes: "96x96", sizes: "96x96",
type: "image/jpeg", type: "image/jpeg",
}, },
{ {
src: current.image, src: track.image,
sizes: "128x128", sizes: "128x128",
type: "image/webp", type: "image/webp",
}, },
{ {
src: current.image, src: track.image,
sizes: "192x192", sizes: "192x192",
type: "image/webp", type: "image/webp",
}, },
{ {
src: current.image, src: track.image,
sizes: "256x256", sizes: "256x256",
type: "image/webp", type: "image/webp",
}, },
{ {
src: current.image, src: track.image,
sizes: "384x384", sizes: "384x384",
type: "image/webp", type: "image/webp",
}, },
{ {
src: current.image, src: track.image,
sizes: "512x512", sizes: "512x512",
type: "image/webp", type: "image/webp",
}, },
@@ -43,22 +46,16 @@ let showMediaNotif = () => {
}); });
navigator.mediaSession.setActionHandler("play", function () { navigator.mediaSession.setActionHandler("play", function () {
playAudio.playPause(); playPause();
}); });
navigator.mediaSession.setActionHandler("pause", function () { navigator.mediaSession.setActionHandler("pause", function () {
playAudio.playPause(); playPause();
}); });
navigator.mediaSession.setActionHandler("seekbackward", function () {});
navigator.mediaSession.setActionHandler("seekforward", function () {});
navigator.mediaSession.setActionHandler("previoustrack", function () { navigator.mediaSession.setActionHandler("previoustrack", function () {
playAudio.playPrev(); playPrev();
}); });
navigator.mediaSession.setActionHandler("nexttrack", function () { navigator.mediaSession.setActionHandler("nexttrack", function () {
playAudio.playNext(); playNext();
}); });
} }
}; };
export default {
showMediaNotif,
};
+3 -114
View File
@@ -1,26 +1,3 @@
import { ref } from "@vue/reactivity";
import { watch } from "@vue/runtime-core";
import media from "./mediaNotification.js";
import playAudio from "./playAudio.js";
import state from "./state";
const current = ref(state.current);
const next = ref({
title: "The next song",
artists: ["... blah blah blah"],
image: "http://127.0.0.1:8900/images/defaults/4.webp",
_id: {
$oid: "",
},
});
const prev = ref(state.prev);
const queue = ref(state.queue);
const search = ref("");
const putCommas = (artists) => { const putCommas = (artists) => {
let result = []; let result = [];
@@ -35,74 +12,6 @@ const putCommas = (artists) => {
return result; return result;
}; };
function updateNext(song_) {
const index = state.queue.value.findIndex(
(item) => item.trackid === song_.trackid
);
if (index === queue.value.length - 1) {
next.value = queue.value[0];
state.prev.value = queue.value[queue.value.length - 2];
} else if (index === 0) {
next.value = queue.value[1];
} else {
next.value = queue.value[index + 1];
}
}
function updatePrev(song) {
const index = state.queue.value.findIndex(
(item) => item.trackid === song.trackid
);
if (index === 0) {
prev.value = queue.value[queue.value.length - 1];
} else if (index === queue.value.length - 1) {
prev.value = queue.value[index - 1];
} else {
prev.value = queue.value[index - 1];
}
}
const readQueue = () => {
const prev_queue = JSON.parse(localStorage.getItem("queue"));
const last_played = JSON.parse(localStorage.getItem("current"));
if (last_played) {
state.current.value = last_played;
}
if (prev_queue) {
state.queue.value = prev_queue;
updateNext(state.current.value);
updatePrev(state.current.value);
}
};
const updateQueue = async (song, type) => {
playAudio.playAudio(song.trackid);
let list;
switch (type) {
case "folder":
list = state.folder_song_list.value;
break;
case "album":
list = state.album.tracklist;
break;
}
if (state.queue.value[0].trackid !== list[0].trackid) {
const new_queue = list;
localStorage.setItem("queue", JSON.stringify(new_queue));
state.queue.value = new_queue;
}
state.current.value = song;
localStorage.setItem("current", JSON.stringify(song));
};
function focusCurrent() { function focusCurrent() {
const elem = document.getElementsByClassName("currentInQueue")[0]; const elem = document.getElementsByClassName("currentInQueue")[0];
@@ -132,17 +41,6 @@ function focusSearchBox() {
elem.focus(); elem.focus();
} }
setTimeout(() => {
watch(current, (new_current) => {
media.showMediaNotif();
updateNext(new_current);
updatePrev(new_current);
localStorage.setItem("current", JSON.stringify(new_current));
});
}, 1000);
let key_down_fired = false; let key_down_fired = false;
window.addEventListener("keydown", (e) => { window.addEventListener("keydown", (e) => {
@@ -161,7 +59,7 @@ window.addEventListener("keydown", (e) => {
key_down_fired = false; key_down_fired = false;
}, 1000); }, 1000);
playAudio.playNext(); // playAudio.playNext();
} }
} }
break; break;
@@ -173,7 +71,7 @@ window.addEventListener("keydown", (e) => {
key_down_fired = true; key_down_fired = true;
playAudio.playPrev(); // playAudio.playPrev();
setTimeout(() => { setTimeout(() => {
key_down_fired = false; key_down_fired = false;
@@ -190,7 +88,7 @@ window.addEventListener("keydown", (e) => {
e.preventDefault(); e.preventDefault();
key_down_fired = true; key_down_fired = true;
playAudio.playPause(); // playAudio.playPause();
} }
} }
@@ -212,8 +110,6 @@ window.addEventListener("keyup", () => {
key_down_fired = false; key_down_fired = false;
}); });
function formatSeconds(seconds) { function formatSeconds(seconds) {
// check if there are arguments // check if there are arguments
@@ -256,14 +152,7 @@ function formatSeconds(seconds) {
export default { export default {
putCommas, putCommas,
readQueue,
focusCurrent, focusCurrent,
updateQueue,
formatSeconds, formatSeconds,
getElem, getElem,
current,
queue,
next,
prev,
search,
}; };
-86
View File
@@ -1,86 +0,0 @@
import {ref} from "@vue/reactivity";
import perks from "./perks";
import media from "./mediaNotification.js";
import state from "./state";
const audio = ref(new Audio()).value;
const pos = ref(0);
const current_time = ref(0);
const playing = ref(state.is_playing);
const url = "http://127.0.0.1:9876//file/";
const playAudio = (trackid) => {
const elem = document.getElementById('progress');
const full_path = url + encodeURIComponent(trackid);
new Promise((resolve, reject) => {
audio.src = full_path;
audio.oncanplaythrough = resolve;
audio.onerror = reject;
})
.then(() => {
audio.play().then(() => {
perks.focusCurrent()
state.is_playing.value = true;
}
);
audio.ontimeupdate = () => {
current_time.value = audio.currentTime;
pos.value = (audio.currentTime / audio.duration) * 100;
let bg_size = ((audio.currentTime / audio.duration) * 100)
elem.style.backgroundSize = `${bg_size}% 100%`;
};
})
.catch((err) => console.log(err));
};
function playNext() {
playAudio(perks.next.value.trackid);
perks.current.value = perks.next.value;
media.showMediaNotif();
}
function playPrev() {
playAudio(state.prev.value.trackid);
perks.current.value = perks.prev.value;
}
function seek(position) {
audio.currentTime = (position / 100) * audio.duration;
}
function playPause() {
if (audio.src === "") {
playAudio(perks.current.value.trackid);
}
if (audio.paused) {
audio.play();
} else {
audio.pause();
}
}
audio.addEventListener("play", () => {
state.is_playing.value = true;
});
audio.addEventListener("pause", () => {
state.is_playing.value = false;
});
audio.addEventListener("ended", () => {
playNext();
});
export default {playAudio, playNext, playPrev, playPause, seek, pos, playing, current_time};
// TODO
// Try implementing classes to play audio .ie. Make the seek, playNext, playPrev, etc the methods of a class. etc
+5
View File
@@ -12,6 +12,7 @@ import SettingsView from "../views/SettingsView.vue";
import usePStore from "../stores/playlists"; import usePStore from "../stores/playlists";
import usePTrackStore from "../stores/p.ptracks"; import usePTrackStore from "../stores/p.ptracks";
import useFStore from "../stores/folder";
const routes = [ const routes = [
{ {
@@ -23,6 +24,10 @@ const routes = [
path: "/folder/:path", path: "/folder/:path",
name: "FolderView", name: "FolderView",
component: FolderView, component: FolderView,
beforeEnter: async (to) => {
console.log("beforeEnter")
await useFStore().fetchAll(to.params.path);
},
}, },
{ {
path: "/folder/", path: "/folder/",
+21
View File
@@ -0,0 +1,21 @@
import { defineStore } from "pinia";
import { Folder, Track } from "../interfaces";
import fetchThem from "../composables/getFilesAndFolders";
export default defineStore("FolderDirs&Tracks", {
state: () => ({
path: <string>{},
dirs: <Folder[]>[],
tracks: <Track[]>[],
}),
actions: {
async fetchAll(path: string) {
const { tracks, folders } = await fetchThem(path);
this.path = path;
this.dirs = folders;
this.tracks = tracks;
},
},
});
+2 -2
View File
@@ -1,8 +1,8 @@
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { Track } from "../interfaces"; import { Track } from "../interfaces";
enum ModalOptions { enum ModalOptions {
newPlaylist = "newPlaylist", newPlaylist,
editPlaylist = "editPlaylist", editPlaylist,
} }
export default defineStore("newModal", { export default defineStore("newModal", {
+1
View File
@@ -22,6 +22,7 @@ const playlist = usePTrackStore().playlist;
const info = { const info = {
name: playlist.name, name: playlist.name,
count: playlist.tracks.length, count: playlist.tracks.length,
desc: playlist.description,
duration: "3 hours, 4 minutes", duration: "3 hours, 4 minutes",
lastUpdated: "yesterday", lastUpdated: "yesterday",
}; };