try adding playlists list to context menu - unsuccsessfully

This commit is contained in:
geoffrey45
2022-03-25 20:51:22 +03:00
parent 642c524a08
commit e2544dbbdc
21 changed files with 394 additions and 75 deletions
+2 -1
View File
@@ -19,13 +19,14 @@ def create_app():
cache.init_app(app) cache.init_app(app)
with app.app_context(): with app.app_context():
from app.api import artist, track, search, folder, album from app.api import artist, track, search, folder, album, playlist
app.register_blueprint(album.album_bp, url_prefix="/") app.register_blueprint(album.album_bp, url_prefix="/")
app.register_blueprint(artist.artist_bp, url_prefix="/") app.register_blueprint(artist.artist_bp, url_prefix="/")
app.register_blueprint(track.track_bp, url_prefix="/") app.register_blueprint(track.track_bp, url_prefix="/")
app.register_blueprint(search.search_bp, url_prefix="/") app.register_blueprint(search.search_bp, url_prefix="/")
app.register_blueprint(folder.folder_bp, url_prefix="/") app.register_blueprint(folder.folder_bp, url_prefix="/")
app.register_blueprint(playlist.playlist_bp, url_prefix="/")
return app return app
+3 -1
View File
@@ -10,6 +10,7 @@ from app import models, instances
from app import functions, helpers, prep from app import functions, helpers, prep
from app.lib import albumslib from app.lib import albumslib
from app.lib import folderslib from app.lib import folderslib
from app.lib import playlistlib
DB_TRACKS = instances.songs_instance.get_all_songs() DB_TRACKS = instances.songs_instance.get_all_songs()
@@ -20,6 +21,7 @@ TRACKS: List[models.Track] = []
PLAYLISTS: List[models.Playlist] = [] PLAYLISTS: List[models.Playlist] = []
FOLDERS: List[models.Folder] = [] FOLDERS: List[models.Folder] = []
@helpers.background @helpers.background
def initialize() -> None: def initialize() -> None:
""" """
@@ -29,8 +31,8 @@ def initialize() -> None:
prep.create_config_dir() prep.create_config_dir()
albumslib.create_everything() albumslib.create_everything()
folderslib.run_scandir() folderslib.run_scandir()
playlistlib.create_all_playlists()
functions.reindex_tracks() functions.reindex_tracks()
initialize() initialize()
+1 -1
View File
@@ -30,7 +30,7 @@ def get_folder_tree():
songs = [] songs = []
for track in api.TRACKS: for track in api.TRACKS:
if track.folder + "/" == req_dir: if track.folder == req_dir:
songs.append(track) songs.append(track)
final_tracks = helpers.remove_duplicates(songs) final_tracks = helpers.remove_duplicates(songs)
+7 -5
View File
@@ -11,22 +11,24 @@ playlist_bp = Blueprint("playlist", __name__, url_prefix="/")
@playlist_bp.route("/playlists", methods=["GET"]) @playlist_bp.route("/playlists", methods=["GET"])
def get_all_playlists(): def get_all_playlists():
print(api.PLAYLISTS)
playlists = [] playlists = []
for playlist in api.PLAYLISTS: for playlist in api.PLAYLISTS:
del playlist.tracks playlist.tracks = []
playlists.append(playlist) playlists.append(playlist)
return playlists return {"data": playlists}
@playlist_bp.route("/playlist/new") @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": data["description"], "tracks": []} playlist = {"name": data["name"], "description": [], "tracks": []}
instances.playlist_instance.insert_playlist(playlist) instances.playlist_instance.insert_playlist(playlist)
return 200 return {"msg": "success"}
@playlist_bp.route("/playlist/<playlist_id>/add", methods=["POST"]) @playlist_bp.route("/playlist/<playlist_id>/add", methods=["POST"])
+3 -2
View File
@@ -26,6 +26,7 @@ from app import settings, models
from app.lib import albumslib from app.lib import albumslib
from app import api from app import api
from app.lib import watchdoge from app.lib import watchdoge
from app.lib import folderslib
@helpers.background @helpers.background
@@ -61,7 +62,6 @@ def populate():
start = time.time() start = time.time()
s, files = helpers.run_fast_scandir(settings.HOME_DIR, [".flac", ".mp3"], full=True) s, files = helpers.run_fast_scandir(settings.HOME_DIR, [".flac", ".mp3"], full=True)
# pprint(s)
_bar = Bar("Processing files", max=len(files)) _bar = Bar("Processing files", max=len(files))
for file in files: for file in files:
@@ -74,6 +74,7 @@ def populate():
_bar.finish() _bar.finish()
albumslib.create_everything() albumslib.create_everything()
folderslib.run_scandir()
end = time.time() end = time.time()
@@ -340,7 +341,7 @@ def get_tags(fullpath: str) -> dict:
"length": round(audio.info.length), "length": round(audio.info.length),
"bitrate": round(int(audio.info.bitrate) / 1000), "bitrate": round(int(audio.info.bitrate) / 1000),
"filepath": fullpath, "filepath": fullpath,
"folder": os.path.dirname(fullpath), "folder": os.path.dirname(fullpath) + "/",
} }
return tags return tags
+10 -4
View File
@@ -29,11 +29,11 @@ def create_folder(foldername: str) -> models.Folder:
return models.Folder(folder) return models.Folder(folder)
def create_all_folders(foldernames: List[str]) -> List[models.Folder]: def create_all_folders() -> List[models.Folder]:
folders_: List[models.Folder] = [] folders_: List[models.Folder] = []
_bar = Bar("Creating folders", max=len(foldernames)) _bar = Bar("Creating folders", max=len(api.VALID_FOLDERS))
for foldername in foldernames: for foldername in api.VALID_FOLDERS:
folder = create_folder(foldername) folder = create_folder(foldername)
folders_.append(folder) folders_.append(folder)
_bar.next() _bar.next()
@@ -61,11 +61,17 @@ def get_subdirs(foldername: str) -> List[models.Folder]:
@helpers.background @helpers.background
def run_scandir(): def run_scandir():
"""
Initiates the creation of all folder objects for each folder with a track in it.
Runs in a background thread after every 5 minutes.
It calls the
"""
flag = False flag = False
while flag is False: while flag is False:
get_valid_folders() get_valid_folders()
folders_ = create_all_folders(api.VALID_FOLDERS) folders_ = create_all_folders()
"""Create all the folder objects before clearing api.FOLDERS""" """Create all the folder objects before clearing api.FOLDERS"""
api.FOLDERS.clear() api.FOLDERS.clear()
+4 -1
View File
@@ -1,5 +1,7 @@
<template> <template>
<ContextMenu /> <ContextMenu />
<Modal />
<Notification />
<div class="l-container" :class="{ collapsed: collapsed }"> <div class="l-container" :class="{ collapsed: collapsed }">
<div class="l-sidebar"> <div class="l-sidebar">
<div id="logo-container"> <div id="logo-container">
@@ -25,7 +27,6 @@
<script setup> <script setup>
import { ref } from "vue"; import { ref } from "vue";
import Navigation from "./components/LeftSidebar/Navigation.vue"; import Navigation from "./components/LeftSidebar/Navigation.vue";
import BottomBar from "@/components/BottomBar/BottomBar.vue";
import perks from "@/composables/perks.js"; import perks from "@/composables/perks.js";
@@ -36,6 +37,8 @@ import Tabs from "./components/RightSideBar/Tabs.vue";
import SearchInput from "./components/RightSideBar/SearchInput.vue"; import SearchInput from "./components/RightSideBar/SearchInput.vue";
import useContextStore from "./stores/context"; import useContextStore from "./stores/context";
import ContextMenu from "./components/contextMenu.vue"; import ContextMenu from "./components/contextMenu.vue";
import Modal from "./components/modal.vue";
import Notification from "./components/Notification.vue";
const context_store = useContextStore(); const context_store = useContextStore();
+1 -3
View File
@@ -20,10 +20,8 @@ body {
} }
.heading { .heading {
font-size: small; font-size: 2rem;
font-weight: bold; font-weight: bold;
display: flex;
align-items: center;
} }
.t-center { .t-center {
+1 -1
View File
@@ -8,7 +8,7 @@
<div class="text"> <div class="text">
<div class="icon image"></div> <div class="icon image"></div>
<div class="ellip"> <div class="ellip">
{{ path.split("/").splice(-1) + "" }} {{ path.split("/").splice(-2).join("") }}
</div> </div>
</div> </div>
</div> </div>
+29
View File
@@ -0,0 +1,29 @@
<template>
<div class="new-notification rounded" v-if="store.visible">
<div>{{ store.text }}</div>
</div>
</template>
<script setup lang="ts">
import { useNotificationStore } from "../stores/notification";
const store = useNotificationStore();
</script>
<style lang="scss">
.new-notification {
position: fixed;
z-index: 2000;
width: 25rem;
bottom: 2rem;
padding: $small;
left: 50%;
translate: -50%;
background-color: rgb(5, 62, 168);
display: grid;
place-items: center;
.link {
font-weight: bold;
text-decoration: underline;
}
}
</style>
+80
View File
@@ -0,0 +1,80 @@
<template>
<div class="new-playlist-modal" v-if="modal.visible">
<div class="bg" @click="modal.hideModal"></div>
<div class="m-content rounded">
<div class="heading">{{ modal.title }}</div>
<div class="cancel image" @click="modal.hideModal"></div>
<NewPlaylist
v-if="modal.component == modal.options.newPlaylist"
@hideModal="hideModal"
@title="title"
/>
</div>
</div>
</template>
<script setup lang="ts">
import useModalStore from "../stores/modal";
import NewPlaylist from "./modals/NewPlaylist.vue";
const modal = useModalStore();
/**
* Sets the modal title
* @param title
*/
function title(title: string) {
modal.setTitle(title);
}
/**
* Handle the emit to hide the modal
*/
function hideModal() {
modal.hideModal();
}
</script>
<style lang="scss">
.new-playlist-modal {
position: fixed;
z-index: 2000;
height: 100vh;
width: 100vw;
display: grid;
place-items: center;
.bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(12, 12, 12, 0.767);
}
.m-content {
width: 30rem;
background-color: $black;
padding: 1rem;
position: relative;
.cancel {
width: 2rem;
height: 2rem;
position: absolute;
top: 1rem;
right: 1rem;
background-image: url("../assets/icons/plus.svg");
transform: rotate(45deg);
&:hover {
cursor: pointer;
transform: rotate(135deg);
}
}
}
}
</style>
+69
View File
@@ -0,0 +1,69 @@
<template>
<form @submit="create" class="new-p-form">
<label for="name">Playlist name</label>
<br />
<input
type="text"
class="rounded"
name="name"
id="modal-playlist-name-input"
/>
<br />
<input type="submit" class="rounded" value="Create" />
</form>
</template>
<script setup lang="ts">
import { createNewPlaylist } from "../../composables/playlists";
const emit = defineEmits<{
(e: "title", title: string): void;
(e: "hideModal"): void;
}>();
emit("title", "New Playlist");
function create(e: Event) {
e.preventDefault();
const name = (e.target as HTMLFormElement).elements["name"].value;
if (name.trim()) {
createNewPlaylist(name).then(() => emit("hideModal"));
}
}
</script>
<style lang="scss">
.new-p-form {
grid-gap: 1rem;
margin-top: 1rem;
label {
font-size: 0.9rem;
color: $gray1;
}
input[type="text"] {
margin: $small 0;
border: 2px solid $gray3;
background-color: transparent;
color: #fff;
width: 100%;
padding: 0.5rem;
font-size: 1rem;
outline: none;
}
input[type="submit"] {
margin: $small 0;
background-color: $accent;
color: #fff;
width: 7rem;
padding: 0.75rem;
font-size: 1rem;
border: none;
outline: none;
cursor: pointer;
}
}
</style>
+10 -1
View File
@@ -64,18 +64,27 @@
import perks from "../../composables/perks.js"; import perks from "../../composables/perks.js";
import state from "../../composables/state"; import state from "../../composables/state";
import useContextStore from "../../stores/context"; import useContextStore from "../../stores/context";
import useModalStore from "../../stores/modal";
import usePlaylistStore from "../../stores/playlists";
import { ref } from "vue"; import { ref } from "vue";
import trackContext from "../../contexts/track_context"; import trackContext from "../../contexts/track_context";
import { Track } from "../../interfaces.js"; import { Track } from "../../interfaces.js";
const contextStore = useContextStore(); const contextStore = useContextStore();
const modalStore = useModalStore();
const playlistStore = usePlaylistStore();
const context_on = ref(false); const context_on = ref(false);
const showContextMenu = (e: Event) => { const showContextMenu = (e: Event) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
contextStore.showContextMenu(e, trackContext(props.song)); contextStore.showContextMenu(
e,
trackContext(props.song, modalStore)
);
context_on.value = true; context_on.value = true;
contextStore.$subscribe((mutation, state) => { contextStore.$subscribe((mutation, state) => {
+3 -29
View File
@@ -1,42 +1,18 @@
import axios from "axios"; import axios from "axios";
import { Folder, Track } from "../interfaces";
import state from "./state"; import state from "./state";
import { Track, Folder } from "../interfaces";
let base_uri = "http://127.0.0.1:9876/"; export default async function (path: string) {
const getTracksAndDirs = async (path) => {
let url;
const encoded_path = encodeURIComponent(path.replaceAll("/", "|"));
url = url = `${base_uri}/f/${encoded_path}`;
const res = await fetch(url);
if (!res.ok) {
const message = `An error has occurred: ${res.status}`;
throw new Error(message);
}
const data = await res.json();
const songs = data.files;
const folders = data.folders;
return { songs, folders };
};
async function fetchThat(path: string) {
let tracks = Array<Track>(); let tracks = Array<Track>();
let folders = Array<Folder>(); let folders = Array<Folder>();
await axios await axios
.post(state.settings.uri + "/folder", { .post(`${state.settings.uri}/folder`, {
folder: path, folder: path,
}) })
.then((res) => { .then((res) => {
tracks = res.data.tracks; tracks = res.data.tracks;
folders = res.data.folders; folders = res.data.folders;
console.log(tracks)
}) })
.catch((err) => { .catch((err) => {
console.error(err); console.error(err);
@@ -44,5 +20,3 @@ async function fetchThat(path: string) {
return { tracks, folders }; return { tracks, folders };
} }
export default fetchThat;
+45
View File
@@ -0,0 +1,45 @@
import axios from "axios";
import { Playlist } from "../interfaces";
import { Notification } from "../stores/notification";
import state from "./state";
/**
* Creates a new playlist on the server.
* @param playlist_name The name of the playlist to create.
*/
async function createNewPlaylist(playlist_name: string) {
await axios
.post(state.settings.uri + "/playlist/new", {
name: playlist_name,
})
.then((res) => {
console.log(res.data);
new Notification("Playlist created!");
})
.catch((err) => {
console.error(err);
});
}
/**
* Fetches all playlists from the server.
* @returns {Promise<Playlist[]>} A promise that resolves to an array of playlists.
*/
async function getAllPlaylists(): Promise<Playlist[]> {
let playlists = <Playlist[]>[];
const newLocal = `${state.settings.uri}/playlists`;
await axios
.get(newLocal)
.then((res) => {
playlists = res.data.data;
})
.catch((err) => {
console.error(err);
});
return playlists;
}
export { createNewPlaylist, getAllPlaylists };
+46 -21
View File
@@ -1,17 +1,19 @@
import { Track } from "../interfaces"; import { Track } from "../interfaces";
import Router from "../router"; import Router from "../router";
import { Option } from "../interfaces"; import { Option } from "../interfaces";
import { getAllPlaylists } from "../composables/playlists";
/** /**
* Returns a list of context menu items for a track. * Returns a list of context menu items for a track.
* @param {any} track a track object. * @param {any} track a track object.
* @param {any} modalStore a pinia store.
* @return {Array<Option>()} a list of context menu items. * @return {Array<Option>()} a list of context menu items.
*/ */
export default (track: Track): Array<Option> => { export default (track: Track, modalStore: any) => {
const single_artist = track.artists.length === 1; const single_artist = track.artists.length === 1;
const children = () => { const goToArtist = () => {
if (single_artist) { if (single_artist) {
return false; return false;
} }
@@ -24,19 +26,42 @@ export default (track: Track): Array<Option> => {
}); });
}; };
const option1: Option = { async function addToPlaylist() {
const p = await getAllPlaylists();
const playlists = p.map((playlist) => {
return <Option>{
label: playlist.name,
action: () => {
console.log("playlist");
},
};
});
const new_playlist = <Option>{
label: "New playlist",
action: () => {
modalStore.showModal(modalStore.options.newPlaylist);
},
};
console.log([new_playlist, ...playlists]);
return [new_playlist, ...playlists];
}
const add_to_playlist: Option = {
label: "Add to Playlist", label: "Add to Playlist",
action: () => console.log("Add to Playlist"), children: addToPlaylist(),
icon: "plus", icon: "plus",
}; };
const option2: Option = { const add_to_q: Option = {
label: "Add to Queue", label: "Add to Queue",
action: () => console.log("Add to Queue"), action: () => console.log("Add to Queue"),
icon: "add_to_queue", icon: "add_to_queue",
}; };
const option3: Option = { const go_to_folder: Option = {
label: "Go to Folder", label: "Go to Folder",
action: () => { action: () => {
Router.push({ Router.push({
@@ -47,7 +72,7 @@ export default (track: Track): Array<Option> => {
icon: "folder", icon: "folder",
}; };
const option4: Option = { const go_to_artist: Option = {
label: single_artist ? "Go to Artist" : "Go to Artists", label: single_artist ? "Go to Artist" : "Go to Artists",
icon: "artist", icon: "artist",
action: () => { action: () => {
@@ -55,16 +80,16 @@ export default (track: Track): Array<Option> => {
console.log("Go to Artist"); console.log("Go to Artist");
} }
}, },
children: children(), children: goToArtist(),
}; };
const option7: Option = { const go_to_alb_artist: Option = {
label: "Go to Album Artist", label: "Go to Album Artist",
action: () => console.log("Go to Album Artist"), action: () => console.log("Go to Album Artist"),
icon: "artist", icon: "artist",
}; };
const option5: Option = { const go_to_album: Option = {
label: "Go to Album", label: "Go to Album",
action: () => { action: () => {
Router.push({ Router.push({
@@ -75,34 +100,34 @@ export default (track: Track): Array<Option> => {
icon: "album", icon: "album",
}; };
const option6: Option = { const del_track: Option = {
label: "Delete Track", label: "Delete Track",
action: () => console.log("Delete Track"), action: () => console.log("Delete Track"),
icon: "delete", icon: "delete",
critical: true, critical: true,
}; };
const addToFav:Option = { const add_to_fav: Option = {
label: "I love this", label: "I love this",
action: () => console.log("I love this"), action: () => console.log("I love this"),
icon: "heart", icon: "heart",
} };
const separator: Option = { const separator: Option = {
type: "separator", type: "separator",
}; };
const options: Option[] = [ const options: Option[] = [
option1, add_to_playlist,
option2, add_to_q,
addToFav, add_to_fav,
separator, separator,
option3, go_to_folder,
option4, go_to_artist,
option7, go_to_alb_artist,
option5, go_to_album,
separator, separator,
option6, del_track,
]; ];
return options; return options;
+9 -2
View File
@@ -40,9 +40,16 @@ interface Option {
type?: string; type?: string;
label?: string; label?: string;
action?: Function; action?: Function;
children?: Option[] | false; children?: Option[] |Promise<Option[]>| false;
icon?: string; icon?: string;
critical?: Boolean; critical?: Boolean;
} }
export { Track, Folder, AlbumInfo, Artist, Option }; interface Playlist {
playlistid: string;
name: string;
description?: string;
image?: string;
}
export { Track, Folder, AlbumInfo, Artist, Option, Playlist };
+27
View File
@@ -0,0 +1,27 @@
import { defineStore } from "pinia";
enum ModalOptions {
newPlaylist = "newPlaylist",
editPlaylist = "editPlaylist",
}
export default defineStore("newModal", {
state: () => ({
title: "",
options: ModalOptions,
component: "",
visible: false,
}),
actions: {
showModal(modalOption: string) {
this.component = modalOption;
this.visible = true;
},
hideModal() {
this.visible = false;
},
setTitle(new_title: string) {
this.title = new_title;
},
},
});
+26
View File
@@ -0,0 +1,26 @@
import { defineStore } from "pinia";
const useNotificationStore = defineStore("notification", {
state: () => ({
text: "",
visible: false,
}),
actions: {
showNotification(new_text: string) {
this.text = new_text;
this.visible = true;
setTimeout(() => {
this.visible = false;
}, 2000);
},
},
});
class Notification {
constructor(text: string) {
useNotificationStore().showNotification(text);
}
}
export { useNotificationStore, Notification };
+14
View File
@@ -0,0 +1,14 @@
import { defineStore } from "pinia";
import { Playlist } from "../interfaces";
import { getAllPlaylists } from "../composables/playlists";
export default defineStore("playlists", {
state: () => ({
playlists: <Playlist[]>[],
}),
actions: {
fetchAll() {
},
},
});
+3 -2
View File
@@ -43,7 +43,7 @@ export default {
const songs = computed(() => { const songs = computed(() => {
const songs_ = []; const songs_ = [];
if (query.value.length > 1) { if (query.value.length) {
for (let i = 0; i < song_list.value.length; i++) { for (let i = 0; i < song_list.value.length; i++) {
if ( if (
song_list.value[i].title song_list.value[i].title
@@ -63,7 +63,7 @@ export default {
const folders = computed(() => { const folders = computed(() => {
const folders_ = []; const folders_ = [];
if (query.value.length > 1) { if (query.value.length) {
for (let i = 0; i < folders_list.value.length; i++) { for (let i = 0; i < folders_list.value.length; i++) {
if ( if (
folders_list.value[i].name folders_list.value[i].name
@@ -110,6 +110,7 @@ export default {
if (!path.value) return; if (!path.value) return;
getDirData(path.value); getDirData(path.value);
console.log(path.value);
} }
); );
}); });