add now playing card settings

+ move left sidebar to separate component
This commit is contained in:
geoffrey45 2022-08-19 21:28:46 +03:00
parent 44bb30fe9f
commit ade8edcba2
18 changed files with 135 additions and 288 deletions

View File

@ -3,15 +3,7 @@
<Modal /> <Modal />
<Notification /> <Notification />
<div id="app-grid"> <div id="app-grid">
<div class="l-sidebar rounded"> <LeftSidebar />
<div class="withlogo">
<Logo />
<Navigation />
</div>
<nowPlaying />
<!-- <Playlists /> -->
</div>
<NavBar /> <NavBar />
<div id="acontent" class="rounded"> <div id="acontent" class="rounded">
<router-view /> <router-view />
@ -33,20 +25,18 @@ import useModalStore from "@/stores/modal";
import useContextStore from "@/stores/context"; import useContextStore from "@/stores/context";
import handleShortcuts from "@/composables/useKeyboard"; import handleShortcuts from "@/composables/useKeyboard";
import Logo from "@/components/Logo.vue";
import Modal from "@/components/modal.vue"; import Modal from "@/components/modal.vue";
import NavBar from "@/components/nav/NavBar.vue"; import NavBar from "@/components/nav/NavBar.vue";
// import Tabs from "@/components/RightSideBar/Tabs.vue"; // import Tabs from "@/components/RightSideBar/Tabs.vue";
import ContextMenu from "@/components/contextMenu.vue"; import ContextMenu from "@/components/contextMenu.vue";
import Notification from "@/components/Notification.vue"; import Notification from "@/components/Notification.vue";
import Navigation from "@/components/LeftSidebar/Navigation.vue";
import nowPlaying from "@/components/LeftSidebar/nowPlaying.vue";
import RightSideBar from "@/components/RightSideBar/Main.vue"; import RightSideBar from "@/components/RightSideBar/Main.vue";
import SearchInput from "@/components/RightSideBar/SearchInput.vue"; import SearchInput from "@/components/RightSideBar/SearchInput.vue";
import BottomBar from "@/components/BottomBar/BottomBar.vue"; import BottomBar from "@/components/BottomBar/BottomBar.vue";
import LeftSidebar from "./components/LeftSidebar/index.vue";
import { readLocalStorage, writeLocalStorage } from "@/utils"; import { readLocalStorage, writeLocalStorage } from "@/utils";
import Playlists from "./components/LeftSidebar/Playlists.vue";
const queue = useQStore(); const queue = useQStore();
const router = useRouter(); const router = useRouter();
@ -71,6 +61,7 @@ onStartTyping(() => {
const elem = document.getElementById("globalsearch") as HTMLInputElement; const elem = document.getElementById("globalsearch") as HTMLInputElement;
elem.focus(); elem.focus();
elem.value = ""; elem.value = "";
document.getElementById("ginner")?.classList.add("search-focused");
}); });
function handleWelcomeModal() { function handleWelcomeModal() {
@ -97,7 +88,6 @@ onMounted(() => {
.l-sidebar { .l-sidebar {
position: relative; position: relative;
.withlogo { .withlogo {
padding: 1rem; padding: 1rem;
} }

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="b-bar bg-black pad-medium rounded"> <div class="b-bar bg-black pad-medium rounded" v-if="settings.use_right_np">
<div class="info"> <div class="info">
<img <img
:src="paths.images.thumb + queue.currenttrack?.image" :src="paths.images.thumb + queue.currenttrack?.image"
@ -22,15 +22,10 @@
</div> </div>
</div> </div>
<Progress /> <Progress />
<!-- <div class="ex-hotkeys">
<HotKeys />
</div> -->
<div class="time"> <div class="time">
<span class="current">{{ formatSeconds(queue.currentTime) }}</span> <span class="current">{{ formatSeconds(queue.duration.current) }}</span>
<HotKeys /> <HotKeys />
<span class="full">{{ <span class="full">{{ formatSeconds(queue.currenttrack.length) }}</span>
formatSeconds(queue.fullTime || queue.currenttrack.length)
}}</span>
</div> </div>
</div> </div>
</template> </template>
@ -40,11 +35,13 @@ import "@/assets/scss/BottomBar/BottomBar.scss";
import { formatSeconds, putCommas } from "@/utils"; import { formatSeconds, putCommas } from "@/utils";
import HotKeys from "../LeftSidebar/NP/HotKeys.vue"; import HotKeys from "../LeftSidebar/NP/HotKeys.vue";
import Progress from "../LeftSidebar/NP/Progress.vue"; import Progress from "../LeftSidebar/NP/Progress.vue";
import useSettingsStore from "@/stores/settings";
import { paths } from "@/config"; import { paths } from "@/config";
import useQStore from "@/stores/queue"; import useQStore from "@/stores/queue";
const queue = useQStore(); const queue = useQStore();
const settings = useSettingsStore();
</script> </script>
<style lang="scss"> <style lang="scss">
@ -66,14 +63,6 @@ const queue = useQStore();
} }
} }
.ex-hotkeys {
position: absolute;
width: 10rem;
right: 1rem;
top: 1rem;
border-radius: $small;
}
.info { .info {
display: grid; display: grid;
grid-template-columns: max-content 1fr; grid-template-columns: max-content 1fr;

View File

@ -7,6 +7,11 @@
:max="q.duration.full" :max="q.duration.full"
step="0.1" step="0.1"
@change="seek()" @change="seek()"
:style="{
backgroundSize: `${
(q.duration.current / (q.currenttrack.length || 0)) * 100
}% 100%`,
}"
/> />
</template> </template>
@ -15,9 +20,9 @@ import useQStore from "../../../stores/queue";
const q = useQStore(); const q = useQStore();
const seek = () => { const seek = () => {
const elem = <HTMLFormElement>document.getElementById("progress"); const elem = document.getElementById("progress") as HTMLInputElement;
const value = elem.value; const value = elem.value;
q.seek(value); q.seek(value as unknown as number);
}; };
</script> </script>

View File

@ -0,0 +1,24 @@
<template>
<div class="l-sidebar rounded">
<div class="withlogo">
<Logo />
<Navigation />
</div>
<nowPlaying v-if="settings.use_side_np" />
<!-- <Playlists /> -->
</div>
</template>
<script setup lang="ts">
import Logo from "@/components/Logo.vue";
import Navigation from "@/components/LeftSidebar/Navigation.vue";
import nowPlaying from "@/components/LeftSidebar/nowPlaying.vue";
// import Playlists from "./components/LeftSidebar/Playlists.vue";
import useSettingsStore from "@/stores/settings";
const settings = useSettingsStore();
</script>
<style lang="scss"></style>

View File

@ -1,161 +0,0 @@
<template>
<div class="now-playing bg-black shadow-lg">
<div class="art-tags">
<div class="duration">{{ formatSeconds(current.length) }}</div>
<div
:style="{
backgroundImage: `url(&quot;${current.image}&quot;)`,
}"
class="album-art image bg-black"
></div>
<div class="t-a">
<p id="title" class="ellipsis">{{ current.title }}</p>
<div class="separator no-bg-black"></div>
<div v-if="current.artists[0] !== ''" id="artist" class="ellip">
<span v-for="artist in putCommas(current.artists)" :key="artist">{{
artist
}}</span>
</div>
<div v-else id="artist">
<span>{{ current.albumartist }}</span>
</div>
<div id="type">
<span v-if="current.bitrate > 330"
>FLAC {{ current.bitrate }} Kbps</span
>
<span v-else>MP3 | {{ current.bitrate }} Kbps</span>
</div>
</div>
</div>
<div class="progress">
<div class="prog">
<Progress />
</div>
</div>
<div class="c-wrapper rounded">
<div class="controls">
<div class="shuffle">
<div class="image"></div>
<div class="image"></div>
</div>
<HotKeys />
<div class="fav">
<div class="image"></div>
<div class="image"></div>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref } from "@vue/reactivity";
import playAudio from "@/composables/playAudio.js";
import { formatSeconds, putCommas } from "@/utils";
import HotKeys from "../shared/HotKeys.vue";
import Progress from "../shared/Progress.vue";
export default {
setup() {
const current = ref(perks.current);
const putCommas = perks.putCommas;
const { playNext } = playAudio;
const { playPrev } = playAudio;
const { playPause } = playAudio;
const isPlaying = playAudio.playing;
const seek = () => {
playAudio.seek(document.getElementById("progress").value);
};
return {
current,
putCommas,
playNext,
playPrev,
playPause,
seek,
isPlaying,
formatSeconds: perks.formatSeconds,
};
},
components: { Progress, HotKeys },
};
</script>
<style lang="scss">
.now-playing {
border-radius: 0.5rem;
height: 13.5rem;
padding: 0.5rem;
// background: rgba(255, 255, 255, 0.055);
display: grid;
grid-template-rows: 3fr 1fr;
.progress {
display: flex;
.prog {
width: 100%;
display: grid;
align-items: center;
}
}
.art-tags {
display: flex;
align-items: center;
position: relative;
.t-a {
#title {
margin: 0;
width: 20rem;
color: #fff;
}
#artist {
font-size: 0.8rem;
width: 20rem;
color: $highlight-blue;
}
}
.duration {
position: absolute;
bottom: $small;
right: 0;
font-size: 0.9rem;
}
#type {
font-size: $medium;
color: $red;
padding: $smaller;
border-radius: $smaller;
position: absolute;
bottom: 0.1rem;
border: solid 1px $red;
}
.album-art {
width: 7rem;
height: 7rem;
border-radius: 0.5rem;
margin-right: 0.5rem;
background-image: url("../../assets/images/null.webp");
}
}
.c-wrapper {
background-color: $bbb;
height: 3.5rem;
padding: 0 $small;
display: grid;
align-items: center;
}
}
</style>

View File

@ -4,15 +4,12 @@
id="ginner" id="ginner"
tabindex="0" tabindex="0"
class="bg-black rounded" class="bg-black rounded"
:class="{ 'search-focused': focused }"
> >
<input <input
id="globalsearch" id="globalsearch"
v-model="search.query" v-model="search.query"
placeholder="Search your library" placeholder="Search your library"
type="search" type="search"
@focus="focused = true"
@blur="focused = false"
/> />
<SearchSvg /> <SearchSvg />
</div> </div>
@ -24,7 +21,6 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue";
import useSearchStore from "../../stores/search"; import useSearchStore from "../../stores/search";
import SearchSvg from "../../assets/icons/search.svg"; import SearchSvg from "../../assets/icons/search.svg";
import QueueSvg from "../../assets/icons/queue.svg"; import QueueSvg from "../../assets/icons/queue.svg";
@ -32,7 +28,6 @@ import useTabStore from "../../stores/tabs";
const search = useSearchStore(); const search = useSearchStore();
const tabs = useTabStore(); const tabs = useTabStore();
const focused = ref(false);
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -1,9 +1,5 @@
<template> <template>
<div <div class="switch rounded" :class="{ toggled: state }">
class="switch rounded"
@click="toggled = !toggled"
:class="{ toggled: toggled }"
>
<div class="circle circular"></div> <div class="circle circular"></div>
</div> </div>
</template> </template>
@ -11,7 +7,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
const toggled = ref(false); defineProps<{
state: boolean;
}>();
</script> </script>
<style lang="scss"> <style lang="scss">

View File

@ -4,46 +4,6 @@
v-for="(group, index) in settingGroups[current].groups" v-for="(group, index) in settingGroups[current].groups"
:key="index" :key="index"
:group="group" :group="group"
/><Group
v-for="(group, index) in settingGroups[current].groups"
:key="index"
:group="group"
/><Group
v-for="(group, index) in settingGroups[current].groups"
:key="index"
:group="group"
/><Group
v-for="(group, index) in settingGroups[current].groups"
:key="index"
:group="group"
/><Group
v-for="(group, index) in settingGroups[current].groups"
:key="index"
:group="group"
/><Group
v-for="(group, index) in settingGroups[current].groups"
:key="index"
:group="group"
/><Group
v-for="(group, index) in settingGroups[current].groups"
:key="index"
:group="group"
/><Group
v-for="(group, index) in settingGroups[current].groups"
:key="index"
:group="group"
/><Group
v-for="(group, index) in settingGroups[current].groups"
:key="index"
:group="group"
/><Group
v-for="(group, index) in settingGroups[current].groups"
:key="index"
:group="group"
/><Group
v-for="(group, index) in settingGroups[current].groups"
:key="index"
:group="group"
/> />
</div> </div>
</template> </template>
@ -61,9 +21,6 @@ defineProps<{
.settingscontent { .settingscontent {
width: 100%; width: 100%;
max-width: 40rem; max-width: 40rem;
padding-right: 1rem;
margin: 0 auto; margin: 0 auto;
} }
</style> </style>

View File

@ -9,14 +9,20 @@
<div class="title"> <div class="title">
{{ setting.title }} {{ setting.title }}
</div> </div>
<div class="options"><Switch /></div> <div class="options">
<Switch
v-if="setting.type == SettingType.switch"
@click="setting.action()"
:state="setting.source()"
/>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { SettingGroup } from "@/interfaces/settings"; import { SettingGroup, SettingType } from "@/interfaces/settings";
import Switch from "./Components/Switch.vue"; import Switch from "./Components/Switch.vue";
defineProps<{ defineProps<{
@ -30,6 +36,10 @@ defineProps<{
gap: $small; gap: $small;
margin-top: 2rem; margin-top: 2rem;
&:first-child {
margin-top: 0;
}
h4 { h4 {
margin: $small auto; margin: $small auto;
} }
@ -39,6 +49,11 @@ defineProps<{
font-size: 0.9rem; font-size: 0.9rem;
} }
.setting {
display: grid;
gap: 1rem;
}
.setting > * { .setting > * {
display: grid; display: grid;
grid-template-columns: 1fr max-content; grid-template-columns: 1fr max-content;

View File

@ -1,44 +1,43 @@
import { Track } from "../interfaces.js"; import { Track } from "../interfaces.js";
import { paths } from "../config"; import { paths } from "../config";
export default ( import useQueueStore from "../stores/queue";
track: Track,
playPause: () => void, export default () => {
playNext: () => void,
playPrev: () => void
) => {
if ("mediaSession" in navigator) { if ("mediaSession" in navigator) {
const queue = useQueueStore();
navigator.mediaSession.metadata = new window.MediaMetadata({ navigator.mediaSession.metadata = new window.MediaMetadata({
title: track.title, title: queue.currenttrack.title,
artist: track.artists.join(", "), artist: queue.currenttrack.artists.join(", "),
artwork: [ artwork: [
{ {
src: paths.images.thumb + track.image, src: paths.images.thumb + queue.currenttrack.image,
sizes: "96x96", sizes: "96x96",
type: "image/jpeg", type: "image/jpeg",
}, },
{ {
src: paths.images.thumb + track.image, src: paths.images.thumb + queue.currenttrack.image,
sizes: "128x128", sizes: "128x128",
type: "image/webp", type: "image/webp",
}, },
{ {
src: paths.images.thumb + track.image, src: paths.images.thumb + queue.currenttrack.image,
sizes: "192x192", sizes: "192x192",
type: "image/webp", type: "image/webp",
}, },
{ {
src: paths.images.thumb + track.image, src: paths.images.thumb + queue.currenttrack.image,
sizes: "256x256", sizes: "256x256",
type: "image/webp", type: "image/webp",
}, },
{ {
src: paths.images.thumb + track.image, src: paths.images.thumb + queue.currenttrack.image,
sizes: "384x384", sizes: "384x384",
type: "image/webp", type: "image/webp",
}, },
{ {
src: paths.images.thumb + track.image, src: paths.images.thumb + queue.currenttrack.image,
sizes: "512x512", sizes: "512x512",
type: "image/webp", type: "image/webp",
}, },
@ -46,16 +45,16 @@ export default (
}); });
navigator.mediaSession.setActionHandler("play", function () { navigator.mediaSession.setActionHandler("play", function () {
playPause(); queue.playPause();
}); });
navigator.mediaSession.setActionHandler("pause", function () { navigator.mediaSession.setActionHandler("pause", function () {
playPause(); queue.playPause();
}); });
navigator.mediaSession.setActionHandler("previoustrack", function () { navigator.mediaSession.setActionHandler("previoustrack", function () {
playPrev(); queue.playPrev();
}); });
navigator.mediaSession.setActionHandler("nexttrack", function () { navigator.mediaSession.setActionHandler("nexttrack", function () {
playNext(); queue.playNext();
}); });
} }
}; };

View File

@ -13,8 +13,9 @@ export interface SettingOption {
export interface Setting { export interface Setting {
title: string; title: string;
type: SettingType; type: SettingType;
options: SettingOption[]; options?: SettingOption[];
action: (arg0?: SettingOption) => void; action: (arg0?: any) => void;
source: () => any;
} }
export interface SettingGroup { export interface SettingGroup {

View File

@ -1,15 +1,11 @@
import { SettingCategory } from "@/interfaces/settings"; import { SettingCategory } from "@/interfaces/settings";
import setNowPlayingComponent from "./now-playing"; import nowPlaying from "./now-playing";
console.log(setNowPlayingComponent);
export default { export default {
title: "General", title: "General",
groups: [ groups: [
{ {
title: "Repeat queue", settings: [...nowPlaying],
desc: "Do you want to do that?",
settings: [setNowPlayingComponent()],
}, },
], ],
} as SettingCategory; } as SettingCategory;

View File

@ -1,7 +1,19 @@
import { SettingType } from "@/interfaces/settings"; import { SettingType } from "@/interfaces/settings";
import useSettingsStore from "@/stores/settings";
export default () => ({ const settings = useSettingsStore;
title: "Use Sidebar now playing card",
type: SettingType.switch, export default [
action: () => console.log("should toggle something"), {
}); title: "Use left now playing card",
type: SettingType.switch,
source: () => settings().use_side_np,
action: () => settings().toggleUseSideNP(),
},
{
title: "Use right bottom now playing card",
type: SettingType.switch,
source: () => settings().use_right_np,
action: () => settings().toggleUseRightNP(),
},
];

View File

@ -3,7 +3,8 @@ import state from "../composables/state";
import { NotifType, useNotifStore } from "./notification"; import { NotifType, useNotifStore } from "./notification";
import { FromOptions } from "../composables/enums"; import { FromOptions } from "../composables/enums";
import notif from "../composables/mediaNotification"; import updateMediaNotif from "../composables/mediaNotification";
import { import {
fromAlbum, fromAlbum,
fromFolder, fromFolder,
@ -24,10 +25,10 @@ function shuffle(tracks: Track[]) {
type From = fromFolder | fromAlbum | fromPlaylist | fromSearch; type From = fromFolder | fromAlbum | fromPlaylist | fromSearch;
let audio = new Audio(); let audio = new Audio();
let elem: HTMLElement;
export default defineStore("Queue", { export default defineStore("Queue", {
state: () => ({ state: () => ({
progressElem: HTMLElement,
duration: { duration: {
current: 0, current: 0,
full: 0, full: 0,
@ -42,14 +43,17 @@ export default defineStore("Queue", {
tracklist: [] as Track[], tracklist: [] as Track[],
}), }),
actions: { actions: {
bindProgressElem() {
elem = document.getElementById("progress");
},
play(index: number = 0) { play(index: number = 0) {
if (this.tracklist.length === 0) return; if (this.tracklist.length === 0) return;
this.current = index; this.current = index;
const track = this.tracklist[index]; const track = this.tracklist[index];
this.currentid = track.trackid; this.currentid = track.trackid;
const uri = state.settings.uri + "/file/" + track.hash; const uri = state.settings.uri + "/file/" + track.hash;
const elem = document.getElementById("progress") as HTMLElement;
this.updateCurrent(index); this.updateCurrent(index);
this.bindProgressElem();
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
audio.autoplay = true; audio.autoplay = true;
@ -61,12 +65,10 @@ export default defineStore("Queue", {
this.duration.full = audio.duration; this.duration.full = audio.duration;
audio.play().then(() => { audio.play().then(() => {
this.playing = true; this.playing = true;
notif(track, this.playPause, this.playNext, this.playPrev); updateMediaNotif();
audio.ontimeupdate = () => { audio.ontimeupdate = () => {
this.duration.current = audio.currentTime; this.duration.current = audio.currentTime;
const bg_size = (audio.currentTime / audio.duration) * 100;
elem.style.backgroundSize = `${bg_size}% 100%`;
}; };
audio.onended = () => { audio.onended = () => {

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,24 @@
import { defineStore } from "pinia";
import useQueueStore from "../queue";
export default defineStore("settings", {
state: () => ({
use_side_np: false,
use_right_np: true,
}),
actions: {
toggleNPs() {
this.use_side_np = !this.use_side_np;
this.use_right_np = !this.use_right_np;
useQueueStore().bindProgressElem();
},
toggleUseSideNP() {
this.toggleNPs();
},
toggleUseRightNP() {
this.toggleNPs();
},
},
getters: {},
persist: true,
});

View File

@ -24,7 +24,7 @@ import Content from "../components/SettingsView/Content.vue";
.scrollable { .scrollable {
overflow: auto; overflow: auto;
margin-top: 1rem; margin-top: 1rem;
@include for-desktop-down { @include for-desktop-down {
margin-top: 0; margin-top: 0;
} }

View File

@ -4,7 +4,7 @@
"useDefineForClassFields": true, "useDefineForClassFields": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "Node",
// "strict": true, "strict": true,
"jsx": "preserve", "jsx": "preserve",
"sourceMap": true, "sourceMap": true,
"resolveJsonModule": true, "resolveJsonModule": true,