mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-05 04:53:01 +00:00
responsiveness improvements
+ extract track context menu handler into a composable
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
#app-grid {
|
#app-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr min-content;
|
grid-template-columns: min-content 1fr 29rem;
|
||||||
grid-template-rows: max-content 1fr max-content;
|
grid-template-rows: max-content 1fr max-content;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"l-sidebar nav search-input"
|
"l-sidebar nav search-input"
|
||||||
@@ -12,6 +12,20 @@
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
margin: $small auto;
|
margin: $small auto;
|
||||||
|
|
||||||
|
@include for-desktop-down {
|
||||||
|
grid-template-columns: min-content 1fr 26rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include tablet-landscape {
|
||||||
|
grid-template-columns: min-content 1fr;
|
||||||
|
|
||||||
|
.r-sidebar,
|
||||||
|
#tabs,
|
||||||
|
#gsearch-input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#acontent {
|
#acontent {
|
||||||
@@ -30,16 +44,11 @@
|
|||||||
grid-area: tabs;
|
grid-area: tabs;
|
||||||
height: 3.5rem;
|
height: 3.5rem;
|
||||||
margin-top: -$small;
|
margin-top: -$small;
|
||||||
|
|
||||||
@include tablet-landscape {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.r-sidebar {
|
.r-sidebar {
|
||||||
grid-area: r-sidebar;
|
grid-area: r-sidebar;
|
||||||
margin-top: -$small;
|
margin-top: -$small;
|
||||||
width: 29rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#gsearch-input {
|
#gsearch-input {
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
@mixin ximage {
|
||||||
|
background-position: center;
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// media query mixins
|
||||||
|
@mixin phone-only {
|
||||||
|
@media (max-width: 599px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@mixin tablet-portrait {
|
||||||
|
@media (max-width: 810) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin tablet-landscape {
|
||||||
|
@media (max-width: 1080px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@mixin for-desktop-down {
|
||||||
|
@media (max-width: 1600px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin for-big-desktop-up {
|
||||||
|
@media (min-width: 1800px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,36 +44,11 @@ $secondary: $gray5;
|
|||||||
$cta: $blue;
|
$cta: $blue;
|
||||||
$danger: $red;
|
$danger: $red;
|
||||||
$track-hover: $gray4;
|
$track-hover: $gray4;
|
||||||
$context: $gray5;
|
$context: $gray;
|
||||||
|
|
||||||
// SVG COLORS
|
// SVG COLORS
|
||||||
$default: $accent;
|
$default: $accent;
|
||||||
$track-btn-svg: $red;
|
$track-btn-svg: $red;
|
||||||
$side-nav-svg: $red;
|
$side-nav-svg: $red;
|
||||||
|
|
||||||
// media query mixins
|
|
||||||
@mixin phone-only {
|
|
||||||
@media (max-width: 599px) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@mixin tablet-portrait {
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@mixin tablet-landscape {
|
|
||||||
@media (max-width: 1200px) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@mixin for-desktop-down {
|
|
||||||
@media (max-width: 1280px) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@mixin for-big-desktop-up {
|
|
||||||
@media (min-width: 1800px) {
|
|
||||||
@content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
@mixin ximage {
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
@@ -44,14 +44,14 @@ defineProps<{
|
|||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: $small;
|
gap: $small;
|
||||||
min-height: 15rem;
|
min-height: 15rem;
|
||||||
}
|
|
||||||
|
|
||||||
@include tablet-portrait {
|
@include for-desktop-down {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr !important;
|
||||||
}
|
|
||||||
|
|
||||||
@include tablet-landscape {
|
.left {
|
||||||
grid-template-columns: 1fr auto;
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.left {
|
.left {
|
||||||
@@ -60,16 +60,12 @@ defineProps<{
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
margin-right: $small;
|
margin-right: $small;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
bg-black: solid 1px $gray5;
|
border: solid 1px $gray5;
|
||||||
background-image: linear-gradient(37deg, $gray5 20%, $gray4);
|
background-image: linear-gradient(37deg, $gray5 20%, $gray4);
|
||||||
|
|
||||||
@include tablet-portrait {
|
// @include for-desktop-down {
|
||||||
display: none;
|
// display: none;
|
||||||
}
|
// }
|
||||||
|
|
||||||
@include tablet-landscape {
|
|
||||||
width: 10rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
$rectpos: calc(50% - 5rem);
|
$rectpos: calc(50% - 5rem);
|
||||||
|
|
||||||
@@ -95,7 +91,7 @@ defineProps<{
|
|||||||
height: 7rem;
|
height: 7rem;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: calc($rectpos + 7rem);
|
left: calc($rectpos + 7rem);
|
||||||
bg-black-radius: 50%;
|
border-radius: 50%;
|
||||||
box-shadow: 0 0 2rem rgb(0, 0, 0);
|
box-shadow: 0 0 2rem rgb(0, 0, 0);
|
||||||
transition: all 0.25s ease-in-out;
|
transition: all 0.25s ease-in-out;
|
||||||
|
|
||||||
@@ -105,7 +101,7 @@ defineProps<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.bio {
|
.bio {
|
||||||
bg-black: solid 1px $gray5;
|
border: solid 1px $gray5;
|
||||||
padding: $small;
|
padding: $small;
|
||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ const emit = defineEmits<{
|
|||||||
(event: "resetBottomPadding"): void;
|
(event: "resetBottomPadding"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const albumheaderthing = ref<HTMLElement>(null);
|
const albumheaderthing = ref<any>(null);
|
||||||
const imguri = paths.images.thumb;
|
const imguri = paths.images.thumb;
|
||||||
const nav = useNavStore();
|
const nav = useNavStore();
|
||||||
|
|
||||||
@@ -121,6 +121,9 @@ useVisibility(albumheaderthing, handleVisibilityState);
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
.top {
|
.top {
|
||||||
|
.h {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
.title {
|
.title {
|
||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -146,5 +149,18 @@ useVisibility(albumheaderthing, handleVisibilityState);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@include for-desktop-down {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
|
||||||
|
.art > img {
|
||||||
|
height: 6rem;
|
||||||
|
box-shadow: 0 0 1rem $black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info > .top > .title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -24,7 +24,5 @@ defineProps<{
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
|
||||||
gap: $medium;
|
gap: $medium;
|
||||||
padding: $small;
|
|
||||||
background-color: $gray5;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="folder">
|
<div class="folder">
|
||||||
<div class="table rounded" v-if="tracks.length">
|
<div class="table rounded" v-if="tracks.length">
|
||||||
<div class="header" v-if="disc && !album.info.is_single">
|
<div class="header">
|
||||||
<div class="disc-number">Disc {{ disc }}</div>
|
<div class="disc-number" v-if="disc">Disc {{ disc }}</div>
|
||||||
|
<div class="disc-number" v-if="$route.name === Routes.folder">
|
||||||
|
In this folder
|
||||||
|
</div>
|
||||||
|
<div class="disc-number" v-if="$route.name === Routes.playlist">
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="songlist">
|
<div class="songlist">
|
||||||
<SongItem
|
<SongItem
|
||||||
@@ -13,7 +19,7 @@
|
|||||||
@updateQueue="updateQueue"
|
@updateQueue="updateQueue"
|
||||||
:isPlaying="queue.playing"
|
:isPlaying="queue.playing"
|
||||||
:isCurrent="queue.currentid == track.trackid"
|
:isCurrent="queue.currentid == track.trackid"
|
||||||
:isHighlighted="highlightid == track.uniq_hash"
|
:isHighlighted="($route.query.highlight as string) == track.uniq_hash"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -38,6 +44,7 @@ import { onMounted, onUpdated, ref } from "vue";
|
|||||||
import { Track } from "@/interfaces";
|
import { Track } from "@/interfaces";
|
||||||
import useQStore from "@/stores/queue";
|
import useQStore from "@/stores/queue";
|
||||||
import useAlbumStore from "@/stores/pages/album";
|
import useAlbumStore from "@/stores/pages/album";
|
||||||
|
import { Routes } from "@/composables/enums";
|
||||||
|
|
||||||
const queue = useQStore();
|
const queue = useQStore();
|
||||||
const album = useAlbumStore();
|
const album = useAlbumStore();
|
||||||
@@ -54,12 +61,18 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const routename = route.name as string;
|
const routename = route.name as string;
|
||||||
const highlightid = ref(route.query.highlight as string);
|
const highlightid = ref(route.query.highlight as string | null);
|
||||||
|
|
||||||
function highlightTrack(t_hash: string) {
|
function highlightTrack(t_hash: string) {
|
||||||
focusElem(`track-${t_hash}`, 500, "center");
|
focusElem(`track-${t_hash}`, 500, "center");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetHighlight() {
|
||||||
|
setTimeout(() => {
|
||||||
|
highlightid.value = null;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeRouteUpdate(async (to, from) => {
|
onBeforeRouteUpdate(async (to, from) => {
|
||||||
const h_hash = to.query.highlight as string;
|
const h_hash = to.query.highlight as string;
|
||||||
highlightid.value = h_hash as string;
|
highlightid.value = h_hash as string;
|
||||||
@@ -72,12 +85,14 @@ onBeforeRouteUpdate(async (to, from) => {
|
|||||||
onUpdated(() => {
|
onUpdated(() => {
|
||||||
if (highlightid.value) {
|
if (highlightid.value) {
|
||||||
highlightTrack(highlightid.value);
|
highlightTrack(highlightid.value);
|
||||||
|
resetHighlight();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (highlightid.value) {
|
if (highlightid.value) {
|
||||||
highlightTrack(highlightid.value);
|
highlightTrack(highlightid.value);
|
||||||
|
resetHighlight();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
/**
|
/**
|
||||||
@@ -187,6 +202,7 @@ function getTrackList() {
|
|||||||
|
|
||||||
.songlist {
|
.songlist {
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="r-home">
|
<div class="r-home">
|
||||||
<UpNext :next="queue.tracklist[queue.next]" :playNext="queue.playNext" />
|
<UpNext :track="queue.tracklist[queue.next]" :playNext="queue.playNext" />
|
||||||
<Recommendations />
|
<!-- <Recommendations /> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -12,8 +12,7 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Recommendations from "./Recommendation.vue";
|
|
||||||
import UpNext from "../Queue/upNext.vue";
|
|
||||||
import useQStore from "../../../stores/queue";
|
import useQStore from "../../../stores/queue";
|
||||||
|
import UpNext from "../Queue/upNext.vue";
|
||||||
const queue = useQStore();
|
const queue = useQStore();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -29,14 +29,6 @@ const tabs = useTabStore();
|
|||||||
.r-sidebar {
|
.r-sidebar {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@include phone-only {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include tablet-landscape {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -44,12 +36,8 @@ const tabs = useTabStore();
|
|||||||
|
|
||||||
.r-content {
|
.r-content {
|
||||||
grid-area: content;
|
grid-area: content;
|
||||||
width: 29rem;
|
overflow: hidden;
|
||||||
// width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
// @include tablet-landscape {
|
|
||||||
// display: none;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.r-search {
|
.r-search {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="up-next">
|
<div class="up-next">
|
||||||
<div class="r-grid">
|
<div class="r-grid">
|
||||||
<UpNext :next="queue.tracklist[queue.next]" :playNext="queue.playNext" />
|
<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 class="inner">
|
<div
|
||||||
<TransitionGroup
|
class="inner"
|
||||||
name="queuelist"
|
|
||||||
@mouseenter="setMouseOver(true)"
|
@mouseenter="setMouseOver(true)"
|
||||||
@mouseleave="setMouseOver(false)"
|
@mouseleave="setMouseOver(false)"
|
||||||
>
|
>
|
||||||
|
<TransitionGroup name="queuelist">
|
||||||
<TrackItem
|
<TrackItem
|
||||||
v-for="(t, index) in queue.tracklist"
|
v-for="(t, index) in queue.tracklist"
|
||||||
:key="t.trackid"
|
:key="index"
|
||||||
:track="t"
|
:track="t"
|
||||||
@playThis="queue.play(index)"
|
@playThis="queue.play(index)"
|
||||||
:isCurrent="index === queue.current"
|
:isCurrent="index === queue.current"
|
||||||
@@ -29,13 +29,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onUpdated, ref } from "vue";
|
import { onUpdated, ref } from "vue";
|
||||||
|
|
||||||
import { focusElem } from "@/utils";
|
|
||||||
import useQStore from "@/stores/queue";
|
import useQStore from "@/stores/queue";
|
||||||
|
import { focusElem } from "@/utils";
|
||||||
|
|
||||||
import UpNext from "./Queue/upNext.vue";
|
|
||||||
import TrackItem from "../shared/TrackItem.vue";
|
import TrackItem from "../shared/TrackItem.vue";
|
||||||
import PlayingFrom from "./Queue/playingFrom.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);
|
||||||
@@ -84,13 +84,10 @@ onUpdated(() => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
grid-template-rows: max-content 1fr max-content;
|
grid-template-rows: max-content 1fr max-content;
|
||||||
gap: $small;
|
gap: $small;
|
||||||
|
|
||||||
.left {
|
|
||||||
display: flex;
|
|
||||||
gap: $small;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollable-r {
|
.scrollable-r {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ const queue = useQueueStore();
|
|||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
display: flex;
|
||||||
|
gap: $small;
|
||||||
|
}
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
padding: $smaller;
|
padding: $smaller;
|
||||||
padding-right: $small;
|
padding-right: $small;
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="main-item bg-black" @click="playNext">
|
|
||||||
<div class="h">Up Next</div>
|
|
||||||
<div class="itemx shadow">
|
|
||||||
<div
|
<div
|
||||||
class="album-art image"
|
class="next-track bg-black"
|
||||||
:style="{
|
:class="{ contexton: context_on }"
|
||||||
backgroundImage: `url("${imguri + next.image}")`,
|
@click="playNext"
|
||||||
}"
|
@contextmenu.prevent="showMenu"
|
||||||
></div>
|
>
|
||||||
|
<div class="nextup abs">next up</div>
|
||||||
|
<img :src="paths.images.thumb + track.image" class="rounded" />
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
<p class="title ellip">{{ next.title }}</p>
|
<div class="title ellip">{{ track.title }}</div>
|
||||||
<hr />
|
<div class="artist ellip">
|
||||||
<p class="artist ellip">
|
<span v-for="artist in putCommas(track.artists)" :key="artist">{{
|
||||||
<span v-for="artist in putCommas(next.artists)" :key="artist">{{
|
|
||||||
artist
|
artist
|
||||||
}}</span>
|
}}</span>
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -23,67 +20,68 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { paths } from "@/config";
|
import { paths } from "@/config";
|
||||||
import { putCommas } from "@/utils";
|
|
||||||
import { Track } from "@/interfaces";
|
import { Track } from "@/interfaces";
|
||||||
|
import { putCommas } from "@/utils";
|
||||||
|
|
||||||
const imguri = paths.images.thumb;
|
import { showTrackContextMenu as showContext } from "@/composables/context";
|
||||||
defineProps<{
|
import { ref } from "vue";
|
||||||
next: Track;
|
|
||||||
|
const props = defineProps<{
|
||||||
|
track: Track;
|
||||||
playNext: () => void;
|
playNext: () => void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const context_on = ref(false);
|
||||||
|
|
||||||
|
function showMenu(e: Event) {
|
||||||
|
showContext(e, props.track, context_on);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.main-item {
|
.next-track {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: $small;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $accent;
|
background-color: $gray4;
|
||||||
|
|
||||||
.h {
|
.h {
|
||||||
background-color: $black;
|
background-color: $black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.h {
|
.nextup {
|
||||||
position: absolute;
|
|
||||||
right: $small;
|
right: $small;
|
||||||
bottom: $small;
|
top: 0;
|
||||||
font-size: 0.9rem;
|
font-size: 0.8rem;
|
||||||
background-color: $accent;
|
|
||||||
padding: $smaller;
|
padding: $smaller;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
|
font-style: oblique;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.itemx {
|
img {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 0.75rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.album-art {
|
|
||||||
width: 4.5rem;
|
width: 4.5rem;
|
||||||
height: 4.5rem;
|
aspect-ratio: 1;
|
||||||
background-image: url(../../assets/images/null.webp);
|
object-fit: contain;
|
||||||
margin: 0 0.5rem 0 0;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
hr {
|
display: flex;
|
||||||
border: none;
|
flex-direction: column;
|
||||||
margin: 0.3rem;
|
align-items: flex-start;
|
||||||
}
|
justify-content: center;
|
||||||
.title {
|
gap: $small;
|
||||||
width: 20rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.artist {
|
.artist {
|
||||||
width: 20rem;
|
|
||||||
margin: 0;
|
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import TracksGrid from "./TracksGrid.vue";
|
|||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
padding: $medium;
|
padding: $medium;
|
||||||
|
|||||||
@@ -16,17 +16,22 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { onMounted } from "vue";
|
||||||
import useSearchStore from "../../stores/search";
|
import useSearchStore from "../../stores/search";
|
||||||
|
|
||||||
const search = useSearchStore();
|
const search = useSearchStore();
|
||||||
|
let input: HTMLInputElement;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
input = document.getElementById("ginner") as HTMLInputElement;
|
||||||
|
});
|
||||||
|
|
||||||
function focusThis() {
|
function focusThis() {
|
||||||
document.getElementById("ginner").classList.add("focused");
|
input.classList.add("focused");
|
||||||
}
|
}
|
||||||
|
|
||||||
function unfocusThis() {
|
function unfocusThis() {
|
||||||
document.getElementById("ginner").classList.remove("focused");
|
input.classList.remove("focused");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -34,11 +39,6 @@ function unfocusThis() {
|
|||||||
#gsearch-input {
|
#gsearch-input {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: max-content;
|
height: max-content;
|
||||||
// margin-bottom: $smaller;
|
|
||||||
|
|
||||||
@include tablet-landscape {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ginner {
|
#ginner {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
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()"
|
@click="() => option.action && option.action()"
|
||||||
>
|
>
|
||||||
<div class="icon image" :class="option.icon"></div>
|
<div class="icon image" :class="option.icon"></div>
|
||||||
<div class="label ellip">{{ option.label }}</div>
|
<div class="label ellip">{{ option.label }}</div>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
v-for="child in option.children"
|
v-for="child in option.children"
|
||||||
:key="child.label"
|
:key="child.label"
|
||||||
:class="[{ critical: child.critical }, child.type]"
|
:class="[{ critical: child.critical }, child.type]"
|
||||||
@click="child.action()"
|
@click="child.action && child.action()"
|
||||||
>
|
>
|
||||||
<div class="label ellip">
|
<div class="label ellip">
|
||||||
{{ child.label }}
|
{{ child.label }}
|
||||||
@@ -69,14 +69,14 @@ const context = useContextStore();
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
padding: $small;
|
padding: $small 1rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.more {
|
.more {
|
||||||
height: 1.5rem;
|
height: 1.5rem;
|
||||||
width: 1.5rem;
|
width: 1.5rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: $small;
|
||||||
background-image: url("../assets/icons/expand.svg");
|
background-image: url("../assets/icons/expand.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
]"
|
]"
|
||||||
v-bind:class="`track-${track.uniq_hash}`"
|
v-bind:class="`track-${track.uniq_hash}`"
|
||||||
@dblclick="emitUpdate(track)"
|
@dblclick="emitUpdate(track)"
|
||||||
@contextmenu="showContextMenu"
|
@contextmenu.prevent="showMenu"
|
||||||
>
|
>
|
||||||
<div class="index t-center">{{ index }}</div>
|
<div class="index t-center ellip">{{ index }}</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<div @click="emitUpdate(track)" class="thumbnail">
|
<div @click="emitUpdate(track)" class="thumbnail">
|
||||||
<img
|
<img
|
||||||
@@ -58,17 +58,12 @@
|
|||||||
{{ track.album }}
|
{{ track.album }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="song-duration">
|
<div class="song-duration">
|
||||||
<div class="text">{{ formatSeconds(track.length) }}</div>
|
<div class="text ellip">{{ formatSeconds(track.length) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="options-icon circular"
|
class="options-icon circular"
|
||||||
:class="{ options_button_clicked }"
|
:class="{ options_button_clicked }"
|
||||||
@click="
|
@click.stop="showMenu"
|
||||||
(e) => {
|
|
||||||
showContextMenu(e);
|
|
||||||
options_button_clicked = true;
|
|
||||||
}
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<OptionSvg />
|
<OptionSvg />
|
||||||
</div>
|
</div>
|
||||||
@@ -78,40 +73,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
import useModalStore from "@/stores/modal";
|
|
||||||
import useQueueStore from "@/stores/queue";
|
|
||||||
import useContextStore from "@/stores/context";
|
|
||||||
import trackContext from "@/contexts/track_context";
|
|
||||||
import OptionSvg from "@/assets/icons/more.svg";
|
import OptionSvg from "@/assets/icons/more.svg";
|
||||||
|
|
||||||
import { paths } from "@/config";
|
import { paths } from "@/config";
|
||||||
import { Track } from "@/interfaces";
|
import { Track } from "@/interfaces";
|
||||||
import { ContextSrc } from "@/composables/enums";
|
|
||||||
import { formatSeconds, putCommas } from "@/utils";
|
import { formatSeconds, putCommas } from "@/utils";
|
||||||
|
import { showTrackContextMenu as showContext } from "@/composables/context";
|
||||||
const contextStore = useContextStore();
|
|
||||||
|
|
||||||
const context_on = ref(false);
|
const context_on = ref(false);
|
||||||
const imguri = paths.images.thumb;
|
const imguri = paths.images.thumb;
|
||||||
const options_button_clicked = ref(false);
|
const options_button_clicked = ref(false);
|
||||||
|
|
||||||
const showContextMenu = (e: Event) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
const menus = trackContext(props.track, useModalStore, useQueueStore);
|
|
||||||
|
|
||||||
contextStore.showContextMenu(e, menus, ContextSrc.Track);
|
|
||||||
context_on.value = true;
|
|
||||||
|
|
||||||
contextStore.$subscribe((mutation, state) => {
|
|
||||||
if (!state.visible) {
|
|
||||||
context_on.value = false;
|
|
||||||
options_button_clicked.value = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
track: Track;
|
track: Track;
|
||||||
index?: number;
|
index?: number;
|
||||||
@@ -127,20 +99,32 @@ const emit = defineEmits<{
|
|||||||
function emitUpdate(track: Track) {
|
function emitUpdate(track: Track) {
|
||||||
emit("updateQueue", track);
|
emit("updateQueue", track);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showMenu(e: Event) {
|
||||||
|
showContext(e, props.track, options_button_clicked);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.songlist-item {
|
.songlist-item {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
|
||||||
grid-template-columns: 1.5rem 1.5fr 1fr 1.5fr 2rem 2.5rem;
|
grid-template-columns: 1.5rem 1.5fr 1fr 1.5fr 2rem 2.5rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: flex-start;
|
||||||
height: 3.75rem;
|
height: 3.75rem;
|
||||||
text-align: left;
|
gap: 1rem;
|
||||||
gap: $small;
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
@include tablet-landscape {
|
@include for-desktop-down {
|
||||||
grid-template-columns: 1.5rem 1.5fr 1fr 1fr 2.5rem;
|
grid-template-columns: 1.5rem 1.5fr 1fr 2.5rem;
|
||||||
|
|
||||||
|
.song-album {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.song-duration {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include tablet-portrait {
|
@include tablet-portrait {
|
||||||
@@ -155,12 +139,6 @@ function emitUpdate(track: Track) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-duration {
|
|
||||||
@include tablet-landscape {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.song-album {
|
.song-album {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
max-width: max-content;
|
max-width: max-content;
|
||||||
@@ -169,10 +147,6 @@ function emitUpdate(track: Track) {
|
|||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include tablet-portrait {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-artists {
|
.song-artists {
|
||||||
@@ -181,26 +155,17 @@ function emitUpdate(track: Track) {
|
|||||||
.artist {
|
.artist {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include phone-only {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.index {
|
.index {
|
||||||
color: grey;
|
opacity: 0.5;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
width: 2rem;
|
width: 100%;
|
||||||
|
margin-left: $small;
|
||||||
@include phone-only {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.song-duration {
|
.song-duration {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
width: 5rem !important;
|
|
||||||
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +176,6 @@ function emitUpdate(track: Track) {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
margin-right: 1rem;
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
transition: all 0.2s ease-in;
|
transition: all 0.2s ease-in;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
{ contexton: context_on },
|
{ contexton: context_on },
|
||||||
]"
|
]"
|
||||||
@contextmenu="showContextMenu"
|
@contextmenu.prevent="showMenu"
|
||||||
>
|
>
|
||||||
<div class="album-art">
|
<div class="album-art">
|
||||||
<img :src="paths.images.thumb + track.image" alt="" class="rounded" />
|
<img :src="paths.images.thumb + track.image" alt="" class="rounded" />
|
||||||
@@ -35,18 +35,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
import useModalStore from "@/stores/modal";
|
|
||||||
import useQueueStore from "@/stores/queue";
|
|
||||||
import useContextStore from "@/stores/context";
|
|
||||||
import trackContext from "@/contexts/track_context";
|
|
||||||
|
|
||||||
import { paths } from "@/config";
|
import { paths } from "@/config";
|
||||||
import { putCommas } from "@/utils";
|
import { putCommas } from "@/utils";
|
||||||
import { Track } from "@/interfaces";
|
import { Track } from "@/interfaces";
|
||||||
import { ContextSrc } from "@/composables/enums";
|
import { showTrackContextMenu as showContext } from "@/composables/context";
|
||||||
|
|
||||||
const contextStore = useContextStore();
|
|
||||||
const imguri = paths.images.thumb;
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
track: Track;
|
track: Track;
|
||||||
@@ -56,21 +48,9 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const context_on = ref(false);
|
const context_on = ref(false);
|
||||||
|
|
||||||
const showContextMenu = (e: Event) => {
|
function showMenu(e: Event) {
|
||||||
e.preventDefault();
|
showContext(e, props.track, context_on);
|
||||||
e.stopPropagation();
|
}
|
||||||
|
|
||||||
const menus = trackContext(props.track, useModalStore, useQueueStore);
|
|
||||||
|
|
||||||
contextStore.showContextMenu(e, menus, ContextSrc.Track);
|
|
||||||
context_on.value = true;
|
|
||||||
|
|
||||||
contextStore.$subscribe((mutation, state) => {
|
|
||||||
if (!state.visible) {
|
|
||||||
context_on.value = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "PlayThis"): void;
|
(e: "PlayThis"): void;
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Ref } from "vue";
|
||||||
|
|
||||||
|
import useModalStore from "@/stores/modal";
|
||||||
|
import useQueueStore from "@/stores/queue";
|
||||||
|
import useContextStore from "@/stores/context";
|
||||||
|
|
||||||
|
import { ContextSrc } from "./enums";
|
||||||
|
import { Track } from "@/interfaces";
|
||||||
|
import trackContext from "@/contexts/track_context";
|
||||||
|
|
||||||
|
export const showTrackContextMenu = (
|
||||||
|
e: Event,
|
||||||
|
track: Track,
|
||||||
|
flag: Ref<boolean>
|
||||||
|
) => {
|
||||||
|
const menu = useContextStore();
|
||||||
|
|
||||||
|
const options = trackContext(track, useModalStore, useQueueStore);
|
||||||
|
|
||||||
|
menu.showContextMenu(e, options, ContextSrc.Track);
|
||||||
|
flag.value = true;
|
||||||
|
|
||||||
|
menu.$subscribe((mutation, state) => {
|
||||||
|
if (!state.visible) {
|
||||||
|
flag.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -23,7 +23,7 @@ export default defineStore("context-menu", {
|
|||||||
y: 500,
|
y: 500,
|
||||||
normalizedX: false,
|
normalizedX: false,
|
||||||
normalizedY: false,
|
normalizedY: false,
|
||||||
src: "",
|
src: <null | string>"",
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
showContextMenu(
|
showContextMenu(
|
||||||
@@ -41,13 +41,13 @@ export default defineStore("context-menu", {
|
|||||||
this.options = options;
|
this.options = options;
|
||||||
});
|
});
|
||||||
|
|
||||||
const yo = normalize(e.clientX, e.clientY);
|
const xy = normalize(e.clientX, e.clientY);
|
||||||
|
|
||||||
this.x = yo.normalX;
|
this.x = xy.normalX;
|
||||||
this.y = yo.normalY;
|
this.y = xy.normalY;
|
||||||
|
|
||||||
this.normalizedX = yo.normalizedX;
|
this.normalizedX = xy.normalizedX;
|
||||||
this.normalizedY = yo.normalizedY;
|
this.normalizedY = xy.normalizedY;
|
||||||
this.src = src;
|
this.src = src;
|
||||||
},
|
},
|
||||||
hideContextMenu() {
|
hideContextMenu() {
|
||||||
|
|||||||
@@ -27,6 +27,14 @@ const pStore = usePStore();
|
|||||||
.grid {
|
.grid {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
|
@include for-desktop-down {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
- Removing track from queue
|
||||||
|
|
||||||
|
|
||||||
|
- Removing track from playlist
|
||||||
|
- Store album bio to db
|
||||||
|
- Saving queue as playlist
|
||||||
|
- Queue context menu
|
||||||
|
- Play full folder without using the play button
|
||||||
|
- Multi select track and do something
|
||||||
|
- Use virtual scroll on queue list
|
||||||
|
- Fix play next bug
|
||||||
|
- Play full folder as next
|
||||||
|
- Append full folder to queue
|
||||||
|
- Deleting a playlist
|
||||||
|
- Removing playlist image
|
||||||
|
- using square image on playlist
|
||||||
|
- Use bottom bar
|
||||||
|
- Fix search album and artist grid on 720p
|
||||||
+1
-1
@@ -14,7 +14,7 @@ export default defineConfig({
|
|||||||
css: {
|
css: {
|
||||||
preprocessorOptions: {
|
preprocessorOptions: {
|
||||||
scss: {
|
scss: {
|
||||||
additionalData: `@import "@/assets/scss/_variables.scss";`,
|
additionalData: `@import "@/assets/scss/_variables.scss", "@/assets/scss/_mixins.scss";`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user