Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
293e95c5b0 | ||
|
|
302095ef80 | ||
|
|
feb35afa9a | ||
|
|
6b925a0434 | ||
|
|
4211ccc685 | ||
|
|
9c982283fb | ||
|
|
888958db54 | ||
|
|
cb3e9ec2cd | ||
|
|
f7e604f11b | ||
|
|
1b36b96239 | ||
|
|
ee3ef6c1e6 | ||
|
|
83bd063bea | ||
|
|
12531edec9 | ||
|
|
7edd501d0f | ||
|
|
67ad2ef206 |
39
src/App.vue
@@ -6,15 +6,25 @@
|
||||
<section
|
||||
id="app-grid"
|
||||
:class="{
|
||||
noSidebar: !settings.use_sidebar || !xl,
|
||||
NoSideBorders: !xxl,
|
||||
useSidebar: settings.use_sidebar && xl,
|
||||
NoSideBorders: settings.is_alt_layout || !xxl,
|
||||
extendWidth: settings.extend_width && settings.can_extend_width,
|
||||
is_alt_layout: settings.is_alt_layout,
|
||||
}"
|
||||
:style="{
|
||||
maxWidth: `${
|
||||
settings.is_default_layout
|
||||
? content_height > 1080
|
||||
? '2220px'
|
||||
: '1760px'
|
||||
: ''
|
||||
}`,
|
||||
}"
|
||||
:style="{ maxWidth: `${content_height > 1080 ? '2220px' : '1760px'}` }"
|
||||
>
|
||||
<LeftSidebar v-if="!isMobile" />
|
||||
<LeftSidebar v-if="settings.is_default_layout && !isMobile" />
|
||||
<NavBar />
|
||||
<div id="acontent" ref="appcontent" v-element-size="updateContentElemSize">
|
||||
<div id="acontent" v-element-size="updateContentElemSize">
|
||||
<div id="contentresizer" ref="appcontent"></div>
|
||||
<BalancerProvider>
|
||||
<RouterView />
|
||||
</BalancerProvider>
|
||||
@@ -101,8 +111,9 @@ function updateContentElemSize({
|
||||
width: number;
|
||||
height: number;
|
||||
}) {
|
||||
// 1372 is the maxwidth of the #acontent. see app-grid.scss > $maxwidth
|
||||
const elem_width = Math.min(1372, appcontent.value?.offsetWidth || 0);
|
||||
// 1572 is the maxwidth of the #acontent. see app-grid.scss > $maxwidth
|
||||
const elem_width = appcontent.value?.offsetWidth || 0;
|
||||
|
||||
content_width.value = elem_width;
|
||||
content_height.value = height;
|
||||
updateCardWidth();
|
||||
@@ -145,15 +156,13 @@ onMounted(() => {
|
||||
settings.mapDbSettings(data);
|
||||
})
|
||||
.then(() => {
|
||||
if (settings.use_lyrics_plugin) return;
|
||||
if (queue.currenttrack && !settings.use_lyrics_plugin) {
|
||||
lyrics.checkExists(
|
||||
queue.currenttrack.filepath,
|
||||
queue.currenttrack.trackhash
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (queue.currenttrack) {
|
||||
lyrics.checkExists(
|
||||
queue.currenttrack.filepath,
|
||||
queue.currenttrack.trackhash
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 13.8477C16.127 13.8477 17.8496 11.9668 17.8496 9.66406C17.8496 7.39648 16.127 5.59473 14 5.59473C11.8818 5.59473 10.1416 7.42285 10.1504 9.68164C10.1592 11.9756 11.873 13.8477 14 13.8477ZM14 12.3096C12.7871 12.3096 11.7588 11.1582 11.7588 9.68164C11.75 8.24023 12.7783 7.13281 14 7.13281C15.2305 7.13281 16.2412 8.22266 16.2412 9.66406C16.2412 11.1406 15.2217 12.3096 14 12.3096ZM8.51562 22.0215H19.4756C20.9961 22.0215 21.7256 21.5381 21.7256 20.501C21.7256 18.084 18.7109 14.8672 14 14.8672C9.28906 14.8672 6.26562 18.084 6.26562 20.501C6.26562 21.5381 6.99512 22.0215 8.51562 22.0215ZM8.24316 20.4834C8.03223 20.4834 7.95312 20.4131 7.95312 20.2549C7.95312 18.9102 10.124 16.4053 14 16.4053C17.8672 16.4053 20.0381 18.9102 20.0381 20.2549C20.0381 20.4131 19.959 20.4834 19.748 20.4834H8.24316Z" fill="#F2F2F2"/>
|
||||
<path d="M14 13.8477C16.127 13.8477 17.8496 11.9668 17.8496 9.66406C17.8496 7.39648 16.127 5.59473 14 5.59473C11.8818 5.59473 10.1416 7.42285 10.1504 9.68164C10.1592 11.9756 11.873 13.8477 14 13.8477ZM14 12.3096C12.7871 12.3096 11.7588 11.1582 11.7588 9.68164C11.75 8.24023 12.7783 7.13281 14 7.13281C15.2305 7.13281 16.2412 8.22266 16.2412 9.66406C16.2412 11.1406 15.2217 12.3096 14 12.3096ZM8.51562 22.0215H19.4756C20.9961 22.0215 21.7256 21.5381 21.7256 20.501C21.7256 18.084 18.7109 14.8672 14 14.8672C9.28906 14.8672 6.26562 18.084 6.26562 20.501C6.26562 21.5381 6.99512 22.0215 8.51562 22.0215ZM8.24316 20.4834C8.03223 20.4834 7.95312 20.4131 7.95312 20.2549C7.95312 18.9102 10.124 16.4053 14 16.4053C17.8672 16.4053 20.0381 18.9102 20.0381 20.2549C20.0381 20.4131 19.959 20.4834 19.748 20.4834H8.24316Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 931 B After Width: | Height: | Size: 936 B |
3
src/assets/icons/avatar.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.10515 24.5753H21.6977C23.0148 24.5753 23.8007 23.9513 23.8007 22.9154C23.8007 19.9004 19.9776 15.7538 13.8955 15.7538C7.82523 15.7538 4 19.9004 4 22.9154C4 23.9513 4.78797 24.5753 6.10515 24.5753ZM13.9052 13.6731C16.4594 13.6731 18.6327 11.4019 18.6327 8.46591C18.6327 5.58403 16.453 3.39099 13.9052 3.39099C11.3573 3.39099 9.17547 5.62458 9.17969 8.48724C9.1818 11.4019 11.3434 13.6731 13.9052 13.6731Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 542 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 1.875C7.75195 1.875 1.875 7.75195 1.875 15C1.875 22.248 7.75195 28.125 15 28.125C22.248 28.125 28.125 22.248 28.125 15C28.125 7.75195 22.248 1.875 15 1.875ZM15 25.8984C8.98242 25.8984 4.10156 21.0176 4.10156 15C4.10156 8.98242 8.98242 4.10156 15 4.10156C21.0176 4.10156 25.8984 8.98242 25.8984 15C25.8984 21.0176 21.0176 25.8984 15 25.8984Z" fill="white"/>
|
||||
<path d="M13.5938 9.84375C13.5938 10.2167 13.7419 10.5744 14.0056 10.8381C14.2694 11.1018 14.627 11.25 15 11.25C15.373 11.25 15.7306 11.1018 15.9944 10.8381C16.2581 10.5744 16.4062 10.2167 16.4062 9.84375C16.4062 9.47079 16.2581 9.1131 15.9944 8.84938C15.7306 8.58566 15.373 8.4375 15 8.4375C14.627 8.4375 14.2694 8.58566 14.0056 8.84938C13.7419 9.1131 13.5938 9.47079 13.5938 9.84375V9.84375ZM15.7031 13.125H14.2969C14.168 13.125 14.0625 13.2305 14.0625 13.3594V21.3281C14.0625 21.457 14.168 21.5625 14.2969 21.5625H15.7031C15.832 21.5625 15.9375 21.457 15.9375 21.3281V13.3594C15.9375 13.2305 15.832 13.125 15.7031 13.125Z" fill="white"/>
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 1.875C7.75195 1.875 1.875 7.75195 1.875 15C1.875 22.248 7.75195 28.125 15 28.125C22.248 28.125 28.125 22.248 28.125 15C28.125 7.75195 22.248 1.875 15 1.875ZM15 25.8984C8.98242 25.8984 4.10156 21.0176 4.10156 15C4.10156 8.98242 8.98242 4.10156 15 4.10156C21.0176 4.10156 25.8984 8.98242 25.8984 15C25.8984 21.0176 21.0176 25.8984 15 25.8984Z" fill="currentColor"/>
|
||||
<path d="M13.5938 9.84375C13.5938 10.2167 13.7419 10.5744 14.0056 10.8381C14.2694 11.1018 14.627 11.25 15 11.25C15.373 11.25 15.7306 11.1018 15.9944 10.8381C16.2581 10.5744 16.4062 10.2167 16.4062 9.84375C16.4062 9.47079 16.2581 9.1131 15.9944 8.84938C15.7306 8.58566 15.373 8.4375 15 8.4375C14.627 8.4375 14.2694 8.58566 14.0056 8.84938C13.7419 9.1131 13.5938 9.47079 13.5938 9.84375V9.84375ZM15.7031 13.125H14.2969C14.168 13.125 14.0625 13.2305 14.0625 13.3594V21.3281C14.0625 21.457 14.168 21.5625 14.2969 21.5625H15.7031C15.832 21.5625 15.9375 21.457 15.9375 21.3281V13.3594C15.9375 13.2305 15.832 13.125 15.7031 13.125Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 28 28" width="28px" height="28px">
|
||||
<g id="surface79432257">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 14 5.214844 C 13.210938 5.214844 12.5 5.625 12.105469 6.308594 L 4.253906 19.90625 C 3.859375 20.589844 3.859375 21.410156 4.253906 22.09375 C 4.652344 22.777344 5.359375 23.1875 6.148438 23.1875 L 21.851562 23.1875 C 22.640625 23.1875 23.347656 22.777344 23.746094 22.09375 C 24.140625 21.410156 24.140625 20.589844 23.746094 19.90625 L 15.894531 6.308594 C 15.5 5.625 14.789062 5.214844 14 5.214844 Z M 14 6.964844 C 14.074219 6.964844 14.265625 6.988281 14.378906 7.183594 L 22.230469 20.78125 C 22.34375 20.980469 22.269531 21.152344 22.230469 21.21875 C 22.191406 21.285156 22.078125 21.4375 21.851562 21.4375 L 6.148438 21.4375 C 5.921875 21.4375 5.808594 21.285156 5.769531 21.21875 C 5.730469 21.152344 5.65625 20.980469 5.769531 20.78125 L 13.621094 7.183594 C 13.734375 6.988281 13.925781 6.964844 14 6.964844 Z M 13.992188 10.058594 C 13.691406 10.058594 13.445312 10.152344 13.253906 10.335938 C 13.066406 10.523438 12.976562 10.753906 12.984375 11.03125 L 13.125 16.214844 C 13.140625 16.792969 13.433594 17.085938 14.007812 17.085938 C 14.5625 17.085938 14.84375 16.792969 14.851562 16.214844 L 15.015625 11.042969 C 15.023438 10.765625 14.929688 10.53125 14.730469 10.34375 C 14.535156 10.152344 14.289062 10.058594 13.992188 10.058594 Z M 14 18.238281 C 13.328125 18.238281 12.902344 18.808594 12.902344 19.289062 C 12.902344 19.769531 13.3125 20.339844 14 20.339844 C 14.6875 20.339844 15.097656 19.800781 15.097656 19.289062 C 15.097656 18.777344 14.671875 18.238281 14 18.238281 Z M 14 18.238281 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:currentColor;fill-opacity:1;" d="M 14 5.214844 C 13.210938 5.214844 12.5 5.625 12.105469 6.308594 L 4.253906 19.90625 C 3.859375 20.589844 3.859375 21.410156 4.253906 22.09375 C 4.652344 22.777344 5.359375 23.1875 6.148438 23.1875 L 21.851562 23.1875 C 22.640625 23.1875 23.347656 22.777344 23.746094 22.09375 C 24.140625 21.410156 24.140625 20.589844 23.746094 19.90625 L 15.894531 6.308594 C 15.5 5.625 14.789062 5.214844 14 5.214844 Z M 14 6.964844 C 14.074219 6.964844 14.265625 6.988281 14.378906 7.183594 L 22.230469 20.78125 C 22.34375 20.980469 22.269531 21.152344 22.230469 21.21875 C 22.191406 21.285156 22.078125 21.4375 21.851562 21.4375 L 6.148438 21.4375 C 5.921875 21.4375 5.808594 21.285156 5.769531 21.21875 C 5.730469 21.152344 5.65625 20.980469 5.769531 20.78125 L 13.621094 7.183594 C 13.734375 6.988281 13.925781 6.964844 14 6.964844 Z M 13.992188 10.058594 C 13.691406 10.058594 13.445312 10.152344 13.253906 10.335938 C 13.066406 10.523438 12.976562 10.753906 12.984375 11.03125 L 13.125 16.214844 C 13.140625 16.792969 13.433594 17.085938 14.007812 17.085938 C 14.5625 17.085938 14.84375 16.792969 14.851562 16.214844 L 15.015625 11.042969 C 15.023438 10.765625 14.929688 10.53125 14.730469 10.34375 C 14.535156 10.152344 14.289062 10.058594 13.992188 10.058594 Z M 14 18.238281 C 13.328125 18.238281 12.902344 18.808594 12.902344 19.289062 C 12.902344 19.769531 13.3125 20.339844 14 20.339844 C 14.6875 20.339844 15.097656 19.800781 15.097656 19.289062 C 15.097656 18.777344 14.671875 18.238281 14 18.238281 Z M 14 18.238281 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 28 28" width="28px" height="28px">
|
||||
<g id="surface79671233">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 14 4.375 C 8.683594 4.375 4.375 8.683594 4.375 14 C 4.375 19.316406 8.683594 23.625 14 23.625 C 19.316406 23.625 23.625 19.316406 23.625 14 C 23.625 8.683594 19.316406 4.375 14 4.375 Z M 14 6.125 C 18.347656 6.125 21.875 9.652344 21.875 14 C 21.875 18.347656 18.347656 21.875 14 21.875 C 9.652344 21.875 6.125 18.347656 6.125 14 C 6.125 9.652344 9.652344 6.125 14 6.125 Z M 14 9.1875 C 13.273438 9.1875 12.6875 9.773438 12.6875 10.5 C 12.6875 11.226562 13.273438 11.8125 14 11.8125 C 14.726562 11.8125 15.3125 11.226562 15.3125 10.5 C 15.3125 9.773438 14.726562 9.1875 14 9.1875 Z M 14 13.125 C 13.515625 13.125 13.125 13.515625 13.125 14 L 13.125 18.375 C 13.125 18.859375 13.515625 19.25 14 19.25 C 14.484375 19.25 14.875 18.859375 14.875 18.375 L 14.875 14 C 14.875 13.515625 14.484375 13.125 14 13.125 Z M 14 13.125 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:currentColor;fill-opacity:1;" d="M 14 4.375 C 8.683594 4.375 4.375 8.683594 4.375 14 C 4.375 19.316406 8.683594 23.625 14 23.625 C 19.316406 23.625 23.625 19.316406 23.625 14 C 23.625 8.683594 19.316406 4.375 14 4.375 Z M 14 6.125 C 18.347656 6.125 21.875 9.652344 21.875 14 C 21.875 18.347656 18.347656 21.875 14 21.875 C 9.652344 21.875 6.125 18.347656 6.125 14 C 6.125 9.652344 9.652344 6.125 14 6.125 Z M 14 9.1875 C 13.273438 9.1875 12.6875 9.773438 12.6875 10.5 C 12.6875 11.226562 13.273438 11.8125 14 11.8125 C 14.726562 11.8125 15.3125 11.226562 15.3125 10.5 C 15.3125 9.773438 14.726562 9.1875 14 9.1875 Z M 14 13.125 C 13.515625 13.125 13.125 13.515625 13.125 14 L 13.125 18.375 C 13.125 18.859375 13.515625 19.25 14 19.25 C 14.484375 19.25 14.875 18.859375 14.875 18.375 L 14.875 14 C 14.875 13.515625 14.484375 13.125 14 13.125 Z M 14 13.125 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 28 28" width="28px" height="28px">
|
||||
<g id="surface79451713">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 22.703125 15.355469 C 22.628906 15.773438 22.449219 16.152344 22.203125 16.476562 C 22.667969 17.058594 22.851562 17.796875 22.671875 18.554688 C 22.550781 19.082031 22.265625 19.539062 21.886719 19.886719 C 22.15625 20.265625 22.3125 20.71875 22.3125 21.203125 C 22.3125 22.542969 21.121094 23.59375 19.597656 23.59375 L 13.121094 23.613281 C 11.023438 23.613281 9.042969 22.796875 7.558594 21.316406 C 6.070312 19.828125 5.25 17.851562 5.25 15.75 C 5.25 11.828125 6.660156 10.425781 7.59375 9.5 L 7.695312 9.398438 C 7.988281 9.101562 8.527344 8.707031 9.210938 8.203125 C 10.59375 7.179688 12.914062 5.464844 13.339844 4.308594 C 13.683594 3.382812 14.109375 2.230469 15.75 2.230469 C 16.480469 2.230469 17.152344 2.683594 17.550781 3.445312 C 17.929688 4.175781 18.496094 6.132812 16.472656 9.625 L 19.757812 9.625 C 21.167969 9.625 22.3125 10.699219 22.3125 12.015625 C 22.3125 12.4375 22.191406 12.832031 21.984375 13.175781 C 22.574219 13.746094 22.851562 14.519531 22.703125 15.355469 Z M 19.597656 21.84375 C 20.121094 21.84375 20.5625 21.550781 20.5625 21.203125 C 20.5625 20.851562 20.203125 20.5625 19.757812 20.5625 L 17.9375 20.5625 C 17.453125 20.5625 17.0625 20.171875 17.0625 19.6875 C 17.0625 19.203125 17.453125 18.8125 17.9375 18.8125 L 20.226562 18.8125 C 20.609375 18.8125 20.894531 18.472656 20.96875 18.15625 C 21.015625 17.949219 21 17.679688 20.71875 17.441406 C 20.558594 17.476562 20.394531 17.5 20.226562 17.5 L 17.9375 17.5 C 17.453125 17.5 17.0625 17.109375 17.0625 16.625 C 17.0625 16.140625 17.453125 15.75 17.9375 15.75 L 20.226562 15.75 C 20.617188 15.75 20.917969 15.394531 20.980469 15.050781 C 21.046875 14.667969 20.820312 14.417969 20.542969 14.261719 C 20.25 14.355469 19.933594 14.40625 19.597656 14.40625 L 17.945312 14.4375 C 17.46875 14.4375 17.070312 14.054688 17.0625 13.578125 C 17.054688 13.09375 17.4375 12.695312 17.921875 12.6875 L 19.582031 12.65625 C 20.121094 12.65625 20.5625 12.363281 20.5625 12.015625 C 20.5625 11.664062 20.203125 11.375 19.757812 11.375 L 14.875 11.375 C 14.550781 11.375 14.25 11.195312 14.097656 10.902344 C 13.949219 10.613281 13.972656 10.265625 14.15625 10 C 16.121094 7.1875 16.417969 5.234375 16.039062 4.339844 C 15.921875 4.058594 15.777344 3.984375 15.742188 3.976562 C 15.386719 3.976562 15.328125 3.976562 14.984375 4.917969 C 14.375 6.5625 11.992188 8.324219 10.25 9.609375 C 9.675781 10.035156 9.136719 10.433594 8.929688 10.636719 L 8.828125 10.738281 C 8.015625 11.550781 7 12.554688 7 15.75 C 7 17.386719 7.636719 18.921875 8.792969 20.078125 C 9.949219 21.230469 11.488281 21.863281 13.117188 21.863281 Z M 19.597656 21.84375 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:currentColor;fill-opacity:1;" d="M 22.703125 15.355469 C 22.628906 15.773438 22.449219 16.152344 22.203125 16.476562 C 22.667969 17.058594 22.851562 17.796875 22.671875 18.554688 C 22.550781 19.082031 22.265625 19.539062 21.886719 19.886719 C 22.15625 20.265625 22.3125 20.71875 22.3125 21.203125 C 22.3125 22.542969 21.121094 23.59375 19.597656 23.59375 L 13.121094 23.613281 C 11.023438 23.613281 9.042969 22.796875 7.558594 21.316406 C 6.070312 19.828125 5.25 17.851562 5.25 15.75 C 5.25 11.828125 6.660156 10.425781 7.59375 9.5 L 7.695312 9.398438 C 7.988281 9.101562 8.527344 8.707031 9.210938 8.203125 C 10.59375 7.179688 12.914062 5.464844 13.339844 4.308594 C 13.683594 3.382812 14.109375 2.230469 15.75 2.230469 C 16.480469 2.230469 17.152344 2.683594 17.550781 3.445312 C 17.929688 4.175781 18.496094 6.132812 16.472656 9.625 L 19.757812 9.625 C 21.167969 9.625 22.3125 10.699219 22.3125 12.015625 C 22.3125 12.4375 22.191406 12.832031 21.984375 13.175781 C 22.574219 13.746094 22.851562 14.519531 22.703125 15.355469 Z M 19.597656 21.84375 C 20.121094 21.84375 20.5625 21.550781 20.5625 21.203125 C 20.5625 20.851562 20.203125 20.5625 19.757812 20.5625 L 17.9375 20.5625 C 17.453125 20.5625 17.0625 20.171875 17.0625 19.6875 C 17.0625 19.203125 17.453125 18.8125 17.9375 18.8125 L 20.226562 18.8125 C 20.609375 18.8125 20.894531 18.472656 20.96875 18.15625 C 21.015625 17.949219 21 17.679688 20.71875 17.441406 C 20.558594 17.476562 20.394531 17.5 20.226562 17.5 L 17.9375 17.5 C 17.453125 17.5 17.0625 17.109375 17.0625 16.625 C 17.0625 16.140625 17.453125 15.75 17.9375 15.75 L 20.226562 15.75 C 20.617188 15.75 20.917969 15.394531 20.980469 15.050781 C 21.046875 14.667969 20.820312 14.417969 20.542969 14.261719 C 20.25 14.355469 19.933594 14.40625 19.597656 14.40625 L 17.945312 14.4375 C 17.46875 14.4375 17.070312 14.054688 17.0625 13.578125 C 17.054688 13.09375 17.4375 12.695312 17.921875 12.6875 L 19.582031 12.65625 C 20.121094 12.65625 20.5625 12.363281 20.5625 12.015625 C 20.5625 11.664062 20.203125 11.375 19.757812 11.375 L 14.875 11.375 C 14.550781 11.375 14.25 11.195312 14.097656 10.902344 C 13.949219 10.613281 13.972656 10.265625 14.15625 10 C 16.121094 7.1875 16.417969 5.234375 16.039062 4.339844 C 15.921875 4.058594 15.777344 3.984375 15.742188 3.976562 C 15.386719 3.976562 15.328125 3.976562 14.984375 4.917969 C 14.375 6.5625 11.992188 8.324219 10.25 9.609375 C 9.675781 10.035156 9.136719 10.433594 8.929688 10.636719 L 8.828125 10.738281 C 8.015625 11.550781 7 12.554688 7 15.75 C 7 17.386719 7.636719 18.921875 8.792969 20.078125 C 9.949219 21.230469 11.488281 21.863281 13.117188 21.863281 Z M 19.597656 21.84375 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -2,21 +2,31 @@ $g-border: solid 1px $gray5;
|
||||
|
||||
#app-grid {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr 29rem;
|
||||
grid-template-rows: max-content 1fr 5rem;
|
||||
// grid-template-columns: min-content 1fr 29rem;
|
||||
grid-template-columns: min-content 1fr;
|
||||
grid-template-rows: $navheight 1fr $navheight;
|
||||
grid-template-areas:
|
||||
"l-sidebar nav r-sidebar"
|
||||
"l-sidebar content r-sidebar"
|
||||
"bottombar bottombar bottombar";
|
||||
|
||||
"l-sidebar nav"
|
||||
"l-sidebar content"
|
||||
"bottombar bottombar";
|
||||
height: 100%;
|
||||
border: $g-border;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
margin: 0 auto;
|
||||
|
||||
@include for-desktop-down {
|
||||
grid-template-columns: min-content 1fr 24rem;
|
||||
#contentresizer {
|
||||
margin: 0 $padright 0 $padleft;
|
||||
}
|
||||
|
||||
@include allPhones {
|
||||
grid-template-columns: 1fr;
|
||||
// grid-template-columns: 1fr;
|
||||
grid-template-rows: max-content 1fr 9.5rem;
|
||||
grid-template-areas:
|
||||
"nav"
|
||||
"content"
|
||||
"bottombar";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +37,18 @@ $g-border: solid 1px $gray5;
|
||||
margin-right: $margright;
|
||||
}
|
||||
|
||||
.topnav {
|
||||
grid-area: nav;
|
||||
height: $navheight;
|
||||
padding: 1rem $padleft;
|
||||
padding-right: $padright;
|
||||
}
|
||||
|
||||
.b-bar {
|
||||
grid-area: bottombar;
|
||||
border-top: $g-border;
|
||||
}
|
||||
|
||||
.content-page {
|
||||
scrollbar-gutter: stable;
|
||||
padding-left: $padleft;
|
||||
@@ -52,19 +74,91 @@ $g-border: solid 1px $gray5;
|
||||
}
|
||||
}
|
||||
|
||||
.topnav {
|
||||
grid-area: nav;
|
||||
padding: 1rem $padleft;
|
||||
padding-right: $padright;
|
||||
}
|
||||
|
||||
.b-bar {
|
||||
grid-area: bottombar;
|
||||
border-top: $g-border;
|
||||
}
|
||||
|
||||
// ====== MODIFIERS =======
|
||||
|
||||
#app-grid.is_alt_layout {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: max-content 1fr 5rem;
|
||||
grid-template-areas:
|
||||
"nav"
|
||||
"content"
|
||||
"bottombar";
|
||||
|
||||
.vue-recycle-scroller,
|
||||
.content-page,
|
||||
.topnav,
|
||||
#songlist-scroller {
|
||||
padding-left: $alt_layout_pad;
|
||||
padding-right: $alt_layout_pad;
|
||||
}
|
||||
|
||||
.b-bar,
|
||||
.search-page-top-results {
|
||||
padding: 0 $alt_layout_pad;
|
||||
}
|
||||
|
||||
#contentresizer {
|
||||
margin: 0 $alt_layout_pad;
|
||||
}
|
||||
|
||||
.topnav {
|
||||
background-color: $gray;
|
||||
}
|
||||
|
||||
.vue-recycle-scroller,
|
||||
.content-page {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.search-page-top-results {
|
||||
padding-bottom: $padbottom;
|
||||
}
|
||||
|
||||
.search-view .buttons-area {
|
||||
padding-left: $alt_layout_pad;
|
||||
}
|
||||
|
||||
.lyricsview {
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
#lyricscontent {
|
||||
padding-top: 0;
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.nolyrics {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1980px) {
|
||||
// NOTE: Styles for 1680px and below
|
||||
$alt_layout_pad: max(2rem, calc((100% - 1680px) / 2));
|
||||
|
||||
.vue-recycle-scroller,
|
||||
.content-page,
|
||||
.topnav,
|
||||
#songlist-scroller {
|
||||
padding-left: $alt_layout_pad;
|
||||
padding-right: $alt_layout_pad;
|
||||
}
|
||||
|
||||
#contentresizer {
|
||||
margin: 0 $alt_layout_pad;
|
||||
}
|
||||
|
||||
.search-view .buttons-area {
|
||||
padding-left: $alt_layout_pad;
|
||||
}
|
||||
|
||||
.b-bar,
|
||||
.search-page-top-results {
|
||||
padding: 0 $alt_layout_pad;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#app-grid.extendWidth {
|
||||
padding-right: 0;
|
||||
border-left: none;
|
||||
@@ -72,31 +166,26 @@ $g-border: solid 1px $gray5;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
#app-grid.noSidebar {
|
||||
grid-template-columns: min-content 1fr;
|
||||
#app-grid.useSidebar {
|
||||
grid-template-columns: min-content 1fr 28rem;
|
||||
grid-template-areas:
|
||||
"l-sidebar nav"
|
||||
"l-sidebar content"
|
||||
"bottombar bottombar";
|
||||
"l-sidebar nav r-sidebar"
|
||||
"l-sidebar content r-sidebar"
|
||||
"bottombar bottombar bottombar";
|
||||
|
||||
#acontent {
|
||||
margin-right: 0 !important;
|
||||
// padding-right: $medium !important;
|
||||
@include for-desktop-down {
|
||||
grid-template-columns: min-content 1fr 24rem;
|
||||
}
|
||||
|
||||
@include allPhones {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: max-content 1fr 9.5rem;
|
||||
grid-template-areas:
|
||||
"nav"
|
||||
"content"
|
||||
"bottombar";
|
||||
#acontent {
|
||||
// margin-right: 0 !important;
|
||||
// padding-right: $medium !important;
|
||||
}
|
||||
}
|
||||
|
||||
#app-grid.NoSideBorders {
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
border-right: none !important;
|
||||
border-left: none !important;
|
||||
}
|
||||
|
||||
.v-scroll-page {
|
||||
|
||||
@@ -28,7 +28,7 @@ input[type="search"]::-webkit-search-cancel-button {
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-size: 2rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ button {
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10.1rem, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -56,15 +56,26 @@ $overshoot: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
|
||||
$separator: $gray4;
|
||||
|
||||
$maxwidth: 1372px;
|
||||
$margright: 0;
|
||||
$padbottom: 4rem;
|
||||
$maxwidth: 1438px;
|
||||
$navheight: 5rem;
|
||||
$cardwidth: 10.75rem;
|
||||
$maxpadleft: 5rem;
|
||||
|
||||
|
||||
$alt_layout_pad: max(2rem, calc((100% - 1280px) / 2));
|
||||
|
||||
$maxpadright: calc(100% - $maxwidth);
|
||||
|
||||
$padbottom: 4rem;
|
||||
$padleft: clamp(2rem, $maxpadright, $maxpadleft);
|
||||
$padright: clamp(
|
||||
2rem,
|
||||
max($maxpadright, 5rem),
|
||||
calc($maxpadright + $maxpadleft)
|
||||
);
|
||||
|
||||
// 👇 fixed width with content floating to the left.
|
||||
// $padright: clamp(
|
||||
// 1rem,
|
||||
// max($maxpadright, 5rem),
|
||||
// calc($maxpadright + $maxpadleft)
|
||||
// );
|
||||
$padright: $padleft;
|
||||
|
||||
$margright: calc(0rem - $padright);
|
||||
|
||||
@@ -38,7 +38,12 @@
|
||||
class="gradient"
|
||||
:style="{
|
||||
backgroundImage: colors.bg
|
||||
? `linear-gradient(${gradientDirection}, transparent 20%,
|
||||
? `linear-gradient(${gradientDirection}, transparent ${
|
||||
isSmall
|
||||
? 60
|
||||
: gradientTransparentWidth -
|
||||
(width < 700 ? 40 : width < 900 ? 20 : 10)
|
||||
}%,
|
||||
${colors.bg} ${gradientWidth}%,
|
||||
${colors.bg} 100%)`
|
||||
: '',
|
||||
@@ -49,7 +54,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { Ref, computed, onMounted, ref } from "vue";
|
||||
import { onBeforeRouteUpdate } from "vue-router";
|
||||
import { useElementSize } from "@vueuse/core";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
@@ -60,7 +65,9 @@ import updatePageTitle from "@/utils/updatePageTitle";
|
||||
import Info from "./HeaderComponents/Info.vue";
|
||||
import useArtistStore from "@/stores/pages/artist";
|
||||
import { getShift } from "@/utils/colortools/shift";
|
||||
import { isSmall } from "@/stores/content-width";
|
||||
|
||||
const image_width_px = 450;
|
||||
const store = useArtistStore();
|
||||
const settings = useSettingsStore();
|
||||
|
||||
@@ -77,13 +84,16 @@ function updateTitle() {
|
||||
onMounted(() => updateTitle());
|
||||
onBeforeRouteUpdate(() => updateTitle());
|
||||
|
||||
const artistheader = ref(null);
|
||||
const artistheader: Ref<HTMLElement | null> = ref(null);
|
||||
const { width } = useElementSize(artistheader);
|
||||
|
||||
const gradientTransparentWidth = computed(() =>
|
||||
Math.floor((image_width_px / (width.value || 1)) * 100)
|
||||
);
|
||||
|
||||
const isSmallPhone = computed(() => width.value <= 550);
|
||||
const useCircularImage = computed(
|
||||
() =>
|
||||
!isSmallPhone.value && (settings.useCircularArtistImg || width.value >= 995)
|
||||
() => !isSmallPhone.value && settings.useCircularArtistImg
|
||||
);
|
||||
|
||||
const gradientDirection = computed(() =>
|
||||
@@ -91,7 +101,7 @@ const gradientDirection = computed(() =>
|
||||
);
|
||||
|
||||
const gradientWidth = computed(() => {
|
||||
return isSmallPhone.value ? "80" : "50";
|
||||
return isSmallPhone.value ? "80" : Math.min(gradientTransparentWidth.value, 50);
|
||||
});
|
||||
|
||||
const containerHeight = computed(() => {
|
||||
@@ -109,7 +119,7 @@ const containerHeight = computed(() => {
|
||||
|
||||
.artist-page-header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr minmax(min-content, 50%);
|
||||
grid-template-columns: 1fr 450px;
|
||||
position: relative;
|
||||
|
||||
.artist-img {
|
||||
@@ -123,7 +133,6 @@ const containerHeight = computed(() => {
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
object-position: 0% 20%;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<div
|
||||
class="artist-name"
|
||||
:class="`${useCircularImage ? 'ellip' : 'ellip2'}`"
|
||||
:title="artist.name"
|
||||
>
|
||||
{{ artist.name }}
|
||||
</div>
|
||||
@@ -77,7 +78,7 @@ defineProps<{
|
||||
.artist-name {
|
||||
font-size: 3.5rem;
|
||||
font-weight: bold;
|
||||
word-wrap: break-word;
|
||||
word-wrap: break-all;
|
||||
}
|
||||
|
||||
.stats {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div class="b-bar">
|
||||
<div
|
||||
class="b-bar"
|
||||
:style="{
|
||||
padding: `${settings.is_default_layout ? '0 1rem 0 1rem' : ''}`,
|
||||
}"
|
||||
>
|
||||
<LeftGroup @handleFav="handleFav" />
|
||||
<div class="center">
|
||||
<div v-if="!isMobile" class="with-time">
|
||||
@@ -32,6 +37,7 @@ import { isMobile } from "@/stores/content-width";
|
||||
import { formatSeconds } from "@/utils";
|
||||
|
||||
import useQStore from "@/stores/queue";
|
||||
import useSettings from "@/stores/settings";
|
||||
|
||||
import HotKeys from "@/components/LeftSidebar/NP/HotKeys.vue";
|
||||
import Progress from "@/components/LeftSidebar/NP/Progress.vue";
|
||||
@@ -41,6 +47,7 @@ import LeftGroup from "./Left.vue";
|
||||
import RightGroup from "./Right.vue";
|
||||
|
||||
const queue = useQStore();
|
||||
const settings = useSettings();
|
||||
|
||||
function handleFav() {
|
||||
favoriteHandler(
|
||||
@@ -60,7 +67,6 @@ function handleFav() {
|
||||
grid-template-columns: 1fr max-content 1fr;
|
||||
align-items: center;
|
||||
z-index: 1;
|
||||
padding: 0 1rem;
|
||||
|
||||
@include allPhones {
|
||||
grid-template-columns: 1fr;
|
||||
@@ -88,6 +94,7 @@ function handleFav() {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
// INFO: Show the progress bar when hovering over the bottom bar
|
||||
#progress::-moz-range-thumb {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
@@ -102,6 +109,11 @@ function handleFav() {
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
// INFO: Also show the expand button
|
||||
.np-image .expandicon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.with-time {
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
},
|
||||
replace: true,
|
||||
}"
|
||||
class="np-image rounded-sm no-scroll"
|
||||
>
|
||||
<img
|
||||
class="rounded-sm"
|
||||
:src="paths.images.thumb.small + queue.currenttrack?.image"
|
||||
alt=""
|
||||
/>
|
||||
<img :src="paths.images.thumb.small + queue.currenttrack?.image" alt="" />
|
||||
<div class="expandicon">
|
||||
<ExpandSvg />
|
||||
</div>
|
||||
</RouterLink>
|
||||
<div
|
||||
class="track-info"
|
||||
@@ -62,6 +62,7 @@ import HeartSvg from "../shared/HeartSvg.vue";
|
||||
import MasterFlag from "../shared/MasterFlag.vue";
|
||||
import HotKeys from "../LeftSidebar/NP/HotKeys.vue";
|
||||
import ArtistName from "@/components/shared/ArtistName.vue";
|
||||
import ExpandSvg from "@/assets/icons/expand.svg";
|
||||
|
||||
const queue = useQStore();
|
||||
const settings = useSettingsStore();
|
||||
@@ -80,12 +81,43 @@ defineEmits<{
|
||||
align-items: center;
|
||||
font-size: small;
|
||||
|
||||
a {
|
||||
font-size: small;
|
||||
.np-image {
|
||||
position: relative;
|
||||
height: 3rem;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.expandicon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(51, 51, 51, 0.575);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: all 0.2s;
|
||||
|
||||
svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.expandicon {
|
||||
transform: translateY(-$medium);
|
||||
height: 130%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
height: 3rem;
|
||||
a {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.heart-button {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="right-group">
|
||||
<LyricsButton v-if="settings.use_lyrics_plugin || lyrics.exists" />
|
||||
<LyricsButton />
|
||||
<Volume />
|
||||
<button
|
||||
class="repeat"
|
||||
@@ -32,7 +32,6 @@
|
||||
<script setup lang="ts">
|
||||
import useQueue from "@/stores/queue";
|
||||
import useSettings from "@/stores/settings";
|
||||
import useLyrics from "@/stores/lyrics";
|
||||
|
||||
import Volume from "./Volume.vue";
|
||||
import HeartSvg from "../shared/HeartSvg.vue";
|
||||
@@ -42,7 +41,6 @@ import RepeatOneSvg from "@/assets/icons/repeat-one.svg";
|
||||
import ShuffleSvg from "@/assets/icons/shuffle.svg";
|
||||
|
||||
const queue = useQueue();
|
||||
const lyrics = useLyrics();
|
||||
const settings = useSettings();
|
||||
|
||||
defineProps<{
|
||||
@@ -90,5 +88,9 @@ defineEmits<{
|
||||
opacity: 0.25;
|
||||
}
|
||||
}
|
||||
|
||||
.heart-button {
|
||||
border: solid 1px $gray4 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -34,7 +34,6 @@ const changeVolume = (event: Event) => {
|
||||
};
|
||||
|
||||
const handleMouseWheel = (event: WheelEvent) => {
|
||||
event.preventDefault();
|
||||
const delta = event.deltaY / 1000;
|
||||
let newVolume = settings.volume - delta / 3;
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ context.$subscribe((mutation, state) => {
|
||||
transform: scale(0);
|
||||
height: min-content;
|
||||
|
||||
padding: $medium;
|
||||
padding: $small;
|
||||
background: $context;
|
||||
transform-origin: top left;
|
||||
font-size: 0.875rem;
|
||||
|
||||
@@ -147,7 +147,7 @@ function runChildAction(action: () => void) {
|
||||
transform: scale(0);
|
||||
background-color: $context;
|
||||
transform: scale(0);
|
||||
padding: $medium;
|
||||
padding: $small;
|
||||
border: solid 1px $gray3;
|
||||
|
||||
max-height: calc(100vh / 2);
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RecentFavResult } from "@/interfaces";
|
||||
import { paths } from "../../config";
|
||||
import { Routes } from "@/router";
|
||||
import { paths } from "../../config";
|
||||
|
||||
defineProps<{
|
||||
fav: RecentFavResult;
|
||||
@@ -45,7 +45,7 @@ defineProps<{
|
||||
|
||||
<style lang="scss">
|
||||
.recent-fav-item {
|
||||
flex: 0 0 10.1rem;
|
||||
flex: 0 0 $cardwidth;
|
||||
|
||||
padding: $medium;
|
||||
height: 100%;
|
||||
|
||||
@@ -14,9 +14,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onUpdated } from "vue";
|
||||
|
||||
import { subPath } from "@/interfaces";
|
||||
import { focusElemByClass } from "@/utils";
|
||||
import { onUpdated } from "vue";
|
||||
|
||||
import useSettings from "@/stores/settings";
|
||||
|
||||
const settings = useSettings();
|
||||
|
||||
defineProps<{
|
||||
subPaths: subPath[];
|
||||
@@ -27,7 +32,9 @@ defineEmits<{
|
||||
}>();
|
||||
|
||||
onUpdated(() => {
|
||||
focusElemByClass("inthisfolder");
|
||||
if (settings.is_default_layout) {
|
||||
focusElemByClass("inthisfolder");
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<div class="info">
|
||||
<div class="f-item-text ellip">{{ folder.name }}</div>
|
||||
<div v-if="folder.count" class="f-count">
|
||||
{{ folder.count + ` File${folder.count == 1 ? "" : "s"}` }}
|
||||
{{ folder.count.toLocaleString() + ` File${folder.count == 1 ? "" : "s"}` }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!folder_page" class="check">
|
||||
|
||||
@@ -35,6 +35,22 @@ const browselist = [
|
||||
title: "Artists",
|
||||
route: Routes.ArtistList,
|
||||
},
|
||||
{
|
||||
title: "Playlists",
|
||||
route: Routes.playlists,
|
||||
},
|
||||
{
|
||||
title: "Favorites",
|
||||
route: Routes.favorites,
|
||||
},
|
||||
{
|
||||
title: "Favorite Tracks",
|
||||
route: Routes.favoriteTracks,
|
||||
},
|
||||
{
|
||||
title: "Favorite Artists",
|
||||
route: Routes.favoriteArtists,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
|
||||
66
src/components/HomeView/Numbers.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div class="home-count">
|
||||
<div
|
||||
class="count-item"
|
||||
:title="count.count.toLocaleString()"
|
||||
v-for="count in parseCount()"
|
||||
:key="count.text"
|
||||
>
|
||||
<div>{{ count.formatted }}</div>
|
||||
<div class="text">{{ count.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
counts: { [key: string]: number }[];
|
||||
}>();
|
||||
|
||||
function formatNumber(num: number) {
|
||||
if (num >= 1e6) {
|
||||
return [(num / 1e6).toFixed(1), "m"];
|
||||
} else if (num >= 1e3) {
|
||||
return [(num / 1e3).toFixed(1), "k"];
|
||||
} else {
|
||||
return [num.toString(), ""];
|
||||
}
|
||||
}
|
||||
|
||||
function parseCount() {
|
||||
return props.counts.map((count) => {
|
||||
const num = count[Object.keys(count)[0]];
|
||||
const formatted = formatNumber(count[Object.keys(count)[0]]);
|
||||
|
||||
return {
|
||||
count: num,
|
||||
formatted: formatted[0] + formatted[1],
|
||||
text: Object.keys(count)[0],
|
||||
};
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.home-count {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
.count-item {
|
||||
font-size: 3rem;
|
||||
font-weight: bolder;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: solid 1px $gray5;
|
||||
padding-right: 1rem;
|
||||
|
||||
.text {
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
text-transform: uppercase;
|
||||
margin-left: $smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -7,9 +7,7 @@
|
||||
:max="time.full"
|
||||
step="0.1"
|
||||
:style="{
|
||||
backgroundSize: `${
|
||||
(time.current / (time.full || 0)) * 100
|
||||
}% 100%`,
|
||||
backgroundSize: `${(time.current / (time.full || 0)) * 100}% 100%`,
|
||||
}"
|
||||
@change="seek"
|
||||
/>
|
||||
@@ -22,10 +20,18 @@ const q = useQStore();
|
||||
|
||||
const { duration: time } = q;
|
||||
|
||||
let prevHash = "";
|
||||
|
||||
const seek = (e: Event) => {
|
||||
if (prevHash && prevHash !== q.currenttrackhash) {
|
||||
prevHash = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const elem = e.target as HTMLInputElement;
|
||||
const value = elem.value;
|
||||
|
||||
prevHash = q.currenttrackhash;
|
||||
q.seek(value as unknown as number);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -8,18 +8,34 @@ import SearchSvg from "@/assets/icons/search.svg";
|
||||
import SettingsSvg from "@/assets/icons/settings.svg";
|
||||
import HomeSvg from "@/assets/icons/home.svg";
|
||||
|
||||
const folder = {
|
||||
name: "folders",
|
||||
route_name: Routes.folder,
|
||||
params: { path: "$home" },
|
||||
icon: FolderSvg,
|
||||
};
|
||||
|
||||
const favorites = {
|
||||
name: "favorites",
|
||||
route_name: Routes.favorites,
|
||||
icon: HeartSvg,
|
||||
};
|
||||
|
||||
const playlists = {
|
||||
name: "playlists",
|
||||
route_name: Routes.playlists,
|
||||
icon: PlaylistSvg,
|
||||
};
|
||||
|
||||
const home = {
|
||||
name: "home",
|
||||
route_name: Routes.Home,
|
||||
icon: HomeSvg,
|
||||
};
|
||||
|
||||
export const menus = [
|
||||
{
|
||||
name: "home",
|
||||
route_name: Routes.Home,
|
||||
icon: HomeSvg,
|
||||
},
|
||||
{
|
||||
name: "folders",
|
||||
route_name: Routes.folder,
|
||||
params: { path: "$home" },
|
||||
icon: FolderSvg,
|
||||
},
|
||||
home,
|
||||
folder,
|
||||
{
|
||||
name: "search",
|
||||
route_name: Routes.search,
|
||||
@@ -30,16 +46,8 @@ export const menus = [
|
||||
{
|
||||
separator: true,
|
||||
},
|
||||
{
|
||||
name: "favorites",
|
||||
route_name: Routes.favorites,
|
||||
icon: HeartSvg,
|
||||
},
|
||||
{
|
||||
name: "playlists",
|
||||
route_name: Routes.playlists,
|
||||
icon: PlaylistSvg,
|
||||
},
|
||||
favorites,
|
||||
playlists,
|
||||
{
|
||||
separator: true,
|
||||
},
|
||||
@@ -50,3 +58,5 @@ export const menus = [
|
||||
icon: SettingsSvg,
|
||||
},
|
||||
];
|
||||
|
||||
export const topnavitems = [home, folder, favorites, playlists];
|
||||
|
||||
@@ -58,10 +58,10 @@ function getSvg(notif: NotifType) {
|
||||
place-items: center;
|
||||
box-shadow: 0px 0px 2rem rgba(0, 0, 0, 0.466);
|
||||
font-size: 0.85rem;
|
||||
padding: 1rem;
|
||||
padding: 1rem $small;
|
||||
|
||||
grid-template-columns: 2rem 3fr;
|
||||
gap: $small;
|
||||
gap: $smaller;
|
||||
|
||||
.notif-text {
|
||||
width: 100%;
|
||||
@@ -74,14 +74,10 @@ function getSvg(notif: NotifType) {
|
||||
}
|
||||
|
||||
.new-notif.info,
|
||||
.new-notif.favorite {
|
||||
$bg: rgb(28, 102, 238);
|
||||
background-color: $bg;
|
||||
}
|
||||
|
||||
.new-notif.success {
|
||||
$bg: rgb(5, 167, 53);
|
||||
.new-notif.favorite,.new-notif.success {
|
||||
$bg: rgb(255, 255, 255);
|
||||
background-color: $bg;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.new-notif.working {
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
:source="dropSources.folder"
|
||||
@play-this="queue.playNext"
|
||||
/>
|
||||
<h3>Queue</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -8,29 +8,34 @@
|
||||
<div class="from">
|
||||
<img
|
||||
v-if="
|
||||
queue.from.type === FromOptions.album ||
|
||||
queue.from.type === FromOptions.artist
|
||||
tracklist.from.type === FromOptions.album ||
|
||||
tracklist.from.type === FromOptions.artist
|
||||
"
|
||||
:src="data.image + '.webp'"
|
||||
:alt="`Now Playing ${queue.from.type} image`"
|
||||
:alt="`Now Playing ${tracklist.from.type} image`"
|
||||
:class="`${
|
||||
queue.from.type === FromOptions.artist ? 'circular' : 'rounded-sm'
|
||||
tracklist.from.type === FromOptions.artist
|
||||
? 'circular'
|
||||
: 'rounded-sm'
|
||||
}`"
|
||||
/>
|
||||
<div v-else class="from-icon border rounded-sm">
|
||||
<component :is="data.icon"></component>
|
||||
</div>
|
||||
<div class="pad-sm">
|
||||
<div class="type">{{ queue.from.type }}</div>
|
||||
<div class="type">{{ tracklist.from.type }}</div>
|
||||
<div class="ellip2">{{ data.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
<button class="options" @click="showContextMenu">
|
||||
<MoreSvg />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { computed, ref } from "vue";
|
||||
import { RouteLocationRaw } from "vue-router";
|
||||
|
||||
import useTracklist from "@/stores/queue/tracklist";
|
||||
@@ -38,12 +43,23 @@ import useTracklist from "@/stores/queue/tracklist";
|
||||
import { FromOptions } from "@/enums";
|
||||
import playingFrom from "@/utils/playingFrom";
|
||||
|
||||
const queue = useTracklist();
|
||||
import MoreSvg from "@/assets/icons/more.svg";
|
||||
import { showQueueContextMenu } from "@/helpers/contextMenuHandler";
|
||||
|
||||
const tracklist = useTracklist();
|
||||
|
||||
const context_showing = ref(false);
|
||||
|
||||
const data = computed(() => {
|
||||
const { name, location, icon, image } = playingFrom(queue.from);
|
||||
const { name, location, icon, image } = playingFrom(tracklist.from);
|
||||
return { name, location, icon, image };
|
||||
});
|
||||
|
||||
function showContextMenu(e: MouseEvent) {
|
||||
if (!tracklist.tracklist.length) return;
|
||||
|
||||
showQueueContextMenu(e, context_showing);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -52,6 +68,14 @@ const data = computed(() => {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.options {
|
||||
transform: rotate(90deg);
|
||||
|
||||
svg {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.now-playling-from-link {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<div class="now-playing-tabs">
|
||||
<div class="tab-items">
|
||||
<h3>Queue</h3>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.now-playing-tabs {
|
||||
.tab-items {
|
||||
border: solid transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -20,7 +20,10 @@
|
||||
:class="{ border: !playlist.thumb }"
|
||||
/>
|
||||
<div class="overlay rounded">
|
||||
<div v-if="playlist.help_text" class="rhelp playlist">{{ playlist.help_text }}</div>
|
||||
<div v-if="playlist.help_text" class="rhelp playlist">
|
||||
<span class="help">{{ playlist.help_text }}</span>
|
||||
<span class="time">{{ playlist.time }}</span>
|
||||
</div>
|
||||
<div class="p-name ellip">{{ playlist.name }}</div>
|
||||
<div class="p-count">
|
||||
<b>{{
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
<template>
|
||||
<div class="gsearch-input">
|
||||
<div
|
||||
class="gsearch-input"
|
||||
@click="
|
||||
!settings.use_sidebar && $route.name !== Routes.search &&
|
||||
$router.push({
|
||||
name: Routes.search,
|
||||
params: { page: 'top' },
|
||||
query: { q: search.query },
|
||||
})
|
||||
"
|
||||
>
|
||||
<div id="ginner" ref="inputRef" tabindex="0">
|
||||
<button
|
||||
v-auto-animate
|
||||
@@ -18,6 +28,7 @@
|
||||
placeholder="Start typing to search"
|
||||
type="search"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
@blur.prevent="removeFocusedClass"
|
||||
@focus.prevent="addFocusedClass"
|
||||
/>
|
||||
@@ -29,20 +40,26 @@
|
||||
import { ref } from "vue";
|
||||
|
||||
import useTabStore from "@/stores/tabs";
|
||||
import useSearchStore from "@/stores/search";
|
||||
import useSearch from "@/stores/search";
|
||||
import useSettings from "@/stores/settings";
|
||||
|
||||
import BackSvg from "@/assets/icons/arrow.svg";
|
||||
import SearchSvg from "@/assets/icons/search.svg";
|
||||
import { Routes } from "@/router";
|
||||
|
||||
const props = defineProps<{
|
||||
on_nav?: boolean;
|
||||
}>();
|
||||
|
||||
const tabs = useTabStore();
|
||||
const search = useSearchStore();
|
||||
const search = useSearch();
|
||||
const settings = useSettings();
|
||||
|
||||
// HANDLE FOCUS
|
||||
const inputRef = ref<HTMLElement>();
|
||||
|
||||
// NOTE: Functions are used because classes are added to the sorrounding element
|
||||
// and not the input itself.
|
||||
function addFocusedClass() {
|
||||
inputRef.value?.classList.add("search-focused");
|
||||
}
|
||||
@@ -56,7 +73,7 @@ function removeFocusedClass() {
|
||||
function handleButton() {
|
||||
if (props.on_nav) return;
|
||||
|
||||
if (tabs.current === tabs.tabs.search || tabs.current === tabs.tabs.lyrics) {
|
||||
if (tabs.current === tabs.tabs.search) {
|
||||
tabs.switchToQueue();
|
||||
} else {
|
||||
tabs.switchToSearch();
|
||||
@@ -73,9 +90,9 @@ function handleButton() {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $small;
|
||||
// gap: $small;
|
||||
border-radius: 3rem;
|
||||
outline: solid 1px $gray3;
|
||||
background-color: $gray5;
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
@@ -101,13 +118,17 @@ function handleButton() {
|
||||
border: none;
|
||||
line-height: 2.25rem;
|
||||
color: inherit;
|
||||
font-size: 1rem;
|
||||
font-size: 14px;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
width: 7rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.search-focused {
|
||||
outline: solid $darkblue !important;
|
||||
outline: solid 2px #fff !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -34,7 +34,7 @@ const actions = [
|
||||
state: () => settings.use_sidebar,
|
||||
},
|
||||
{
|
||||
label: "Silcence skip",
|
||||
label: "Silence skip",
|
||||
action: settings.toggleUseSilenceSkip,
|
||||
state: () => settings.use_silence_skip,
|
||||
},
|
||||
|
||||
@@ -54,7 +54,7 @@ const currentTab = computed(() => {
|
||||
|
||||
<style lang="scss">
|
||||
.settingscontent {
|
||||
width: 35rem;
|
||||
width: 31rem;
|
||||
max-width: 100%;
|
||||
|
||||
.version {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div v-if="group.title || group.desc" class="info">
|
||||
<h4 v-if="group.title">
|
||||
{{ group.title
|
||||
}}<span v-if="group.experimental" class="experimental circular">
|
||||
}}<span v-if="group.experimental" class="badge experimental circular">
|
||||
{{ group.experimental ? "experimental" : "" }}
|
||||
</span>
|
||||
</h4>
|
||||
@@ -30,9 +30,15 @@
|
||||
<div class="title">
|
||||
<span class="ellip">
|
||||
{{ setting.title }}
|
||||
<span v-if="setting.experimental" class="experimental circular">
|
||||
<span
|
||||
v-if="setting.experimental"
|
||||
class="badge experimental circular"
|
||||
>
|
||||
{{ setting.experimental ? "experimental" : "" }}
|
||||
</span>
|
||||
<span v-if="setting.new" class="badge new circular">
|
||||
{{ setting.new ? "new" : "" }}
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
v-if="setting.type == SettingType.root_dirs"
|
||||
@@ -116,13 +122,22 @@ defineProps<{
|
||||
border-bottom: solid 1px $gray;
|
||||
padding-bottom: 2rem;
|
||||
|
||||
.experimental {
|
||||
font-size: 12px;
|
||||
.badge {
|
||||
margin-left: $small;
|
||||
opacity: 0.5;
|
||||
opacity: 0.75;
|
||||
padding: 0 $smaller;
|
||||
border-radius: $smaller;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.experimental {
|
||||
border: solid 1px $yellow;
|
||||
color: $yellow;
|
||||
padding: 0 $smaller;
|
||||
}
|
||||
|
||||
.badge.new {
|
||||
background-color: $blue;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
|
||||
@@ -10,9 +10,6 @@
|
||||
}"
|
||||
>
|
||||
<div class="heading">{{ modal.title }}</div>
|
||||
<div class="close circular" @click="modal.hideModal">
|
||||
<PlusSvg />
|
||||
</div>
|
||||
<NewPlaylist
|
||||
v-if="modal.component == modal.options.newPlaylist"
|
||||
v-bind="modal.props"
|
||||
@@ -20,7 +17,7 @@
|
||||
@setTitle="setTitle"
|
||||
/>
|
||||
<UpdatePlaylist
|
||||
v-if="modal.component == modal.options.updatePlaylist"
|
||||
v-if="modal.component == modal.options.updatePlaylist"
|
||||
v-bind="modal.props"
|
||||
@hideModal="hideModal"
|
||||
@setTitle="setTitle"
|
||||
@@ -50,7 +47,6 @@ import { deletePlaylist as delPlaylist } from "@/requests/playlists";
|
||||
import { useRouter } from "vue-router";
|
||||
import useModalStore from "@/stores/modal";
|
||||
|
||||
import PlusSvg from "@/assets/icons/plus.svg";
|
||||
import WelcomeModal from "./WelcomeModal.vue";
|
||||
import ConfirmModal from "./modals/ConfirmModal.vue";
|
||||
import NewPlaylist from "./modals/NewPlaylist.vue";
|
||||
@@ -87,13 +83,15 @@ function deletePlaylist() {
|
||||
|
||||
input[type="search"] {
|
||||
margin: $small 0;
|
||||
border: 2px solid $gray3;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
background-color: $gray5;
|
||||
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
font-size: 1rem;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
height: 2.75rem !important;
|
||||
}
|
||||
|
||||
.bg {
|
||||
@@ -116,28 +114,6 @@ function deletePlaylist() {
|
||||
width: calc(100% - 2rem);
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 1.5rem;
|
||||
right: 1.25rem;
|
||||
|
||||
transform: rotate(45deg);
|
||||
|
||||
svg {
|
||||
background-color: $gray3;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
opacity: 0.75;
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
background-color: $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
type="search"
|
||||
class="rounded-sm"
|
||||
name="name"
|
||||
placeholder="Type a name..."
|
||||
spellcheck="false"
|
||||
/>
|
||||
<br /><br />
|
||||
<button type="submit">Create</button>
|
||||
@@ -159,7 +161,6 @@ function create(e: Event) {
|
||||
label {
|
||||
font-size: 0.9rem;
|
||||
color: $gray1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.submit {
|
||||
@@ -170,8 +171,15 @@ function create(e: Event) {
|
||||
button {
|
||||
margin: 0 auto;
|
||||
width: 8rem;
|
||||
padding: 1.25rem;
|
||||
// font-weight: normal;
|
||||
transition: all 0.25s ease-out;
|
||||
background-color: $pink;
|
||||
background-color: $white;
|
||||
color: $black;
|
||||
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
type="search"
|
||||
class="rounded-sm"
|
||||
name="name"
|
||||
spellcheck="false"
|
||||
@keypress.enter.prevent="update_playlist"
|
||||
/>
|
||||
|
||||
@@ -81,7 +82,7 @@
|
||||
</div>
|
||||
|
||||
<button type="submit">
|
||||
{{ clicked ? "Saving" : "Save" }}
|
||||
{{ clicked ? "Saving" : "Update" }}
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -1,68 +1,74 @@
|
||||
<template>
|
||||
<div class="topnav">
|
||||
<div
|
||||
class="topnav"
|
||||
:class="{
|
||||
use_links: settings.is_alt_layout,
|
||||
use_sidebar: settings.use_sidebar && isSmall,
|
||||
}"
|
||||
>
|
||||
<div class="left">
|
||||
<!-- back/forward -->
|
||||
<NavButtons />
|
||||
|
||||
<div class="info">
|
||||
<SettingsTitle
|
||||
v-if="$route.name == Routes.settings"
|
||||
:text="'Settings'"
|
||||
/>
|
||||
<FolderTitle
|
||||
v-if="$route.name == Routes.folder"
|
||||
:sub-paths="subPaths"
|
||||
/>
|
||||
<SearchTitle v-if="$route.name == Routes.search" />
|
||||
<PlaylistsTitle v-if="$route.name == Routes.playlists" />
|
||||
<QueueTitle v-if="$route.name == Routes.nowPlaying" />
|
||||
<SimpleNav
|
||||
v-if="$route.name == Routes.artistTracks"
|
||||
:text="$route.query.artist as string || 'Artist Tracks'"
|
||||
/>
|
||||
<SimpleNav
|
||||
v-if="$route.name === Routes.favoriteAlbums"
|
||||
:text="'Favorite Albums'"
|
||||
/>
|
||||
<SimpleNav
|
||||
v-if="$route.name === Routes.favoriteArtists"
|
||||
:text="'Favorite Artists'"
|
||||
/>
|
||||
<SimpleNav
|
||||
v-if="$route.name === Routes.favoriteTracks"
|
||||
:text="'Favorite Tracks'"
|
||||
/>
|
||||
<NavLinks v-if="settings.is_alt_layout" />
|
||||
<div
|
||||
v-if="settings.is_default_layout && $route.name == Routes.folder"
|
||||
class="info"
|
||||
>
|
||||
<Folder :sub-paths="subPaths" />
|
||||
</div>
|
||||
<NavTitles v-else-if="settings.is_default_layout && !isSmall" />
|
||||
</div>
|
||||
<RouterLink v-if="settings.is_alt_layout" to="/" class="logo rounded-sm"
|
||||
><LogoSvg
|
||||
/></RouterLink>
|
||||
<div v-if="settings.is_alt_layout || !settings.use_sidebar" class="right">
|
||||
<SearchInput :on_nav="true" />
|
||||
<!-- v-if="settings.is_alt_layout" -->
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: Routes.settings,
|
||||
params: {
|
||||
tab: 'general',
|
||||
},
|
||||
}"
|
||||
class="avatar"
|
||||
>
|
||||
<AvatarSvg />
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from "vue";
|
||||
import { Routes } from "@/router";
|
||||
import { useRoute } from "vue-router";
|
||||
import { computed, onMounted, ref, watch } from "vue";
|
||||
|
||||
import { subPath } from "@/interfaces";
|
||||
import { Routes } from "@/router";
|
||||
import { createSubPaths } from "@/utils";
|
||||
import useSettings from "@/stores/settings";
|
||||
import { content_width } from "@/stores/content-width";
|
||||
|
||||
import NavLinks from "./NavLinks.vue";
|
||||
import NavTitles from "./NavTitles.vue";
|
||||
import Folder from "./Titles/Folder.vue";
|
||||
import NavButtons from "./NavButtons.vue";
|
||||
import AvatarSvg from "@/assets/icons/settings.svg";
|
||||
import SearchInput from "../RightSideBar/SearchInput.vue";
|
||||
import LogoSvg from "@/assets/icons/logos/logo-fill.light.svg";
|
||||
|
||||
import FolderTitle from "./Titles/Folder.vue";
|
||||
import PlaylistsTitle from "./Titles/PlaylistsTitle.vue";
|
||||
import QueueTitle from "./Titles/QueueTitle.vue";
|
||||
import SearchTitle from "./Titles/SearchTitle.vue";
|
||||
import SettingsTitle from "./Titles/SettingsTitle.vue";
|
||||
import SimpleNav from "./Titles/SimpleNav.vue";
|
||||
const settings = useSettings();
|
||||
const isSmall = computed(() => content_width.value < 800);
|
||||
|
||||
const route = useRoute();
|
||||
const subPaths = ref<subPath[]>([]);
|
||||
|
||||
let oldpath = "";
|
||||
|
||||
watch(
|
||||
() => route.name,
|
||||
(newRoute) => {
|
||||
switch (newRoute) {
|
||||
case Routes.folder: {
|
||||
let oldpath = "";
|
||||
[oldpath, subPaths.value] = createSubPaths(
|
||||
route.params.path as string,
|
||||
oldpath
|
||||
@@ -84,18 +90,42 @@ watch(
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
if (route.name == Routes.folder) {
|
||||
[oldpath, subPaths.value] = createSubPaths(
|
||||
route.params.path as string,
|
||||
oldpath
|
||||
);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.topnav {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content;
|
||||
grid-template-columns: 1fr max-content;
|
||||
|
||||
input {
|
||||
min-width: 6rem;
|
||||
}
|
||||
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
font-size: 14px;
|
||||
|
||||
&.use_links {
|
||||
grid-template-columns: 1fr max-content 1fr;
|
||||
}
|
||||
|
||||
&.use_sidebar {
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.left {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
gap: 1rem;
|
||||
height: 2.25rem;
|
||||
|
||||
.info {
|
||||
margin: auto 0;
|
||||
@@ -109,10 +139,45 @@ watch(
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin-right: 1rem;
|
||||
.logo {
|
||||
width: max-content;
|
||||
margin: 0 auto;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.avatar {
|
||||
height: 2.25rem;
|
||||
aspect-ratio: 1;
|
||||
background-color: $gray4;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
svg {
|
||||
transform: scale(0.75);
|
||||
color: $gray1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
|
||||
svg {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,17 +19,18 @@ import ArrowSvg from "../../assets/icons/right-arrow.svg";
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
padding-right: 1rem;
|
||||
border-right: 1px solid $gray3;
|
||||
height: 100%;
|
||||
border-right: 1px solid $gray5;
|
||||
height: max-content;
|
||||
|
||||
& > * {
|
||||
width: 2.25rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
border-radius: 0.65rem;
|
||||
border-radius: 5rem;
|
||||
|
||||
svg {
|
||||
transform: scale(1.25);
|
||||
|
||||
73
src/components/nav/NavLinks.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div class="topnavlinks">
|
||||
<RouterLink
|
||||
v-for="link in topnavitems"
|
||||
:key="link.name"
|
||||
class="link"
|
||||
:to="{
|
||||
name: link.route_name,
|
||||
params: link?.params,
|
||||
}"
|
||||
:class="{
|
||||
active: $route.name && $route.name === link.route_name,
|
||||
}"
|
||||
>
|
||||
{{ link.name || "|" }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { topnavitems } from "../LeftSidebar/navitems";
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.topnavlinks {
|
||||
display: flex;
|
||||
gap: $smaller;
|
||||
align-items: center;
|
||||
|
||||
width: max-content;
|
||||
user-select: none;
|
||||
|
||||
.link {
|
||||
text-transform: capitalize;
|
||||
padding: 0.25rem $medium;
|
||||
cursor: pointer;
|
||||
border-radius: 2rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
transform: scale(0.75);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $primary;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #fff;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.link.home {
|
||||
padding: 0;
|
||||
padding-right: $small;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: $smaller;
|
||||
}
|
||||
|
||||
.separator_ {
|
||||
padding: 0;
|
||||
pointer-events: none;
|
||||
background-color: transparent;
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
33
src/components/nav/NavTitles.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div class="info">
|
||||
<SettingsTitle v-if="$route.name == Routes.settings" :text="'Settings'" />
|
||||
<SearchTitle v-if="$route.name == Routes.search" />
|
||||
<PlaylistsTitle v-if="$route.name == Routes.playlists" />
|
||||
<SimpleNav
|
||||
v-if="$route.name == Routes.artistTracks"
|
||||
:text="$route.query.artist as string || 'Artist Tracks'"
|
||||
/>
|
||||
<SimpleNav
|
||||
v-if="$route.name === Routes.favoriteAlbums"
|
||||
:text="'Favorite Albums'"
|
||||
/>
|
||||
<SimpleNav
|
||||
v-if="$route.name === Routes.favoriteArtists"
|
||||
:text="'Favorite Artists'"
|
||||
/>
|
||||
<SimpleNav
|
||||
v-if="$route.name === Routes.favoriteTracks"
|
||||
:text="'Favorite Tracks'"
|
||||
/>
|
||||
<SimpleNav v-if="$route.name === Routes.nowPlaying" :text="'Now Playing'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Routes } from "@/router";
|
||||
|
||||
import SimpleNav from "./Titles/SimpleNav.vue";
|
||||
import SearchTitle from "./Titles/SearchTitle.vue";
|
||||
import SettingsTitle from "./Titles/SettingsTitle.vue";
|
||||
import PlaylistsTitle from "./Titles/PlaylistsTitle.vue";
|
||||
</script>
|
||||
@@ -1,48 +1,26 @@
|
||||
<template>
|
||||
<div id="folder-nav-title">
|
||||
<div class="folder">
|
||||
<div class="fname-wrapper">
|
||||
<div class="fname">
|
||||
<div
|
||||
class="icon image"
|
||||
@click="
|
||||
$router.push({ name: Routes.folder, params: { path: '$home' } })
|
||||
"
|
||||
></div>
|
||||
<BreadCrumbNav :sub-paths="subPaths" @navigate="navigate" />
|
||||
</div>
|
||||
</div>
|
||||
<SearchInput v-if="!isSmallPhone" :page="Routes.folder" />
|
||||
<button
|
||||
class="options"
|
||||
:class="{ 'btn-active': context_menu_showing }"
|
||||
title="show more options"
|
||||
@click="showContextMenu"
|
||||
>
|
||||
<MoreSvg />
|
||||
</button>
|
||||
<div class="fname">
|
||||
<div
|
||||
class="icon image"
|
||||
@click="
|
||||
$router.push({ name: Routes.folder, params: { path: '$home' } })
|
||||
"
|
||||
></div>
|
||||
<BreadCrumbNav :sub-paths="subPaths" @navigate="navigate" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { Routes } from "@/router";
|
||||
import { subPath } from "@/interfaces";
|
||||
import { isSmallPhone } from "@/stores/content-width";
|
||||
|
||||
import SearchInput from "@/components/shared/NavSearchInput.vue";
|
||||
import BreadCrumbNav from "@/components/FolderView/BreadCrumbNav.vue";
|
||||
import MoreSvg from "@/assets/icons/more.svg";
|
||||
import { showFolderContextMenu } from "@/helpers/contextMenuHandler";
|
||||
import { ref } from "vue";
|
||||
import { ContextSrc } from "@/enums";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const context_menu_showing = ref(false);
|
||||
|
||||
defineProps<{
|
||||
subPaths: subPath[];
|
||||
@@ -51,69 +29,36 @@ defineProps<{
|
||||
function navigate(path: string) {
|
||||
router.push({ name: Routes.folder, params: { path } });
|
||||
}
|
||||
|
||||
function showContextMenu(e: MouseEvent) {
|
||||
showFolderContextMenu(
|
||||
e,
|
||||
context_menu_showing,
|
||||
ContextSrc.FolderNav,
|
||||
route.params.path as string
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#folder-nav-title {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
|
||||
.folder {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content max-content;
|
||||
.fname {
|
||||
background-color: $gray4;
|
||||
border-radius: $small;
|
||||
height: 2.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: $smaller;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
overflow: scroll;
|
||||
scrollbar-width: none;
|
||||
|
||||
@include iphoneSE {
|
||||
grid-template-columns: 1fr max-content;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fname-wrapper {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fname {
|
||||
background-color: $gray4;
|
||||
border-radius: $small;
|
||||
height: 2.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: $smaller;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
overflow: scroll;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 2rem;
|
||||
aspect-ratio: 1;
|
||||
background-image: url("../../../assets/icons/folder.fill.svg");
|
||||
background-size: 1.5rem;
|
||||
margin-left: $smaller;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
margin-left: 1rem;
|
||||
height: 2.25rem;
|
||||
width: 2.25rem;
|
||||
.icon {
|
||||
height: 2rem;
|
||||
aspect-ratio: 1;
|
||||
padding: 0;
|
||||
|
||||
svg {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
background-image: url("../../../assets/icons/folder.fill.svg");
|
||||
background-size: 1.5rem;
|
||||
margin-left: $smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<template>
|
||||
<div class="nav-queue-title">
|
||||
<QueueActions :on-now-playing="true" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import QueueActions from "@/components/RightSideBar/Queue/QueueActions.vue";
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.nav-queue-title {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
align-items: center;
|
||||
|
||||
.queue-actions {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,8 +1,7 @@
|
||||
<template>
|
||||
<div class="nav-search-input">
|
||||
<SearchInput :on_nav="true" />
|
||||
<Tabs
|
||||
v-if="!isMobile"
|
||||
v-if="!(content_width < 800)"
|
||||
:tabs="tabs"
|
||||
:current-tab="($route.params.page as string)"
|
||||
@switchTab="(tab: string) => {
|
||||
@@ -18,10 +17,10 @@
|
||||
<script setup lang="ts">
|
||||
import { Routes } from "@/router";
|
||||
|
||||
import Tabs from "@/components/RightSideBar/Search/TabsWrapper.vue";
|
||||
import SearchInput from "@/components/RightSideBar/SearchInput.vue";
|
||||
import useSearchStore from "@/stores/search";
|
||||
import { isMobile } from "@/stores/content-width";
|
||||
import { content_width } from "@/stores/content-width";
|
||||
|
||||
import Tabs from "@/components/RightSideBar/Search/TabsWrapper.vue";
|
||||
|
||||
const search = useSearchStore();
|
||||
const tabs = ["top", "tracks", "albums", "artists"];
|
||||
@@ -29,17 +28,6 @@ const tabs = ["top", "tracks", "albums", "artists"];
|
||||
|
||||
<style lang="scss">
|
||||
.nav-search-input {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(10rem, 20rem) max-content;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
|
||||
@include allPhones {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
#right-tabs {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="album.help_text" class="rhelp album">
|
||||
{{ album.help_text }}
|
||||
<span class="help">{{ album.help_text }}</span>
|
||||
<span class="time">{{ album.time }}</span>
|
||||
</div>
|
||||
<h4 v-tooltip class="title ellip">
|
||||
{{ album.title }}
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
/>
|
||||
</div>
|
||||
<div v-if="artist.help_text" class="rhelp t-center">
|
||||
{{ artist.help_text }}
|
||||
<span class="help">{{ artist.help_text }}</span>
|
||||
<span class="time">{{ artist.time }}</span>
|
||||
</div>
|
||||
<div class="artist-name t-center">
|
||||
{{ artist.name }}
|
||||
|
||||
@@ -31,7 +31,7 @@ defineProps<{
|
||||
<style lang="scss">
|
||||
.cardlistrow {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10.1rem, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
|
||||
padding-bottom: 2rem;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ import SeeAll from "../shared/SeeAll.vue";
|
||||
import FolderCard from "./FolderCard.vue";
|
||||
import AlbumCard from "./AlbumCard.vue";
|
||||
import ArtistCard from "./ArtistCard.vue";
|
||||
import PlaylistCard from "../PlaylistsList/PlaylistCard.vue";
|
||||
import FavoritesCard from "./FavoritesCard.vue";
|
||||
import PlaylistCard from "../PlaylistsList/PlaylistCard.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
title: string;
|
||||
@@ -104,7 +104,7 @@ function getProps(item: { type: string; item: any }) {
|
||||
.recentitems {
|
||||
gap: 1.5rem 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10.1rem, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
|
||||
}
|
||||
|
||||
.p-card {
|
||||
@@ -135,5 +135,26 @@ function getProps(item: { type: string; item: any }) {
|
||||
padding: 1.25rem 2rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.hlistitem {
|
||||
// TODO: Handle when there's no time
|
||||
// INFO: Set the time to display none by default
|
||||
|
||||
.rhelp .time {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
// INFO: Set the help text to display none on hover
|
||||
.rhelp .help {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// INFO: Set the time to display block on hover
|
||||
.rhelp .time {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
<PlayBtn :source="playSources.favorite" />
|
||||
</div>
|
||||
<div class="info">
|
||||
<div class="rhelp playlist">PLAYLIST</div>
|
||||
<div class="rhelp playlist">
|
||||
<span class="help">PLAYLIST</span>
|
||||
<span class="time">{{ item.time }}</span>
|
||||
</div>
|
||||
<div class="title">Favorite Tracks</div>
|
||||
<div class="fcount">
|
||||
<b>{{ item.count + ` Track${item.count == 1 ? "" : "s"}` }}</b>
|
||||
|
||||
@@ -26,7 +26,8 @@
|
||||
</div>
|
||||
|
||||
<div v-if="folder.help_text" class="rhelp folder">
|
||||
{{ folder.help_text }}
|
||||
<span class="help">{{ folder.help_text }}</span>
|
||||
<span class="time">{{ folder.time }}</span>
|
||||
</div>
|
||||
<div class="ellip" :title="name(folder.path)">
|
||||
{{ name(folder.path) }}
|
||||
@@ -47,6 +48,7 @@ defineProps<{
|
||||
path: string;
|
||||
count: number;
|
||||
help_text: string;
|
||||
time?: string;
|
||||
};
|
||||
}>();
|
||||
|
||||
|
||||
@@ -28,13 +28,12 @@ const lyrics = useLyrics();
|
||||
let prevRoute = ref(route.name);
|
||||
|
||||
function handleClick() {
|
||||
if (route.name === Routes.nowPlaying && route.params.tab === "lyrics") {
|
||||
if (route.name === Routes.Lyrics) {
|
||||
return router.back();
|
||||
}
|
||||
|
||||
router.push({
|
||||
name: Routes.nowPlaying,
|
||||
params: { tab: "lyrics" },
|
||||
name: Routes.Lyrics,
|
||||
});
|
||||
|
||||
prevRoute.value = route.name;
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
<template>
|
||||
<div class="header-input-wrapper rounded-sm" :class="{ showInput: clicked }">
|
||||
<button
|
||||
id="page-search-trigger"
|
||||
v-wave
|
||||
class="search-btn"
|
||||
:class="{ 'btn-active': clicked }"
|
||||
@click="handleFocus"
|
||||
>
|
||||
<SearchSvg />
|
||||
</button>
|
||||
<input
|
||||
id="page-search"
|
||||
ref="inputRef"
|
||||
v-model.trim="query"
|
||||
class="header-input pad-sm rounded-sm"
|
||||
:class="{ showInput: clicked }"
|
||||
:placeholder="currentEmoji"
|
||||
type="search"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
import useAlbumStore from "@/stores/pages/album";
|
||||
import useFolderStore from "@/stores/pages/folder";
|
||||
import usePStore from "@/stores/pages/playlist";
|
||||
|
||||
import SearchSvg from "@/assets/icons/search.svg";
|
||||
import { Routes } from "@/router";
|
||||
|
||||
const clicked = ref(false);
|
||||
const [playlist, album, folder] = [
|
||||
usePStore(),
|
||||
useAlbumStore(),
|
||||
useFolderStore(),
|
||||
];
|
||||
|
||||
const { query: playlistQuery } = storeToRefs(playlist);
|
||||
const { query: folderQuery } = storeToRefs(folder);
|
||||
const { query: albumQuery } = storeToRefs(album);
|
||||
|
||||
const props = defineProps<{
|
||||
page: typeof Routes | string;
|
||||
}>();
|
||||
const inputRef = ref<HTMLElement>();
|
||||
|
||||
function handleFocus() {
|
||||
clicked.value = !clicked.value;
|
||||
if (clicked.value) {
|
||||
inputRef.value?.focus();
|
||||
setRandomEmoji();
|
||||
} else {
|
||||
inputRef.value?.blur();
|
||||
resetQuery();
|
||||
}
|
||||
}
|
||||
|
||||
function getRef() {
|
||||
switch (props.page) {
|
||||
case Routes.playlist:
|
||||
return [playlistQuery, playlist.resetQuery];
|
||||
|
||||
case Routes.folder:
|
||||
return [folderQuery, folder.resetQuery];
|
||||
|
||||
case Routes.album:
|
||||
return [albumQuery, album.resetQuery];
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const source = getRef();
|
||||
let query: any;
|
||||
let resetQuery: any;
|
||||
|
||||
if (source) {
|
||||
query = source[0];
|
||||
resetQuery = source[1];
|
||||
}
|
||||
|
||||
const currentEmoji = ref("");
|
||||
|
||||
function setRandomEmoji() {
|
||||
const emojis = ["😹", "😺", "😻", "😸", "😼", "😽", "🙀", "😿", "😾"];
|
||||
const randomIndex = Math.floor(Math.random() * emojis.length);
|
||||
currentEmoji.value = emojis[randomIndex];
|
||||
}
|
||||
|
||||
onMounted(() => setRandomEmoji());
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.header-input-wrapper {
|
||||
&.showInput {
|
||||
width: 19rem;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
width: 5rem;
|
||||
gap: $small;
|
||||
transition: all 0.25s;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
.header-input {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
color: white;
|
||||
z-index: 200;
|
||||
font-size: 1rem;
|
||||
transition: all 0.25s $overshoot;
|
||||
opacity: 0;
|
||||
transform: translateY(-3.5rem);
|
||||
padding-left: 1rem !important;
|
||||
outline: none;
|
||||
background-color: $gray3;
|
||||
|
||||
&:focus {
|
||||
background-color: $darkestblue;
|
||||
}
|
||||
|
||||
&.showInput {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
cursor: pointer;
|
||||
padding: 0 $small;
|
||||
// padding-right: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -14,7 +14,8 @@
|
||||
</div>
|
||||
<div class="tinfo">
|
||||
<div v-if="track.help_text" class="rhelp track">
|
||||
{{ track.help_text }}
|
||||
<span class="help">{{ track.help_text }}</span>
|
||||
<span class="time">{{ track.time }}</span>
|
||||
</div>
|
||||
<div class="ttitle ellip">{{ track.title }}</div>
|
||||
<ArtistName :albumartists="track.albumartists" :artists="track.artists" />
|
||||
@@ -78,6 +79,8 @@ defineEmits<{
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.artist {
|
||||
|
||||
@@ -4,26 +4,14 @@ import { Option, Playlist } from "@/interfaces";
|
||||
import { getTracksInPath } from "@/requests/folders";
|
||||
|
||||
import useModal from "@/stores/modal";
|
||||
import useSettings from "@/stores/settings";
|
||||
import useTracklist from "@/stores/queue/tracklist";
|
||||
|
||||
import { addFolderToPlaylist } from "@/requests/playlists";
|
||||
import { getAddToPlaylistOptions } from "./utils";
|
||||
|
||||
export default async (trigger_src: ContextSrc, path: string) => {
|
||||
const settings = useSettings();
|
||||
export default async (path: string) => {
|
||||
const modal = useModal();
|
||||
|
||||
const getListModeOption = () =>
|
||||
<Option>{
|
||||
label: settings.folder_list_mode ? "Grid Mode" : "List Mode",
|
||||
action: () => settings.toggleFolderListMode(),
|
||||
icon: settings.folder_list_mode ? icons.GridIcon : icons.PlaylistIcon,
|
||||
};
|
||||
|
||||
// if trigger source is folder nav, show list mode option
|
||||
let items = trigger_src === ContextSrc.FolderNav ? [getListModeOption()] : [];
|
||||
|
||||
const play_next = <Option>{
|
||||
label: "Play next",
|
||||
action: () => {
|
||||
@@ -65,5 +53,5 @@ export default async (trigger_src: ContextSrc, path: string) => {
|
||||
icon: icons.PlaylistIcon,
|
||||
};
|
||||
|
||||
return [play_next, add_to_queue, add_to_playlist, save_as_playlist, ...items];
|
||||
return [play_next, add_to_queue, add_to_playlist, save_as_playlist];
|
||||
};
|
||||
|
||||
@@ -36,10 +36,11 @@ export default async () => {
|
||||
useQueue().clearQueue();
|
||||
},
|
||||
icon: DeleteIcon,
|
||||
critical: true,
|
||||
};
|
||||
|
||||
const saveAsPlaylist: Option = {
|
||||
label: "Save as playlist",
|
||||
label: "Save queue as playlist",
|
||||
action: () => {
|
||||
useModalStore().showSaveQueueAsPlaylistModal(getQueueName(store.from));
|
||||
},
|
||||
@@ -51,7 +52,7 @@ export default async () => {
|
||||
};
|
||||
|
||||
const addToPlaylist: Option = {
|
||||
label: "Add to Playlist",
|
||||
label: "Add queue to playlist",
|
||||
children: await getAddToPlaylistOptions(AddToPlaylistAction, {
|
||||
trackhash: store.tracklist.map((t) => t.trackhash).join(","),
|
||||
}),
|
||||
|
||||
@@ -60,7 +60,7 @@ export const showFolderContextMenu = (
|
||||
) => {
|
||||
const menu = useContextStore();
|
||||
|
||||
const options = () => folderContextItems(source, path);
|
||||
const options = () => folderContextItems(path);
|
||||
menu.showContextMenu(e, options, source);
|
||||
|
||||
flagWatcher(menu, flag);
|
||||
|
||||
@@ -23,13 +23,14 @@ export default function (queue: typeof useQueue, modal: typeof useModal) {
|
||||
|
||||
window.addEventListener("keydown", (e: KeyboardEvent) => {
|
||||
const target = e.target as HTMLElement;
|
||||
|
||||
if (e.altKey) return;
|
||||
const ctrlKey = e.ctrlKey;
|
||||
const shiftKey = e.shiftKey;
|
||||
|
||||
const no_text_selection = !window.getSelection()?.toString();
|
||||
|
||||
function FocusedOnInput(target: HTMLElement) {
|
||||
function focusedOnInput() {
|
||||
const targett = target as HTMLInputElement;
|
||||
|
||||
return (
|
||||
@@ -43,7 +44,7 @@ export default function (queue: typeof useQueue, modal: typeof useModal) {
|
||||
if (ctrlKey) return true;
|
||||
}
|
||||
|
||||
if (FocusedOnInput(target)) {
|
||||
if (focusedOnInput()) {
|
||||
if (e.key == "Escape") {
|
||||
target.blur();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface Track extends AlbumDisc {
|
||||
copyright?: string;
|
||||
master_index?: number;
|
||||
help_text?: string;
|
||||
time?: string;
|
||||
}
|
||||
|
||||
export interface Folder {
|
||||
@@ -56,6 +57,7 @@ export interface Album {
|
||||
copyright?: string;
|
||||
|
||||
help_text?: string;
|
||||
time?: string;
|
||||
is_live: boolean;
|
||||
is_compilation: boolean;
|
||||
is_soundtrack: boolean;
|
||||
@@ -76,6 +78,7 @@ export interface Artist {
|
||||
colors: string[];
|
||||
is_favorite?: boolean;
|
||||
help_text?: string;
|
||||
time?: string;
|
||||
}
|
||||
|
||||
export interface Option {
|
||||
@@ -107,6 +110,7 @@ export interface Playlist {
|
||||
settings: PlaylistSettings;
|
||||
pinned: boolean;
|
||||
help_text?: string;
|
||||
time?: string;
|
||||
images:
|
||||
| {
|
||||
image: string;
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface Setting {
|
||||
defaultAction?: () => void;
|
||||
show_if?: () => boolean;
|
||||
experimental?: boolean;
|
||||
new?: boolean;
|
||||
}
|
||||
|
||||
export interface SettingGroup {
|
||||
|
||||
@@ -33,9 +33,9 @@ app.use(MotionPlugin);
|
||||
|
||||
app.directive("tooltip", vTooltip);
|
||||
|
||||
app.component("WrapBalancer", WrapBalancer);
|
||||
app.component("RecycleScroller", RecycleScroller);
|
||||
app.component("DynamicScroller", DynamicScroller);
|
||||
app.component("DynamicScrollerItem", DynamicScrollerItem);
|
||||
app.component("WrapBalancer", WrapBalancer);
|
||||
|
||||
app.mount("#app");
|
||||
|
||||
@@ -7,23 +7,24 @@ import usePlaylistPageStore from "@/stores/pages/playlist";
|
||||
import usePlaylistListPageStore from "@/stores/pages/playlists";
|
||||
import useArtistPageStore from "@/stores/pages/artist";
|
||||
|
||||
import HomeView from "@/views/HomeView";
|
||||
|
||||
const FolderView = () => import("@/views/FolderView.vue");
|
||||
const PlaylistListView = () => import("@/views/PlaylistList.vue");
|
||||
const PlaylistView = () => import("@/views/PlaylistView/index.vue");
|
||||
const AlbumView = () => import("@/views/AlbumView/index.vue");
|
||||
import HomeView from "@/views/HomeView";
|
||||
const Lyrics = () => import("@/views/LyricsView");
|
||||
const ArtistView = () => import("@/views/ArtistView");
|
||||
const ArtistTracksView = () => import("@/views/ArtistTracks.vue");
|
||||
const ArtistDiscographyView = () => import("@/views/ArtistDiscography.vue");
|
||||
const SettingsView = () => import("@/views/SettingsView.vue");
|
||||
const SearchView = () => import("@/views/SearchView");
|
||||
const FavoritesView = () => import("@/views/Favorites.vue");
|
||||
const FavoriteCardScroller = () => import("@/views/FavoriteCardScroller.vue");
|
||||
const FavoriteTracks = () => import("@/views/FavoriteTracks.vue");
|
||||
const NotFound = () => import("@/views/NotFound.vue");
|
||||
const NowPlaying = () => import("@/views/NowPlaying");
|
||||
const SearchView = () => import("@/views/SearchView");
|
||||
const AlbumList = () => import("@/views/AlbumListView");
|
||||
const FolderView = () => import("@/views/FolderView.vue");
|
||||
const FavoritesView = () => import("@/views/Favorites.vue");
|
||||
const SettingsView = () => import("@/views/SettingsView.vue");
|
||||
const AlbumView = () => import("@/views/AlbumView/index.vue");
|
||||
const ArtistTracksView = () => import("@/views/ArtistTracks.vue");
|
||||
const PlaylistListView = () => import("@/views/PlaylistList.vue");
|
||||
const FavoriteTracks = () => import("@/views/FavoriteTracks.vue");
|
||||
const PlaylistView = () => import("@/views/PlaylistView/index.vue");
|
||||
const ArtistDiscographyView = () => import("@/views/ArtistDiscography.vue");
|
||||
const FavoriteCardScroller = () => import("@/views/FavoriteCardScroller.vue");
|
||||
|
||||
const folder = {
|
||||
path: "/folder/:path",
|
||||
@@ -102,6 +103,12 @@ const NowPlayingView = {
|
||||
component: NowPlaying,
|
||||
};
|
||||
|
||||
const LyricsView = {
|
||||
path: "/lyrics",
|
||||
name: "LyricsView",
|
||||
component: Lyrics,
|
||||
};
|
||||
|
||||
const ArtistTracks = {
|
||||
path: "/artists/:hash/tracks",
|
||||
name: "ArtistTracks",
|
||||
@@ -193,9 +200,10 @@ const routes = [
|
||||
Home,
|
||||
AlbumListView,
|
||||
ArtistListView,
|
||||
LyricsView,
|
||||
];
|
||||
|
||||
export const Routes = {
|
||||
const Routes = {
|
||||
folder: folder.name,
|
||||
playlists: playlists.name,
|
||||
playlist: playlistView.name,
|
||||
@@ -214,6 +222,7 @@ export const Routes = {
|
||||
Home: Home.name,
|
||||
AlbumList: AlbumListView.name,
|
||||
ArtistList: ArtistListView.name,
|
||||
Lyrics: LyricsView.name,
|
||||
};
|
||||
|
||||
const router = createRouter({
|
||||
@@ -222,4 +231,4 @@ const router = createRouter({
|
||||
routes,
|
||||
} as RouterOptions);
|
||||
|
||||
export { router };
|
||||
export { router, Routes };
|
||||
|
||||
15
src/settings/general/folderlistmode.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { SettingType } from "../enums";
|
||||
import { Setting } from "@/interfaces/settings";
|
||||
|
||||
import useSettings from "@/stores/settings";
|
||||
|
||||
const settings = useSettings;
|
||||
|
||||
const folder_list_mode: Setting = {
|
||||
title: "Display folders in list mode",
|
||||
type: SettingType.binary,
|
||||
state: () => settings().folder_list_mode,
|
||||
action: () => settings().toggleFolderListMode(),
|
||||
};
|
||||
|
||||
export default [folder_list_mode];
|
||||
@@ -12,6 +12,8 @@ import separators from "./separators";
|
||||
import tracks from "./tracks";
|
||||
import circularArtistImg from "./circular-artist-img";
|
||||
import quickactions from "./quickactions";
|
||||
import layout from "./layout";
|
||||
import folderlistmode from "./folderlistmode";
|
||||
|
||||
const npStrings = strings.nowPlayingStrings;
|
||||
const rootRootStrings = strings.manageRootDirsStrings;
|
||||
@@ -26,16 +28,19 @@ export default {
|
||||
title: "UI Settings",
|
||||
desc: "Settings for various parts of the user interface.",
|
||||
settings: [
|
||||
...layout,
|
||||
...extendWidth,
|
||||
...sidebarSettings,
|
||||
circularArtistImg,
|
||||
...contextChildrenShowMode,
|
||||
...folderlistmode,
|
||||
],
|
||||
},
|
||||
{
|
||||
title: npStrings.title,
|
||||
desc: npStrings.desc,
|
||||
settings: [...nowPlaying],
|
||||
show_if: () => !useSettingsStore().is_alt_layout,
|
||||
},
|
||||
{
|
||||
title: rootRootStrings.title,
|
||||
@@ -66,4 +71,4 @@ export default {
|
||||
],
|
||||
} as SettingCategory;
|
||||
|
||||
// ENHANCEMENT: Decouple components from Group.vue and pass them as part of the Setting interface (maybe?)
|
||||
// ENHANCEMENT: Decouple components from Group.vue and pass them as part of the Setting interface (maybe?)
|
||||
|
||||
16
src/settings/general/layout.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { SettingType } from "../enums";
|
||||
import { Setting } from "@/interfaces/settings";
|
||||
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
|
||||
const settings = useSettingsStore;
|
||||
|
||||
const use_alt_layout: Setting = {
|
||||
title: "Use no sidebar layout",
|
||||
type: SettingType.binary,
|
||||
state: () => settings().is_alt_layout,
|
||||
action: () => settings().toggleLayout(),
|
||||
new: true,
|
||||
};
|
||||
|
||||
export default [use_alt_layout];
|
||||
@@ -11,6 +11,7 @@ const use_sidebar: Setting = {
|
||||
type: SettingType.binary,
|
||||
state: () => settings().use_sidebar,
|
||||
action: () => settings().toggleDisableSidebar(),
|
||||
show_if: () => !settings().is_alt_layout,
|
||||
};
|
||||
|
||||
export default [use_sidebar];
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
import useQueue from "./queue";
|
||||
import useTabs from "./tabs";
|
||||
import useLyricsPlugin from "./plugins/lyrics";
|
||||
import useQueue from "./queue";
|
||||
import useSettings from "./settings";
|
||||
|
||||
import { LyricsLine } from "@/interfaces";
|
||||
import { checkExists, getLyrics } from "@/requests/lyrics";
|
||||
import { Routes, router } from "@/router";
|
||||
|
||||
// a custom error class called HasNoSyncedLyricsError
|
||||
class HasUnSyncedLyricsError extends Error {
|
||||
@@ -87,7 +87,7 @@ export default defineStore("lyrics", {
|
||||
});
|
||||
},
|
||||
scrollToContainerTop() {
|
||||
const container = document.getElementById("sidelyrics");
|
||||
const container = document.getElementById("lyricscontent");
|
||||
|
||||
if (container) {
|
||||
container.scroll({
|
||||
@@ -97,9 +97,7 @@ export default defineStore("lyrics", {
|
||||
}
|
||||
},
|
||||
checkExists(filepath: string, trackhash: string) {
|
||||
const tabs = useTabs();
|
||||
|
||||
if (tabs.nowplaying !== tabs.tabs.lyrics) {
|
||||
if (router.currentRoute.value.name !== Routes.Lyrics) {
|
||||
this.lyrics = <LyricsLine[]>[];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { router, Routes } from "@/router";
|
||||
|
||||
import useColors from "./colors";
|
||||
import useLyrics from "./lyrics";
|
||||
@@ -7,7 +8,6 @@ import { NotifType, useNotifStore } from "./notification";
|
||||
import useQueue from "./queue";
|
||||
import useTracklist from "./queue/tracklist";
|
||||
import useSettings from "./settings";
|
||||
import useTabs from "./tabs";
|
||||
import useTracker from "./tracker";
|
||||
|
||||
import { paths } from "@/config";
|
||||
@@ -23,7 +23,6 @@ export function getUrl(filepath: string, trackhash: string) {
|
||||
let audio = new Audio();
|
||||
|
||||
export const usePlayer = defineStore("player", () => {
|
||||
const tabs = useTabs();
|
||||
const queue = useQueue();
|
||||
const colors = useColors();
|
||||
const lyrics = useLyrics();
|
||||
@@ -110,8 +109,14 @@ export const usePlayer = defineStore("player", () => {
|
||||
if (queue.currentindex !== tracklist.tracklist.length - 1) {
|
||||
if (!queue.playing) return;
|
||||
|
||||
// if (queue.currenttrack.trackhash !== track.trackhash) return;
|
||||
let onErrorPrevTrackHash: string = queue.currenttrackhash;
|
||||
|
||||
setTimeout(() => {
|
||||
// if another track has been played, don't play next track
|
||||
if (queue.currenttrackhash !== onErrorPrevTrackHash) {
|
||||
return;
|
||||
}
|
||||
|
||||
queue.playNext();
|
||||
}, 3000);
|
||||
return;
|
||||
@@ -153,7 +158,7 @@ export const usePlayer = defineStore("player", () => {
|
||||
updateMediaNotif();
|
||||
colors.setTheme1Color(paths.images.thumb.small + queue.currenttrack.image);
|
||||
|
||||
if (tabs.nowplaying == tabs.tabs.lyrics) {
|
||||
if (router.currentRoute.value.name == Routes.Lyrics) {
|
||||
return lyrics.getLyrics();
|
||||
}
|
||||
|
||||
@@ -190,7 +195,8 @@ export const usePlayer = defineStore("player", () => {
|
||||
};
|
||||
|
||||
const updateLyricsPosition = () => {
|
||||
if (!lyrics.exists || tabs.nowplaying !== tabs.tabs.lyrics) return;
|
||||
if (!lyrics.exists || router.currentRoute.value.name !== Routes.Lyrics)
|
||||
return;
|
||||
|
||||
const millis = Math.round(audio.currentTime * 1000);
|
||||
const diff = lyrics.nextLineTime - millis;
|
||||
@@ -384,10 +390,6 @@ export const usePlayer = defineStore("player", () => {
|
||||
|
||||
audio.src = uri;
|
||||
|
||||
// when progress bar is focused, changing a track will trigger the
|
||||
// @change event which will in turn seek the current track
|
||||
// to the previous' currentTime
|
||||
document.getElementById("progress")?.blur();
|
||||
clearNextAudioData();
|
||||
assignEventHandlers(audio);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { defineStore } from "pinia";
|
||||
import { router, Routes } from "@/router";
|
||||
|
||||
import { favType } from "@/enums";
|
||||
import { Track } from "@/interfaces";
|
||||
@@ -6,7 +7,6 @@ import useInterface from "./interface";
|
||||
import { isFavorite } from "@/requests/favorite";
|
||||
import updateMediaNotif from "@/helpers/mediaNotification";
|
||||
|
||||
import useTabs from "./tabs";
|
||||
import useLyrics from "./lyrics";
|
||||
import useSettings from "./settings";
|
||||
import useTracklist from "./queue/tracklist";
|
||||
@@ -105,7 +105,7 @@ export default defineStore("Queue", {
|
||||
const lyrics = useLyrics();
|
||||
|
||||
if (audio.currentTime > 3) {
|
||||
audio.currentTime = 0;
|
||||
this.seek(0);
|
||||
lyrics.setCurrentLine(-1);
|
||||
return;
|
||||
}
|
||||
@@ -117,7 +117,6 @@ export default defineStore("Queue", {
|
||||
this.currentindex = this.nextindex;
|
||||
},
|
||||
seek(pos: number) {
|
||||
const tabs = useTabs();
|
||||
const lyrics = useLyrics();
|
||||
|
||||
try {
|
||||
@@ -129,7 +128,7 @@ export default defineStore("Queue", {
|
||||
}
|
||||
}
|
||||
|
||||
if (tabs.nowplaying == tabs.tabs.lyrics) {
|
||||
if (router.currentRoute.value.name == Routes.Lyrics) {
|
||||
const line = lyrics.calculateCurrentLine();
|
||||
lyrics.setCurrentLine(line);
|
||||
}
|
||||
|
||||
@@ -14,16 +14,20 @@ import {
|
||||
searchTopResults,
|
||||
searchTracks,
|
||||
} from "@/requests/searchMusic";
|
||||
import useTabStore from "./tabs";
|
||||
import useLoaderStore from "./loader";
|
||||
|
||||
import useTabs from "./tabs";
|
||||
import useLoader from "./loader";
|
||||
import useSettings from "./settings";
|
||||
import { maxAbumCards } from "./content-width";
|
||||
|
||||
import { Album, Artist, Playlist, Track } from "../interfaces";
|
||||
|
||||
export default defineStore("search", () => {
|
||||
const query = ref("");
|
||||
const settings = useSettings();
|
||||
const route = computed(() => router.currentRoute.value);
|
||||
const debouncedQuery = useDebounce(query, 500);
|
||||
const { startLoading, stopLoading } = useLoaderStore();
|
||||
const { startLoading, stopLoading } = useLoader();
|
||||
|
||||
const currentTab = ref("top");
|
||||
const RESULT_COUNT = 30;
|
||||
@@ -153,6 +157,16 @@ export default defineStore("search", () => {
|
||||
watch(
|
||||
() => debouncedQuery.value,
|
||||
(newQuery) => {
|
||||
if (!settings.use_sidebar && route.value.name !== Routes.search) {
|
||||
router.push({
|
||||
name: Routes.search,
|
||||
params: {
|
||||
page: "top",
|
||||
},
|
||||
query: { q: newQuery },
|
||||
});
|
||||
}
|
||||
|
||||
if (route.value.name === Routes.search) {
|
||||
router.replace({
|
||||
name: Routes.search,
|
||||
@@ -162,13 +176,14 @@ export default defineStore("search", () => {
|
||||
query: { q: newQuery },
|
||||
});
|
||||
}
|
||||
|
||||
// reset all counters
|
||||
for (const key in loadCounter) {
|
||||
// @ts-ignore
|
||||
loadCounter[key] = 0;
|
||||
}
|
||||
|
||||
const tabs = useTabStore();
|
||||
const tabs = useTabs();
|
||||
|
||||
if (route.value.name !== Routes.search && tabs.current !== "search") {
|
||||
tabs.switchToSearch();
|
||||
|
||||
@@ -3,7 +3,9 @@ import { defineStore } from "pinia";
|
||||
import { xxl } from "@/composables/useBreakpoints";
|
||||
import { DBSettings, contextChildrenShowMode } from "@/enums";
|
||||
import { pluginSetActive, updatePluginSettings } from "@/requests/plugins";
|
||||
|
||||
import { usePlayer } from "@/stores/player";
|
||||
import { content_width } from "../content-width";
|
||||
|
||||
export default defineStore("settings", {
|
||||
state: () => ({
|
||||
@@ -30,7 +32,7 @@ export default defineStore("settings", {
|
||||
separators: <string[]>[],
|
||||
|
||||
// client
|
||||
useCircularArtistImg: false,
|
||||
useCircularArtistImg: true,
|
||||
|
||||
// plugins
|
||||
use_lyrics_plugin: <boolean | undefined>false,
|
||||
@@ -43,6 +45,9 @@ export default defineStore("settings", {
|
||||
use_silence_skip: true,
|
||||
use_crossfade: false,
|
||||
crossfade_duration: 2000, // milliseconds
|
||||
|
||||
// layout
|
||||
layout: "",
|
||||
}),
|
||||
actions: {
|
||||
mapDbSettings(settings: DBSettings) {
|
||||
@@ -75,6 +80,11 @@ export default defineStore("settings", {
|
||||
},
|
||||
// sidebar 👇
|
||||
toggleDisableSidebar() {
|
||||
if (this.is_alt_layout) {
|
||||
this.use_sidebar = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.use_sidebar = !this.use_sidebar;
|
||||
},
|
||||
toggleExtendWidth() {
|
||||
@@ -194,10 +204,21 @@ export default defineStore("settings", {
|
||||
setCrossfadeDuration(duration: number) {
|
||||
this.crossfade_duration = duration * 1000;
|
||||
},
|
||||
toggleLayout() {
|
||||
if (this.layout == "") {
|
||||
this.layout = "alternate";
|
||||
this.use_sidebar = false;
|
||||
this.use_np_img = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.layout = "";
|
||||
this.use_np_img = true;
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
can_extend_width(): boolean {
|
||||
return xxl.value;
|
||||
return this.is_default_layout && xxl.value;
|
||||
},
|
||||
no_repeat(): boolean {
|
||||
return !this.repeat_all && !this.repeat_one;
|
||||
@@ -208,6 +229,9 @@ export default defineStore("settings", {
|
||||
crossfade_on(): boolean {
|
||||
return this.use_crossfade && this.crossfade_duration > 0;
|
||||
},
|
||||
is_default_layout: (state) => state.layout === "",
|
||||
is_alt_layout: (state) =>
|
||||
state.layout === "alternate" && content_width.value > 800,
|
||||
},
|
||||
persist: {
|
||||
afterRestore: (context) => {
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
import { focusElemByClass } from "@/utils";
|
||||
import { defineStore } from "pinia";
|
||||
import lyrics from "./lyrics";
|
||||
|
||||
const tablist = {
|
||||
home: "home",
|
||||
queue: "queue",
|
||||
search: "search",
|
||||
lyrics: "lyrics",
|
||||
thumbnail: "thumbnail",
|
||||
};
|
||||
|
||||
export default defineStore("tabs", {
|
||||
state: () => ({
|
||||
tabs: tablist,
|
||||
current: tablist.queue,
|
||||
nowplaying: tablist.thumbnail,
|
||||
}),
|
||||
actions: {
|
||||
changeTab(tab: string) {
|
||||
@@ -34,11 +30,5 @@ export default defineStore("tabs", {
|
||||
switchToHome() {
|
||||
this.changeTab(tablist.home);
|
||||
},
|
||||
npSwitchToLyrics() {
|
||||
this.nowplaying = tablist.lyrics;
|
||||
},
|
||||
npSwitchToThumbnail() {
|
||||
this.nowplaying = tablist.thumbnail;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -121,6 +121,10 @@ export default defineStore(
|
||||
}
|
||||
|
||||
function reassignEventListener() {
|
||||
if (trackhash.value == "") {
|
||||
trackhash.value = queue.currenttrackhash;
|
||||
}
|
||||
|
||||
audio.addEventListener(
|
||||
"timeupdate",
|
||||
throttle(() => {
|
||||
@@ -158,6 +162,7 @@ export default defineStore(
|
||||
setTimestamp,
|
||||
reassignEventListener,
|
||||
changeKey,
|
||||
resetData,
|
||||
};
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,7 +8,10 @@ import useSettings from "../../stores/settings";
|
||||
* @param then_destroy - Specifies whether to destroy the audio element after the cross-fade ends. Default is false.
|
||||
*/
|
||||
export function crossFade({
|
||||
audio, duration = 1000, start_volume = 0, then_destroy = false,
|
||||
audio,
|
||||
duration = 1000,
|
||||
start_volume = 0,
|
||||
then_destroy = false,
|
||||
}: {
|
||||
audio: HTMLAudioElement;
|
||||
duration?: number;
|
||||
@@ -19,6 +22,7 @@ export function crossFade({
|
||||
const { volume, use_crossfade } = useSettings();
|
||||
|
||||
if (audio.muted || duration < 1000 || !use_crossfade) {
|
||||
audio.volume = volume;
|
||||
endCrossfade();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
style="height: 100%"
|
||||
>
|
||||
<DynamicScroller
|
||||
id="album-scroller"
|
||||
style="height: 100%"
|
||||
class="scroller"
|
||||
:min-item-size="64"
|
||||
@@ -30,7 +31,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { computed, nextTick } from "vue";
|
||||
import { onBeforeRouteLeave, onBeforeRouteUpdate, useRoute } from "vue-router";
|
||||
|
||||
import { Track } from "@/interfaces";
|
||||
@@ -219,10 +220,18 @@ function playFromAlbum(index: number) {
|
||||
}
|
||||
|
||||
onBeforeRouteUpdate(async (to) => {
|
||||
await album.fetchTracksAndArtists(to.params.albumhash.toString()).then(() => {
|
||||
album.resetAlbumArtists();
|
||||
album.fetchArtistAlbums();
|
||||
});
|
||||
await album
|
||||
.fetchTracksAndArtists(to.params.albumhash.toString())
|
||||
.then(async () => {
|
||||
album.resetAlbumArtists();
|
||||
album.fetchArtistAlbums();
|
||||
|
||||
await nextTick();
|
||||
|
||||
document.getElementById("album-scroller")?.scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeRouteLeave(() => {
|
||||
|
||||
@@ -60,19 +60,19 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
import { Routes } from "@/router";
|
||||
import { onMounted } from "vue";
|
||||
import { onBeforeRouteLeave, onBeforeRouteUpdate, useRoute } from "vue-router";
|
||||
|
||||
import { discographyAlbumTypes } from "@/enums";
|
||||
import updatePageTitle from "@/utils/updatePageTitle";
|
||||
import useArtistDiscography from "@/stores/pages/artistDiscog";
|
||||
import updatePageTitle from "@/utils/updatePageTitle";
|
||||
|
||||
import AlbumSvg from "@/assets/icons/album.svg";
|
||||
import NoItems from "@/components/shared/NoItems.vue";
|
||||
import AlbumCard from "@/components/shared/AlbumCard.vue";
|
||||
import GenericTabs from "@/components/shared/GenericTabs.vue";
|
||||
import GenericHeader from "@/components/shared/GenericHeader.vue";
|
||||
import GenericTabs from "@/components/shared/GenericTabs.vue";
|
||||
import NoItems from "@/components/shared/NoItems.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const artist = useArtistDiscography();
|
||||
@@ -108,7 +108,7 @@ onBeforeRouteLeave(() => artist.resetStore());
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10.1rem, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
|
||||
gap: 2rem 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div class="artist-page v-scroll-page" style="height: 100%">
|
||||
<DynamicScroller
|
||||
id="artist-scroller"
|
||||
:items="scrollerItems"
|
||||
:min-item-size="64"
|
||||
class="scroller"
|
||||
@@ -25,7 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { computed, nextTick } from "vue";
|
||||
import { onBeforeRouteLeave, onBeforeRouteUpdate, useRoute } from "vue-router";
|
||||
|
||||
import useArtist from "@/stores/pages/artist";
|
||||
@@ -232,7 +233,14 @@ async function handlePlay(index: number) {
|
||||
}
|
||||
|
||||
onBeforeRouteUpdate(async (to) => {
|
||||
await store.getData(to.params.hash as string);
|
||||
// fetch data and scroll to top
|
||||
await store.getData(to.params.hash as string).then(async () => {
|
||||
await nextTick();
|
||||
|
||||
document.getElementById("artist-scroller")?.scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeRouteLeave(() => store.resetAll());
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div
|
||||
class="folder-view v-scroll-page"
|
||||
style="height: 100%"
|
||||
:class="{ isSmall, isMedium }"
|
||||
:class="{ isSmall, isMedium, is_alt_layout }"
|
||||
>
|
||||
<NoItems
|
||||
:flag="folder.tracks.length === 0 && folder.dirs.length === 0"
|
||||
@@ -16,11 +16,16 @@
|
||||
:icon="FolderSvg"
|
||||
/>
|
||||
<DynamicScroller
|
||||
id="contentscroller"
|
||||
:items="scrollerItems"
|
||||
:min-item-size="64"
|
||||
class="scroller"
|
||||
style="height: 100%"
|
||||
>
|
||||
<template v-if="is_alt_layout" #before>
|
||||
<Folder :sub-paths="subPaths" />
|
||||
</template>
|
||||
|
||||
<template #default="{ item, index, active }">
|
||||
<DynamicScrollerItem
|
||||
:item="item"
|
||||
@@ -41,30 +46,55 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, computed } from "vue";
|
||||
import { onBeforeRouteLeave, onBeforeRouteUpdate } from "vue-router";
|
||||
import { onMounted, computed, ref, watch, nextTick } from "vue";
|
||||
import { onBeforeRouteLeave, onBeforeRouteUpdate, useRoute } from "vue-router";
|
||||
|
||||
import useQueue from "@/stores/queue";
|
||||
import useLoader from "@/stores/loader";
|
||||
import useSettings from "@/stores/settings";
|
||||
import useFolder from "@/stores/pages/folder";
|
||||
import useTracklist from "@/stores/queue/tracklist";
|
||||
import { isMedium, isSmall } from "@/stores/content-width";
|
||||
import { content_width, isMedium, isSmall } from "@/stores/content-width";
|
||||
|
||||
import { Track } from "@/interfaces";
|
||||
import { dropSources } from "@/enums";
|
||||
import { createTrackProps } from "@/utils";
|
||||
import { Track, subPath } from "@/interfaces";
|
||||
import updatePageTitle from "@/utils/updatePageTitle";
|
||||
import { createSubPaths, createTrackProps } from "@/utils";
|
||||
|
||||
import FolderSvg from "@/assets/icons/folder.svg";
|
||||
import NoItems from "@/components/shared/NoItems.vue";
|
||||
import SongItem from "@/components/shared/SongItem.vue";
|
||||
import Folder from "@/components/nav/Titles/Folder.vue";
|
||||
import FolderList from "@/components/FolderView/FolderList.vue";
|
||||
import { xl } from "@/composables/useBreakpoints";
|
||||
|
||||
const queue = useQueue();
|
||||
const loader = useLoader();
|
||||
const folder = useFolder();
|
||||
const settings = useSettings();
|
||||
const tracklist = useTracklist();
|
||||
|
||||
const route = useRoute();
|
||||
const subPaths = ref<subPath[]>([]);
|
||||
|
||||
const is_alt_layout = computed(() => settings.is_alt_layout || !xl);
|
||||
|
||||
let oldpath = "";
|
||||
|
||||
const getSubPaths = (newPath: string) => {
|
||||
[oldpath, subPaths.value] = createSubPaths(newPath, oldpath);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => route.params.path,
|
||||
(newPath) => {
|
||||
newPath = newPath as string;
|
||||
if (newPath == undefined) return;
|
||||
|
||||
getSubPaths(newPath);
|
||||
}
|
||||
);
|
||||
|
||||
interface ScrollerItem {
|
||||
id: string | undefined;
|
||||
component: typeof FolderList | typeof SongItem;
|
||||
@@ -117,6 +147,13 @@ onBeforeRouteUpdate((to, from) => {
|
||||
})
|
||||
.then(() => {
|
||||
loader.stopLoading();
|
||||
})
|
||||
.then(async () => {
|
||||
await nextTick();
|
||||
|
||||
document.getElementById("folder-scroller")?.scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,5 +161,24 @@ onBeforeRouteLeave(() => {
|
||||
folder.resetAll();
|
||||
});
|
||||
|
||||
onMounted(() => updatePageTitle("Folders"));
|
||||
onMounted(() => {
|
||||
updatePageTitle("Folders");
|
||||
getSubPaths(route.params.path as string);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.folder-view.is_alt_layout {
|
||||
.scroller {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
.scroller > div.vue-recycle-scroller__slot:first-child {
|
||||
padding: 1rem 0;
|
||||
background-color: $body;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,9 +3,13 @@
|
||||
<GenericHeader>
|
||||
<template #name>Home</template>
|
||||
<template #description>{{ getGreetings("") }}</template>
|
||||
<template #right?> </template>
|
||||
</GenericHeader>
|
||||
<Browse />
|
||||
<RecentItems
|
||||
v-if="home.recentlyPlayed.length"
|
||||
:title="'Recently Played'"
|
||||
:items="home.recentlyPlayed"
|
||||
:play-source="playSources.track"
|
||||
/>
|
||||
<RecentItems
|
||||
v-if="home.recentlyAdded.length"
|
||||
:title="'Recently Added'"
|
||||
@@ -13,27 +17,22 @@
|
||||
:play-source="playSources.recentlyAdded"
|
||||
:route="'/playlist/recentlyadded'"
|
||||
/>
|
||||
<RecentItems
|
||||
v-if="home.recentlyPlayed.length"
|
||||
:title="'Recently Played'"
|
||||
:items="home.recentlyPlayed"
|
||||
:play-source="playSources.track"
|
||||
/>
|
||||
<Browse />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, onMounted } from "vue";
|
||||
import { onBeforeRouteLeave } from "vue-router";
|
||||
|
||||
import useHome from "@/stores/home";
|
||||
import { playSources } from "@/enums";
|
||||
import updatePageTitle from "@/utils/updatePageTitle";
|
||||
import { updateCardWidth } from "@/stores/content-width";
|
||||
import useHome from "@/stores/home";
|
||||
import updatePageTitle from "@/utils/updatePageTitle";
|
||||
|
||||
import Browse from "@/components/HomeView/Browse.vue";
|
||||
import RecentItems from "@/components/shared/CardScroller.vue";
|
||||
import GenericHeader from "@/components/shared/GenericHeader.vue";
|
||||
import { onBeforeRouteLeave } from "vue-router";
|
||||
|
||||
const home = useHome();
|
||||
|
||||
|
||||
@@ -51,17 +51,20 @@ defineProps<{
|
||||
|
||||
<style lang="scss">
|
||||
.lyricsinfo {
|
||||
padding: 2rem 2.5rem 1rem;
|
||||
padding: 2rem 0 1rem 0;
|
||||
font-size: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr max-content;
|
||||
gap: $small;
|
||||
align-items: center;
|
||||
position: sticky;
|
||||
top: -2rem;
|
||||
margin: -2.5rem;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
|
||||
@include tablet-portrait {
|
||||
padding: $medium 0;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 2.5rem;
|
||||
border-radius: $smaller;
|
||||
3
src/views/LyricsView/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import Lyricsview from './main.vue';
|
||||
|
||||
export default Lyricsview;
|
||||
@@ -2,10 +2,10 @@
|
||||
<div class="lyricsview content-page">
|
||||
<div
|
||||
v-if="queue.currenttrack"
|
||||
id="sidelyrics"
|
||||
id="lyricscontent"
|
||||
:style="{ background: bgColor }"
|
||||
class="content-page rounded"
|
||||
@wheel="onScroll"
|
||||
@wheel.passive="onScroll"
|
||||
>
|
||||
<LyricsHead :bg-color="bgColor" />
|
||||
<div v-if="lyrics.synced" class="synced">
|
||||
@@ -48,10 +48,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from "vue";
|
||||
|
||||
import useTabs from "@/stores/tabs";
|
||||
import useQueue from "@/stores/queue";
|
||||
import useLyrics from "@/stores/lyrics";
|
||||
import useColors from "@/stores/colors";
|
||||
import useLyrics from "@/stores/lyrics";
|
||||
import useQueue from "@/stores/queue";
|
||||
import useSettings from "@/stores/settings";
|
||||
|
||||
import { getShift } from "@/utils/colortools/shift";
|
||||
@@ -59,7 +58,6 @@ import { getShift } from "@/utils/colortools/shift";
|
||||
import LyricsHead from "./Head.vue";
|
||||
import PluginFind from "./Plugins/Find.vue";
|
||||
|
||||
const tabs = useTabs();
|
||||
const queue = useQueue();
|
||||
const lyrics = useLyrics();
|
||||
const colors = useColors();
|
||||
@@ -80,7 +78,6 @@ function fetchLyrics() {
|
||||
onMounted(() => {
|
||||
if (!queue.currenttrack) return;
|
||||
fetchLyrics();
|
||||
tabs.npSwitchToLyrics();
|
||||
lyrics.scrollToCurrentLine();
|
||||
});
|
||||
</script>
|
||||
@@ -91,8 +88,8 @@ onMounted(() => {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
#sidelyrics {
|
||||
padding: 2rem 4rem;
|
||||
#lyricscontent {
|
||||
padding: 0 4rem;
|
||||
padding-bottom: 4rem;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
@@ -106,7 +103,7 @@ onMounted(() => {
|
||||
|
||||
@include tablet-portrait {
|
||||
font-size: 2rem !important;
|
||||
padding: $medium 1.5rem;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.nolyrics {
|
||||
@@ -136,7 +133,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
#lyricsline--1 {
|
||||
margin-top: 3.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.opacity_75 {
|
||||
@@ -27,7 +27,6 @@
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
</div>
|
||||
<LyricsBody v-if="$route.params.tab == 'lyrics'" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -41,19 +40,12 @@ import { isMedium, isSmall } from "@/stores/content-width";
|
||||
import Header from "@/components/NowPlaying/Header.vue";
|
||||
import SongItem from "@/components/shared/SongItem.vue";
|
||||
import updatePageTitle from "@/utils/updatePageTitle";
|
||||
import Tabs from "@/components/NowPlaying/Tabs.vue";
|
||||
import LyricsBody from "./Lyrics.vue";
|
||||
|
||||
const header: ScrollerItem = {
|
||||
id: "header",
|
||||
component: Header,
|
||||
};
|
||||
|
||||
const tabs: ScrollerItem = {
|
||||
id: "tabs",
|
||||
component: Tabs,
|
||||
};
|
||||
|
||||
const queue = useQueueStore();
|
||||
const store = useTracklist();
|
||||
|
||||
@@ -62,7 +54,7 @@ function playFromQueue(index: number) {
|
||||
}
|
||||
|
||||
const scrollerItems = computed(() => {
|
||||
const items = [header, tabs];
|
||||
const items = [header];
|
||||
|
||||
const trackComponents = store.tracklist.map((track, index) => {
|
||||
track.index = index; // used in context menu to remove from queue
|
||||
|
||||
@@ -50,16 +50,16 @@
|
||||
import { debouncedRef } from "@vueuse/core";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
|
||||
import { useFuse } from "@/utils";
|
||||
import usePStore from "@/stores/pages/playlists";
|
||||
import updatePageTitle from "@/utils/updatePageTitle";
|
||||
import { isSmall } from "@/stores/content-width";
|
||||
import usePStore from "@/stores/pages/playlists";
|
||||
import { useFuse } from "@/utils";
|
||||
import updatePageTitle from "@/utils/updatePageTitle";
|
||||
|
||||
import NoItems from "@/components/shared/NoItems.vue";
|
||||
import Header from "@/components/shared/GenericHeader.vue";
|
||||
import PlaylistSvg from "@/assets/icons/playlist-1.svg";
|
||||
import PlaylistCardGroup from "@/components/PlaylistsList/PlaylistCardGroup.vue";
|
||||
import PlusSvg from "@/assets/icons/plus.svg";
|
||||
import PlaylistCardGroup from "@/components/PlaylistsList/PlaylistCardGroup.vue";
|
||||
import Header from "@/components/shared/GenericHeader.vue";
|
||||
import NoItems from "@/components/shared/NoItems.vue";
|
||||
import useModalStore from "@/stores/modal";
|
||||
|
||||
const pStore = usePStore();
|
||||
@@ -79,10 +79,6 @@ const pinnedPlaylists = computed(() => {
|
||||
|
||||
onMounted(() => {
|
||||
updatePageTitle("Playlists");
|
||||
|
||||
// focus on search input
|
||||
const elem = document.getElementById("playlistsearch");
|
||||
if (elem) elem.focus();
|
||||
});
|
||||
|
||||
const playlists = computed(() => {
|
||||
@@ -109,8 +105,8 @@ const playlists = computed(() => {
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(10.1rem, 1fr));
|
||||
gap: 2.5rem 1.75rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
|
||||
gap: 2.5rem 1.5rem;
|
||||
}
|
||||
|
||||
#playlistsearch {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="search-view">
|
||||
<div v-if="isMobile" class="buttons-area">
|
||||
<div class="search-view" :class="{ is_alt_layout }">
|
||||
<div v-if="is_alt_layout" class="buttons-area">
|
||||
<Tabs
|
||||
:tabs="pages"
|
||||
:current-tab="($route.params.page as string)"
|
||||
@@ -24,16 +24,22 @@ import { useRoute } from "vue-router";
|
||||
import { computed, onMounted } from "vue";
|
||||
|
||||
import useSearchStore from "@/stores/search";
|
||||
import { isMobile } from "@/stores/content-width";
|
||||
import useSettings from "@/stores/settings";
|
||||
import { content_width } from "@/stores/content-width";
|
||||
import updatePageTitle from "@/utils/updatePageTitle";
|
||||
|
||||
import CardGridPage from "./CardGridPage.vue";
|
||||
import TracksPage from "./tracks.vue";
|
||||
import TopResults from "./TopResults.vue";
|
||||
import CardGridPage from "./CardGridPage.vue";
|
||||
import Tabs from "@/components/RightSideBar/Search/TabsWrapper.vue";
|
||||
|
||||
const settings = useSettings();
|
||||
const search = useSearchStore();
|
||||
|
||||
const is_alt_layout = computed(
|
||||
() => settings.is_alt_layout || content_width.value < 800
|
||||
);
|
||||
|
||||
const pages = ["top", "tracks", "albums", "artists"];
|
||||
|
||||
const route = useRoute();
|
||||
@@ -79,13 +85,8 @@ onMounted(() => {
|
||||
<style lang="scss">
|
||||
.search-view {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
position: relative;
|
||||
|
||||
@include allPhones {
|
||||
display: grid;
|
||||
grid-template-rows: 3.5rem 1fr;
|
||||
}
|
||||
display: grid;
|
||||
|
||||
.buttons-area {
|
||||
position: relative;
|
||||
@@ -95,5 +96,15 @@ onMounted(() => {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.is_alt_layout {
|
||||
grid-template-rows: 2rem 1fr;
|
||||
gap: 1rem;
|
||||
padding-top: 1rem;
|
||||
|
||||
.vue-recycle-scroller {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -22,7 +22,7 @@ export default defineConfig({
|
||||
name: "Swing Music",
|
||||
short_name: "Swing Music",
|
||||
description: "Swing Music",
|
||||
theme_color: "#ff2171",
|
||||
theme_color: "#111",
|
||||
icons: [
|
||||
{
|
||||
src: "pwa-192x192.png",
|
||||
|
||||