convert songlist table to grid

This commit is contained in:
geoffrey45
2022-03-13 08:58:28 +03:00
parent 658e7cdbb7
commit 092d967f49
7 changed files with 164 additions and 194 deletions
+12 -12
View File
@@ -1,6 +1,6 @@
<template>
<div class="album-h" @contextmenu="hideShowContext">
<ContextMenu />
<div class="album-h" @contextmenu="hideShowContext" id="album-h">
<!-- <ContextMenu /> -->
<div class="a-header">
<div
class="image art shadow-lg"
@@ -35,11 +35,11 @@
<script setup>
import state from "@/composables/state.js";
import perks from "@/composables/perks.js";
import ContextMenu from "../contextMenu.vue";
import { reactive, ref } from "vue";
import useContextStore from "@/stores/context.js";
// import ContextMenu from "../contextMenu.vue";
// import { reactive, ref } from "vue";
// import useContextStore from "@/stores/context.js";
const contextStore = useContextStore();
// const contextStore = useContextStore();
const props = defineProps({
album_info: {
@@ -48,14 +48,14 @@ const props = defineProps({
},
});
const hideShowContext = (e) => {
e.preventDefault();
e.stopPropagation();
// const hideShowContext = (e) => {
// e.preventDefault();
// e.stopPropagation();
contextStore.showContextMenu(e);
};
// contextStore.showContextMenu(e);
// };
const context_hide = ref(true);
// const context_hide = ref(true);
function playAlbum() {
perks.updateQueue(state.album.tracklist[0], "album");
+47 -58
View File
@@ -1,27 +1,23 @@
<template>
<div class="folder">
<div class="table rounded" v-if="props.songs.length">
<table>
<thead>
<tr>
<th class="index"></th>
<th class="track-header">Track</th>
<th class="artists-header">Artist</th>
<th class="album-header">Album</th>
<th class="duration-header">Duration</th>
</tr>
</thead>
<tbody>
<SongItem
v-for="(song, index) in props.songs"
:key="song"
:song="song"
:index="index + 1"
@updateQueue="updateQueue"
@loadAlbum="loadAlbum"
/>
</tbody>
</table>
<div class="thead">
<div class="index"></div>
<div class="track-header">Track</div>
<div class="artists-header">Artist</div>
<div class="album-header">Album</div>
<div class="duration-header">Duration</div>
</div>
<div>
<SongItem
v-for="(song, index) in props.songs"
:key="song"
:song="song"
:index="index + 1"
@updateQueue="updateQueue"
@loadAlbum="loadAlbum"
/>
</div>
</div>
<div v-else-if="props.songs.length === 0 && search_query">
<div class="no-results">
@@ -92,46 +88,29 @@ function loadAlbum(title, albumartist) {
height: 100%;
overflow-y: auto;
&::-webkit-scrollbar {
display: none;
}
.current * {
color: $highlight-blue;
}
.current:hover {
* {
color: rgb(255, 255, 255);
}
}
}
table {
border-collapse: collapse;
text-transform: capitalize;
width: 100%;
table-layout: fixed;
@include phone-only {
border-collapse: separate;
border-spacing: 0 $small;
}
thead {
height: 2rem;
.thead {
display: grid;
grid-template-columns: 1.5rem 1.5fr 1fr 1.5fr 0.25fr;
height: 2.5rem;
align-items: center;
text-transform: uppercase;
font-weight: bold;
color: $gray1;
gap: $small;
@include tablet-landscape {
grid-template-columns: 1.5rem 1.5fr 1fr 1.5fr;
}
@include tablet-portrait {
grid-template-columns: 1.5rem 1.5fr 1fr;
}
@include phone-only {
display: none;
}
th {
text-align: left;
padding-left: $small;
}
th.duration-header {
.duration-header {
@include tablet-landscape {
display: none;
}
@@ -139,14 +118,24 @@ table {
width: 6rem;
}
th.album-header {
.album-header {
@include tablet-portrait {
display: none;
}
}
th.index {
width: 2rem;
&::-webkit-scrollbar {
display: none;
}
.current * {
color: $highlight-blue;
}
.current:hover {
* {
color: rgb(255, 255, 255);
}
}
}
}
@@ -48,6 +48,10 @@ function removeLastFilter() {
padding: 0 $small;
display: flex;
@include tablet-landscape {
display: none;
}
.input-loader {
width: 100%;
border-radius: 0.4rem;
+9 -4
View File
@@ -1,8 +1,8 @@
<template>
<!-- v-show="context.visible" -->
<div
class="context-menu rounded"
:class="{ 'context-menu-visible': context.visible }"
v-show="context.visible"
:style="{
left: context.x + 'px',
top: context.y + 'px',
@@ -84,6 +84,10 @@ const options = [
flex-direction: column;
justify-content: center;
align-items: center;
// transform: scale(0);
transform-origin: top left;
display: none;
.context-item {
width: 100%;
@@ -116,7 +120,8 @@ const options = [
}
}
// .visible {
// display: unset;
// }
.context-menu-visible {
transform: scale(1);
transition: transform .2s ease-in-out;
}
</style>
+61 -93
View File
@@ -1,11 +1,11 @@
<template>
<tr
class="songlist-item"
<div
class="songlist-item rounded"
:class="{ current: current.trackid === song.trackid }"
@dblclick="emitUpdate(song)"
>
<td class="index">{{ index }}</td>
<td class="flex">
<div class="index">{{ index }}</div>
<div class="flex">
<div
class="album-art image rounded"
:style="{ backgroundImage: `url(&quot;${song.image}&quot;` }"
@@ -25,8 +25,8 @@
</span>
</div>
</div>
</td>
<td class="song-artists">
</div>
<div class="song-artists">
<div class="ellip" v-if="song.artists[0] !== ''">
<span
class="artist"
@@ -38,22 +38,24 @@
<div class="ellip" v-else>
<span class="artist">{{ song.albumartist }}</span>
</div>
</td>
<td class="song-album">
</div>
<div class="song-album">
<div
class="album ellip"
@click="emitLoadAlbum(song.album, song.albumartist)"
>
{{ song.album }}
</div>
</td>
<td class="song-duration">{{ formatSeconds(song.length) }}</td>
</tr>
</div>
<div class="song-duration">{{ formatSeconds(song.length) }}</div>
<ContextMenu />
</div>
</template>
<script>
import perks from "@/composables/perks.js";
import state from "@/composables/state.js";
import ContextMenu from "../contextMenu.vue";
export default {
props: ["song", "index"],
@@ -62,11 +64,9 @@ export default {
function emitUpdate(song) {
emit("updateQueue", song);
}
function emitLoadAlbum(title, artist) {
emit("loadAlbum", title, artist);
}
return {
putCommas: perks.putCommas,
emitUpdate,
@@ -76,11 +76,58 @@ export default {
formatSeconds: perks.formatSeconds,
};
},
components: { ContextMenu },
};
</script>
<style lang="scss">
.songlist-item {
display: grid;
align-items: center;
grid-template-columns: 1.5rem 1.5fr 1fr 1.5fr 0.25fr;
height: 3.75rem;
text-align: left;
gap: $small;
@include tablet-landscape {
grid-template-columns: 1.5rem 1.5fr 1fr 1.5fr;
}
@include tablet-portrait {
grid-template-columns: 1.5rem 1.5fr 1fr;
}
&:hover {
background-color: $gray;
}
.song-duration {
@include tablet-landscape {
display: none;
}
}
.song-album {
.album {
cursor: pointer;
max-width: max-content;
}
@include tablet-portrait {
display: none;
}
}
.song-artists {
.artist {
cursor: pointer;
}
@include phone-only {
display: none;
}
}
.index {
color: grey;
font-size: 0.8rem;
@@ -92,17 +139,8 @@ export default {
}
}
@include phone-only {
width: 100%;
td {
background-color: #14161a;
border-radius: $small;
}
}
.song-duration {
font-size: 0.8rem;
font-size: 0.9rem;
width: 5rem !important;
}
@@ -154,75 +192,5 @@ export default {
border-radius: $small;
}
}
&:hover {
* {
color: #fff;
}
& {
& td {
background-color: #3131313b;
}
td:first-child {
border-radius: $small 0 0 $small;
}
td:nth-child(2) {
border-radius: 0;
@include phone-only {
border-radius: $small;
}
}
td:nth-child(3) {
@include tablet-portrait {
border-radius: 0 $small $small 0 !important;
}
@include tablet-landscape {
border-radius: 0;
}
}
& > td:nth-child(4) {
@include tablet-landscape {
border-radius: 0 $small $small 0 !important;
}
}
& td:last-child {
border-radius: 0 $small $small 0;
}
}
}
.song-duration {
@include tablet-landscape {
display: none;
}
}
.song-album {
.album {
cursor: pointer;
max-width: max-content;
}
@include tablet-portrait {
display: none;
}
}
.song-artists {
.artist {
cursor: pointer;
}
@include phone-only {
display: none;
}
}
}
</style>
+26 -23
View File
@@ -1,34 +1,37 @@
import perks from "./perks";
export default function normalizeContextMenu(x, y) {
const app_dom = perks.getElem("app", "id");
const context_menu = perks.getElem("context-menu-visible", "class");
export default (mouseX, mouseY) => {
const scope = perks.getElem("app", "id");
const contextMenu = perks.getElem("context-menu", "class");
// ? compute what is the mouse position relative to the container element (scope)
let { left: scopeOffsetX, top: scopeOffsetY } = scope.getBoundingClientRect();
const { left: scopeOffsetX, top: scopeOffsetY } =
app_dom.getBoundingClientRect();
scopeOffsetX = scopeOffsetX < 0 ? 0 : scopeOffsetX;
scopeOffsetY = scopeOffsetY < 0 ? 0 : scopeOffsetY;
const scopeX = x - scopeOffsetX;
const scopeY = y - scopeOffsetY;
const scopeX = mouseX - scopeOffsetX;
const scopeY = mouseY - scopeOffsetY;
const outOfBoundsX = scopeX + context_menu.clientHeight > app_dom.clientWidth;
const outOfBoundsY =
scopeY + context_menu.clientHeight > app_dom.clientHeight;
// ? check if the element will go out of bounds
const outOfBoundsOnX = scopeX + contextMenu.clientWidth > scope.clientWidth;
let normalizedX = x;
let normalizedY = y;
const outOfBoundsOnY = scopeY + contextMenu.clientHeight > scope.clientHeight;
if (outOfBoundsX) {
normalizedX =
scopeOffsetX + app_dom.clientWidth - context_menu.clientHeight;
let normalizedX = mouseX;
let normalizedY = mouseY;
// console.log(mouseX, mouseY);
// ? normalize on X
if (outOfBoundsOnX) {
normalizedX = scopeOffsetX + scope.clientWidth - contextMenu.clientWidth;
}
if (outOfBoundsY) {
normalizedY =
scopeOffsetY + app_dom.clientHeight - context_menu.clientHeight;
// ? normalize on Y
if (outOfBoundsOnY) {
normalizedY = scopeOffsetY + scope.clientHeight - contextMenu.clientHeight;
}
return {
normalizedX,
normalizedY,
};
}
console.log(normalizedX, normalizedY);
return { normalizedX, normalizedY };
};
+5 -4
View File
@@ -1,5 +1,5 @@
import { defineStore } from "pinia";
import normalizeContextMenu from "../composables/normalizeContextMenu";
import normalize from "../composables/normalizeContextMenu";
export default defineStore("context-menu", {
state: () => ({
@@ -10,9 +10,10 @@ export default defineStore("context-menu", {
actions: {
showContextMenu(e) {
this.visible = true;
const { normalX, normalY } = normalizeContextMenu(e.clientX, e.clientY);
this.x = normalX;
this.y = normalY;
const yo = normalize(e.clientX, e.clientY);
this.x = yo.normalizedX;
this.y = yo.normalizedY;
console.log(yo);
},
hideContextMenu() {
this.visible = false;