mirror of
https://github.com/tcsenpai/swingmusic.git
synced 2025-06-08 12:15:39 +00:00
use third-party module to auto-persist queue store
+ more redesign + convert js files to ts
This commit is contained in:
parent
5476575d10
commit
03219166c5
@ -16,6 +16,6 @@
|
|||||||
</noscript>
|
</noscript>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
<script type="module" src="/src/main.js"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
12
src/App.vue
12
src/App.vue
@ -4,11 +4,13 @@
|
|||||||
<Notification />
|
<Notification />
|
||||||
<div id="app-grid">
|
<div id="app-grid">
|
||||||
<div class="l-sidebar rounded">
|
<div class="l-sidebar rounded">
|
||||||
|
<div class="withlogo">
|
||||||
<Logo />
|
<Logo />
|
||||||
<Navigation />
|
<Navigation />
|
||||||
<div class="l-album-art">
|
|
||||||
<nowPlaying />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<nowPlaying />
|
||||||
|
<!-- <Playlists /> -->
|
||||||
</div>
|
</div>
|
||||||
<NavBar />
|
<NavBar />
|
||||||
<div id="acontent" class="rounded">
|
<div id="acontent" class="rounded">
|
||||||
@ -44,6 +46,7 @@ import SearchInput from "@/components/RightSideBar/SearchInput.vue";
|
|||||||
import BottomBar from "@/components/BottomBar/BottomBar.vue";
|
import BottomBar from "@/components/BottomBar/BottomBar.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();
|
||||||
@ -94,6 +97,11 @@ onMounted(() => {
|
|||||||
.l-sidebar {
|
.l-sidebar {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|
||||||
|
.withlogo {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.l-album-art {
|
.l-album-art {
|
||||||
width: calc(100% - 2rem);
|
width: calc(100% - 2rem);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -62,10 +62,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.l-sidebar {
|
.l-sidebar {
|
||||||
width: 17rem;
|
width: 15rem;
|
||||||
grid-area: l-sidebar;
|
grid-area: l-sidebar;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 1fr max-content;
|
||||||
|
gap: 1rem;
|
||||||
background-color: $black;
|
background-color: $black;
|
||||||
padding: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.b-bar {
|
.b-bar {
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
.bg-black {
|
.bg-black {
|
||||||
background-color: $gray4;
|
background-color: $gray4;
|
||||||
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.1);
|
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.425)
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
|
@ -4,11 +4,15 @@
|
|||||||
<img
|
<img
|
||||||
:src="paths.images.thumb + queue.currenttrack?.image"
|
:src="paths.images.thumb + queue.currenttrack?.image"
|
||||||
alt=""
|
alt=""
|
||||||
class="rounded"
|
class="rounded shadow-lg"
|
||||||
/>
|
/>
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
<div class="np-artist ellip">
|
<div class="np-artist ellip">
|
||||||
<span v-for="artist in putCommas(queue.currenttrack?.artists || ['Artist'])">
|
<span
|
||||||
|
v-for="artist in putCommas(
|
||||||
|
queue.currenttrack?.artists || ['Artist']
|
||||||
|
)"
|
||||||
|
>
|
||||||
{{ artist }}
|
{{ artist }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -18,16 +22,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Progress />
|
<Progress />
|
||||||
|
<!-- <div class="ex-hotkeys">
|
||||||
|
<HotKeys />
|
||||||
|
</div> -->
|
||||||
|
<div class="time">
|
||||||
|
<span class="current">{{ formatSeconds(queue.currentTime) }}</span>
|
||||||
|
<HotKeys />
|
||||||
|
<span class="full">{{
|
||||||
|
formatSeconds(queue.fullTime || queue.currenttrack.length)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import "@/assets/scss/BottomBar/BottomBar.scss";
|
import "@/assets/scss/BottomBar/BottomBar.scss";
|
||||||
import { formatSeconds, putCommas } from "@/utils";
|
import { formatSeconds, putCommas } from "@/utils";
|
||||||
|
import HotKeys from "../LeftSidebar/NP/HotKeys.vue";
|
||||||
import Progress from "../LeftSidebar/NP/Progress.vue";
|
import Progress from "../LeftSidebar/NP/Progress.vue";
|
||||||
|
|
||||||
import useQStore from "@/stores/queue";
|
|
||||||
import { paths } from "@/config";
|
import { paths } from "@/config";
|
||||||
|
import useQStore from "@/stores/queue";
|
||||||
|
|
||||||
const queue = useQStore();
|
const queue = useQStore();
|
||||||
</script>
|
</script>
|
||||||
@ -36,10 +51,28 @@ const queue = useQStore();
|
|||||||
.b-bar {
|
.b-bar {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 1fr max-content;
|
grid-template-rows: 1fr max-content;
|
||||||
border-radius: 1rem;
|
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
padding-bottom: 2rem;
|
padding-bottom: 1rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.time {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.full {
|
||||||
|
text-align: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ex-hotkeys {
|
||||||
|
position: absolute;
|
||||||
|
width: 10rem;
|
||||||
|
right: 1rem;
|
||||||
|
top: 1rem;
|
||||||
|
border-radius: $small;
|
||||||
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -55,11 +88,11 @@ const queue = useQStore();
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
gap: $smaller;
|
||||||
|
|
||||||
.np-title {
|
.np-title {
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin-bottom: $small;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.np-artist {
|
.np-artist {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
|
|
||||||
<div class="hotkeys">
|
<div class="hotkeys rounded noscroll">
|
||||||
<div class="image ctrl-btn" id="previous" @click="q.playPrev"></div>
|
<div class="image ctrl-btn" id="previous" @click="q.playPrev"></div>
|
||||||
<div
|
<div
|
||||||
class="image ctrl-btn play-pause"
|
class="image ctrl-btn play-pause"
|
||||||
@ -28,13 +28,13 @@ const q = useQStore();
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
place-content: flex-end;
|
place-content: flex-end;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
background-color: $gray2;
|
||||||
|
|
||||||
.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;
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $accent;
|
background-color: $accent;
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="info">
|
<div class="sidebar-songcard">
|
||||||
<div class="desc">
|
|
||||||
<div>
|
|
||||||
<router-link
|
<router-link
|
||||||
:to="{
|
:to="{
|
||||||
name: 'AlbumView',
|
name: 'AlbumView',
|
||||||
@ -17,17 +15,17 @@
|
|||||||
class="l-image rounded force-lm"
|
class="l-image rounded force-lm"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</router-link>
|
|
||||||
|
|
||||||
<div id="bitrate" v-if="track?.bitrate">
|
<div id="bitrate" v-if="track?.bitrate">
|
||||||
<span v-if="track.bitrate > 1500">MASTER</span>
|
<span v-if="track.bitrate > 1500">MASTER</span>
|
||||||
<span v-else-if="track.bitrate > 330">FLAC</span>
|
<span v-else-if="track.bitrate > 330">FLAC</span>
|
||||||
<span v-else>MP3</span>
|
<span v-else>MP3</span>
|
||||||
• {{ track.bitrate }}
|
• {{ track.bitrate }}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<div class="bottom">
|
||||||
<div class="title ellip">{{ props.track?.title }}</div>
|
<div class="title ellip">{{ props.track?.title }}</div>
|
||||||
<div class="separator no-border"></div>
|
|
||||||
<div
|
<div
|
||||||
class="artists ellip"
|
class="artists ellip"
|
||||||
v-if="track?.artists && track?.artists[0] !== ''"
|
v-if="track?.artists && track?.artists[0] !== ''"
|
||||||
@ -44,7 +42,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@ -57,3 +54,53 @@ const props = defineProps<{
|
|||||||
track: Track | null;
|
track: Track | null;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.sidebar-songcard {
|
||||||
|
.art {
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
place-items: center;
|
||||||
|
margin-bottom: $small;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bitrate {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
width: max-content;
|
||||||
|
padding: 0.2rem 0.35rem;
|
||||||
|
bottom: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
background-color: $black;
|
||||||
|
border-radius: $smaller;
|
||||||
|
box-shadow: 0rem 0rem 1rem rgba(0, 0, 0, 0.438);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
display: grid;
|
||||||
|
gap: $smaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 900;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.artists {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
opacity: 0.75;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline 1px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
54
src/components/LeftSidebar/Playlists.vue
Normal file
54
src/components/LeftSidebar/Playlists.vue
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div class="sidebar-playlists">
|
||||||
|
<div class="header">your playlists</div>
|
||||||
|
<div class="list rounded">
|
||||||
|
<div v-for="p in pStore.playlists" class="ellip">
|
||||||
|
<router-link
|
||||||
|
:to="{
|
||||||
|
name: 'PlaylistView',
|
||||||
|
params: {
|
||||||
|
pid: p.playlistid,
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ p.name }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import usePStore from "@/stores/pages/playlists";
|
||||||
|
import { onMounted } from "vue";
|
||||||
|
const pStore = usePStore();
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (pStore.playlists.length == 0) {
|
||||||
|
pStore.fetchAll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.sidebar-playlists {
|
||||||
|
// outline: solid 1px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: max-content 1fr;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
opacity: 0.5;
|
||||||
|
margin-bottom: $small;
|
||||||
|
margin-left: 1rem;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
padding: $small;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
padding: $small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,14 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="now-playing-card t-center rounded">
|
<div class="now-playing-card t-center rounded">
|
||||||
<div class="headin">Now playing</div>
|
|
||||||
<div
|
|
||||||
class="button menu rounded"
|
|
||||||
@click="showContextMenu"
|
|
||||||
:class="{ context_on: context_on }"
|
|
||||||
>
|
|
||||||
<MenuSvg />
|
|
||||||
</div>
|
|
||||||
<div class="separator no-border"></div>
|
|
||||||
<div>
|
<div>
|
||||||
<SongCard :track="queue.currenttrack" />
|
<SongCard :track="queue.currenttrack" />
|
||||||
<div class="l-track-time">
|
<div class="l-track-time">
|
||||||
@ -16,8 +7,8 @@
|
|||||||
><span class="rounded">{{ formatSeconds(queue.duration.full) }}</span>
|
><span class="rounded">{{ formatSeconds(queue.duration.full) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress />
|
<Progress />
|
||||||
<HotKeys />
|
|
||||||
</div>
|
</div>
|
||||||
|
<HotKeys />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -65,10 +56,11 @@ const showContextMenu = (e: Event) => {
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.now-playing-card {
|
.now-playing-card {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background-color: $primary;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-rows: 1fr max-content;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
.l-track-time {
|
.l-track-time {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -96,24 +88,6 @@ const showContextMenu = (e: Event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.headin {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
position: absolute;
|
|
||||||
top: $small;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 200ms;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: $smaller;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $accent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.context_on {
|
.context_on {
|
||||||
background-color: $accent;
|
background-color: $accent;
|
||||||
@ -123,43 +97,5 @@ const showContextMenu = (e: Event) => {
|
|||||||
right: $small;
|
right: $small;
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.art {
|
|
||||||
width: 100%;
|
|
||||||
aspect-ratio: 1;
|
|
||||||
place-items: center;
|
|
||||||
margin-bottom: $small;
|
|
||||||
|
|
||||||
.l-image {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#bitrate {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
width: max-content;
|
|
||||||
padding: 0.2rem 0.35rem;
|
|
||||||
top: 14rem;
|
|
||||||
left: 2rem;
|
|
||||||
background-color: $black;
|
|
||||||
border-radius: $smaller;
|
|
||||||
box-shadow: 0rem 0rem 1rem rgba(0, 0, 0, 0.438);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-weight: 900;
|
|
||||||
word-break: break-all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.artists {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: rgba(255, 255, 255, 0.808);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline 1px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
#logo {
|
#logo {
|
||||||
height: 4.5rem !important;
|
height: 4.5rem !important;
|
||||||
width: 15rem;
|
|
||||||
background-image: url(./../assets/images/logo.webp);
|
background-image: url(./../assets/images/logo.webp);
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
@include ximage;
|
@include ximage;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="up-next">
|
<div class="up-next">
|
||||||
<div class="r-grid">
|
<div class="r-grid">
|
||||||
<UpNext :track="queue.tracklist[queue.next]" :playNext="queue.playNext" />
|
|
||||||
<div class="scrollable-r bg-black rounded">
|
<div class="scrollable-r bg-black rounded">
|
||||||
<QueueActions />
|
<QueueActions />
|
||||||
<div
|
<div
|
||||||
@ -23,7 +22,6 @@
|
|||||||
</TransitionGroup>
|
</TransitionGroup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <PlayingFrom :from="queue.from" /> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -35,9 +33,7 @@ import useQStore from "@/stores/queue";
|
|||||||
import { focusElem } from "@/utils";
|
import { focusElem } from "@/utils";
|
||||||
|
|
||||||
import TrackItem from "../shared/TrackItem.vue";
|
import TrackItem from "../shared/TrackItem.vue";
|
||||||
import PlayingFrom from "./Queue/playingFrom.vue";
|
|
||||||
import QueueActions from "./Queue/QueueActions.vue";
|
import QueueActions from "./Queue/QueueActions.vue";
|
||||||
import UpNext from "./Queue/upNext.vue";
|
|
||||||
|
|
||||||
const queue = useQStore();
|
const queue = useQStore();
|
||||||
const mouseover = ref(false);
|
const mouseover = ref(false);
|
||||||
@ -66,8 +62,6 @@ onUpdated(() => {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ensure leaving items are taken out of layout flow so that moving
|
|
||||||
animations can be calculated correctly. */
|
|
||||||
.queuelist-leave-active {
|
.queuelist-leave-active {
|
||||||
transition: none;
|
transition: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -85,10 +79,9 @@ onUpdated(() => {
|
|||||||
.r-grid {
|
.r-grid {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: grid;
|
// display: grid;
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-rows: max-content 1fr;
|
// gap: 1rem;
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
.scrollable-r {
|
.scrollable-r {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -2,13 +2,16 @@ import "./assets/scss/index.scss";
|
|||||||
|
|
||||||
import { createPinia } from "pinia";
|
import { createPinia } from "pinia";
|
||||||
import { createApp } from "vue";
|
import { createApp } from "vue";
|
||||||
|
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||||
|
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import router from "./router";
|
import router from "./router";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
const pinia = createPinia();
|
||||||
|
pinia.use(piniaPluginPersistedstate);
|
||||||
|
|
||||||
app.use(createPinia());
|
app.use(pinia);
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
@ -1,5 +1,3 @@
|
|||||||
// @ts-strict
|
|
||||||
|
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import state from "../composables/state";
|
import state from "../composables/state";
|
||||||
import { NotifType, useNotifStore } from "./notification";
|
import { NotifType, useNotifStore } from "./notification";
|
||||||
@ -14,29 +12,6 @@ import {
|
|||||||
Track,
|
Track,
|
||||||
} from "../interfaces";
|
} from "../interfaces";
|
||||||
|
|
||||||
function writeQueue(from: From, tracks: Track[]) {
|
|
||||||
localStorage.setItem(
|
|
||||||
"queue",
|
|
||||||
JSON.stringify({
|
|
||||||
from: from,
|
|
||||||
tracks: tracks,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeCurrent(index: number) {
|
|
||||||
localStorage.setItem("current", JSON.stringify(index));
|
|
||||||
}
|
|
||||||
|
|
||||||
function readCurrent(): number {
|
|
||||||
const current = localStorage.getItem("current");
|
|
||||||
|
|
||||||
if (current) {
|
|
||||||
return JSON.parse(current);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
function shuffle(tracks: Track[]) {
|
function shuffle(tracks: Track[]) {
|
||||||
const shuffled = tracks.slice();
|
const shuffled = tracks.slice();
|
||||||
for (let i = shuffled.length - 1; i > 0; i--) {
|
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||||
@ -48,19 +23,15 @@ function shuffle(tracks: Track[]) {
|
|||||||
|
|
||||||
type From = fromFolder | fromAlbum | fromPlaylist | fromSearch;
|
type From = fromFolder | fromAlbum | fromPlaylist | fromSearch;
|
||||||
|
|
||||||
|
let audio = new Audio();
|
||||||
|
|
||||||
export default defineStore("Queue", {
|
export default defineStore("Queue", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
progressElem: HTMLElement,
|
progressElem: HTMLElement,
|
||||||
audio: new Audio(),
|
|
||||||
duration: {
|
duration: {
|
||||||
current: 0,
|
current: 0,
|
||||||
full: 0,
|
full: 0,
|
||||||
},
|
},
|
||||||
indexes: {
|
|
||||||
current: 0,
|
|
||||||
next: 0,
|
|
||||||
previous: 0,
|
|
||||||
},
|
|
||||||
current: 0,
|
current: 0,
|
||||||
next: 0,
|
next: 0,
|
||||||
prev: 0,
|
prev: 0,
|
||||||
@ -81,25 +52,24 @@ export default defineStore("Queue", {
|
|||||||
this.updateCurrent(index);
|
this.updateCurrent(index);
|
||||||
|
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
this.audio.autoplay = true;
|
audio.autoplay = true;
|
||||||
this.audio.src = uri;
|
audio.src = uri;
|
||||||
this.audio.oncanplaythrough = resolve;
|
audio.oncanplaythrough = resolve;
|
||||||
this.audio.onerror = reject;
|
audio.onerror = reject;
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.duration.full = this.audio.duration;
|
this.duration.full = audio.duration;
|
||||||
this.audio.play().then(() => {
|
audio.play().then(() => {
|
||||||
this.playing = true;
|
this.playing = true;
|
||||||
notif(track, this.playPause, this.playNext, this.playPrev);
|
notif(track, this.playPause, this.playNext, this.playPrev);
|
||||||
|
|
||||||
this.audio.ontimeupdate = () => {
|
audio.ontimeupdate = () => {
|
||||||
this.duration.current = this.audio.currentTime;
|
this.duration.current = audio.currentTime;
|
||||||
const bg_size =
|
const bg_size = (audio.currentTime / audio.duration) * 100;
|
||||||
(this.audio.currentTime / this.audio.duration) * 100;
|
|
||||||
elem.style.backgroundSize = `${bg_size}% 100%`;
|
elem.style.backgroundSize = `${bg_size}% 100%`;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.audio.onended = () => {
|
audio.onended = () => {
|
||||||
this.playNext();
|
this.playNext();
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -119,13 +89,13 @@ export default defineStore("Queue", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
playPause() {
|
playPause() {
|
||||||
if (this.audio.src === "") {
|
if (audio.src === "") {
|
||||||
this.play(this.current);
|
this.play(this.current);
|
||||||
} else if (this.audio.paused) {
|
} else if (audio.paused) {
|
||||||
this.audio.play();
|
audio.play();
|
||||||
this.playing = true;
|
this.playing = true;
|
||||||
} else {
|
} else {
|
||||||
this.audio.pause();
|
audio.pause();
|
||||||
this.playing = false;
|
this.playing = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -137,7 +107,7 @@ export default defineStore("Queue", {
|
|||||||
},
|
},
|
||||||
seek(pos: number) {
|
seek(pos: number) {
|
||||||
try {
|
try {
|
||||||
this.audio.currentTime = pos;
|
audio.currentTime = pos;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof TypeError) {
|
if (error instanceof TypeError) {
|
||||||
console.error("Seek error: no audio");
|
console.error("Seek error: no audio");
|
||||||
@ -152,15 +122,11 @@ export default defineStore("Queue", {
|
|||||||
this.from = parsed.from;
|
this.from = parsed.from;
|
||||||
this.tracklist = parsed.tracks;
|
this.tracklist = parsed.tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateCurrent(readCurrent());
|
|
||||||
},
|
},
|
||||||
updateCurrent(index: number) {
|
updateCurrent(index: number) {
|
||||||
this.setCurrent(index);
|
this.setCurrent(index);
|
||||||
this.updateNext(index);
|
this.updateNext(index);
|
||||||
this.updatePrev(index);
|
this.updatePrev(index);
|
||||||
|
|
||||||
writeCurrent(index);
|
|
||||||
},
|
},
|
||||||
updateNext(index: number) {
|
updateNext(index: number) {
|
||||||
if (index == this.tracklist.length - 1) {
|
if (index == this.tracklist.length - 1) {
|
||||||
@ -190,7 +156,6 @@ export default defineStore("Queue", {
|
|||||||
if (this.tracklist !== tracklist) {
|
if (this.tracklist !== tracklist) {
|
||||||
this.tracklist = [];
|
this.tracklist = [];
|
||||||
this.tracklist.push(...tracklist);
|
this.tracklist.push(...tracklist);
|
||||||
writeQueue(this.from, this.tracklist);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
playFromFolder(fpath: string, tracks: Track[]) {
|
playFromFolder(fpath: string, tracks: Track[]) {
|
||||||
@ -235,7 +200,7 @@ export default defineStore("Queue", {
|
|||||||
},
|
},
|
||||||
addTrackToQueue(track: Track) {
|
addTrackToQueue(track: Track) {
|
||||||
this.tracklist.push(track);
|
this.tracklist.push(track);
|
||||||
writeQueue(this.from, this.tracklist);
|
// writeQueue(this.from, this.tracklist);
|
||||||
this.updateNext(this.current);
|
this.updateNext(this.current);
|
||||||
},
|
},
|
||||||
playTrackNext(track: Track) {
|
playTrackNext(track: Track) {
|
||||||
@ -264,16 +229,12 @@ export default defineStore("Queue", {
|
|||||||
`Added ${track.title} to queue`,
|
`Added ${track.title} to queue`,
|
||||||
NotifType.Success
|
NotifType.Success
|
||||||
);
|
);
|
||||||
writeQueue(this.from, this.tracklist);
|
|
||||||
},
|
},
|
||||||
clearQueue() {
|
clearQueue() {
|
||||||
this.tracklist = [] as Track[];
|
this.tracklist = [] as Track[];
|
||||||
this.currentid = "";
|
this.currentid = "";
|
||||||
this.current, this.next, (this.prev = 0);
|
this.current, this.next, (this.prev = 0);
|
||||||
this.from = <From>{};
|
this.from = <From>{};
|
||||||
|
|
||||||
writeCurrent(0);
|
|
||||||
writeQueue(this.from, [] as Track[]);
|
|
||||||
},
|
},
|
||||||
shuffleQueue() {
|
shuffleQueue() {
|
||||||
const Toast = useNotifStore();
|
const Toast = useNotifStore();
|
||||||
@ -291,12 +252,38 @@ export default defineStore("Queue", {
|
|||||||
this.currentid = shuffled[0].trackid;
|
this.currentid = shuffled[0].trackid;
|
||||||
this.next = 1;
|
this.next = 1;
|
||||||
this.prev = this.tracklist.length - 1;
|
this.prev = this.tracklist.length - 1;
|
||||||
|
|
||||||
writeQueue(this.from, shuffled);
|
|
||||||
writeCurrent(0);
|
|
||||||
},
|
},
|
||||||
removeFromQueue(index: number = 0) {
|
removeFromQueue(index: number = 0) {
|
||||||
this.tracklist.splice(index, 1);
|
this.tracklist.splice(index, 1);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
getters: {
|
||||||
|
getNextTrack() {
|
||||||
|
if (this.current == this.tracklist.length - 1) {
|
||||||
|
return this.tracklist[0];
|
||||||
|
} else {
|
||||||
|
return this.tracklist[this.current + 1];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getPrevTrack() {
|
||||||
|
if (this.current === 0) {
|
||||||
|
return this.tracklist[this.tracklist.length - 1];
|
||||||
|
} else {
|
||||||
|
return this.tracklist[this.current - 1];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fullTime() {
|
||||||
|
return audio.duration;
|
||||||
|
},
|
||||||
|
currentTime() {
|
||||||
|
return audio.currentTime;
|
||||||
|
},
|
||||||
|
getCurrentTrack() {
|
||||||
|
return this.tracklist[this.current];
|
||||||
|
},
|
||||||
|
getIsplaying() {
|
||||||
|
return audio.paused ? false : true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
persist: true,
|
||||||
});
|
});
|
||||||
|
@ -106,7 +106,8 @@ onBeforeRouteUpdate((to, from) => {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: bottom;
|
object-position: bottom right;
|
||||||
|
transition: all .25s ease;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,8 +14,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import PlaylistCard from "@/components/playlists/PlaylistCard.vue";
|
import PlaylistCard from "@/components/playlists/PlaylistCard.vue";
|
||||||
|
|
||||||
import usePStore from "@/stores/pages/playlists";
|
|
||||||
import NewPlaylistCard from "@/components/playlists/NewPlaylistCard.vue";
|
import NewPlaylistCard from "@/components/playlists/NewPlaylistCard.vue";
|
||||||
|
import usePStore from "@/stores/pages/playlists";
|
||||||
const pStore = usePStore();
|
const pStore = usePStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
7
src/vite-env.d.ts
vendored
Normal file
7
src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
declare module '*.vue' {
|
||||||
|
import type { DefineComponent } from 'vue'
|
||||||
|
const component: DefineComponent<{}, {}, any>
|
||||||
|
export default component
|
||||||
|
}
|
@ -1,11 +1,40 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"strict": true,
|
"target": "ESNext",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
// "strict": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
|
"sourceMap": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"skipLibCheck": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"baseUrl": ["./"],
|
"baseUrl": ["./"],
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"]
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.d.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"src/**/*.svg"
|
||||||
|
],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// {
|
||||||
|
// "compilerOptions": {
|
||||||
|
// "strict": false,
|
||||||
|
// "jsx": "preserve",
|
||||||
|
// "paths": {
|
||||||
|
// "baseUrl": ["./"],
|
||||||
|
// "@/*": ["./src/*"]
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// "include": ["src/**/*"]
|
||||||
|
// }
|
||||||
|
9
tsconfig.node.json
Normal file
9
tsconfig.node.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import vue from "@vitejs/plugin-vue";
|
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
|
import vue from "@vitejs/plugin-vue";
|
||||||
import svgLoader from "vite-svg-loader";
|
import svgLoader from "vite-svg-loader";
|
||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
Loading…
x
Reference in New Issue
Block a user