rename setting to "use alt now playing ..."

+ fix right now playing component gap
+ show emoji if there's no search result
+ abstract now playing component settings into one setting
+ break context menu's context item into a component
This commit is contained in:
geoffrey45
2022-08-21 04:28:18 +03:00
parent ddeec77ccd
commit 824dcaecdf
19 changed files with 200 additions and 204 deletions
-1
View File
@@ -61,7 +61,6 @@ 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() {
+9 -1
View File
@@ -46,7 +46,7 @@
.bg-primary { .bg-primary {
background-color: $gray4; background-color: $gray4;
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.425) box-shadow: 0 0 1rem rgba(0, 0, 0, 0.425);
} }
.heading { .heading {
@@ -79,6 +79,14 @@ button {
background-image: linear-gradient(70deg, #234ece, $darkblue); background-image: linear-gradient(70deg, #234ece, $darkblue);
} }
.btn-more {
width: 2.5rem;
svg {
transform: scale(1.25);
}
}
.separator { .separator {
border-top: 0.1px $separator solid; border-top: 0.1px $separator solid;
color: transparent; color: transparent;
+116
View File
@@ -0,0 +1,116 @@
<template>
<div class="context-item">
<div class="icon image" :class="option.icon"></div>
<div class="label ellip">{{ option.label }}</div>
<div class="more image" v-if="option.children"></div>
<div class="children rounded shadow-sm" v-if="option.children">
<div
class="context-item"
v-for="child in option.children"
:key="child.label"
:class="[{ critical: child.critical }, child.type]"
@click="child.action()"
>
<div class="label ellip">
{{ child.label }}
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Option } from "@/interfaces";
defineProps<{
option: Option;
}>();
</script>
<style lang="scss">
.context-item {
width: 100%;
display: flex;
align-items: center;
cursor: default;
padding: 0.4rem 1rem;
position: relative;
.more {
height: 1.5rem;
width: 1.5rem;
position: absolute;
right: $small;
background-image: url("../assets/icons/expand.svg");
}
.children {
position: absolute;
right: -13rem;
width: 13rem;
top: -0.5rem;
max-height: 23.5rem;
background-color: $context;
transform: scale(0);
transform-origin: top left;
padding: $small 0;
.context-item {
padding: $small 1rem;
}
.separator {
padding: 0;
}
}
&:hover {
background: #234ece;
.children {
transform: scale(1);
transition: transform 0.1s ease-in-out;
transition-delay: 0.3s;
}
}
.icon {
height: 1.25rem;
width: 1.25rem;
margin-right: $small;
}
.label {
width: 9rem;
}
.folder {
background-image: url("../../assets/icons/folder.svg");
}
.artist {
background-image: url("../../assets/icons/artist.svg");
}
.album {
background-image: url("../../assets/icons/album.svg");
}
.delete {
background-image: url("../../assets/icons/delete.svg");
}
.plus {
background-image: url("../../assets/icons/plus.svg");
}
.add_to_queue {
background-image: url("../../assets/icons/add_to_queue.svg");
}
.heart {
background-image: url("../../assets/icons/heart.svg");
}
}
</style>
+2 -2
View File
@@ -5,15 +5,15 @@
<Navigation /> <Navigation />
</div> </div>
<nowPlaying v-if="settings.use_side_np" /> <nowPlaying v-if="!settings.use_alt_np" />
<!-- <Playlists /> --> <!-- <Playlists /> -->
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Logo from "@/components/Logo.vue";
import Navigation from "@/components/LeftSidebar/Navigation.vue"; import Navigation from "@/components/LeftSidebar/Navigation.vue";
import nowPlaying from "@/components/LeftSidebar/nowPlaying.vue"; import nowPlaying from "@/components/LeftSidebar/nowPlaying.vue";
import Logo from "@/components/Logo.vue";
// import Playlists from "./components/LeftSidebar/Playlists.vue"; // import Playlists from "./components/LeftSidebar/Playlists.vue";
import useSettingsStore from "@/stores/settings"; import useSettingsStore from "@/stores/settings";
+8 -1
View File
@@ -1,5 +1,10 @@
<template> <template>
<div class="r-sidebar"> <div
class="r-sidebar"
:style="{
marginBottom: !settings.use_alt_np ? '-1rem' : '',
}"
>
<div class="grid"> <div class="grid">
<div class="r-content"> <div class="r-content">
<div class="r-dash" v-show="tabs.current === tabs.tabs.home"> <div class="r-dash" v-show="tabs.current === tabs.tabs.home">
@@ -21,8 +26,10 @@ import Search from "./Search/Main.vue";
import Queue from "./Queue.vue"; import Queue from "./Queue.vue";
import DashBoard from "./Home/Main.vue"; import DashBoard from "./Home/Main.vue";
import useTabStore from "../../stores/tabs"; import useTabStore from "../../stores/tabs";
import useSettingsStore from "@/stores/settings";
const tabs = useTabStore(); const tabs = useTabStore();
const settings = useSettingsStore();
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -1,5 +1,5 @@
<template> <template>
<div class="b-bar bg-primary pad-medium rounded" v-if="settings.use_right_np"> <div class="b-bar bg-primary pad-medium rounded" v-if="settings.use_alt_np">
<div class="info"> <div class="info">
<img <img
:src="paths.images.thumb + queue.currenttrack?.image" :src="paths.images.thumb + queue.currenttrack?.image"
@@ -11,9 +11,7 @@
</button> </button>
</div> </div>
<div class="right"> <div class="right">
<button class="more-action action"> <Option />
<MoreSvg />
</button>
</div> </div>
</div> </div>
</template> </template>
@@ -23,7 +21,7 @@ import useQueueStore from "../../../stores/queue";
import ClearSvg from "@/assets/icons/delete.svg"; import ClearSvg from "@/assets/icons/delete.svg";
import ShuffleSvg from "@/assets/icons/shuffle.svg"; import ShuffleSvg from "@/assets/icons/shuffle.svg";
import MoreSvg from "@/assets/icons/more.svg"; import Option from "@/components/shared/Option.vue";
const queue = useQueueStore(); const queue = useQueueStore();
</script> </script>
@@ -50,8 +48,9 @@ const queue = useQueueStore();
} }
} }
.more-action { .btn-more {
padding-right: $smaller; padding-right: $smaller;
svg { svg {
transform: scale(1.25); transform: scale(1.25);
} }
@@ -1,25 +0,0 @@
<template>
<div class="artists-results">
<div class="search-results-grid">
<AlbumCard
v-for="album in search.albums.value"
:key="`${album.artist}-${album.title}`"
:album="album"
/>
</div>
<LoadMore v-if="search.albums.more" @loadMore="loadMore()" />
</div>
</template>
<script setup lang="ts">
import AlbumCard from "../../shared/AlbumCard.vue";
import LoadMore from "./LoadMore.vue";
import useSearchStore from "../../../stores/search";
const search = useSearchStore();
function loadMore() {
search.updateLoadCounter("albums");
search.loadAlbums(search.loadCounter.albums);
}
</script>
@@ -1,9 +1,15 @@
<template> <template>
<div class="artists-results"> <div class="artists-results">
<div class="search-results-grid" v-if="album_grid == true"> <div
class="search-results-grid"
v-if="album_grid == true && search.albums.value.length"
>
<AlbumCard v-for="a in search.albums.value" :key="a.albumid" :album="a" /> <AlbumCard v-for="a in search.albums.value" :key="a.albumid" :album="a" />
</div> </div>
<div class="search-results-grid" v-else> <div
class="search-results-grid"
v-else-if="!album_grid && search.artists.value.length"
>
<ArtistCard <ArtistCard
v-for="artist in search.artists.value" v-for="artist in search.artists.value"
:key="artist.image" :key="artist.image"
@@ -11,6 +17,7 @@
:alt="true" :alt="true"
/> />
</div> </div>
<div v-else class="t-center"><h5>🤷</h5></div>
<LoadMore <LoadMore
v-if="album_grid && search.albums.more" v-if="album_grid && search.albums.more"
:loader="search.loadAlbums" :loader="search.loadAlbums"
@@ -1,9 +1,9 @@
<template> <template>
<div id="tracks-results" v-if="search.tracks.value"> <div id="tracks-results">
<TransitionGroup name="list"> <TransitionGroup name="list" v-if="search.tracks.value.length">
<TrackItem <TrackItem
v-for="(track, index) in search.tracks.value" v-for="(track, index) in search.tracks.value"
:key="track.trackid" :key="track?.trackid"
:track="track" :track="track"
:isPlaying="queue.playing" :isPlaying="queue.playing"
:isCurrent="queue.currentid == track.trackid" :isCurrent="queue.currentid == track.trackid"
@@ -11,6 +11,7 @@
@PlayThis="updateQueue(index)" @PlayThis="updateQueue(index)"
/> />
</TransitionGroup> </TransitionGroup>
<div v-else class="t-center"><h5>🤷</h5></div>
<LoadMore v-if="search.tracks.more" :loader="search.loadTracks" /> <LoadMore v-if="search.tracks.more" :loader="search.loadTracks" />
</div> </div>
</template> </template>
+18 -5
View File
@@ -1,15 +1,13 @@
<template> <template>
<div id="gsearch-input" class="rounded"> <div id="gsearch-input" class="rounded">
<div <div id="ginner" tabindex="0" class="bg-primary rounded">
id="ginner"
tabindex="0"
class="bg-primary rounded"
>
<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"
@blur.prevent="removeFocusedClass"
@focus.prevent="addFocusedClass"
/> />
<SearchSvg /> <SearchSvg />
</div> </div>
@@ -21,6 +19,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from "vue";
import QueueSvg from "../../assets/icons/queue.svg"; import QueueSvg from "../../assets/icons/queue.svg";
import SearchSvg from "../../assets/icons/search.svg"; import SearchSvg from "../../assets/icons/search.svg";
import useSearchStore from "../../stores/search"; import useSearchStore from "../../stores/search";
@@ -28,6 +27,20 @@ import useTabStore from "../../stores/tabs";
const search = useSearchStore(); const search = useSearchStore();
const tabs = useTabStore(); const tabs = useTabStore();
const focused = ref(false);
let classList: DOMTokenList | undefined;
onMounted(() => {
classList = document.getElementById("ginner")?.classList;
});
function addFocusedClass() {
classList?.add("search-focused");
}
function removeFocusedClass() {
classList?.remove("search-focused");
}
</script> </script>
<style lang="scss"> <style lang="scss">
+5 -105
View File
@@ -17,35 +17,20 @@
top: context.y + 'px', top: context.y + 'px',
}" }"
> >
<div <ContextItem
class="context-item" class="context-item"
v-for="option in context.options" v-for="option in context.options"
:key="option.label" :key="option.label"
:class="[{ critical: option.critical }, option.type]" :class="[{ critical: option.critical }, option.type]"
@click="() => option.action && option.action()" :option="option"
> @click="option.action()"
<div class="icon image" :class="option.icon"></div> />
<div class="label ellip">{{ option.label }}</div>
<div class="more image" v-if="option.children"></div>
<div class="children rounded shadow-sm" v-if="option.children">
<div
class="context-item"
v-for="child in option.children"
:key="child.label"
:class="[{ critical: child.critical }, child.type]"
@click="child.action && child.action()"
>
<div class="label ellip">
{{ child.label }}
</div>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import useContextStore from "../stores/context"; import useContextStore from "../stores/context";
import ContextItem from "./Contextmenu/ContextItem.vue";
const context = useContextStore(); const context = useContextStore();
</script> </script>
@@ -64,91 +49,6 @@ const context = useContextStore();
transform-origin: top left; transform-origin: top left;
font-size: 0.875rem; font-size: 0.875rem;
.context-item {
width: 100%;
display: flex;
align-items: center;
cursor: default;
padding: $small 1rem;
position: relative;
.more {
height: 1.5rem;
width: 1.5rem;
position: absolute;
right: $small;
background-image: url("../assets/icons/expand.svg");
}
.children {
position: absolute;
right: -13rem;
width: 13rem;
top: -0.5rem;
max-height: 23.5rem;
padding: $small 0 !important;
background-color: $context;
transform: scale(0);
transform-origin: top left;
.context-item {
padding: $small 1rem;
}
.separator {
padding: 0;
}
}
.icon {
height: 1.25rem;
width: 1.25rem;
margin-right: $small;
}
.label {
width: 9rem;
}
.folder {
background-image: url("../assets/icons/folder.svg");
}
.artist {
background-image: url("../assets/icons/artist.svg");
}
.album {
background-image: url("../assets/icons/album.svg");
}
.delete {
background-image: url("../assets/icons/delete.svg");
}
.plus {
background-image: url("../assets/icons/plus.svg");
}
.add_to_queue {
background-image: url("../assets/icons/add_to_queue.svg");
}
.heart {
background-image: url("../assets/icons/heart.svg");
}
&:hover {
background: #234ece;
.children {
transform: scale(1);
transition: transform 0.1s ease-in-out;
}
}
}
.separator { .separator {
height: 1px; height: 1px;
padding: 0; padding: 0;
+4 -12
View File
@@ -1,5 +1,5 @@
<template> <template>
<button id="option-drop" @click.stop.prevent="showDropdown"> <button id="option-drop" @click.stop.prevent="showDropdown" class="btn-more">
<MoreSvg /> <MoreSvg />
</button> </button>
</template> </template>
@@ -8,7 +8,7 @@ import { onMounted } from "vue";
import MoreSvg from "../../assets/icons/more.svg"; import MoreSvg from "../../assets/icons/more.svg";
let elem: DOMRect; let elem: DOMRect;
defineProps<{ const props = defineProps<{
src?: string; src?: string;
}>(); }>();
@@ -22,19 +22,11 @@ onMounted(() => {
}); });
function showDropdown(e: Event) { function showDropdown(e: Event) {
if (!props.src) return;
emit("showDropdown", { emit("showDropdown", {
clientX: elem.left + 35, clientX: elem.left + 35,
clientY: elem.top, clientY: elem.top,
}); });
} }
</script> </script>
<style lang="scss">
#option-drop {
display: flex;
align-items: center;
svg {
transform: scale(1.25);
}
}
</style>
+4 -3
View File
@@ -140,15 +140,16 @@ export default async (
}; };
const options: Option[] = [ const options: Option[] = [
add_to_playlist,
play_next, play_next,
add_to_q, add_to_q,
// add_to_fav, separator,
add_to_playlist,
separator, separator,
go_to_folder, go_to_folder,
go_to_album,
// add_to_fav,
// go_to_artist, // go_to_artist,
// go_to_alb_artist, // go_to_alb_artist,
go_to_album,
// separator, // separator,
// del_track, // del_track,
]; ];
+1 -1
View File
@@ -53,7 +53,7 @@ export interface Artist {
export interface Option { export interface Option {
type?: string; type?: string;
label?: string; label?: string;
action?: () => void; action: () => void;
children?: Option[] | false; children?: Option[] | false;
icon?: string; icon?: string;
critical?: Boolean; critical?: Boolean;
+2 -8
View File
@@ -5,15 +5,9 @@ const settings = useSettingsStore;
export default [ export default [
{ {
title: "Use right bottom now playing card", title: "Use alternate now playing card",
type: SettingType.switch, type: SettingType.switch,
source: () => settings().use_right_np, source: () => settings().use_alt_np,
action: () => settings().toggleUseRightNP(), action: () => settings().toggleUseRightNP(),
}, },
{
title: "Use left now playing card",
type: SettingType.switch,
source: () => settings().use_side_np,
action: () => settings().toggleUseSideNP(),
},
]; ];
+9 -17
View File
@@ -25,7 +25,6 @@ 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: () => ({
@@ -43,9 +42,6 @@ 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;
@@ -53,7 +49,6 @@ export default defineStore("Queue", {
this.currentid = track.trackid; this.currentid = track.trackid;
const uri = state.settings.uri + "/file/" + track.hash; const uri = state.settings.uri + "/file/" + track.hash;
this.updateCurrent(index); this.updateCurrent(index);
this.bindProgressElem();
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
audio.autoplay = true; audio.autoplay = true;
@@ -260,32 +255,29 @@ export default defineStore("Queue", {
}, },
}, },
getters: { getters: {
getNextTrack() { getNextTrack(): Track {
if (this.current == this.tracklist.length - 1) { if (this.current == this.tracklist.length - 1) {
return this.tracklist[0]; return this.tracklist[0];
} else { } else {
return this.tracklist[this.current + 1]; return this.tracklist[this.current + 1];
} }
}, },
getPrevTrack() { getPrevTrack(): Track {
if (this.current === 0) { if (this.current === 0) {
return this.tracklist[this.tracklist.length - 1]; return this.tracklist[this.tracklist.length - 1];
} else { } else {
return this.tracklist[this.current - 1]; return this.tracklist[this.current - 1];
} }
}, },
fullTime() { getCurrentTrack(): Track {
return audio.duration;
},
currentTime() {
return audio.currentTime;
},
getCurrentTrack() {
return this.tracklist[this.current]; return this.tracklist[this.current];
}, },
getIsplaying() { },
return audio.paused ? false : true; persist: {
afterRestore: (context) => {
let store = context.store;
store.duration.current = 0;
store.playing = false;
}, },
}, },
persist: true,
}); });
+3 -11
View File
@@ -3,20 +3,12 @@ import useQueueStore from "../queue";
export default defineStore("settings", { export default defineStore("settings", {
state: () => ({ state: () => ({
use_side_np: false, use_alt_np: false,
use_right_np: true,
}), }),
actions: { actions: {
toggleNPs() {
this.use_side_np = !this.use_side_np;
this.use_right_np = !this.use_right_np;
useQueueStore().bindProgressElem();
},
toggleUseSideNP() {
this.toggleNPs();
},
toggleUseRightNP() { toggleUseRightNP() {
this.toggleNPs(); this.use_alt_np = !this.use_alt_np;
useQueueStore();
}, },
}, },
getters: {}, getters: {},
+1 -1
View File
@@ -19,5 +19,5 @@ export default function focusElem(
inline: "center", inline: "center",
}); });
} }
}, delay | 300); }, delay || 300);
} }