Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
293e95c5b0 | ||
|
|
302095ef80 | ||
|
|
feb35afa9a | ||
|
|
6b925a0434 | ||
|
|
4211ccc685 | ||
|
|
9c982283fb | ||
|
|
888958db54 | ||
|
|
cb3e9ec2cd | ||
|
|
f7e604f11b | ||
|
|
1b36b96239 | ||
|
|
ee3ef6c1e6 | ||
|
|
83bd063bea | ||
|
|
12531edec9 | ||
|
|
7edd501d0f | ||
|
|
c7036b3a5d | ||
|
|
3075517af9 | ||
|
|
e8f0bc6b8b | ||
|
|
ded3a48e0b | ||
|
|
a04a3c4fe4 | ||
|
|
4aaf70ba68 | ||
|
|
fa176cb027 | ||
|
|
c4ce344487 | ||
|
|
f2c7cccba1 | ||
|
|
901406a337 | ||
|
|
4b48b13561 | ||
|
|
10b29f6349 | ||
|
|
b24d833d12 | ||
|
|
9004c02898 | ||
|
|
409edba74c | ||
|
|
2f1c07f786 | ||
|
|
278e73745a | ||
|
|
3652432e0b | ||
|
|
67ad2ef206 | ||
|
|
f2e157a746 | ||
|
|
606515ffd5 | ||
|
|
6ff67e5f94 | ||
|
|
5f037fc647 | ||
|
|
d1c62f701e | ||
|
|
b2bef03373 | ||
|
|
49fe308da0 | ||
|
|
bce2772dcb | ||
|
|
dd0b9d6d61 | ||
|
|
1e90298f72 | ||
|
|
7f4385f8cb | ||
|
|
48f2a73291 | ||
|
|
8cf33089a2 | ||
|
|
c16be33d40 | ||
|
|
a9691a2a25 |
18
public/workers/silence.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
onmessage = async (e) => {
|
||||||
|
const { ending_file, starting_file } = e.data;
|
||||||
|
|
||||||
|
const is_dev = location.port === "5173";
|
||||||
|
const base_url = is_dev ? "http://localhost:1980" : location.origin;
|
||||||
|
const url = base_url + "/file/silence";
|
||||||
|
|
||||||
|
const res = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ end: ending_file, start: starting_file }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
postMessage(data);
|
||||||
|
};
|
||||||
43
src/App.vue
@@ -6,17 +6,27 @@
|
|||||||
<section
|
<section
|
||||||
id="app-grid"
|
id="app-grid"
|
||||||
:class="{
|
:class="{
|
||||||
noSidebar: !settings.use_sidebar || !xl,
|
useSidebar: settings.use_sidebar && xl,
|
||||||
NoSideBorders: !xxl,
|
NoSideBorders: settings.is_alt_layout || !xxl,
|
||||||
extendWidth: settings.extend_width && settings.can_extend_width,
|
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 />
|
<NavBar />
|
||||||
<div id="acontent" ref="appcontent" v-element-size="updateContentElemSize">
|
<div id="acontent" v-element-size="updateContentElemSize">
|
||||||
|
<div id="contentresizer" ref="appcontent"></div>
|
||||||
<BalancerProvider>
|
<BalancerProvider>
|
||||||
<router-view />
|
<RouterView />
|
||||||
</BalancerProvider>
|
</BalancerProvider>
|
||||||
</div>
|
</div>
|
||||||
<RightSideBar v-if="settings.use_sidebar && xl" />
|
<RightSideBar v-if="settings.use_sidebar && xl" />
|
||||||
@@ -81,8 +91,6 @@ router.afterEach(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onStartTyping(() => {
|
onStartTyping(() => {
|
||||||
if (isMobile.value) return;
|
|
||||||
|
|
||||||
const elem = document.getElementById("globalsearch") as HTMLInputElement;
|
const elem = document.getElementById("globalsearch") as HTMLInputElement;
|
||||||
elem.focus();
|
elem.focus();
|
||||||
elem.value = "";
|
elem.value = "";
|
||||||
@@ -103,8 +111,9 @@ function updateContentElemSize({
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}) {
|
}) {
|
||||||
// 1372 is the maxwidth of the #acontent. see app-grid.scss > $maxwidth
|
// 1572 is the maxwidth of the #acontent. see app-grid.scss > $maxwidth
|
||||||
const elem_width = Math.min(1372, appcontent.value?.offsetWidth || 0);
|
const elem_width = appcontent.value?.offsetWidth || 0;
|
||||||
|
|
||||||
content_width.value = elem_width;
|
content_width.value = elem_width;
|
||||||
content_height.value = height;
|
content_height.value = height;
|
||||||
updateCardWidth();
|
updateCardWidth();
|
||||||
@@ -147,15 +156,13 @@ onMounted(() => {
|
|||||||
settings.mapDbSettings(data);
|
settings.mapDbSettings(data);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.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>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<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>
|
</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">
|
<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="white"/>
|
<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="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="currentColor"/>
|
||||||
</svg>
|
</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"?>
|
<?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">
|
<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">
|
<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>
|
</g>
|
||||||
</svg>
|
</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"?>
|
<?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">
|
<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">
|
<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>
|
</g>
|
||||||
</svg>
|
</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"?>
|
<?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">
|
<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">
|
<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>
|
</g>
|
||||||
</svg>
|
</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 {
|
#app-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr 29rem;
|
// grid-template-columns: min-content 1fr 29rem;
|
||||||
grid-template-rows: max-content 1fr 5rem;
|
grid-template-columns: min-content 1fr;
|
||||||
|
grid-template-rows: $navheight 1fr $navheight;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"l-sidebar nav r-sidebar"
|
"l-sidebar nav"
|
||||||
"l-sidebar content r-sidebar"
|
"l-sidebar content"
|
||||||
"bottombar bottombar bottombar";
|
"bottombar bottombar";
|
||||||
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: $g-border;
|
border: $g-border;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
@include for-desktop-down {
|
#contentresizer {
|
||||||
grid-template-columns: min-content 1fr 24rem;
|
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;
|
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 {
|
.content-page {
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
padding-left: $padleft;
|
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 =======
|
// ====== 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 {
|
#app-grid.extendWidth {
|
||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
border-left: none;
|
border-left: none;
|
||||||
@@ -72,31 +166,26 @@ $g-border: solid 1px $gray5;
|
|||||||
max-width: 100% !important;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#app-grid.noSidebar {
|
#app-grid.useSidebar {
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: min-content 1fr 28rem;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
"l-sidebar nav"
|
"l-sidebar nav r-sidebar"
|
||||||
"l-sidebar content"
|
"l-sidebar content r-sidebar"
|
||||||
"bottombar bottombar";
|
"bottombar bottombar bottombar";
|
||||||
|
|
||||||
#acontent {
|
@include for-desktop-down {
|
||||||
margin-right: 0 !important;
|
grid-template-columns: min-content 1fr 24rem;
|
||||||
// padding-right: $medium !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@include allPhones {
|
#acontent {
|
||||||
grid-template-columns: 1fr;
|
// margin-right: 0 !important;
|
||||||
grid-template-rows: max-content 1fr 9.5rem;
|
// padding-right: $medium !important;
|
||||||
grid-template-areas:
|
|
||||||
"nav"
|
|
||||||
"content"
|
|
||||||
"bottombar";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#app-grid.NoSideBorders {
|
#app-grid.NoSideBorders {
|
||||||
border-right: none;
|
border-right: none !important;
|
||||||
border-left: none;
|
border-left: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-scroll-page {
|
.v-scroll-page {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ input[type="search"]::-webkit-search-cancel-button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
font-size: 2rem;
|
font-size: 1.5rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ button {
|
|||||||
|
|
||||||
.cards {
|
.cards {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(10.1rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
scroll-snap-type: x mandatory;
|
scroll-snap-type: x mandatory;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@@ -56,15 +56,26 @@ $overshoot: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|||||||
|
|
||||||
$separator: $gray4;
|
$separator: $gray4;
|
||||||
|
|
||||||
$maxwidth: 1372px;
|
$margright: 0;
|
||||||
|
$padbottom: 4rem;
|
||||||
|
$maxwidth: 1438px;
|
||||||
|
$navheight: 5rem;
|
||||||
|
$cardwidth: 10.75rem;
|
||||||
$maxpadleft: 5rem;
|
$maxpadleft: 5rem;
|
||||||
|
|
||||||
|
|
||||||
|
$alt_layout_pad: max(2rem, calc((100% - 1280px) / 2));
|
||||||
|
|
||||||
$maxpadright: calc(100% - $maxwidth);
|
$maxpadright: calc(100% - $maxwidth);
|
||||||
|
|
||||||
$padbottom: 4rem;
|
|
||||||
$padleft: clamp(2rem, $maxpadright, $maxpadleft);
|
$padleft: clamp(2rem, $maxpadright, $maxpadleft);
|
||||||
$padright: clamp(
|
|
||||||
2rem,
|
// 👇 fixed width with content floating to the left.
|
||||||
max($maxpadright, 5rem),
|
// $padright: clamp(
|
||||||
calc($maxpadright + $maxpadleft)
|
// 1rem,
|
||||||
);
|
// max($maxpadright, 5rem),
|
||||||
|
// calc($maxpadright + $maxpadleft)
|
||||||
|
// );
|
||||||
|
$padright: $padleft;
|
||||||
|
|
||||||
$margright: calc(0rem - $padright);
|
$margright: calc(0rem - $padright);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ const props = defineProps<{
|
|||||||
show_text?: boolean;
|
show_text?: boolean;
|
||||||
fetch_callback: () => Promise<void>;
|
fetch_callback: () => Promise<void>;
|
||||||
reset_callback?: () => Promise<void>;
|
reset_callback?: () => Promise<void>;
|
||||||
|
outside_route?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const update = async () => {
|
const update = async () => {
|
||||||
@@ -26,8 +27,9 @@ onMounted(async () => {
|
|||||||
props.fetch_callback().then(update);
|
props.fetch_callback().then(update);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeRouteUpdate(() => {
|
!props.outside_route &&
|
||||||
if (!props.reset_callback) return;
|
onBeforeRouteUpdate(() => {
|
||||||
props.reset_callback().then(update);
|
if (!props.reset_callback) return;
|
||||||
});
|
props.reset_callback().then(update);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -38,7 +38,12 @@
|
|||||||
class="gradient"
|
class="gradient"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundImage: colors.bg
|
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} ${gradientWidth}%,
|
||||||
${colors.bg} 100%)`
|
${colors.bg} 100%)`
|
||||||
: '',
|
: '',
|
||||||
@@ -49,7 +54,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { Ref, computed, onMounted, ref } from "vue";
|
||||||
import { onBeforeRouteUpdate } from "vue-router";
|
import { onBeforeRouteUpdate } from "vue-router";
|
||||||
import { useElementSize } from "@vueuse/core";
|
import { useElementSize } from "@vueuse/core";
|
||||||
import useSettingsStore from "@/stores/settings";
|
import useSettingsStore from "@/stores/settings";
|
||||||
@@ -60,7 +65,9 @@ import updatePageTitle from "@/utils/updatePageTitle";
|
|||||||
import Info from "./HeaderComponents/Info.vue";
|
import Info from "./HeaderComponents/Info.vue";
|
||||||
import useArtistStore from "@/stores/pages/artist";
|
import useArtistStore from "@/stores/pages/artist";
|
||||||
import { getShift } from "@/utils/colortools/shift";
|
import { getShift } from "@/utils/colortools/shift";
|
||||||
|
import { isSmall } from "@/stores/content-width";
|
||||||
|
|
||||||
|
const image_width_px = 450;
|
||||||
const store = useArtistStore();
|
const store = useArtistStore();
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
|
|
||||||
@@ -77,13 +84,16 @@ function updateTitle() {
|
|||||||
onMounted(() => updateTitle());
|
onMounted(() => updateTitle());
|
||||||
onBeforeRouteUpdate(() => updateTitle());
|
onBeforeRouteUpdate(() => updateTitle());
|
||||||
|
|
||||||
const artistheader = ref(null);
|
const artistheader: Ref<HTMLElement | null> = ref(null);
|
||||||
const { width } = useElementSize(artistheader);
|
const { width } = useElementSize(artistheader);
|
||||||
|
|
||||||
|
const gradientTransparentWidth = computed(() =>
|
||||||
|
Math.floor((image_width_px / (width.value || 1)) * 100)
|
||||||
|
);
|
||||||
|
|
||||||
const isSmallPhone = computed(() => width.value <= 550);
|
const isSmallPhone = computed(() => width.value <= 550);
|
||||||
const useCircularImage = computed(
|
const useCircularImage = computed(
|
||||||
() =>
|
() => !isSmallPhone.value && settings.useCircularArtistImg
|
||||||
!isSmallPhone.value && (settings.useCircularArtistImg || width.value >= 995)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const gradientDirection = computed(() =>
|
const gradientDirection = computed(() =>
|
||||||
@@ -91,7 +101,7 @@ const gradientDirection = computed(() =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
const gradientWidth = computed(() => {
|
const gradientWidth = computed(() => {
|
||||||
return isSmallPhone.value ? "80" : "50";
|
return isSmallPhone.value ? "80" : Math.min(gradientTransparentWidth.value, 50);
|
||||||
});
|
});
|
||||||
|
|
||||||
const containerHeight = computed(() => {
|
const containerHeight = computed(() => {
|
||||||
@@ -109,7 +119,7 @@ const containerHeight = computed(() => {
|
|||||||
|
|
||||||
.artist-page-header {
|
.artist-page-header {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr minmax(min-content, 50%);
|
grid-template-columns: 1fr 450px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.artist-img {
|
.artist-img {
|
||||||
@@ -123,7 +133,6 @@ const containerHeight = computed(() => {
|
|||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: 0% 20%;
|
object-position: 0% 20%;
|
||||||
float: right;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
<div
|
<div
|
||||||
class="artist-name"
|
class="artist-name"
|
||||||
:class="`${useCircularImage ? 'ellip' : 'ellip2'}`"
|
:class="`${useCircularImage ? 'ellip' : 'ellip2'}`"
|
||||||
|
:title="artist.name"
|
||||||
>
|
>
|
||||||
{{ artist.name }}
|
{{ artist.name }}
|
||||||
</div>
|
</div>
|
||||||
@@ -77,7 +78,7 @@ defineProps<{
|
|||||||
.artist-name {
|
.artist-name {
|
||||||
font-size: 3.5rem;
|
font-size: 3.5rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
word-wrap: break-word;
|
word-wrap: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats {
|
.stats {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
v-for="(song, index) in tracks"
|
v-for="(song, index) in tracks"
|
||||||
:key="index"
|
:key="index"
|
||||||
:track="song"
|
:track="song"
|
||||||
:index="index + 1"
|
:index="total ? total - index : index + 1"
|
||||||
:source="source"
|
:source="source"
|
||||||
@playThis="playHandler(index)"
|
@playThis="playHandler(index)"
|
||||||
/>
|
/>
|
||||||
@@ -31,6 +31,7 @@ defineProps<{
|
|||||||
title: string;
|
title: string;
|
||||||
playHandler: (index: number) => void;
|
playHandler: (index: number) => void;
|
||||||
source: dropSources;
|
source: dropSources;
|
||||||
|
total?: number;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="b-bar">
|
<div
|
||||||
|
class="b-bar"
|
||||||
|
:style="{
|
||||||
|
padding: `${settings.is_default_layout ? '0 1rem 0 1rem' : ''}`,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<LeftGroup @handleFav="handleFav" />
|
<LeftGroup @handleFav="handleFav" />
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<div v-if="!isMobile" class="with-time">
|
<div v-if="!isMobile" class="with-time">
|
||||||
@@ -32,6 +37,7 @@ import { isMobile } from "@/stores/content-width";
|
|||||||
import { formatSeconds } from "@/utils";
|
import { formatSeconds } from "@/utils";
|
||||||
|
|
||||||
import useQStore from "@/stores/queue";
|
import useQStore from "@/stores/queue";
|
||||||
|
import useSettings from "@/stores/settings";
|
||||||
|
|
||||||
import HotKeys from "@/components/LeftSidebar/NP/HotKeys.vue";
|
import HotKeys from "@/components/LeftSidebar/NP/HotKeys.vue";
|
||||||
import Progress from "@/components/LeftSidebar/NP/Progress.vue";
|
import Progress from "@/components/LeftSidebar/NP/Progress.vue";
|
||||||
@@ -41,6 +47,7 @@ import LeftGroup from "./Left.vue";
|
|||||||
import RightGroup from "./Right.vue";
|
import RightGroup from "./Right.vue";
|
||||||
|
|
||||||
const queue = useQStore();
|
const queue = useQStore();
|
||||||
|
const settings = useSettings();
|
||||||
|
|
||||||
function handleFav() {
|
function handleFav() {
|
||||||
favoriteHandler(
|
favoriteHandler(
|
||||||
@@ -60,7 +67,6 @@ function handleFav() {
|
|||||||
grid-template-columns: 1fr max-content 1fr;
|
grid-template-columns: 1fr max-content 1fr;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding: 0 1rem;
|
|
||||||
|
|
||||||
@include allPhones {
|
@include allPhones {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -88,6 +94,7 @@ function handleFav() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
// INFO: Show the progress bar when hovering over the bottom bar
|
||||||
#progress::-moz-range-thumb {
|
#progress::-moz-range-thumb {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
@@ -102,6 +109,11 @@ function handleFav() {
|
|||||||
height: 1rem;
|
height: 1rem;
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// INFO: Also show the expand button
|
||||||
|
.np-image .expandicon {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.with-time {
|
.with-time {
|
||||||
|
|||||||
@@ -15,12 +15,12 @@
|
|||||||
},
|
},
|
||||||
replace: true,
|
replace: true,
|
||||||
}"
|
}"
|
||||||
|
class="np-image rounded-sm no-scroll"
|
||||||
>
|
>
|
||||||
<img
|
<img :src="paths.images.thumb.small + queue.currenttrack?.image" alt="" />
|
||||||
class="rounded-sm"
|
<div class="expandicon">
|
||||||
:src="paths.images.thumb.small + queue.currenttrack?.image"
|
<ExpandSvg />
|
||||||
alt=""
|
</div>
|
||||||
/>
|
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<div
|
<div
|
||||||
class="track-info"
|
class="track-info"
|
||||||
@@ -62,6 +62,7 @@ import HeartSvg from "../shared/HeartSvg.vue";
|
|||||||
import MasterFlag from "../shared/MasterFlag.vue";
|
import MasterFlag from "../shared/MasterFlag.vue";
|
||||||
import HotKeys from "../LeftSidebar/NP/HotKeys.vue";
|
import HotKeys from "../LeftSidebar/NP/HotKeys.vue";
|
||||||
import ArtistName from "@/components/shared/ArtistName.vue";
|
import ArtistName from "@/components/shared/ArtistName.vue";
|
||||||
|
import ExpandSvg from "@/assets/icons/expand.svg";
|
||||||
|
|
||||||
const queue = useQStore();
|
const queue = useQStore();
|
||||||
const settings = useSettingsStore();
|
const settings = useSettingsStore();
|
||||||
@@ -80,12 +81,43 @@ defineEmits<{
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: small;
|
font-size: small;
|
||||||
|
|
||||||
a {
|
.np-image {
|
||||||
font-size: small;
|
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 {
|
a {
|
||||||
height: 3rem;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heart-button {
|
.heart-button {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="right-group">
|
<div class="right-group">
|
||||||
<LyricsButton v-if="settings.use_lyrics_plugin || lyrics.exists" />
|
<LyricsButton />
|
||||||
<Volume />
|
<Volume />
|
||||||
<button
|
<button
|
||||||
class="repeat"
|
class="repeat"
|
||||||
@@ -17,8 +17,12 @@
|
|||||||
<RepeatOneSvg v-if="settings.repeat_one" />
|
<RepeatOneSvg v-if="settings.repeat_one" />
|
||||||
<RepeatAllSvg v-else />
|
<RepeatAllSvg v-else />
|
||||||
</button>
|
</button>
|
||||||
|
<button title="Shuffle" @click="queue.shuffleQueue">
|
||||||
|
<ShuffleSvg />
|
||||||
|
</button>
|
||||||
<HeartSvg
|
<HeartSvg
|
||||||
v-if="!hideHeart"
|
v-if="!hideHeart"
|
||||||
|
title="Favorite"
|
||||||
:state="queue.currenttrack?.is_favorite"
|
:state="queue.currenttrack?.is_favorite"
|
||||||
@handleFav="() => $emit('handleFav')"
|
@handleFav="() => $emit('handleFav')"
|
||||||
/>
|
/>
|
||||||
@@ -28,16 +32,15 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import useQueue from "@/stores/queue";
|
import useQueue from "@/stores/queue";
|
||||||
import useSettings from "@/stores/settings";
|
import useSettings from "@/stores/settings";
|
||||||
import useLyrics from "@/stores/lyrics";
|
|
||||||
|
|
||||||
|
import Volume from "./Volume.vue";
|
||||||
import HeartSvg from "../shared/HeartSvg.vue";
|
import HeartSvg from "../shared/HeartSvg.vue";
|
||||||
|
import LyricsButton from "../shared/LyricsButton.vue";
|
||||||
import RepeatAllSvg from "@/assets/icons/repeat.svg";
|
import RepeatAllSvg from "@/assets/icons/repeat.svg";
|
||||||
import RepeatOneSvg from "@/assets/icons/repeat-one.svg";
|
import RepeatOneSvg from "@/assets/icons/repeat-one.svg";
|
||||||
import Volume from "./Volume.vue";
|
import ShuffleSvg from "@/assets/icons/shuffle.svg";
|
||||||
import LyricsButton from "../shared/LyricsButton.vue";
|
|
||||||
|
|
||||||
const queue = useQueue();
|
const queue = useQueue();
|
||||||
const lyrics = useLyrics();
|
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -53,7 +56,7 @@ defineEmits<{
|
|||||||
.right-group {
|
.right-group {
|
||||||
display: grid;
|
display: grid;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
grid-template-columns: repeat(4, max-content);
|
grid-template-columns: repeat(5, max-content);
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
|
|
||||||
@@ -85,5 +88,9 @@ defineEmits<{
|
|||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heart-button {
|
||||||
|
border: solid 1px $gray4 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ const changeVolume = (event: Event) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseWheel = (event: WheelEvent) => {
|
const handleMouseWheel = (event: WheelEvent) => {
|
||||||
event.preventDefault();
|
|
||||||
const delta = event.deltaY / 1000;
|
const delta = event.deltaY / 1000;
|
||||||
let newVolume = settings.volume - delta / 3;
|
let newVolume = settings.volume - delta / 3;
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ context.$subscribe((mutation, state) => {
|
|||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
height: min-content;
|
height: min-content;
|
||||||
|
|
||||||
padding: $medium;
|
padding: $small;
|
||||||
background: $context;
|
background: $context;
|
||||||
transform-origin: top left;
|
transform-origin: top left;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ function runChildAction(action: () => void) {
|
|||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
background-color: $context;
|
background-color: $context;
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
padding: $medium;
|
padding: $small;
|
||||||
border: solid 1px $gray3;
|
border: solid 1px $gray3;
|
||||||
|
|
||||||
max-height: calc(100vh / 2);
|
max-height: calc(100vh / 2);
|
||||||
@@ -178,7 +178,8 @@ function runChildAction(action: () => void) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(2) .icon > svg {
|
// add to queue icon
|
||||||
|
&:nth-child(3) .icon > svg {
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RecentFavResult } from "@/interfaces";
|
import { RecentFavResult } from "@/interfaces";
|
||||||
import { paths } from "../../config";
|
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
|
import { paths } from "../../config";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
fav: RecentFavResult;
|
fav: RecentFavResult;
|
||||||
@@ -45,7 +45,7 @@ defineProps<{
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.recent-fav-item {
|
.recent-fav-item {
|
||||||
flex: 0 0 10.1rem;
|
flex: 0 0 $cardwidth;
|
||||||
|
|
||||||
padding: $medium;
|
padding: $medium;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -14,9 +14,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { onUpdated } from "vue";
|
||||||
|
|
||||||
import { subPath } from "@/interfaces";
|
import { subPath } from "@/interfaces";
|
||||||
import { focusElemByClass } from "@/utils";
|
import { focusElemByClass } from "@/utils";
|
||||||
import { onUpdated } from "vue";
|
|
||||||
|
import useSettings from "@/stores/settings";
|
||||||
|
|
||||||
|
const settings = useSettings();
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
subPaths: subPath[];
|
subPaths: subPath[];
|
||||||
@@ -27,7 +32,9 @@ defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
onUpdated(() => {
|
onUpdated(() => {
|
||||||
focusElemByClass("inthisfolder");
|
if (settings.is_default_layout) {
|
||||||
|
focusElemByClass("inthisfolder");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
<div class="info">
|
<div class="info">
|
||||||
<div class="f-item-text ellip">{{ folder.name }}</div>
|
<div class="f-item-text ellip">{{ folder.name }}</div>
|
||||||
<div v-if="folder.count" class="f-count">
|
<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>
|
</div>
|
||||||
<div v-if="!folder_page" class="check">
|
<div v-if="!folder_page" class="check">
|
||||||
|
|||||||
@@ -35,13 +35,29 @@ const browselist = [
|
|||||||
title: "Artists",
|
title: "Artists",
|
||||||
route: Routes.ArtistList,
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.homebrowse {
|
.homebrowse {
|
||||||
padding: 1rem 0;
|
padding: 1.5rem 0;
|
||||||
padding-left: $medium;
|
padding-left: $small;
|
||||||
|
|
||||||
.btitle {
|
.btitle {
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
|
|||||||
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"
|
:max="time.full"
|
||||||
step="0.1"
|
step="0.1"
|
||||||
:style="{
|
:style="{
|
||||||
backgroundSize: `${
|
backgroundSize: `${(time.current / (time.full || 0)) * 100}% 100%`,
|
||||||
(time.current / (time.full || 0)) * 100
|
|
||||||
}% 100%`,
|
|
||||||
}"
|
}"
|
||||||
@change="seek"
|
@change="seek"
|
||||||
/>
|
/>
|
||||||
@@ -22,10 +20,18 @@ const q = useQStore();
|
|||||||
|
|
||||||
const { duration: time } = q;
|
const { duration: time } = q;
|
||||||
|
|
||||||
|
let prevHash = "";
|
||||||
|
|
||||||
const seek = (e: Event) => {
|
const seek = (e: Event) => {
|
||||||
|
if (prevHash && prevHash !== q.currenttrackhash) {
|
||||||
|
prevHash = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const elem = e.target as HTMLInputElement;
|
const elem = e.target as HTMLInputElement;
|
||||||
const value = elem.value;
|
const value = elem.value;
|
||||||
|
|
||||||
|
prevHash = q.currenttrackhash;
|
||||||
q.seek(value as unknown as number);
|
q.seek(value as unknown as number);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,18 +8,34 @@ import SearchSvg from "@/assets/icons/search.svg";
|
|||||||
import SettingsSvg from "@/assets/icons/settings.svg";
|
import SettingsSvg from "@/assets/icons/settings.svg";
|
||||||
import HomeSvg from "@/assets/icons/home.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 = [
|
export const menus = [
|
||||||
{
|
home,
|
||||||
name: "home",
|
folder,
|
||||||
route_name: Routes.Home,
|
|
||||||
icon: HomeSvg,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "folders",
|
|
||||||
route_name: Routes.folder,
|
|
||||||
params: { path: "$home" },
|
|
||||||
icon: FolderSvg,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "search",
|
name: "search",
|
||||||
route_name: Routes.search,
|
route_name: Routes.search,
|
||||||
@@ -30,16 +46,8 @@ export const menus = [
|
|||||||
{
|
{
|
||||||
separator: true,
|
separator: true,
|
||||||
},
|
},
|
||||||
{
|
favorites,
|
||||||
name: "favorites",
|
playlists,
|
||||||
route_name: Routes.favorites,
|
|
||||||
icon: HeartSvg,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "playlists",
|
|
||||||
route_name: Routes.playlists,
|
|
||||||
icon: PlaylistSvg,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
separator: true,
|
separator: true,
|
||||||
},
|
},
|
||||||
@@ -50,3 +58,5 @@ export const menus = [
|
|||||||
icon: SettingsSvg,
|
icon: SettingsSvg,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const topnavitems = [home, folder, favorites, playlists];
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ function getSvg(notif: NotifType) {
|
|||||||
place-items: center;
|
place-items: center;
|
||||||
box-shadow: 0px 0px 2rem rgba(0, 0, 0, 0.466);
|
box-shadow: 0px 0px 2rem rgba(0, 0, 0, 0.466);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
padding: 1rem;
|
padding: 1rem $small;
|
||||||
|
|
||||||
grid-template-columns: 2rem 3fr;
|
grid-template-columns: 2rem 3fr;
|
||||||
gap: $small;
|
gap: $smaller;
|
||||||
|
|
||||||
.notif-text {
|
.notif-text {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -74,14 +74,10 @@ function getSvg(notif: NotifType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.new-notif.info,
|
.new-notif.info,
|
||||||
.new-notif.favorite {
|
.new-notif.favorite,.new-notif.success {
|
||||||
$bg: rgb(28, 102, 238);
|
$bg: rgb(255, 255, 255);
|
||||||
background-color: $bg;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-notif.success {
|
|
||||||
$bg: rgb(5, 167, 53);
|
|
||||||
background-color: $bg;
|
background-color: $bg;
|
||||||
|
color: $black;
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-notif.working {
|
.new-notif.working {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
:source="dropSources.folder"
|
:source="dropSources.folder"
|
||||||
@play-this="queue.playNext"
|
@play-this="queue.playNext"
|
||||||
/>
|
/>
|
||||||
|
<h3>Queue</h3>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -8,29 +8,34 @@
|
|||||||
<div class="from">
|
<div class="from">
|
||||||
<img
|
<img
|
||||||
v-if="
|
v-if="
|
||||||
queue.from.type === FromOptions.album ||
|
tracklist.from.type === FromOptions.album ||
|
||||||
queue.from.type === FromOptions.artist
|
tracklist.from.type === FromOptions.artist
|
||||||
"
|
"
|
||||||
:src="data.image + '.webp'"
|
:src="data.image + '.webp'"
|
||||||
:alt="`Now Playing ${queue.from.type} image`"
|
:alt="`Now Playing ${tracklist.from.type} image`"
|
||||||
:class="`${
|
: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">
|
<div v-else class="from-icon border rounded-sm">
|
||||||
<component :is="data.icon"></component>
|
<component :is="data.icon"></component>
|
||||||
</div>
|
</div>
|
||||||
<div class="pad-sm">
|
<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 class="ellip2">{{ data.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<button class="options" @click="showContextMenu">
|
||||||
|
<MoreSvg />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import { RouteLocationRaw } from "vue-router";
|
import { RouteLocationRaw } from "vue-router";
|
||||||
|
|
||||||
import useTracklist from "@/stores/queue/tracklist";
|
import useTracklist from "@/stores/queue/tracklist";
|
||||||
@@ -38,12 +43,23 @@ import useTracklist from "@/stores/queue/tracklist";
|
|||||||
import { FromOptions } from "@/enums";
|
import { FromOptions } from "@/enums";
|
||||||
import playingFrom from "@/utils/playingFrom";
|
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 data = computed(() => {
|
||||||
const { name, location, icon, image } = playingFrom(queue.from);
|
const { name, location, icon, image } = playingFrom(tracklist.from);
|
||||||
return { name, location, icon, image };
|
return { name, location, icon, image };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function showContextMenu(e: MouseEvent) {
|
||||||
|
if (!tracklist.tracklist.length) return;
|
||||||
|
|
||||||
|
showQueueContextMenu(e, context_showing);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -52,6 +68,14 @@ const data = computed(() => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
.options {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
transform: scale(1.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.now-playling-from-link {
|
.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>
|
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
:class="{ 'use-sqr_img': useSqrImg }"
|
:class="{ 'use-sqr_img': useSqrImg }"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="Number.isNaN"
|
v-if="Number.isInteger(info.id)"
|
||||||
class="float"
|
class="float"
|
||||||
:style="{
|
:style="{
|
||||||
color: textColor,
|
color: textColor,
|
||||||
|
|||||||
@@ -3,8 +3,14 @@
|
|||||||
<span v-if="!isHeaderSmall" class="status"
|
<span v-if="!isHeaderSmall" class="status"
|
||||||
>Last updated {{ playlist.info.last_updated }}  |  </span
|
>Last updated {{ playlist.info.last_updated }}  |  </span
|
||||||
>
|
>
|
||||||
<div class="edit" @click="editPlaylist">Edit  </div>
|
<div
|
||||||
|
|
v-if="Number.isInteger(playlist.info.id)"
|
||||||
|
class="edit"
|
||||||
|
@click="editPlaylist"
|
||||||
|
>
|
||||||
|
Edit  
|
||||||
|
</div>
|
||||||
|
{{ Number.isInteger(playlist.info.id) ? " | " : "" }}
|
||||||
<DeleteSvg class="edit" @click="deletePlaylist" />
|
<DeleteSvg class="edit" @click="deletePlaylist" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -34,11 +40,8 @@ function deletePlaylist() {
|
|||||||
bottom: 1rem;
|
bottom: 1rem;
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
padding: $smaller $small;
|
padding: $smaller $small;
|
||||||
// background-color: $body;
|
|
||||||
// color: rgb(255, 255, 255);
|
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
border-radius: $smaller;
|
border-radius: $smaller;
|
||||||
// box-shadow: 0 0 1rem rgba(0, 0, 0, 0.479);
|
|
||||||
z-index: 12;
|
z-index: 12;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -20,7 +20,10 @@
|
|||||||
:class="{ border: !playlist.thumb }"
|
:class="{ border: !playlist.thumb }"
|
||||||
/>
|
/>
|
||||||
<div class="overlay rounded">
|
<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-name ellip">{{ playlist.name }}</div>
|
||||||
<div class="p-count">
|
<div class="p-count">
|
||||||
<b>{{
|
<b>{{
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="queue-actions">
|
<div class="queue-actions">
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<button v-wave class="shuffle-queue action" @click="queue.shuffleQueue">
|
<button
|
||||||
|
v-if="!onNowPlaying"
|
||||||
|
v-wave
|
||||||
|
class="shuffle-queue action"
|
||||||
|
@click="queue.shuffleQueue"
|
||||||
|
>
|
||||||
<ShuffleSvg />
|
<ShuffleSvg />
|
||||||
<span>Shuffle</span>
|
<span>Shuffle</span>
|
||||||
</button>
|
</button>
|
||||||
|
<h2 v-else style="margin: 0">Now Playing</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="right">
|
<div class="right">
|
||||||
<button
|
<button
|
||||||
@@ -68,6 +74,7 @@ defineProps<{
|
|||||||
|
|
||||||
.left {
|
.left {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
gap: $small;
|
gap: $small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-auto-animate class="artists-results">
|
|
||||||
<div>
|
|
||||||
<div
|
|
||||||
v-if="album_grid == true && search.albums.value.length"
|
|
||||||
class="search-results-grid"
|
|
||||||
>
|
|
||||||
<AlbumCard
|
|
||||||
v-for="a in search.albums.value"
|
|
||||||
:key="a.albumid"
|
|
||||||
:album="a"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-else-if="!album_grid && search.artists.value.length"
|
|
||||||
class="search-results-grid"
|
|
||||||
>
|
|
||||||
<ArtistCard
|
|
||||||
v-for="artist in search.artists.value"
|
|
||||||
:key="artist.image"
|
|
||||||
:artist="artist"
|
|
||||||
:alt="true"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-else class="t-center">
|
|
||||||
<h5>No {{ album_grid ? "albums" : "artists" }}</h5>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<LoadMore
|
|
||||||
v-if="search.albums.value.length || search.artists.value.length"
|
|
||||||
:loader="album_grid ? search.loadAlbums : search.loadArtists"
|
|
||||||
:can_load_more="album_grid ? search.albums.more : search.artists.more"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import useSearchStore from "../../../stores/search";
|
|
||||||
|
|
||||||
import AlbumCard from "@/components/shared/AlbumCard.vue";
|
|
||||||
import ArtistCard from "../../shared/ArtistCard.vue";
|
|
||||||
import LoadMore from "./LoadMore.vue";
|
|
||||||
|
|
||||||
const search = useSearchStore();
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
album_grid?: boolean;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.artists-results {
|
|
||||||
height: 100%;
|
|
||||||
display: grid;
|
|
||||||
margin: 0 1rem;
|
|
||||||
grid-template-rows: 1fr max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-results-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
|
|
||||||
min-height: 10rem;
|
|
||||||
padding-bottom: $small;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="morexx">
|
|
||||||
<button
|
|
||||||
:class="{
|
|
||||||
load_disabled: !can_load_more,
|
|
||||||
}"
|
|
||||||
@click.prevent="loader()"
|
|
||||||
>
|
|
||||||
<div class="text">Load More</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
defineProps<{
|
|
||||||
loader: () => void;
|
|
||||||
can_load_more: boolean;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.morexx {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
margin-top: $small;
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 0 1rem !important;
|
|
||||||
width: calc(100% + 2rem);
|
|
||||||
border-radius: 0;
|
|
||||||
height: 3rem;
|
|
||||||
background: $darkestblue;
|
|
||||||
border: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: $darkblue;
|
|
||||||
border-color: $darkblue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -48,5 +48,7 @@ function switchTab(tab: string) {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ArtistGrid from "./ArtistGrid.vue";
|
import useSearch from "@/stores/search";
|
||||||
|
|
||||||
import TracksGrid from "./TracksGrid.vue";
|
import TracksGrid from "./TracksGrid.vue";
|
||||||
import TopResults from "./TopResults.vue";
|
import TopResults from "./TopResults.vue";
|
||||||
|
import CardPage from "@/views/SearchView/CardGridPage.vue";
|
||||||
|
|
||||||
|
const search = useSearch();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
name: string;
|
name: string;
|
||||||
}>();
|
}>();
|
||||||
@@ -24,15 +27,23 @@ function getComponent() {
|
|||||||
};
|
};
|
||||||
case "albums":
|
case "albums":
|
||||||
return {
|
return {
|
||||||
component: ArtistGrid,
|
component: CardPage,
|
||||||
props: {
|
props: {
|
||||||
album_grid: true,
|
page: "album",
|
||||||
|
items: search.albums.value,
|
||||||
|
outside_route: true,
|
||||||
|
fetch_callback: search.loadAlbums,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
case "artists":
|
case "artists":
|
||||||
return {
|
return {
|
||||||
component: ArtistGrid,
|
component: CardPage,
|
||||||
props: {},
|
props: {
|
||||||
|
page: "artist",
|
||||||
|
items: search.artists.value,
|
||||||
|
outside_route: true,
|
||||||
|
fetch_callback: search.loadArtists,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ defineEmits<{
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vue-recycle-scroller {
|
||||||
|
padding: 0 $small;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardlistrow {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(8.1rem, 1fr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#tab-content {
|
#tab-content {
|
||||||
|
|||||||
@@ -1,37 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="tracks-results">
|
<div id="tracks-results">
|
||||||
<div v-if="search.tracks.value.length">
|
<div v-if="!search.tracks.value.length" class="t-center">
|
||||||
<TrackItem
|
<h5>No tracks</h5>
|
||||||
v-for="(track, index) in search.tracks.value"
|
</div>
|
||||||
:key="track.id"
|
|
||||||
:is-current="queue.currenttrackhash === track.trackhash"
|
<RecycleScroller
|
||||||
:is-highlighted="false"
|
v-slot="{ item, index }"
|
||||||
:is-current-playing="
|
class="scroller"
|
||||||
queue.currenttrackhash === track.trackhash && queue.playing
|
style="height: 100%"
|
||||||
"
|
:items="scrollerItems"
|
||||||
:track="track"
|
:item-size="64"
|
||||||
:index="index + 1"
|
key-field="id"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="item.component"
|
||||||
|
v-bind="item.props"
|
||||||
@playThis="updateQueue(index)"
|
@playThis="updateQueue(index)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</RecycleScroller>
|
||||||
<div v-else class="t-center"><h5>No tracks</h5></div>
|
|
||||||
<LoadMore
|
|
||||||
v-if="search.tracks.value.length"
|
|
||||||
:loader="search.loadTracks"
|
|
||||||
:can_load_more="search.tracks.more"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
|
|
||||||
import useQueue from "@/stores/queue";
|
import useQueue from "@/stores/queue";
|
||||||
import useSearch from "@/stores/search";
|
import useSearch from "@/stores/search";
|
||||||
import useTracklist from "@/stores/queue/tracklist";
|
import useTracklist from "@/stores/queue/tracklist";
|
||||||
|
|
||||||
import LoadMore from "./LoadMore.vue";
|
|
||||||
import TrackItem from "@/components/shared/TrackItem.vue";
|
import TrackItem from "@/components/shared/TrackItem.vue";
|
||||||
|
import AlbumsFetcher from "@/components/ArtistView/AlbumsFetcher.vue";
|
||||||
|
|
||||||
const queue = useQueue();
|
const queue = useQueue();
|
||||||
const search = useSearch();
|
const search = useSearch();
|
||||||
@@ -42,6 +40,36 @@ function updateQueue(index: number) {
|
|||||||
queue.play(index);
|
queue.play(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scrollerItems = computed(() => {
|
||||||
|
const items: any[] = search.tracks.value.map((track, index) => {
|
||||||
|
return {
|
||||||
|
track,
|
||||||
|
id: index,
|
||||||
|
component: TrackItem,
|
||||||
|
props: {
|
||||||
|
track,
|
||||||
|
index: index + 1,
|
||||||
|
isCurrent: queue.currenttrackhash === track.trackhash,
|
||||||
|
isCurrentPlaying:
|
||||||
|
queue.currenttrackhash === track.trackhash && queue.playing,
|
||||||
|
isHighlighted: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
id: Math.random(),
|
||||||
|
component: AlbumsFetcher,
|
||||||
|
props: {
|
||||||
|
fetch_callback: search.loadTracks,
|
||||||
|
showtext: search.tracks.more,
|
||||||
|
outside_route: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return items;
|
||||||
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
search.switchTab("tracks");
|
search.switchTab("tracks");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
<template>
|
<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">
|
<div id="ginner" ref="inputRef" tabindex="0">
|
||||||
<button
|
<button
|
||||||
v-auto-animate
|
v-auto-animate
|
||||||
@@ -18,6 +28,7 @@
|
|||||||
placeholder="Start typing to search"
|
placeholder="Start typing to search"
|
||||||
type="search"
|
type="search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
spellcheck="false"
|
||||||
@blur.prevent="removeFocusedClass"
|
@blur.prevent="removeFocusedClass"
|
||||||
@focus.prevent="addFocusedClass"
|
@focus.prevent="addFocusedClass"
|
||||||
/>
|
/>
|
||||||
@@ -29,20 +40,26 @@
|
|||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
import useTabStore from "@/stores/tabs";
|
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 BackSvg from "@/assets/icons/arrow.svg";
|
||||||
import SearchSvg from "@/assets/icons/search.svg";
|
import SearchSvg from "@/assets/icons/search.svg";
|
||||||
|
import { Routes } from "@/router";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
on_nav?: boolean;
|
on_nav?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tabs = useTabStore();
|
const tabs = useTabStore();
|
||||||
const search = useSearchStore();
|
const search = useSearch();
|
||||||
|
const settings = useSettings();
|
||||||
|
|
||||||
// HANDLE FOCUS
|
// HANDLE FOCUS
|
||||||
const inputRef = ref<HTMLElement>();
|
const inputRef = ref<HTMLElement>();
|
||||||
|
|
||||||
|
// NOTE: Functions are used because classes are added to the sorrounding element
|
||||||
|
// and not the input itself.
|
||||||
function addFocusedClass() {
|
function addFocusedClass() {
|
||||||
inputRef.value?.classList.add("search-focused");
|
inputRef.value?.classList.add("search-focused");
|
||||||
}
|
}
|
||||||
@@ -56,7 +73,7 @@ function removeFocusedClass() {
|
|||||||
function handleButton() {
|
function handleButton() {
|
||||||
if (props.on_nav) return;
|
if (props.on_nav) return;
|
||||||
|
|
||||||
if (tabs.current === tabs.tabs.search || tabs.current === tabs.tabs.lyrics) {
|
if (tabs.current === tabs.tabs.search) {
|
||||||
tabs.switchToQueue();
|
tabs.switchToQueue();
|
||||||
} else {
|
} else {
|
||||||
tabs.switchToSearch();
|
tabs.switchToSearch();
|
||||||
@@ -73,9 +90,9 @@ function handleButton() {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: $small;
|
// gap: $small;
|
||||||
border-radius: 3rem;
|
border-radius: 3rem;
|
||||||
outline: solid 1px $gray3;
|
background-color: $gray5;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
@@ -101,13 +118,17 @@ function handleButton() {
|
|||||||
border: none;
|
border: none;
|
||||||
line-height: 2.25rem;
|
line-height: 2.25rem;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
font-size: 1rem;
|
font-size: 14px;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
width: 7rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.search-focused {
|
.search-focused {
|
||||||
outline: solid $darkblue !important;
|
outline: solid 2px #fff !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { onMounted } from "vue";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
action: () => void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
props.action();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -39,7 +39,22 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
Let there be music 💃🕺!
|
Hope you enjoy using Swing Music as much as I enjoy building it.
|
||||||
|
<br /><br />
|
||||||
|
<div class="hireme rounded">
|
||||||
|
<h2>Hire me</h2>
|
||||||
|
If you like my work, and would like me to work for you or your company,
|
||||||
|
I'm open to offers. Feel free to reach out to me via email.
|
||||||
|
<br /><br />
|
||||||
|
<div class="flex">
|
||||||
|
<a
|
||||||
|
href="mailto:geoffreymungai45@gmail.com?subject=Job Offer&body=Hi Mungai,
|
||||||
|
"
|
||||||
|
target="_blank"
|
||||||
|
><button>Write Email</button></a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -53,5 +68,25 @@
|
|||||||
.flex {
|
.flex {
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hireme {
|
||||||
|
background-color: #ffffff;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
37deg,
|
||||||
|
#bfeaf0 0%,
|
||||||
|
#ffffff00 50%,
|
||||||
|
#a7dcff 100%
|
||||||
|
);
|
||||||
|
padding: 1rem;
|
||||||
|
color: $black;
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: $small;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
40
src/components/SettingsView/Components/LockedNumberInput.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<div class="lockernumberinput">
|
||||||
|
<button class="minus" @click="submit('minus')">-</button>
|
||||||
|
<div class="number">{{ value }}{{ unit }}</div>
|
||||||
|
<button class="plus" @click="submit('plus')">+</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
value: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
step: number;
|
||||||
|
unit: string;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function submit(action: "plus" | "minus") {
|
||||||
|
const newValue = props.value + (action === "plus" ? props.step : -props.step);
|
||||||
|
if (newValue < props.min || newValue > props.max) return;
|
||||||
|
props.onChange(newValue);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.lockernumberinput {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.number {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 2.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
80
src/components/SettingsView/Components/QuickSettings.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="settings-quickactions grid">
|
||||||
|
<button
|
||||||
|
v-for="a in actions"
|
||||||
|
:key="a.label"
|
||||||
|
class="qaction rounded-sm"
|
||||||
|
@click="a.action"
|
||||||
|
>
|
||||||
|
<component :is="a.icon" v-if="a.icon" />
|
||||||
|
<div class="label">{{ a.label }}</div>
|
||||||
|
<Switch v-if="a.state" :state="a.state()" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import useSettings from "@/stores/settings";
|
||||||
|
import { triggerScan } from "@/requests/settings/rootdirs";
|
||||||
|
|
||||||
|
import Switch from "./Switch.vue";
|
||||||
|
import ReloadSvg from "@/assets/icons/reload.svg";
|
||||||
|
|
||||||
|
const settings = useSettings();
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
{
|
||||||
|
label: "Rescan library",
|
||||||
|
action: triggerScan,
|
||||||
|
icon: ReloadSvg,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Sidebar",
|
||||||
|
action: settings.toggleDisableSidebar,
|
||||||
|
state: () => settings.use_sidebar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Silence skip",
|
||||||
|
action: settings.toggleUseSilenceSkip,
|
||||||
|
state: () => settings.use_silence_skip,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Crossfade",
|
||||||
|
action: settings.toggleCrossfade,
|
||||||
|
state: () => settings.use_crossfade,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.settings-quickactions.grid {
|
||||||
|
gap: 1.25rem;
|
||||||
|
padding-top: $medium;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
|
||||||
|
.qaction {
|
||||||
|
background-color: $gray5;
|
||||||
|
padding: 1.5rem $small;
|
||||||
|
font-size: 14px;
|
||||||
|
border: solid 1px $gray4;
|
||||||
|
// justify-content: space-between;
|
||||||
|
|
||||||
|
gap: $smaller;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: $gray4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.switch {
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.qaction svg {
|
||||||
|
transform: scale(0.75);
|
||||||
|
}
|
||||||
|
.qaction:nth-child(2) svg {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
defineProps<{
|
defineProps<{
|
||||||
state: undefined;
|
state: undefined | boolean;
|
||||||
}>();
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const currentTab = computed(() => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.settingscontent {
|
.settingscontent {
|
||||||
width: 35rem;
|
width: 31rem;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
|
||||||
.version {
|
.version {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div v-if="group.title || group.desc" class="info">
|
<div v-if="group.title || group.desc" class="info">
|
||||||
<h4 v-if="group.title">
|
<h4 v-if="group.title">
|
||||||
{{ group.title
|
{{ group.title
|
||||||
}}<span v-if="group.experimental" class="experimental circular">
|
}}<span v-if="group.experimental" class="badge experimental circular">
|
||||||
{{ group.experimental ? "experimental" : "" }}
|
{{ group.experimental ? "experimental" : "" }}
|
||||||
</span>
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
@@ -30,6 +30,15 @@
|
|||||||
<div class="title">
|
<div class="title">
|
||||||
<span class="ellip">
|
<span class="ellip">
|
||||||
{{ setting.title }}
|
{{ setting.title }}
|
||||||
|
<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>
|
</span>
|
||||||
<button
|
<button
|
||||||
v-if="setting.type == SettingType.root_dirs"
|
v-if="setting.type == SettingType.root_dirs"
|
||||||
@@ -60,8 +69,18 @@
|
|||||||
>
|
>
|
||||||
{{ setting.button_text && setting.button_text() }}
|
{{ setting.button_text && setting.button_text() }}
|
||||||
</button>
|
</button>
|
||||||
|
<LockedNumberInput
|
||||||
|
v-if="setting.type == SettingType.locked_number_input"
|
||||||
|
:value="setting.state !== null ? setting.state() : 0"
|
||||||
|
:min="0"
|
||||||
|
:max="10"
|
||||||
|
:step="1"
|
||||||
|
:unit="'s'"
|
||||||
|
:on-change="setting.action"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<QuickActions v-if="setting.type == SettingType.quick_actions" />
|
||||||
<List
|
<List
|
||||||
v-if="setting.type === SettingType.root_dirs"
|
v-if="setting.type === SettingType.root_dirs"
|
||||||
icon="folder"
|
icon="folder"
|
||||||
@@ -81,11 +100,13 @@
|
|||||||
import { SettingType } from "@/settings/enums";
|
import { SettingType } from "@/settings/enums";
|
||||||
import { SettingGroup } from "@/interfaces/settings";
|
import { SettingGroup } from "@/interfaces/settings";
|
||||||
|
|
||||||
|
import List from "./Components/List.vue";
|
||||||
import Switch from "./Components/Switch.vue";
|
import Switch from "./Components/Switch.vue";
|
||||||
import Select from "./Components/Select.vue";
|
import Select from "./Components/Select.vue";
|
||||||
import List from "./Components/List.vue";
|
|
||||||
import SeparatorsInput from "./Components/SeparatorsInput.vue";
|
|
||||||
import ReloadSvg from "@/assets/icons/reload.svg";
|
import ReloadSvg from "@/assets/icons/reload.svg";
|
||||||
|
import QuickActions from "./Components/QuickSettings.vue";
|
||||||
|
import SeparatorsInput from "./Components/SeparatorsInput.vue";
|
||||||
|
import LockedNumberInput from "./Components/LockedNumberInput.vue";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
group: SettingGroup;
|
group: SettingGroup;
|
||||||
@@ -101,13 +122,22 @@ defineProps<{
|
|||||||
border-bottom: solid 1px $gray;
|
border-bottom: solid 1px $gray;
|
||||||
padding-bottom: 2rem;
|
padding-bottom: 2rem;
|
||||||
|
|
||||||
.experimental {
|
.badge {
|
||||||
font-size: 12px;
|
|
||||||
margin-left: $small;
|
margin-left: $small;
|
||||||
opacity: 0.5;
|
opacity: 0.75;
|
||||||
|
padding: 0 $smaller;
|
||||||
|
border-radius: $smaller;
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.experimental {
|
||||||
border: solid 1px $yellow;
|
border: solid 1px $yellow;
|
||||||
color: $yellow;
|
color: $yellow;
|
||||||
padding: 0 $smaller;
|
}
|
||||||
|
|
||||||
|
.badge.new {
|
||||||
|
background-color: $blue;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
@@ -130,8 +160,6 @@ defineProps<{
|
|||||||
|
|
||||||
.setting {
|
.setting {
|
||||||
background-color: $gray;
|
background-color: $gray;
|
||||||
// display: grid;
|
|
||||||
// gap: 1rem;
|
|
||||||
|
|
||||||
.inactive {
|
.inactive {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
<div class="creator t-center">
|
<div class="creator t-center">
|
||||||
Designed and developed by
|
Designed and developed by
|
||||||
<span class="name"
|
<span class="name"
|
||||||
><a target="_blank" href="https://github.com/mungai-njoroge"
|
><a target="_blank" href="https://github.com/cwilvx"
|
||||||
>Mungai Njoroge</a
|
>@cwilvx</a
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,12 +44,6 @@
|
|||||||
color: $pink;
|
color: $pink;
|
||||||
}
|
}
|
||||||
|
|
||||||
.release {
|
|
||||||
margin-left: $smaller;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
color: $gray1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-banner {
|
.bottom-banner {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
|||||||
@@ -10,9 +10,6 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="heading">{{ modal.title }}</div>
|
<div class="heading">{{ modal.title }}</div>
|
||||||
<div class="close circular" @click="modal.hideModal">
|
|
||||||
<PlusSvg />
|
|
||||||
</div>
|
|
||||||
<NewPlaylist
|
<NewPlaylist
|
||||||
v-if="modal.component == modal.options.newPlaylist"
|
v-if="modal.component == modal.options.newPlaylist"
|
||||||
v-bind="modal.props"
|
v-bind="modal.props"
|
||||||
@@ -20,7 +17,7 @@
|
|||||||
@setTitle="setTitle"
|
@setTitle="setTitle"
|
||||||
/>
|
/>
|
||||||
<UpdatePlaylist
|
<UpdatePlaylist
|
||||||
v-if="modal.component == modal.options.updatePlaylist"
|
v-if="modal.component == modal.options.updatePlaylist"
|
||||||
v-bind="modal.props"
|
v-bind="modal.props"
|
||||||
@hideModal="hideModal"
|
@hideModal="hideModal"
|
||||||
@setTitle="setTitle"
|
@setTitle="setTitle"
|
||||||
@@ -50,7 +47,6 @@ import { deletePlaylist as delPlaylist } from "@/requests/playlists";
|
|||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import useModalStore from "@/stores/modal";
|
import useModalStore from "@/stores/modal";
|
||||||
|
|
||||||
import PlusSvg from "@/assets/icons/plus.svg";
|
|
||||||
import WelcomeModal from "./WelcomeModal.vue";
|
import WelcomeModal from "./WelcomeModal.vue";
|
||||||
import ConfirmModal from "./modals/ConfirmModal.vue";
|
import ConfirmModal from "./modals/ConfirmModal.vue";
|
||||||
import NewPlaylist from "./modals/NewPlaylist.vue";
|
import NewPlaylist from "./modals/NewPlaylist.vue";
|
||||||
@@ -87,13 +83,15 @@ function deletePlaylist() {
|
|||||||
|
|
||||||
input[type="search"] {
|
input[type="search"] {
|
||||||
margin: $small 0;
|
margin: $small 0;
|
||||||
border: 2px solid $gray3;
|
border: none;
|
||||||
background-color: transparent;
|
background-color: $gray5;
|
||||||
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
font-size: 1rem;
|
font-size: 14px;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
height: 2.75rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg {
|
.bg {
|
||||||
@@ -116,28 +114,6 @@ function deletePlaylist() {
|
|||||||
width: calc(100% - 2rem);
|
width: calc(100% - 2rem);
|
||||||
padding: 2rem 1rem;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -7,6 +7,8 @@
|
|||||||
type="search"
|
type="search"
|
||||||
class="rounded-sm"
|
class="rounded-sm"
|
||||||
name="name"
|
name="name"
|
||||||
|
placeholder="Type a name..."
|
||||||
|
spellcheck="false"
|
||||||
/>
|
/>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<button type="submit">Create</button>
|
<button type="submit">Create</button>
|
||||||
@@ -15,17 +17,17 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from "vue";
|
import { onMounted } from "vue";
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
saveAlbumAsPlaylist,
|
saveAlbumAsPlaylist,
|
||||||
saveArtistAsPlaylist,
|
saveArtistAsPlaylist,
|
||||||
saveTrackAsPlaylist,
|
saveTrackAsPlaylist,
|
||||||
} from "@/requests/playlists";
|
} from "@/requests/playlists";
|
||||||
import useQueueStore from "@/stores/queue";
|
|
||||||
import { NotifType, Notification } from "@/stores/notification";
|
|
||||||
import { createNewPlaylist, saveFolderAsPlaylist } from "@/requests/playlists";
|
import { createNewPlaylist, saveFolderAsPlaylist } from "@/requests/playlists";
|
||||||
|
|
||||||
|
import useTracklist from "@/stores/queue/tracklist";
|
||||||
import usePlaylistStore from "@/stores/pages/playlists";
|
import usePlaylistStore from "@/stores/pages/playlists";
|
||||||
|
import { NotifType, Notification } from "@/stores/notification";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
trackhash?: string;
|
trackhash?: string;
|
||||||
@@ -37,7 +39,6 @@ const props = defineProps<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
const store = usePlaylistStore();
|
const store = usePlaylistStore();
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const input_elem = document.getElementById(
|
const input_elem = document.getElementById(
|
||||||
@@ -112,8 +113,8 @@ function create(e: Event) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createQueuePlaylist = () => {
|
const createQueuePlaylist = () => {
|
||||||
const queue = useQueueStore();
|
const { tracklist } = useTracklist();
|
||||||
const trackhashes = queue.tracklist.map((track) => track.trackhash);
|
const trackhashes = tracklist.map((track) => track.trackhash);
|
||||||
const itemhash = trackhashes.join(",");
|
const itemhash = trackhashes.join(",");
|
||||||
|
|
||||||
saveTrackAsPlaylist(name, itemhash).then((res) => {
|
saveTrackAsPlaylist(name, itemhash).then((res) => {
|
||||||
@@ -160,7 +161,6 @@ function create(e: Event) {
|
|||||||
label {
|
label {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: $gray1;
|
color: $gray1;
|
||||||
font-weight: 700;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.submit {
|
.submit {
|
||||||
@@ -171,8 +171,15 @@ function create(e: Event) {
|
|||||||
button {
|
button {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
width: 8rem;
|
width: 8rem;
|
||||||
|
padding: 1.25rem;
|
||||||
|
// font-weight: normal;
|
||||||
transition: all 0.25s ease-out;
|
transition: all 0.25s ease-out;
|
||||||
background-color: $pink;
|
background-color: $white;
|
||||||
|
color: $black;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<br /><br />
|
<br /><br />
|
||||||
<div style="position: relative">
|
<div style="position: relative">
|
||||||
<div id="bread-nav" class="bread-nav rounded-sm">
|
<div id="bread-nav" class="bread-nav rounded-sm">
|
||||||
<span @click="fetchDirs('$root')">📁</span
|
<span @click="fetchDirs('$root')">$root</span
|
||||||
> <BreadCrumbNav :sub-paths="subPaths" @navigate="fetchDirs" />
|
> <BreadCrumbNav :sub-paths="subPaths" @navigate="fetchDirs" />
|
||||||
</div>
|
</div>
|
||||||
<div class="set-root-dirs-browser">
|
<div class="set-root-dirs-browser">
|
||||||
@@ -26,10 +26,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button class="btn-active select-here" @click="selectHere">
|
<button class="btn-active select-here" @click="selectHere">
|
||||||
Select here
|
Add this folder
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-active finish" @click="submitFolders">
|
<button class="btn-active finish" @click="submitFolders">
|
||||||
Select checked ({{ getNewDirs().length }})
|
Add all checked ({{ getNewDirs().length }})
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,9 +40,9 @@
|
|||||||
import { onMounted, Ref, ref } from "vue";
|
import { onMounted, Ref, ref } from "vue";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addRootDirs,
|
addRootDirs,
|
||||||
getFolders,
|
getFolders,
|
||||||
getRootDirs,
|
getRootDirs,
|
||||||
} from "@/requests/settings/rootdirs";
|
} from "@/requests/settings/rootdirs";
|
||||||
|
|
||||||
import { Folder, subPath } from "@/interfaces";
|
import { Folder, subPath } from "@/interfaces";
|
||||||
@@ -113,7 +113,7 @@ function submitFolders() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function selectHere() {
|
function selectHere() {
|
||||||
if (current == "$root") return;
|
if (current == "$root" || current == "/") return;
|
||||||
|
|
||||||
addRootDirs([current], [])
|
addRootDirs([current], [])
|
||||||
.then((res) => settings.setRootDirs(res))
|
.then((res) => settings.setRootDirs(res))
|
||||||
@@ -188,7 +188,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: center;
|
||||||
gap: $medium;
|
gap: $medium;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
margin-bottom: -$medium;
|
margin-bottom: -$medium;
|
||||||
@@ -199,17 +199,9 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
font-weight: normal;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.select-here {
|
|
||||||
border: solid $darkestblue;
|
|
||||||
background: transparent;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: $darkestblue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.f-item {
|
.f-item {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
type="search"
|
type="search"
|
||||||
class="rounded-sm"
|
class="rounded-sm"
|
||||||
name="name"
|
name="name"
|
||||||
|
spellcheck="false"
|
||||||
@keypress.enter.prevent="update_playlist"
|
@keypress.enter.prevent="update_playlist"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -81,7 +82,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
{{ clicked ? "Saving" : "Save" }}
|
{{ clicked ? "Saving" : "Update" }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,68 +1,74 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="topnav">
|
<div
|
||||||
|
class="topnav"
|
||||||
|
:class="{
|
||||||
|
use_links: settings.is_alt_layout,
|
||||||
|
use_sidebar: settings.use_sidebar && isSmall,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<div class="left">
|
<div class="left">
|
||||||
<!-- back/forward -->
|
|
||||||
<NavButtons />
|
<NavButtons />
|
||||||
|
<NavLinks v-if="settings.is_alt_layout" />
|
||||||
<div class="info">
|
<div
|
||||||
<SettingsTitle
|
v-if="settings.is_default_layout && $route.name == Routes.folder"
|
||||||
v-if="$route.name == Routes.settings"
|
class="info"
|
||||||
:text="'Settings'"
|
>
|
||||||
/>
|
<Folder :sub-paths="subPaths" />
|
||||||
<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'"
|
|
||||||
/>
|
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from "vue";
|
import { Routes } from "@/router";
|
||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
|
import { computed, onMounted, ref, watch } from "vue";
|
||||||
|
|
||||||
import { subPath } from "@/interfaces";
|
import { subPath } from "@/interfaces";
|
||||||
import { Routes } from "@/router";
|
|
||||||
import { createSubPaths } from "@/utils";
|
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 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";
|
const settings = useSettings();
|
||||||
import PlaylistsTitle from "./Titles/PlaylistsTitle.vue";
|
const isSmall = computed(() => content_width.value < 800);
|
||||||
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 route = useRoute();
|
const route = useRoute();
|
||||||
const subPaths = ref<subPath[]>([]);
|
const subPaths = ref<subPath[]>([]);
|
||||||
|
|
||||||
|
let oldpath = "";
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.name,
|
() => route.name,
|
||||||
(newRoute) => {
|
(newRoute) => {
|
||||||
switch (newRoute) {
|
switch (newRoute) {
|
||||||
case Routes.folder: {
|
case Routes.folder: {
|
||||||
let oldpath = "";
|
|
||||||
[oldpath, subPaths.value] = createSubPaths(
|
[oldpath, subPaths.value] = createSubPaths(
|
||||||
route.params.path as string,
|
route.params.path as string,
|
||||||
oldpath
|
oldpath
|
||||||
@@ -84,18 +90,42 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (route.name == Routes.folder) {
|
||||||
|
[oldpath, subPaths.value] = createSubPaths(
|
||||||
|
route.params.path as string,
|
||||||
|
oldpath
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.topnav {
|
.topnav {
|
||||||
display: grid;
|
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 {
|
.left {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: max-content 1fr;
|
grid-template-columns: max-content 1fr;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
height: 2.25rem;
|
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
margin: auto 0;
|
margin: auto 0;
|
||||||
@@ -109,10 +139,45 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.center {
|
.logo {
|
||||||
display: grid;
|
width: max-content;
|
||||||
place-items: center;
|
margin: 0 auto;
|
||||||
margin-right: 1rem;
|
|
||||||
|
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>
|
</style>
|
||||||
|
|||||||
@@ -19,17 +19,18 @@ import ArrowSvg from "../../assets/icons/right-arrow.svg";
|
|||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
border-right: 1px solid $gray3;
|
border-right: 1px solid $gray5;
|
||||||
height: 100%;
|
height: max-content;
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
width: 2.25rem;
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-radius: 0.65rem;
|
border-radius: 5rem;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
transform: scale(1.25);
|
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>
|
<template>
|
||||||
<div id="folder-nav-title">
|
<div id="folder-nav-title">
|
||||||
<div class="folder">
|
<div class="fname">
|
||||||
<div class="fname-wrapper">
|
<div
|
||||||
<div class="fname">
|
class="icon image"
|
||||||
<div
|
@click="
|
||||||
class="icon image"
|
$router.push({ name: Routes.folder, params: { path: '$home' } })
|
||||||
@click="
|
"
|
||||||
$router.push({ name: Routes.folder, params: { path: '$home' } })
|
></div>
|
||||||
"
|
<BreadCrumbNav :sub-paths="subPaths" @navigate="navigate" />
|
||||||
></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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRoute, useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
import { Routes } from "@/router";
|
import { Routes } from "@/router";
|
||||||
import { subPath } from "@/interfaces";
|
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 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 router = useRouter();
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
const context_menu_showing = ref(false);
|
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
subPaths: subPath[];
|
subPaths: subPath[];
|
||||||
@@ -51,69 +29,36 @@ defineProps<{
|
|||||||
function navigate(path: string) {
|
function navigate(path: string) {
|
||||||
router.push({ name: Routes.folder, params: { path } });
|
router.push({ name: Routes.folder, params: { path } });
|
||||||
}
|
}
|
||||||
|
|
||||||
function showContextMenu(e: MouseEvent) {
|
|
||||||
showFolderContextMenu(
|
|
||||||
e,
|
|
||||||
context_menu_showing,
|
|
||||||
ContextSrc.FolderNav,
|
|
||||||
route.params.path as string
|
|
||||||
);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#folder-nav-title {
|
#folder-nav-title {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.folder {
|
.fname {
|
||||||
display: grid;
|
background-color: $gray4;
|
||||||
grid-template-columns: 1fr max-content max-content;
|
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 {
|
&::-webkit-scrollbar {
|
||||||
grid-template-columns: 1fr max-content;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fname-wrapper {
|
.icon {
|
||||||
width: 100%;
|
height: 2rem;
|
||||||
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;
|
|
||||||
aspect-ratio: 1;
|
aspect-ratio: 1;
|
||||||
padding: 0;
|
background-image: url("../../../assets/icons/folder.fill.svg");
|
||||||
|
background-size: 1.5rem;
|
||||||
svg {
|
margin-left: $smaller;
|
||||||
transform: scale(1.2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
<template>
|
||||||
<div class="nav-search-input">
|
<div class="nav-search-input">
|
||||||
<SearchInput :on_nav="true" />
|
|
||||||
<Tabs
|
<Tabs
|
||||||
v-if="!isMobile"
|
v-if="!(content_width < 800)"
|
||||||
:tabs="tabs"
|
:tabs="tabs"
|
||||||
:current-tab="($route.params.page as string)"
|
:current-tab="($route.params.page as string)"
|
||||||
@switchTab="(tab: string) => {
|
@switchTab="(tab: string) => {
|
||||||
@@ -18,10 +17,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Routes } from "@/router";
|
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 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 search = useSearchStore();
|
||||||
const tabs = ["top", "tracks", "albums", "artists"];
|
const tabs = ["top", "tracks", "albums", "artists"];
|
||||||
@@ -29,17 +28,6 @@ const tabs = ["top", "tracks", "albums", "artists"];
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.nav-search-input {
|
.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 {
|
#right-tabs {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div v-if="album.help_text" class="rhelp album">
|
<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>
|
</div>
|
||||||
<h4 v-tooltip class="title ellip">
|
<h4 v-tooltip class="title ellip">
|
||||||
{{ album.title }}
|
{{ album.title }}
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="artist.help_text" class="rhelp t-center">
|
<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>
|
||||||
<div class="artist-name t-center">
|
<div class="artist-name t-center">
|
||||||
{{ artist.name }}
|
{{ artist.name }}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ defineProps<{
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.cardlistrow {
|
.cardlistrow {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(10.1rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
|
||||||
padding-bottom: 2rem;
|
padding-bottom: 2rem;
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ import SeeAll from "../shared/SeeAll.vue";
|
|||||||
import FolderCard from "./FolderCard.vue";
|
import FolderCard from "./FolderCard.vue";
|
||||||
import AlbumCard from "./AlbumCard.vue";
|
import AlbumCard from "./AlbumCard.vue";
|
||||||
import ArtistCard from "./ArtistCard.vue";
|
import ArtistCard from "./ArtistCard.vue";
|
||||||
import PlaylistCard from "../PlaylistsList/PlaylistCard.vue";
|
|
||||||
import FavoritesCard from "./FavoritesCard.vue";
|
import FavoritesCard from "./FavoritesCard.vue";
|
||||||
|
import PlaylistCard from "../PlaylistsList/PlaylistCard.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
title: string;
|
title: string;
|
||||||
@@ -99,12 +99,12 @@ function getProps(item: { type: string; item: any }) {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.cardscroller {
|
.cardscroller {
|
||||||
padding: 2rem 0;
|
padding: 1.5rem 0;
|
||||||
|
|
||||||
.recentitems {
|
.recentitems {
|
||||||
gap: 1.5rem 0;
|
gap: 1.5rem 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(10.1rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-card {
|
.p-card {
|
||||||
@@ -135,5 +135,26 @@ function getProps(item: { type: string; item: any }) {
|
|||||||
padding: 1.25rem 2rem;
|
padding: 1.25rem 2rem;
|
||||||
margin: 1rem;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -18,7 +18,10 @@
|
|||||||
<PlayBtn :source="playSources.favorite" />
|
<PlayBtn :source="playSources.favorite" />
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<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="title">Favorite Tracks</div>
|
||||||
<div class="fcount">
|
<div class="fcount">
|
||||||
<b>{{ item.count + ` Track${item.count == 1 ? "" : "s"}` }}</b>
|
<b>{{ item.count + ` Track${item.count == 1 ? "" : "s"}` }}</b>
|
||||||
|
|||||||
@@ -26,7 +26,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="folder.help_text" class="rhelp folder">
|
<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>
|
||||||
<div class="ellip" :title="name(folder.path)">
|
<div class="ellip" :title="name(folder.path)">
|
||||||
{{ name(folder.path) }}
|
{{ name(folder.path) }}
|
||||||
@@ -47,6 +48,7 @@ defineProps<{
|
|||||||
path: string;
|
path: string;
|
||||||
count: number;
|
count: number;
|
||||||
help_text: string;
|
help_text: string;
|
||||||
|
time?: string;
|
||||||
};
|
};
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
|
title="Lyrics"
|
||||||
class="lyrics"
|
class="lyrics"
|
||||||
:class="{ showStatus: lyrics.exists }"
|
:class="{ showStatus: lyrics.exists }"
|
||||||
@click="handleClick"
|
@click="handleClick"
|
||||||
@@ -27,13 +28,12 @@ const lyrics = useLyrics();
|
|||||||
let prevRoute = ref(route.name);
|
let prevRoute = ref(route.name);
|
||||||
|
|
||||||
function handleClick() {
|
function handleClick() {
|
||||||
if (route.name === Routes.nowPlaying && route.params.tab === "lyrics") {
|
if (route.name === Routes.Lyrics) {
|
||||||
return router.back();
|
return router.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
name: Routes.nowPlaying,
|
name: Routes.Lyrics,
|
||||||
params: { tab: "lyrics" },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
prevRoute.value = route.name;
|
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>
|
|
||||||
@@ -127,7 +127,7 @@ onBeforeUnmount(() => {
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.songlist-item {
|
.songlist-item {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1.75rem 2fr 1fr 1.5fr 5.5rem;
|
grid-template-columns: 1.75rem 1.25fr 1fr 1fr 5.5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
height: $song-item-height;
|
height: $song-item-height;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
>
|
>
|
||||||
<SongItem
|
<SongItem
|
||||||
:track="item.track"
|
:track="item.track"
|
||||||
:index="index + 1"
|
:index="total ? total - index : index + 1"
|
||||||
:is_queue_track="is_queue"
|
:is_queue_track="is_queue"
|
||||||
:is_last="index == tracks.length - 1"
|
:is_last="index == tracks.length - 1"
|
||||||
:droppable="false"
|
:droppable="false"
|
||||||
@@ -44,6 +44,7 @@ defineProps<{
|
|||||||
oldIndex: number
|
oldIndex: number
|
||||||
) => void;
|
) => void;
|
||||||
source: dropSources;
|
source: dropSources;
|
||||||
|
total?: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const itemHeight = 64;
|
const itemHeight = 64;
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="trackcard rounded">
|
<RouterLink
|
||||||
|
:to="{
|
||||||
|
name: Routes.album,
|
||||||
|
params: {
|
||||||
|
albumhash: track.albumhash,
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
class="trackcard rounded"
|
||||||
|
>
|
||||||
<div class="image">
|
<div class="image">
|
||||||
<img class="rounded-sm" :src="paths.images.thumb.large + track.image" />
|
<img class="rounded-sm" :src="paths.images.thumb.large + track.image" />
|
||||||
<PlayBtn :source="playSource" :track="track" />
|
<PlayBtn :source="playSource" :track="track" />
|
||||||
</div>
|
</div>
|
||||||
<div class="tinfo">
|
<div class="tinfo">
|
||||||
<div v-if="track.help_text" class="rhelp track">
|
<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>
|
||||||
<div class="ttitle ellip">{{ track.title }}</div>
|
<div class="ttitle ellip">{{ track.title }}</div>
|
||||||
<ArtistName :albumartists="track.albumartists" :artists="track.artists" />
|
<ArtistName :albumartists="track.albumartists" :artists="track.artists" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</RouterLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -21,6 +30,7 @@ import { playSources } from "@/enums";
|
|||||||
|
|
||||||
import PlayBtn from "../shared/PlayBtn.vue";
|
import PlayBtn from "../shared/PlayBtn.vue";
|
||||||
import ArtistName from "../shared/ArtistName.vue";
|
import ArtistName from "../shared/ArtistName.vue";
|
||||||
|
import { Routes } from "@/router";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
track: Track;
|
track: Track;
|
||||||
@@ -69,6 +79,8 @@ defineEmits<{
|
|||||||
|
|
||||||
img {
|
img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist {
|
.artist {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
v-if="isQueueTrack"
|
v-if="isQueueTrack"
|
||||||
class="remove-track"
|
class="remove-track"
|
||||||
title="Remove from queue"
|
title="Remove from queue"
|
||||||
@click.stop="queue.removeFromQueue(index)"
|
@click.stop="player.removeByIndex(index)"
|
||||||
>
|
>
|
||||||
<DelSvg />
|
<DelSvg />
|
||||||
</div>
|
</div>
|
||||||
@@ -60,10 +60,11 @@
|
|||||||
import { useRoute } from "vue-router";
|
import { useRoute } from "vue-router";
|
||||||
import { onBeforeUnmount, ref, watch } from "vue";
|
import { onBeforeUnmount, ref, watch } from "vue";
|
||||||
|
|
||||||
|
import useTracklist from "@/stores/queue/tracklist";
|
||||||
|
|
||||||
import { paths } from "@/config";
|
import { paths } from "@/config";
|
||||||
import { favType } from "@/enums";
|
import { favType } from "@/enums";
|
||||||
import { Track } from "@/interfaces";
|
import { Track } from "@/interfaces";
|
||||||
import useQueueStore from "@/stores/queue";
|
|
||||||
import favoriteHandler from "@/helpers/favoriteHandler";
|
import favoriteHandler from "@/helpers/favoriteHandler";
|
||||||
import { showTrackContextMenu as showContext } from "@/helpers/contextMenuHandler";
|
import { showTrackContextMenu as showContext } from "@/helpers/contextMenuHandler";
|
||||||
|
|
||||||
@@ -79,10 +80,11 @@ const props = defineProps<{
|
|||||||
index?: number;
|
index?: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const queue = useQueueStore();
|
const player = useTracklist();
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
const context_on = ref(false);
|
const context_on = ref(false);
|
||||||
const is_fav = ref(props.track.is_favorite);
|
const is_fav = ref(props.track.is_favorite);
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
function showMenu(e: MouseEvent) {
|
function showMenu(e: MouseEvent) {
|
||||||
showContext(e, props.track, context_on, route);
|
showContext(e, props.track, context_on, route);
|
||||||
|
|||||||
@@ -4,26 +4,14 @@ import { Option, Playlist } from "@/interfaces";
|
|||||||
import { getTracksInPath } from "@/requests/folders";
|
import { getTracksInPath } from "@/requests/folders";
|
||||||
|
|
||||||
import useModal from "@/stores/modal";
|
import useModal from "@/stores/modal";
|
||||||
import useSettings from "@/stores/settings";
|
|
||||||
import useTracklist from "@/stores/queue/tracklist";
|
import useTracklist from "@/stores/queue/tracklist";
|
||||||
|
|
||||||
import { addFolderToPlaylist } from "@/requests/playlists";
|
import { addFolderToPlaylist } from "@/requests/playlists";
|
||||||
import { getAddToPlaylistOptions } from "./utils";
|
import { getAddToPlaylistOptions } from "./utils";
|
||||||
|
|
||||||
export default async (trigger_src: ContextSrc, path: string) => {
|
export default async (path: string) => {
|
||||||
const settings = useSettings();
|
|
||||||
const modal = useModal();
|
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>{
|
const play_next = <Option>{
|
||||||
label: "Play next",
|
label: "Play next",
|
||||||
action: () => {
|
action: () => {
|
||||||
@@ -65,5 +53,5 @@ export default async (trigger_src: ContextSrc, path: string) => {
|
|||||||
icon: icons.PlaylistIcon,
|
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();
|
useQueue().clearQueue();
|
||||||
},
|
},
|
||||||
icon: DeleteIcon,
|
icon: DeleteIcon,
|
||||||
|
critical: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveAsPlaylist: Option = {
|
const saveAsPlaylist: Option = {
|
||||||
label: "Save as playlist",
|
label: "Save queue as playlist",
|
||||||
action: () => {
|
action: () => {
|
||||||
useModalStore().showSaveQueueAsPlaylistModal(getQueueName(store.from));
|
useModalStore().showSaveQueueAsPlaylistModal(getQueueName(store.from));
|
||||||
},
|
},
|
||||||
@@ -51,7 +52,7 @@ export default async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addToPlaylist: Option = {
|
const addToPlaylist: Option = {
|
||||||
label: "Add to Playlist",
|
label: "Add queue to playlist",
|
||||||
children: await getAddToPlaylistOptions(AddToPlaylistAction, {
|
children: await getAddToPlaylistOptions(AddToPlaylistAction, {
|
||||||
trackhash: store.tracklist.map((t) => t.trackhash).join(","),
|
trackhash: store.tracklist.map((t) => t.trackhash).join(","),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
import usePlaylistStore from "@/stores/pages/playlist";
|
import usePlaylistStore from "@/stores/pages/playlist";
|
||||||
import useQueueStore from "@/stores/queue";
|
import useQueueStore from "@/stores/queue";
|
||||||
import useTracklist from "@/stores/queue/tracklist";
|
import useTracklist from "@/stores/queue/tracklist";
|
||||||
import { getAddToPlaylistOptions } from "./utils";
|
import { getAddToPlaylistOptions, get_find_on_social } from "./utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of context menu items for a track.
|
* Returns a list of context menu items for a track.
|
||||||
@@ -190,11 +190,23 @@ export default async (
|
|||||||
go_to_artist,
|
go_to_artist,
|
||||||
go_to_alb_artist,
|
go_to_alb_artist,
|
||||||
open_in_explorer,
|
open_in_explorer,
|
||||||
|
get_find_on_social("track", `${track.title} ${track.artists[0].name}`),
|
||||||
// del_track,
|
// del_track,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (route.name === Routes.playlist && on_playlist) {
|
if (route.name === Routes.playlist && on_playlist) {
|
||||||
options.splice(4, 0, getRemoveFromPlaylistOption());
|
options.splice(0, 0, getRemoveFromPlaylistOption());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.name === Routes.nowPlaying) {
|
||||||
|
options.splice(0, 0, <Option>{
|
||||||
|
label: "Remove from Queue",
|
||||||
|
action: () => {
|
||||||
|
useTracklist().removeByIndex(track.index);
|
||||||
|
},
|
||||||
|
icon: DeleteIcon,
|
||||||
|
critical: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export async function getAddToPlaylistOptions(
|
|||||||
return [...items, separator, ...playlists];
|
return [...items, separator, ...playlists];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const get_find_on_social = (page = "album") => {
|
export const get_find_on_social = (page = "album", query = "") => {
|
||||||
const is_album = page === "album";
|
const is_album = page === "album";
|
||||||
const getAlbumSearchTerm = () => {
|
const getAlbumSearchTerm = () => {
|
||||||
const store = useAlbum();
|
const store = useAlbum();
|
||||||
@@ -65,7 +65,11 @@ export const get_find_on_social = (page = "album") => {
|
|||||||
.map((a) => a.name)
|
.map((a) => a.name)
|
||||||
.join(", ")}`;
|
.join(", ")}`;
|
||||||
};
|
};
|
||||||
const search_term = is_album ? getAlbumSearchTerm() : useArtist().info.name;
|
const search_term = query
|
||||||
|
? query
|
||||||
|
: is_album
|
||||||
|
? getAlbumSearchTerm()
|
||||||
|
: useArtist().info.name;
|
||||||
|
|
||||||
return <Option>{
|
return <Option>{
|
||||||
label: "Search on",
|
label: "Search on",
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const showFolderContextMenu = (
|
|||||||
) => {
|
) => {
|
||||||
const menu = useContextStore();
|
const menu = useContextStore();
|
||||||
|
|
||||||
const options = () => folderContextItems(source, path);
|
const options = () => folderContextItems(path);
|
||||||
menu.showContextMenu(e, options, source);
|
menu.showContextMenu(e, options, source);
|
||||||
|
|
||||||
flagWatcher(menu, flag);
|
flagWatcher(menu, flag);
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import useQStore from "@/stores/queue";
|
import useQueue from "@/stores/queue";
|
||||||
import useModalStore from "@/stores/modal";
|
import useModal from "@/stores/modal";
|
||||||
import useContextStore from "@/stores/context";
|
import useContextMenu from "@/stores/context";
|
||||||
|
import useSettings from "@/stores/settings";
|
||||||
|
|
||||||
let key_down_fired = false;
|
let key_down_fired = "";
|
||||||
|
|
||||||
function focusPageSearchBox() {
|
function focusPageSearchBox() {
|
||||||
const elem = document.getElementById(
|
const elem = document.getElementById(
|
||||||
@@ -13,19 +14,23 @@ function focusPageSearchBox() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (queue: typeof useQStore, modal: typeof useModalStore) {
|
function resetKeyFired() {
|
||||||
|
key_down_fired = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function (queue: typeof useQueue, modal: typeof useModal) {
|
||||||
const q = queue();
|
const q = queue();
|
||||||
|
|
||||||
window.addEventListener("keydown", (e: KeyboardEvent) => {
|
window.addEventListener("keydown", (e: KeyboardEvent) => {
|
||||||
const target = e.target as HTMLElement;
|
const target = e.target as HTMLElement;
|
||||||
if (e.altKey) return;
|
|
||||||
|
|
||||||
|
if (e.altKey) return;
|
||||||
const ctrlKey = e.ctrlKey;
|
const ctrlKey = e.ctrlKey;
|
||||||
const shiftKey = e.shiftKey;
|
const shiftKey = e.shiftKey;
|
||||||
|
|
||||||
const no_text_selection = !window.getSelection()?.toString();
|
const no_text_selection = !window.getSelection()?.toString();
|
||||||
|
|
||||||
function FocusedOnInput(target: HTMLElement) {
|
function focusedOnInput() {
|
||||||
const targett = target as HTMLInputElement;
|
const targett = target as HTMLInputElement;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -39,7 +44,7 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
|
|||||||
if (ctrlKey) return true;
|
if (ctrlKey) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FocusedOnInput(target)) {
|
if (focusedOnInput()) {
|
||||||
if (e.key == "Escape") {
|
if (e.key == "Escape") {
|
||||||
target.blur();
|
target.blur();
|
||||||
}
|
}
|
||||||
@@ -47,10 +52,16 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key_down_fired) return;
|
if (key_down_fired == e.key.toLowerCase()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (e.key) {
|
function setKeyFired() {
|
||||||
case "ArrowRight":
|
key_down_fired = e.key.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (e.key.toLowerCase()) {
|
||||||
|
case "arrowright":
|
||||||
{
|
{
|
||||||
const doSeek = triggerSeek();
|
const doSeek = triggerSeek();
|
||||||
|
|
||||||
@@ -62,7 +73,7 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
|
|||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// fire event after 1 second
|
// fire event after 1 second
|
||||||
key_down_fired = false;
|
resetKeyFired();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
if (no_text_selection) {
|
if (no_text_selection) {
|
||||||
@@ -71,7 +82,7 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "ArrowLeft":
|
case "arrowleft":
|
||||||
{
|
{
|
||||||
const doSeek = triggerSeek();
|
const doSeek = triggerSeek();
|
||||||
|
|
||||||
@@ -86,7 +97,7 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
key_down_fired = false;
|
resetKeyFired();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +106,6 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
|
|||||||
case " ": // space
|
case " ": // space
|
||||||
{
|
{
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
q.playPause();
|
q.playPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,9 +118,21 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "Escape": {
|
case "b": {
|
||||||
|
if (!ctrlKey) return;
|
||||||
|
e.stopPropagation();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const { toggleDisableSidebar } = useSettings();
|
||||||
|
toggleDisableSidebar();
|
||||||
|
setKeyFired();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "escape": {
|
||||||
const m = modal();
|
const m = modal();
|
||||||
const c = useContextStore();
|
const c = useContextMenu();
|
||||||
|
|
||||||
if (m.visible) {
|
if (m.visible) {
|
||||||
m.hideModal();
|
m.hideModal();
|
||||||
@@ -122,10 +144,10 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
key_down_fired = true;
|
setKeyFired();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("keyup", () => {
|
window.addEventListener("keyup", () => {
|
||||||
key_down_fired = false;
|
resetKeyFired();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,3 +21,4 @@ export { default as AddToQueueIcon } from "@/assets/icons/add-to-queue.svg?raw";
|
|||||||
export { default as ExpandIcon } from "@/assets/icons/expand.svg?raw";
|
export { default as ExpandIcon } from "@/assets/icons/expand.svg?raw";
|
||||||
export { default as PlaylistIcon } from "@/assets/icons/playlist.svg?raw";
|
export { default as PlaylistIcon } from "@/assets/icons/playlist.svg?raw";
|
||||||
export { default as GridIcon } from "@/assets/icons/grid.svg?raw";
|
export { default as GridIcon } from "@/assets/icons/grid.svg?raw";
|
||||||
|
export { default as ReloadIcon } from "@/assets/icons/reload.svg?raw";
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export interface Track extends AlbumDisc {
|
|||||||
copyright?: string;
|
copyright?: string;
|
||||||
master_index?: number;
|
master_index?: number;
|
||||||
help_text?: string;
|
help_text?: string;
|
||||||
|
time?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Folder {
|
export interface Folder {
|
||||||
@@ -56,6 +57,7 @@ export interface Album {
|
|||||||
copyright?: string;
|
copyright?: string;
|
||||||
|
|
||||||
help_text?: string;
|
help_text?: string;
|
||||||
|
time?: string;
|
||||||
is_live: boolean;
|
is_live: boolean;
|
||||||
is_compilation: boolean;
|
is_compilation: boolean;
|
||||||
is_soundtrack: boolean;
|
is_soundtrack: boolean;
|
||||||
@@ -76,6 +78,7 @@ export interface Artist {
|
|||||||
colors: string[];
|
colors: string[];
|
||||||
is_favorite?: boolean;
|
is_favorite?: boolean;
|
||||||
help_text?: string;
|
help_text?: string;
|
||||||
|
time?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Option {
|
export interface Option {
|
||||||
@@ -107,6 +110,7 @@ export interface Playlist {
|
|||||||
settings: PlaylistSettings;
|
settings: PlaylistSettings;
|
||||||
pinned: boolean;
|
pinned: boolean;
|
||||||
help_text?: string;
|
help_text?: string;
|
||||||
|
time?: string;
|
||||||
images:
|
images:
|
||||||
| {
|
| {
|
||||||
image: string;
|
image: string;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export interface SettingOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Setting {
|
export interface Setting {
|
||||||
title: string;
|
title?: string;
|
||||||
desc?: string;
|
desc?: string;
|
||||||
type: SettingType;
|
type: SettingType;
|
||||||
options?: SettingOption[];
|
options?: SettingOption[];
|
||||||
@@ -16,6 +16,8 @@ export interface Setting {
|
|||||||
button_text?: () => string;
|
button_text?: () => string;
|
||||||
defaultAction?: () => void;
|
defaultAction?: () => void;
|
||||||
show_if?: () => boolean;
|
show_if?: () => boolean;
|
||||||
|
experimental?: boolean;
|
||||||
|
new?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SettingGroup {
|
export interface SettingGroup {
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ app.use(MotionPlugin);
|
|||||||
|
|
||||||
app.directive("tooltip", vTooltip);
|
app.directive("tooltip", vTooltip);
|
||||||
|
|
||||||
|
app.component("WrapBalancer", WrapBalancer);
|
||||||
app.component("RecycleScroller", RecycleScroller);
|
app.component("RecycleScroller", RecycleScroller);
|
||||||
app.component("DynamicScroller", DynamicScroller);
|
app.component("DynamicScroller", DynamicScroller);
|
||||||
app.component("DynamicScrollerItem", DynamicScrollerItem);
|
app.component("DynamicScrollerItem", DynamicScrollerItem);
|
||||||
app.component("WrapBalancer", WrapBalancer);
|
|
||||||
|
|
||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
|
|||||||
@@ -7,24 +7,24 @@ import usePlaylistPageStore from "@/stores/pages/playlist";
|
|||||||
import usePlaylistListPageStore from "@/stores/pages/playlists";
|
import usePlaylistListPageStore from "@/stores/pages/playlists";
|
||||||
import useArtistPageStore from "@/stores/pages/artist";
|
import useArtistPageStore from "@/stores/pages/artist";
|
||||||
|
|
||||||
import HomeView from "@/views/HomeView";
|
|
||||||
|
|
||||||
const FolderView = () => import("@/views/FolderView.vue");
|
import HomeView from "@/views/HomeView";
|
||||||
const PlaylistListView = () => import("@/views/PlaylistList.vue");
|
const Lyrics = () => import("@/views/LyricsView");
|
||||||
const PlaylistView = () => import("@/views/PlaylistView/index.vue");
|
|
||||||
const AlbumView = () => import("@/views/AlbumView/index.vue");
|
|
||||||
const ArtistView = () => import("@/views/ArtistView");
|
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 FavoriteAlbums = () => import("@/views/FavoriteAlbums.vue");
|
|
||||||
const FavoriteTracks = () => import("@/views/FavoriteTracks.vue");
|
|
||||||
const FavoriteArtists = () => import("@/views/FavoriteArtists.vue");
|
|
||||||
const NotFound = () => import("@/views/NotFound.vue");
|
const NotFound = () => import("@/views/NotFound.vue");
|
||||||
const NowPlaying = () => import("@/views/NowPlaying");
|
const NowPlaying = () => import("@/views/NowPlaying");
|
||||||
|
const SearchView = () => import("@/views/SearchView");
|
||||||
const AlbumList = () => import("@/views/AlbumListView");
|
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 = {
|
const folder = {
|
||||||
path: "/folder/:path",
|
path: "/folder/:path",
|
||||||
@@ -103,6 +103,12 @@ const NowPlayingView = {
|
|||||||
component: NowPlaying,
|
component: NowPlaying,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LyricsView = {
|
||||||
|
path: "/lyrics",
|
||||||
|
name: "LyricsView",
|
||||||
|
component: Lyrics,
|
||||||
|
};
|
||||||
|
|
||||||
const ArtistTracks = {
|
const ArtistTracks = {
|
||||||
path: "/artists/:hash/tracks",
|
path: "/artists/:hash/tracks",
|
||||||
name: "ArtistTracks",
|
name: "ArtistTracks",
|
||||||
@@ -136,7 +142,13 @@ const favorites = {
|
|||||||
const favoriteAlbums = {
|
const favoriteAlbums = {
|
||||||
path: "/favorites/albums",
|
path: "/favorites/albums",
|
||||||
name: "FavoriteAlbums",
|
name: "FavoriteAlbums",
|
||||||
component: FavoriteAlbums,
|
component: FavoriteCardScroller,
|
||||||
|
};
|
||||||
|
|
||||||
|
const favoriteArtists = {
|
||||||
|
path: "/favorites/artists",
|
||||||
|
name: "FavoriteArtists",
|
||||||
|
component: FavoriteCardScroller,
|
||||||
};
|
};
|
||||||
|
|
||||||
const favoriteTracks = {
|
const favoriteTracks = {
|
||||||
@@ -145,12 +157,6 @@ const favoriteTracks = {
|
|||||||
component: FavoriteTracks,
|
component: FavoriteTracks,
|
||||||
};
|
};
|
||||||
|
|
||||||
const favoriteArtists = {
|
|
||||||
path: "/favorites/artists",
|
|
||||||
name: "FavoriteArtists",
|
|
||||||
component: FavoriteArtists,
|
|
||||||
};
|
|
||||||
|
|
||||||
const notFound = {
|
const notFound = {
|
||||||
name: "NotFound",
|
name: "NotFound",
|
||||||
path: "/:pathMatch(.*)",
|
path: "/:pathMatch(.*)",
|
||||||
@@ -194,9 +200,10 @@ const routes = [
|
|||||||
Home,
|
Home,
|
||||||
AlbumListView,
|
AlbumListView,
|
||||||
ArtistListView,
|
ArtistListView,
|
||||||
|
LyricsView,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Routes = {
|
const Routes = {
|
||||||
folder: folder.name,
|
folder: folder.name,
|
||||||
playlists: playlists.name,
|
playlists: playlists.name,
|
||||||
playlist: playlistView.name,
|
playlist: playlistView.name,
|
||||||
@@ -215,6 +222,7 @@ export const Routes = {
|
|||||||
Home: Home.name,
|
Home: Home.name,
|
||||||
AlbumList: AlbumListView.name,
|
AlbumList: AlbumListView.name,
|
||||||
ArtistList: ArtistListView.name,
|
ArtistList: ArtistListView.name,
|
||||||
|
Lyrics: LyricsView.name,
|
||||||
};
|
};
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@@ -223,4 +231,4 @@ const router = createRouter({
|
|||||||
routes,
|
routes,
|
||||||
} as RouterOptions);
|
} as RouterOptions);
|
||||||
|
|
||||||
export { router };
|
export { router, Routes };
|
||||||
|
|||||||
33
src/settings/audio/groups.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Setting } from "@/interfaces/settings";
|
||||||
|
import settings from "@/stores/settings";
|
||||||
|
import { SettingType } from "../enums";
|
||||||
|
|
||||||
|
const use_silence: Setting = {
|
||||||
|
title: "Silence padding removal",
|
||||||
|
desc: "Automatically skip silence between tracks.",
|
||||||
|
type: SettingType.binary,
|
||||||
|
state: () => settings().use_silence_skip,
|
||||||
|
action: () => settings().toggleUseSilenceSkip(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const use_crossfade: Setting = {
|
||||||
|
title: "Enable crossfade",
|
||||||
|
desc: "Fade out the current track while fading in the next track",
|
||||||
|
type: SettingType.binary,
|
||||||
|
state: () => settings().use_crossfade,
|
||||||
|
action: () => settings().toggleCrossfade(),
|
||||||
|
// @ts-ignore
|
||||||
|
experimental: !(window.chrome),
|
||||||
|
};
|
||||||
|
|
||||||
|
const crossfade: Setting = {
|
||||||
|
title: "Crossfade duration",
|
||||||
|
desc: "Duration of the crossfade in seconds",
|
||||||
|
type: SettingType.locked_number_input,
|
||||||
|
state: () => settings().crossfade_duration_seconds,
|
||||||
|
action: settings().setCrossfadeDuration,
|
||||||
|
defaultAction: () => {},
|
||||||
|
show_if: () => settings().use_crossfade,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [use_silence, use_crossfade, crossfade];
|
||||||
13
src/settings/audio/index.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { SettingCategory } from "@/interfaces/settings";
|
||||||
|
import gapless from "./groups";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: "Audio",
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
title: "Playback",
|
||||||
|
desc: "Settings related to audio playback",
|
||||||
|
settings: [...gapless],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as SettingCategory;
|
||||||
@@ -5,4 +5,6 @@ export enum SettingType {
|
|||||||
binary,
|
binary,
|
||||||
button,
|
button,
|
||||||
root_dirs,
|
root_dirs,
|
||||||
|
locked_number_input,
|
||||||
|
quick_actions,
|
||||||
}
|
}
|
||||||
|
|||||||
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];
|
||||||
@@ -11,6 +11,9 @@ import albums from "./albums";
|
|||||||
import separators from "./separators";
|
import separators from "./separators";
|
||||||
import tracks from "./tracks";
|
import tracks from "./tracks";
|
||||||
import circularArtistImg from "./circular-artist-img";
|
import circularArtistImg from "./circular-artist-img";
|
||||||
|
import quickactions from "./quickactions";
|
||||||
|
import layout from "./layout";
|
||||||
|
import folderlistmode from "./folderlistmode";
|
||||||
|
|
||||||
const npStrings = strings.nowPlayingStrings;
|
const npStrings = strings.nowPlayingStrings;
|
||||||
const rootRootStrings = strings.manageRootDirsStrings;
|
const rootRootStrings = strings.manageRootDirsStrings;
|
||||||
@@ -18,20 +21,26 @@ const rootRootStrings = strings.manageRootDirsStrings;
|
|||||||
export default {
|
export default {
|
||||||
title: "General",
|
title: "General",
|
||||||
groups: [
|
groups: [
|
||||||
|
{
|
||||||
|
settings: quickactions,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "UI Settings",
|
title: "UI Settings",
|
||||||
desc: "Settings for various parts of the user interface.",
|
desc: "Settings for various parts of the user interface.",
|
||||||
settings: [
|
settings: [
|
||||||
|
...layout,
|
||||||
...extendWidth,
|
...extendWidth,
|
||||||
...sidebarSettings,
|
...sidebarSettings,
|
||||||
circularArtistImg,
|
circularArtistImg,
|
||||||
...contextChildrenShowMode,
|
...contextChildrenShowMode,
|
||||||
|
...folderlistmode,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: npStrings.title,
|
title: npStrings.title,
|
||||||
desc: npStrings.desc,
|
desc: npStrings.desc,
|
||||||
settings: [...nowPlaying],
|
settings: [...nowPlaying],
|
||||||
|
show_if: () => !useSettingsStore().is_alt_layout,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: rootRootStrings.title,
|
title: rootRootStrings.title,
|
||||||
@@ -61,3 +70,5 @@ export default {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as SettingCategory;
|
} as SettingCategory;
|
||||||
|
|
||||||
|
// 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
src/settings/general/quickactions.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Setting } from "@/interfaces/settings";
|
||||||
|
import { SettingType } from "../enums";
|
||||||
|
|
||||||
|
const quick_settings: Setting = {
|
||||||
|
title: "Quick settings",
|
||||||
|
type: SettingType.quick_actions,
|
||||||
|
state: () => {},
|
||||||
|
action: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default [quick_settings];
|
||||||
@@ -6,10 +6,12 @@ import useSettingsStore from "@/stores/settings";
|
|||||||
const settings = useSettingsStore;
|
const settings = useSettingsStore;
|
||||||
|
|
||||||
const use_sidebar: Setting = {
|
const use_sidebar: Setting = {
|
||||||
title: "Hide right sidebar",
|
title: "Toggle right sidebar",
|
||||||
|
desc: "CTRL + B",
|
||||||
type: SettingType.binary,
|
type: SettingType.binary,
|
||||||
state: () => !settings().use_sidebar,
|
state: () => settings().use_sidebar,
|
||||||
action: () => settings().toggleDisableSidebar(),
|
action: () => settings().toggleDisableSidebar(),
|
||||||
|
show_if: () => !settings().is_alt_layout,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [use_sidebar];
|
export default [use_sidebar];
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import audio from "./audio";
|
||||||
|
import about from "./about";
|
||||||
import general from "./general";
|
import general from "./general";
|
||||||
import plugins from "./plugins";
|
import plugins from "./plugins";
|
||||||
import about from "./about";
|
|
||||||
|
|
||||||
export default [general, plugins, about];
|
export default [general, audio, plugins, about];
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -3,6 +3,7 @@ import { defineStore } from "pinia";
|
|||||||
|
|
||||||
import { getRecentlyAdded, getRecentlyPlayed } from "@/requests/home";
|
import { getRecentlyAdded, getRecentlyPlayed } from "@/requests/home";
|
||||||
import { maxAbumCards } from "./content-width";
|
import { maxAbumCards } from "./content-width";
|
||||||
|
import { Routes, router } from "@/router";
|
||||||
|
|
||||||
export default defineStore("homepage", () => {
|
export default defineStore("homepage", () => {
|
||||||
const recentlyAddedCutoff = ref(0);
|
const recentlyAddedCutoff = ref(0);
|
||||||
@@ -22,11 +23,19 @@ export default defineStore("homepage", () => {
|
|||||||
recentlyPlayed.value = data.items;
|
recentlyPlayed.value = data.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetAll() {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (router.currentRoute.value.name == Routes.Home) return;
|
||||||
|
[recentlyAdded.value, recentlyPlayed.value] = [[], []];
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
recentlyAddedCutoff,
|
recentlyAddedCutoff,
|
||||||
recentlyAdded,
|
recentlyAdded,
|
||||||
recentlyPlayed,
|
recentlyPlayed,
|
||||||
fetchRecentlyAdded,
|
fetchRecentlyAdded,
|
||||||
fetchRecentlyPlayed,
|
fetchRecentlyPlayed,
|
||||||
|
resetAll,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
import useQueue from "./queue";
|
|
||||||
import useTabs from "./tabs";
|
|
||||||
import useLyricsPlugin from "./plugins/lyrics";
|
import useLyricsPlugin from "./plugins/lyrics";
|
||||||
|
import useQueue from "./queue";
|
||||||
import useSettings from "./settings";
|
import useSettings from "./settings";
|
||||||
|
|
||||||
import { LyricsLine } from "@/interfaces";
|
import { LyricsLine } from "@/interfaces";
|
||||||
import { checkExists, getLyrics } from "@/requests/lyrics";
|
import { checkExists, getLyrics } from "@/requests/lyrics";
|
||||||
|
import { Routes, router } from "@/router";
|
||||||
|
|
||||||
// a custom error class called HasNoSyncedLyricsError
|
// a custom error class called HasNoSyncedLyricsError
|
||||||
class HasUnSyncedLyricsError extends Error {
|
class HasUnSyncedLyricsError extends Error {
|
||||||
@@ -87,7 +87,7 @@ export default defineStore("lyrics", {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
scrollToContainerTop() {
|
scrollToContainerTop() {
|
||||||
const container = document.getElementById("sidelyrics");
|
const container = document.getElementById("lyricscontent");
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
container.scroll({
|
container.scroll({
|
||||||
@@ -97,9 +97,7 @@ export default defineStore("lyrics", {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
checkExists(filepath: string, trackhash: string) {
|
checkExists(filepath: string, trackhash: string) {
|
||||||
const tabs = useTabs();
|
if (router.currentRoute.value.name !== Routes.Lyrics) {
|
||||||
|
|
||||||
if (tabs.nowplaying !== tabs.tabs.lyrics) {
|
|
||||||
this.lyrics = <LyricsLine[]>[];
|
this.lyrics = <LyricsLine[]>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { maxAbumCards } from "@/stores/content-width";
|
|||||||
import { useFuse } from "@/utils";
|
import { useFuse } from "@/utils";
|
||||||
import setColorsToStore from "@/utils/colortools/setColorsToStore";
|
import setColorsToStore from "@/utils/colortools/setColorsToStore";
|
||||||
import { useNotifStore } from "../notification";
|
import { useNotifStore } from "../notification";
|
||||||
|
import { router, Routes } from "@/router";
|
||||||
|
|
||||||
interface Disc {
|
interface Disc {
|
||||||
[key: string]: Track[];
|
[key: string]: Track[];
|
||||||
@@ -136,13 +137,19 @@ export default defineStore("album", {
|
|||||||
this.query = "";
|
this.query = "";
|
||||||
},
|
},
|
||||||
resetAlbumArtists() {
|
resetAlbumArtists() {
|
||||||
this.albumArtists = [];
|
setTimeout(() => {
|
||||||
|
if (router.currentRoute.value.name == Routes.album) return;
|
||||||
|
this.albumArtists = [];
|
||||||
|
this.fetched_other_hash = "";
|
||||||
|
}, 10000);
|
||||||
},
|
},
|
||||||
resetOtherVersions() {
|
resetOtherVersions() {
|
||||||
this.otherVersions = [];
|
this.otherVersions = [];
|
||||||
|
this.fetched_version_hash = "";
|
||||||
},
|
},
|
||||||
resetSimilarAlbums() {
|
resetSimilarAlbums() {
|
||||||
this.similarAlbums = [];
|
this.similarAlbums = [];
|
||||||
|
this.fetched_similar_hash = "";
|
||||||
},
|
},
|
||||||
makeFavorite() {
|
makeFavorite() {
|
||||||
this.info.is_favorite = true;
|
this.info.is_favorite = true;
|
||||||
@@ -150,6 +157,14 @@ export default defineStore("album", {
|
|||||||
removeFavorite() {
|
removeFavorite() {
|
||||||
this.info.is_favorite = false;
|
this.info.is_favorite = false;
|
||||||
},
|
},
|
||||||
|
resetAll() {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (router.currentRoute.value.name == Routes.album) return;
|
||||||
|
this.resetAlbumArtists();
|
||||||
|
this.resetOtherVersions();
|
||||||
|
this.resetSimilarAlbums();
|
||||||
|
}, 5000);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
filteredTracks(): ComputedRef<FuseResult[]> {
|
filteredTracks(): ComputedRef<FuseResult[]> {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Album, Artist, Track } from "@/interfaces";
|
|||||||
import { maxAbumCards } from "@/stores/content-width";
|
import { maxAbumCards } from "@/stores/content-width";
|
||||||
import useSettingsStore from "@/stores/settings";
|
import useSettingsStore from "@/stores/settings";
|
||||||
import setColorsToStore from "@/utils/colortools/setColorsToStore";
|
import setColorsToStore from "@/utils/colortools/setColorsToStore";
|
||||||
|
import { Routes, router } from "@/router";
|
||||||
|
|
||||||
export default defineStore("artistPage", {
|
export default defineStore("artistPage", {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -27,7 +28,6 @@ export default defineStore("artistPage", {
|
|||||||
btn: "",
|
btn: "",
|
||||||
},
|
},
|
||||||
genres: <string[]>[],
|
genres: <string[]>[],
|
||||||
|
|
||||||
fetched_similar_hash: "",
|
fetched_similar_hash: "",
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
@@ -60,7 +60,6 @@ export default defineStore("artistPage", {
|
|||||||
this.info.artisthash,
|
this.info.artisthash,
|
||||||
maxAbumCards.value
|
maxAbumCards.value
|
||||||
);
|
);
|
||||||
|
|
||||||
},
|
},
|
||||||
extractColors() {
|
extractColors() {
|
||||||
const url = paths.images.artist.large + this.info.image;
|
const url = paths.images.artist.large + this.info.image;
|
||||||
@@ -79,6 +78,14 @@ export default defineStore("artistPage", {
|
|||||||
resetSimilarArtists() {
|
resetSimilarArtists() {
|
||||||
this.similar_artists = [];
|
this.similar_artists = [];
|
||||||
},
|
},
|
||||||
|
resetAll() {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (router.currentRoute.value.name == Routes.artist) return;
|
||||||
|
this.resetAlbums();
|
||||||
|
this.resetSimilarArtists();
|
||||||
|
this.fetched_similar_hash = "";
|
||||||
|
}, 5000);
|
||||||
|
},
|
||||||
makeFavorite() {
|
makeFavorite() {
|
||||||
this.info.is_favorite = true;
|
this.info.is_favorite = true;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { router } from "@/router";
|
import { Routes, router } from "@/router";
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
|
|
||||||
import { discographyAlbumTypes } from "@/enums";
|
import { discographyAlbumTypes } from "@/enums";
|
||||||
@@ -71,5 +71,11 @@ export default defineStore("artistDiscography", {
|
|||||||
this.toShow = [];
|
this.toShow = [];
|
||||||
this.artistname = "";
|
this.artistname = "";
|
||||||
},
|
},
|
||||||
|
resetAll() {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (router.currentRoute.value.name == Routes.artistDiscography) return;
|
||||||
|
this.resetStore();
|
||||||
|
}, 5000);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||