48 Commits
next ... v1.4.8

Author SHA1 Message Date
mungai-njoroge
293e95c5b0 disable wide artist header by default 2024-02-25 23:53:28 +03:00
Mungai Njoroge
302095ef80 v1.4.8 - No Sidebar Layout 2024-02-16 22:05:05 +03:00
mungai-njoroge
feb35afa9a fix: clicking on search input taking you to the
+ add an unused library numbers component
+ add fav artists to browse section
2024-02-16 21:32:57 +03:00
mungai-njoroge
6b925a0434 add expand icon to bottom bar thumbnail
+ remove options button from nav in now playing page
2024-02-12 21:48:21 +03:00
mungai-njoroge
4211ccc685 add no sidebar layout
+ add search input on the top nav
+ scroll to top on page navigation
+ move folder list view to settings page
+ remove close button from modals
+ change notification background
+ add fav tracks to home>browse
+ redesign modal inputs and buttons
+ show time on recent items cards on hover (homepage)
+ make nav buttons circular
+ move homepage browse group to bottom of page
+ reduce context menu paddings
+ fix: artist header artist image gradient
+ fix: card size calculation with a contentresizer element
+ fix: next track not starting at 0 duration when progressbar is focused
+ fix: track not pausing when timer is set off when silence thing is on
+ refactor: break down nav bar into multiple components
+ enhancement: make queue context menu more verbose
+ code enhancements
2024-02-12 00:44:05 +03:00
mungai-njoroge
9c982283fb move lyrics to separate route 2024-01-18 23:53:50 +03:00
mungai-njoroge
888958db54 fix: volume not carrying over new track with no crossfade
+ fix: move next timer not being reset on cue track
2024-01-18 15:44:43 +03:00
mungai-njoroge
cb3e9ec2cd fix: pwa window color on latest chrome 2024-01-18 14:11:33 +03:00
mungai-njoroge
f7e604f11b Merge branch 'master' of github.com:cwilvx/swingmusic-client 2024-01-12 19:40:47 +03:00
mungai-njoroge
1b36b96239 fix track logging 2024-01-12 19:40:39 +03:00
Mungai Njoroge
ee3ef6c1e6 Merge pull request #22 from tpatchg/patch-1
Update QuickSettings.vue for spelling
2024-01-12 08:17:46 +03:00
tpatchg
83bd063bea Update QuickSettings.vue
Spelling Fix
2024-01-10 23:06:45 -06:00
mungai-njoroge
12531edec9 Merge branch 'master' of github.com:cwilvx/swingmusic-client 2024-01-09 15:02:30 +03:00
Mungai Njoroge
7edd501d0f Merge pull request #21 from swing-opensource/gapless
v1.4.6: Silence removal, Crossfade and others
2024-01-09 15:01:52 +03:00
mungai-njoroge
c7036b3a5d fix: enable typing to search on mobile view 2024-01-09 14:29:48 +03:00
mungai-njoroge
3075517af9 add silence skip to quick settings 2024-01-08 17:23:46 +03:00
mungai-njoroge
e8f0bc6b8b add quick actions section in settings
+ set CTRL + B to toggle sidebar
2024-01-07 17:20:50 +03:00
mungai-njoroge
ded3a48e0b extract crossfade into separate file 2024-01-05 01:01:41 +03:00
mungai-njoroge
a04a3c4fe4 fix: queue save as playlist 2024-01-04 20:07:45 +03:00
mungai-njoroge
4aaf70ba68 add context menu option to remove track from queue 2024-01-04 10:53:54 +03:00
mungai-njoroge
fa176cb027 remove queue is too short for shuffle warning 2024-01-04 10:45:55 +03:00
mungai-njoroge
c4ce344487 Add shuffle to bottom bar 2024-01-04 10:43:30 +03:00
mungai-njoroge
f2c7cccba1 increase playlist list grid width 2024-01-04 10:30:34 +03:00
mungai-njoroge
901406a337 respect use_crossfade in preloaded track move forward 2024-01-04 01:38:16 +03:00
mungai-njoroge
4b48b13561 add switch to explictly turn on crossfade
~ makes it possible to provide a default crossfade duration
+ hide the experimental flag on chromium browsers
2024-01-02 00:36:46 +03:00
mungai-njoroge
10b29f6349 rewrite tracker to use keys
+ fix tracker throttle function not working
2024-01-01 22:34:57 +03:00
mungai-njoroge
b24d833d12 add search on social to track context menu 2024-01-01 21:02:51 +03:00
mungai-njoroge
9004c02898 fix track play next
+ fix no repeat
2024-01-01 20:40:04 +03:00
mungai-njoroge
409edba74c add audio settings group
+ add components for crossfade duration
2024-01-01 20:18:08 +03:00
mungai-njoroge
2f1c07f786 disable crossfade if duration < 1000 2023-12-27 21:12:54 +03:00
mungai-njoroge
278e73745a rewrite add tracks to queue methods to use the insertAt method
+ clear timeout on seek
2023-12-27 20:23:56 +03:00
mungai-njoroge
3652432e0b + fix crossfade
+ move silence fetch to worker
+ disablecrossfade on manually triggered track plays
+ move remove track from queue to tracklist
+ fix tracker not working by reassigning event listeners
2023-12-27 19:38:19 +03:00
mungai-njoroge
67ad2ef206 Fix file count display in FolderItem.vue and add
new routes in Browse.vue
2023-12-23 18:53:34 +03:00
mungai-njoroge
f2e157a746 gapless initial try 2023-12-22 11:20:51 +03:00
mungai-njoroge
606515ffd5 hide recent items on homepage if they don't exist 2023-12-19 09:27:44 +03:00
mungai-njoroge
6ff67e5f94 resize song items grid size 2023-12-14 10:03:26 +03:00
mungai-njoroge
5f037fc647 add fetch callbacks to sidebar search 2023-12-14 09:52:43 +03:00
mungai-njoroge
d1c62f701e fix broken layout on favorite artists & albums page 2023-12-14 09:51:32 +03:00
mungai-njoroge
b2bef03373 remove see all from top search results 2023-12-14 09:23:14 +03:00
mungai-njoroge
49fe308da0 fix no bottom album rows on album page
+ add store resetter to artist page
2023-12-12 19:54:20 +03:00
mungai-njoroge
bce2772dcb show favorite tracks index in reverse order 2023-12-11 17:40:50 +03:00
mungai-njoroge
dd0b9d6d61 add store resetter methods
+ hide edit button on recently added playlist
2023-12-11 13:29:27 +03:00
mungai-njoroge
1e90298f72 fix padding left on tab headers on search page 2023-12-11 08:31:25 +03:00
mungai-njoroge
7f4385f8cb reduce cardScroller padding
+ spice up hire me
2023-12-11 08:27:21 +03:00
mungai-njoroge
48f2a73291 update settings text 2023-12-10 18:33:34 +03:00
mungai-njoroge
8cf33089a2 default to no sidebar 2023-12-10 18:15:03 +03:00
mungai-njoroge
c16be33d40 fix items glitch on search tracks page 2023-12-10 18:01:33 +03:00
mungai-njoroge
a9691a2a25 rewrite search grid pages with card row virtual list
+ rewrite sidebar tabs with infinite scroll and card row virtual lists
2023-12-10 16:22:43 +03:00
138 changed files with 2361 additions and 1395 deletions

18
public/workers/silence.js Normal file
View 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);
};

View File

@@ -6,17 +6,27 @@
<section
id="app-grid"
:class="{
noSidebar: !settings.use_sidebar || !xl,
NoSideBorders: !xxl,
useSidebar: settings.use_sidebar && xl,
NoSideBorders: settings.is_alt_layout || !xxl,
extendWidth: settings.extend_width && settings.can_extend_width,
is_alt_layout: settings.is_alt_layout,
}"
:style="{
maxWidth: `${
settings.is_default_layout
? content_height > 1080
? '2220px'
: '1760px'
: ''
}`,
}"
:style="{ maxWidth: `${content_height > 1080 ? '2220px' : '1760px'}` }"
>
<LeftSidebar v-if="!isMobile" />
<LeftSidebar v-if="settings.is_default_layout && !isMobile" />
<NavBar />
<div id="acontent" ref="appcontent" v-element-size="updateContentElemSize">
<div id="acontent" v-element-size="updateContentElemSize">
<div id="contentresizer" ref="appcontent"></div>
<BalancerProvider>
<router-view />
<RouterView />
</BalancerProvider>
</div>
<RightSideBar v-if="settings.use_sidebar && xl" />
@@ -81,8 +91,6 @@ router.afterEach(() => {
});
onStartTyping(() => {
if (isMobile.value) return;
const elem = document.getElementById("globalsearch") as HTMLInputElement;
elem.focus();
elem.value = "";
@@ -103,8 +111,9 @@ function updateContentElemSize({
width: number;
height: number;
}) {
// 1372 is the maxwidth of the #acontent. see app-grid.scss > $maxwidth
const elem_width = Math.min(1372, appcontent.value?.offsetWidth || 0);
// 1572 is the maxwidth of the #acontent. see app-grid.scss > $maxwidth
const elem_width = appcontent.value?.offsetWidth || 0;
content_width.value = elem_width;
content_height.value = height;
updateCardWidth();
@@ -147,15 +156,13 @@ onMounted(() => {
settings.mapDbSettings(data);
})
.then(() => {
if (settings.use_lyrics_plugin) return;
if (queue.currenttrack && !settings.use_lyrics_plugin) {
lyrics.checkExists(
queue.currenttrack.filepath,
queue.currenttrack.trackhash
);
}
});
if (queue.currenttrack) {
lyrics.checkExists(
queue.currenttrack.filepath,
queue.currenttrack.trackhash
);
}
});
</script>

View File

@@ -1,3 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 13.8477C16.127 13.8477 17.8496 11.9668 17.8496 9.66406C17.8496 7.39648 16.127 5.59473 14 5.59473C11.8818 5.59473 10.1416 7.42285 10.1504 9.68164C10.1592 11.9756 11.873 13.8477 14 13.8477ZM14 12.3096C12.7871 12.3096 11.7588 11.1582 11.7588 9.68164C11.75 8.24023 12.7783 7.13281 14 7.13281C15.2305 7.13281 16.2412 8.22266 16.2412 9.66406C16.2412 11.1406 15.2217 12.3096 14 12.3096ZM8.51562 22.0215H19.4756C20.9961 22.0215 21.7256 21.5381 21.7256 20.501C21.7256 18.084 18.7109 14.8672 14 14.8672C9.28906 14.8672 6.26562 18.084 6.26562 20.501C6.26562 21.5381 6.99512 22.0215 8.51562 22.0215ZM8.24316 20.4834C8.03223 20.4834 7.95312 20.4131 7.95312 20.2549C7.95312 18.9102 10.124 16.4053 14 16.4053C17.8672 16.4053 20.0381 18.9102 20.0381 20.2549C20.0381 20.4131 19.959 20.4834 19.748 20.4834H8.24316Z" fill="#F2F2F2"/>
<path d="M14 13.8477C16.127 13.8477 17.8496 11.9668 17.8496 9.66406C17.8496 7.39648 16.127 5.59473 14 5.59473C11.8818 5.59473 10.1416 7.42285 10.1504 9.68164C10.1592 11.9756 11.873 13.8477 14 13.8477ZM14 12.3096C12.7871 12.3096 11.7588 11.1582 11.7588 9.68164C11.75 8.24023 12.7783 7.13281 14 7.13281C15.2305 7.13281 16.2412 8.22266 16.2412 9.66406C16.2412 11.1406 15.2217 12.3096 14 12.3096ZM8.51562 22.0215H19.4756C20.9961 22.0215 21.7256 21.5381 21.7256 20.501C21.7256 18.084 18.7109 14.8672 14 14.8672C9.28906 14.8672 6.26562 18.084 6.26562 20.501C6.26562 21.5381 6.99512 22.0215 8.51562 22.0215ZM8.24316 20.4834C8.03223 20.4834 7.95312 20.4131 7.95312 20.2549C7.95312 18.9102 10.124 16.4053 14 16.4053C17.8672 16.4053 20.0381 18.9102 20.0381 20.2549C20.0381 20.4131 19.959 20.4834 19.748 20.4834H8.24316Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 931 B

After

Width:  |  Height:  |  Size: 936 B

View 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

View File

@@ -1,4 +1,4 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 1.875C7.75195 1.875 1.875 7.75195 1.875 15C1.875 22.248 7.75195 28.125 15 28.125C22.248 28.125 28.125 22.248 28.125 15C28.125 7.75195 22.248 1.875 15 1.875ZM15 25.8984C8.98242 25.8984 4.10156 21.0176 4.10156 15C4.10156 8.98242 8.98242 4.10156 15 4.10156C21.0176 4.10156 25.8984 8.98242 25.8984 15C25.8984 21.0176 21.0176 25.8984 15 25.8984Z" fill="white"/>
<path d="M13.5938 9.84375C13.5938 10.2167 13.7419 10.5744 14.0056 10.8381C14.2694 11.1018 14.627 11.25 15 11.25C15.373 11.25 15.7306 11.1018 15.9944 10.8381C16.2581 10.5744 16.4062 10.2167 16.4062 9.84375C16.4062 9.47079 16.2581 9.1131 15.9944 8.84938C15.7306 8.58566 15.373 8.4375 15 8.4375C14.627 8.4375 14.2694 8.58566 14.0056 8.84938C13.7419 9.1131 13.5938 9.47079 13.5938 9.84375V9.84375ZM15.7031 13.125H14.2969C14.168 13.125 14.0625 13.2305 14.0625 13.3594V21.3281C14.0625 21.457 14.168 21.5625 14.2969 21.5625H15.7031C15.832 21.5625 15.9375 21.457 15.9375 21.3281V13.3594C15.9375 13.2305 15.832 13.125 15.7031 13.125Z" fill="white"/>
<svg width="30" height="30" viewBox="0 0 30 30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M15 1.875C7.75195 1.875 1.875 7.75195 1.875 15C1.875 22.248 7.75195 28.125 15 28.125C22.248 28.125 28.125 22.248 28.125 15C28.125 7.75195 22.248 1.875 15 1.875ZM15 25.8984C8.98242 25.8984 4.10156 21.0176 4.10156 15C4.10156 8.98242 8.98242 4.10156 15 4.10156C21.0176 4.10156 25.8984 8.98242 25.8984 15C25.8984 21.0176 21.0176 25.8984 15 25.8984Z" fill="currentColor"/>
<path d="M13.5938 9.84375C13.5938 10.2167 13.7419 10.5744 14.0056 10.8381C14.2694 11.1018 14.627 11.25 15 11.25C15.373 11.25 15.7306 11.1018 15.9944 10.8381C16.2581 10.5744 16.4062 10.2167 16.4062 9.84375C16.4062 9.47079 16.2581 9.1131 15.9944 8.84938C15.7306 8.58566 15.373 8.4375 15 8.4375C14.627 8.4375 14.2694 8.58566 14.0056 8.84938C13.7419 9.1131 13.5938 9.47079 13.5938 9.84375V9.84375ZM15.7031 13.125H14.2969C14.168 13.125 14.0625 13.2305 14.0625 13.3594V21.3281C14.0625 21.457 14.168 21.5625 14.2969 21.5625H15.7031C15.832 21.5625 15.9375 21.457 15.9375 21.3281V13.3594C15.9375 13.2305 15.832 13.125 15.7031 13.125Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 28 28" width="28px" height="28px">
<g id="surface79432257">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 14 5.214844 C 13.210938 5.214844 12.5 5.625 12.105469 6.308594 L 4.253906 19.90625 C 3.859375 20.589844 3.859375 21.410156 4.253906 22.09375 C 4.652344 22.777344 5.359375 23.1875 6.148438 23.1875 L 21.851562 23.1875 C 22.640625 23.1875 23.347656 22.777344 23.746094 22.09375 C 24.140625 21.410156 24.140625 20.589844 23.746094 19.90625 L 15.894531 6.308594 C 15.5 5.625 14.789062 5.214844 14 5.214844 Z M 14 6.964844 C 14.074219 6.964844 14.265625 6.988281 14.378906 7.183594 L 22.230469 20.78125 C 22.34375 20.980469 22.269531 21.152344 22.230469 21.21875 C 22.191406 21.285156 22.078125 21.4375 21.851562 21.4375 L 6.148438 21.4375 C 5.921875 21.4375 5.808594 21.285156 5.769531 21.21875 C 5.730469 21.152344 5.65625 20.980469 5.769531 20.78125 L 13.621094 7.183594 C 13.734375 6.988281 13.925781 6.964844 14 6.964844 Z M 13.992188 10.058594 C 13.691406 10.058594 13.445312 10.152344 13.253906 10.335938 C 13.066406 10.523438 12.976562 10.753906 12.984375 11.03125 L 13.125 16.214844 C 13.140625 16.792969 13.433594 17.085938 14.007812 17.085938 C 14.5625 17.085938 14.84375 16.792969 14.851562 16.214844 L 15.015625 11.042969 C 15.023438 10.765625 14.929688 10.53125 14.730469 10.34375 C 14.535156 10.152344 14.289062 10.058594 13.992188 10.058594 Z M 14 18.238281 C 13.328125 18.238281 12.902344 18.808594 12.902344 19.289062 C 12.902344 19.769531 13.3125 20.339844 14 20.339844 C 14.6875 20.339844 15.097656 19.800781 15.097656 19.289062 C 15.097656 18.777344 14.671875 18.238281 14 18.238281 Z M 14 18.238281 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:currentColor;fill-opacity:1;" d="M 14 5.214844 C 13.210938 5.214844 12.5 5.625 12.105469 6.308594 L 4.253906 19.90625 C 3.859375 20.589844 3.859375 21.410156 4.253906 22.09375 C 4.652344 22.777344 5.359375 23.1875 6.148438 23.1875 L 21.851562 23.1875 C 22.640625 23.1875 23.347656 22.777344 23.746094 22.09375 C 24.140625 21.410156 24.140625 20.589844 23.746094 19.90625 L 15.894531 6.308594 C 15.5 5.625 14.789062 5.214844 14 5.214844 Z M 14 6.964844 C 14.074219 6.964844 14.265625 6.988281 14.378906 7.183594 L 22.230469 20.78125 C 22.34375 20.980469 22.269531 21.152344 22.230469 21.21875 C 22.191406 21.285156 22.078125 21.4375 21.851562 21.4375 L 6.148438 21.4375 C 5.921875 21.4375 5.808594 21.285156 5.769531 21.21875 C 5.730469 21.152344 5.65625 20.980469 5.769531 20.78125 L 13.621094 7.183594 C 13.734375 6.988281 13.925781 6.964844 14 6.964844 Z M 13.992188 10.058594 C 13.691406 10.058594 13.445312 10.152344 13.253906 10.335938 C 13.066406 10.523438 12.976562 10.753906 12.984375 11.03125 L 13.125 16.214844 C 13.140625 16.792969 13.433594 17.085938 14.007812 17.085938 C 14.5625 17.085938 14.84375 16.792969 14.851562 16.214844 L 15.015625 11.042969 C 15.023438 10.765625 14.929688 10.53125 14.730469 10.34375 C 14.535156 10.152344 14.289062 10.058594 13.992188 10.058594 Z M 14 18.238281 C 13.328125 18.238281 12.902344 18.808594 12.902344 19.289062 C 12.902344 19.769531 13.3125 20.339844 14 20.339844 C 14.6875 20.339844 15.097656 19.800781 15.097656 19.289062 C 15.097656 18.777344 14.671875 18.238281 14 18.238281 Z M 14 18.238281 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 28 28" width="28px" height="28px">
<g id="surface79671233">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 14 4.375 C 8.683594 4.375 4.375 8.683594 4.375 14 C 4.375 19.316406 8.683594 23.625 14 23.625 C 19.316406 23.625 23.625 19.316406 23.625 14 C 23.625 8.683594 19.316406 4.375 14 4.375 Z M 14 6.125 C 18.347656 6.125 21.875 9.652344 21.875 14 C 21.875 18.347656 18.347656 21.875 14 21.875 C 9.652344 21.875 6.125 18.347656 6.125 14 C 6.125 9.652344 9.652344 6.125 14 6.125 Z M 14 9.1875 C 13.273438 9.1875 12.6875 9.773438 12.6875 10.5 C 12.6875 11.226562 13.273438 11.8125 14 11.8125 C 14.726562 11.8125 15.3125 11.226562 15.3125 10.5 C 15.3125 9.773438 14.726562 9.1875 14 9.1875 Z M 14 13.125 C 13.515625 13.125 13.125 13.515625 13.125 14 L 13.125 18.375 C 13.125 18.859375 13.515625 19.25 14 19.25 C 14.484375 19.25 14.875 18.859375 14.875 18.375 L 14.875 14 C 14.875 13.515625 14.484375 13.125 14 13.125 Z M 14 13.125 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:currentColor;fill-opacity:1;" d="M 14 4.375 C 8.683594 4.375 4.375 8.683594 4.375 14 C 4.375 19.316406 8.683594 23.625 14 23.625 C 19.316406 23.625 23.625 19.316406 23.625 14 C 23.625 8.683594 19.316406 4.375 14 4.375 Z M 14 6.125 C 18.347656 6.125 21.875 9.652344 21.875 14 C 21.875 18.347656 18.347656 21.875 14 21.875 C 9.652344 21.875 6.125 18.347656 6.125 14 C 6.125 9.652344 9.652344 6.125 14 6.125 Z M 14 9.1875 C 13.273438 9.1875 12.6875 9.773438 12.6875 10.5 C 12.6875 11.226562 13.273438 11.8125 14 11.8125 C 14.726562 11.8125 15.3125 11.226562 15.3125 10.5 C 15.3125 9.773438 14.726562 9.1875 14 9.1875 Z M 14 13.125 C 13.515625 13.125 13.125 13.515625 13.125 14 L 13.125 18.375 C 13.125 18.859375 13.515625 19.25 14 19.25 C 14.484375 19.25 14.875 18.859375 14.875 18.375 L 14.875 14 C 14.875 13.515625 14.484375 13.125 14 13.125 Z M 14 13.125 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 28 28" width="28px" height="28px">
<g id="surface79451713">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 22.703125 15.355469 C 22.628906 15.773438 22.449219 16.152344 22.203125 16.476562 C 22.667969 17.058594 22.851562 17.796875 22.671875 18.554688 C 22.550781 19.082031 22.265625 19.539062 21.886719 19.886719 C 22.15625 20.265625 22.3125 20.71875 22.3125 21.203125 C 22.3125 22.542969 21.121094 23.59375 19.597656 23.59375 L 13.121094 23.613281 C 11.023438 23.613281 9.042969 22.796875 7.558594 21.316406 C 6.070312 19.828125 5.25 17.851562 5.25 15.75 C 5.25 11.828125 6.660156 10.425781 7.59375 9.5 L 7.695312 9.398438 C 7.988281 9.101562 8.527344 8.707031 9.210938 8.203125 C 10.59375 7.179688 12.914062 5.464844 13.339844 4.308594 C 13.683594 3.382812 14.109375 2.230469 15.75 2.230469 C 16.480469 2.230469 17.152344 2.683594 17.550781 3.445312 C 17.929688 4.175781 18.496094 6.132812 16.472656 9.625 L 19.757812 9.625 C 21.167969 9.625 22.3125 10.699219 22.3125 12.015625 C 22.3125 12.4375 22.191406 12.832031 21.984375 13.175781 C 22.574219 13.746094 22.851562 14.519531 22.703125 15.355469 Z M 19.597656 21.84375 C 20.121094 21.84375 20.5625 21.550781 20.5625 21.203125 C 20.5625 20.851562 20.203125 20.5625 19.757812 20.5625 L 17.9375 20.5625 C 17.453125 20.5625 17.0625 20.171875 17.0625 19.6875 C 17.0625 19.203125 17.453125 18.8125 17.9375 18.8125 L 20.226562 18.8125 C 20.609375 18.8125 20.894531 18.472656 20.96875 18.15625 C 21.015625 17.949219 21 17.679688 20.71875 17.441406 C 20.558594 17.476562 20.394531 17.5 20.226562 17.5 L 17.9375 17.5 C 17.453125 17.5 17.0625 17.109375 17.0625 16.625 C 17.0625 16.140625 17.453125 15.75 17.9375 15.75 L 20.226562 15.75 C 20.617188 15.75 20.917969 15.394531 20.980469 15.050781 C 21.046875 14.667969 20.820312 14.417969 20.542969 14.261719 C 20.25 14.355469 19.933594 14.40625 19.597656 14.40625 L 17.945312 14.4375 C 17.46875 14.4375 17.070312 14.054688 17.0625 13.578125 C 17.054688 13.09375 17.4375 12.695312 17.921875 12.6875 L 19.582031 12.65625 C 20.121094 12.65625 20.5625 12.363281 20.5625 12.015625 C 20.5625 11.664062 20.203125 11.375 19.757812 11.375 L 14.875 11.375 C 14.550781 11.375 14.25 11.195312 14.097656 10.902344 C 13.949219 10.613281 13.972656 10.265625 14.15625 10 C 16.121094 7.1875 16.417969 5.234375 16.039062 4.339844 C 15.921875 4.058594 15.777344 3.984375 15.742188 3.976562 C 15.386719 3.976562 15.328125 3.976562 14.984375 4.917969 C 14.375 6.5625 11.992188 8.324219 10.25 9.609375 C 9.675781 10.035156 9.136719 10.433594 8.929688 10.636719 L 8.828125 10.738281 C 8.015625 11.550781 7 12.554688 7 15.75 C 7 17.386719 7.636719 18.921875 8.792969 20.078125 C 9.949219 21.230469 11.488281 21.863281 13.117188 21.863281 Z M 19.597656 21.84375 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:currentColor;fill-opacity:1;" d="M 22.703125 15.355469 C 22.628906 15.773438 22.449219 16.152344 22.203125 16.476562 C 22.667969 17.058594 22.851562 17.796875 22.671875 18.554688 C 22.550781 19.082031 22.265625 19.539062 21.886719 19.886719 C 22.15625 20.265625 22.3125 20.71875 22.3125 21.203125 C 22.3125 22.542969 21.121094 23.59375 19.597656 23.59375 L 13.121094 23.613281 C 11.023438 23.613281 9.042969 22.796875 7.558594 21.316406 C 6.070312 19.828125 5.25 17.851562 5.25 15.75 C 5.25 11.828125 6.660156 10.425781 7.59375 9.5 L 7.695312 9.398438 C 7.988281 9.101562 8.527344 8.707031 9.210938 8.203125 C 10.59375 7.179688 12.914062 5.464844 13.339844 4.308594 C 13.683594 3.382812 14.109375 2.230469 15.75 2.230469 C 16.480469 2.230469 17.152344 2.683594 17.550781 3.445312 C 17.929688 4.175781 18.496094 6.132812 16.472656 9.625 L 19.757812 9.625 C 21.167969 9.625 22.3125 10.699219 22.3125 12.015625 C 22.3125 12.4375 22.191406 12.832031 21.984375 13.175781 C 22.574219 13.746094 22.851562 14.519531 22.703125 15.355469 Z M 19.597656 21.84375 C 20.121094 21.84375 20.5625 21.550781 20.5625 21.203125 C 20.5625 20.851562 20.203125 20.5625 19.757812 20.5625 L 17.9375 20.5625 C 17.453125 20.5625 17.0625 20.171875 17.0625 19.6875 C 17.0625 19.203125 17.453125 18.8125 17.9375 18.8125 L 20.226562 18.8125 C 20.609375 18.8125 20.894531 18.472656 20.96875 18.15625 C 21.015625 17.949219 21 17.679688 20.71875 17.441406 C 20.558594 17.476562 20.394531 17.5 20.226562 17.5 L 17.9375 17.5 C 17.453125 17.5 17.0625 17.109375 17.0625 16.625 C 17.0625 16.140625 17.453125 15.75 17.9375 15.75 L 20.226562 15.75 C 20.617188 15.75 20.917969 15.394531 20.980469 15.050781 C 21.046875 14.667969 20.820312 14.417969 20.542969 14.261719 C 20.25 14.355469 19.933594 14.40625 19.597656 14.40625 L 17.945312 14.4375 C 17.46875 14.4375 17.070312 14.054688 17.0625 13.578125 C 17.054688 13.09375 17.4375 12.695312 17.921875 12.6875 L 19.582031 12.65625 C 20.121094 12.65625 20.5625 12.363281 20.5625 12.015625 C 20.5625 11.664062 20.203125 11.375 19.757812 11.375 L 14.875 11.375 C 14.550781 11.375 14.25 11.195312 14.097656 10.902344 C 13.949219 10.613281 13.972656 10.265625 14.15625 10 C 16.121094 7.1875 16.417969 5.234375 16.039062 4.339844 C 15.921875 4.058594 15.777344 3.984375 15.742188 3.976562 C 15.386719 3.976562 15.328125 3.976562 14.984375 4.917969 C 14.375 6.5625 11.992188 8.324219 10.25 9.609375 C 9.675781 10.035156 9.136719 10.433594 8.929688 10.636719 L 8.828125 10.738281 C 8.015625 11.550781 7 12.554688 7 15.75 C 7 17.386719 7.636719 18.921875 8.792969 20.078125 C 9.949219 21.230469 11.488281 21.863281 13.117188 21.863281 Z M 19.597656 21.84375 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -2,21 +2,31 @@ $g-border: solid 1px $gray5;
#app-grid {
display: grid;
grid-template-columns: min-content 1fr 29rem;
grid-template-rows: max-content 1fr 5rem;
// grid-template-columns: min-content 1fr 29rem;
grid-template-columns: min-content 1fr;
grid-template-rows: $navheight 1fr $navheight;
grid-template-areas:
"l-sidebar nav r-sidebar"
"l-sidebar content r-sidebar"
"bottombar bottombar bottombar";
"l-sidebar nav"
"l-sidebar content"
"bottombar bottombar";
height: 100%;
border: $g-border;
border-top: none;
border-bottom: none;
margin: 0 auto;
@include for-desktop-down {
grid-template-columns: min-content 1fr 24rem;
#contentresizer {
margin: 0 $padright 0 $padleft;
}
@include allPhones {
grid-template-columns: 1fr;
// grid-template-columns: 1fr;
grid-template-rows: max-content 1fr 9.5rem;
grid-template-areas:
"nav"
"content"
"bottombar";
}
}
@@ -27,6 +37,18 @@ $g-border: solid 1px $gray5;
margin-right: $margright;
}
.topnav {
grid-area: nav;
height: $navheight;
padding: 1rem $padleft;
padding-right: $padright;
}
.b-bar {
grid-area: bottombar;
border-top: $g-border;
}
.content-page {
scrollbar-gutter: stable;
padding-left: $padleft;
@@ -52,19 +74,91 @@ $g-border: solid 1px $gray5;
}
}
.topnav {
grid-area: nav;
padding: 1rem $padleft;
padding-right: $padright;
}
.b-bar {
grid-area: bottombar;
border-top: $g-border;
}
// ====== MODIFIERS =======
#app-grid.is_alt_layout {
grid-template-columns: 1fr;
grid-template-rows: max-content 1fr 5rem;
grid-template-areas:
"nav"
"content"
"bottombar";
.vue-recycle-scroller,
.content-page,
.topnav,
#songlist-scroller {
padding-left: $alt_layout_pad;
padding-right: $alt_layout_pad;
}
.b-bar,
.search-page-top-results {
padding: 0 $alt_layout_pad;
}
#contentresizer {
margin: 0 $alt_layout_pad;
}
.topnav {
background-color: $gray;
}
.vue-recycle-scroller,
.content-page {
padding-top: 2rem;
}
.search-page-top-results {
padding-bottom: $padbottom;
}
.search-view .buttons-area {
padding-left: $alt_layout_pad;
}
.lyricsview {
padding-bottom: 2rem;
}
#lyricscontent {
padding-top: 0;
padding-left: 2rem;
padding-right: 2rem;
}
.nolyrics {
margin-top: 0;
}
@media (min-width: 1980px) {
// NOTE: Styles for 1680px and below
$alt_layout_pad: max(2rem, calc((100% - 1680px) / 2));
.vue-recycle-scroller,
.content-page,
.topnav,
#songlist-scroller {
padding-left: $alt_layout_pad;
padding-right: $alt_layout_pad;
}
#contentresizer {
margin: 0 $alt_layout_pad;
}
.search-view .buttons-area {
padding-left: $alt_layout_pad;
}
.b-bar,
.search-page-top-results {
padding: 0 $alt_layout_pad;
}
}
}
#app-grid.extendWidth {
padding-right: 0;
border-left: none;
@@ -72,31 +166,26 @@ $g-border: solid 1px $gray5;
max-width: 100% !important;
}
#app-grid.noSidebar {
grid-template-columns: min-content 1fr;
#app-grid.useSidebar {
grid-template-columns: min-content 1fr 28rem;
grid-template-areas:
"l-sidebar nav"
"l-sidebar content"
"bottombar bottombar";
"l-sidebar nav r-sidebar"
"l-sidebar content r-sidebar"
"bottombar bottombar bottombar";
#acontent {
margin-right: 0 !important;
// padding-right: $medium !important;
@include for-desktop-down {
grid-template-columns: min-content 1fr 24rem;
}
@include allPhones {
grid-template-columns: 1fr;
grid-template-rows: max-content 1fr 9.5rem;
grid-template-areas:
"nav"
"content"
"bottombar";
#acontent {
// margin-right: 0 !important;
// padding-right: $medium !important;
}
}
#app-grid.NoSideBorders {
border-right: none;
border-left: none;
border-right: none !important;
border-left: none !important;
}
.v-scroll-page {

View File

@@ -28,7 +28,7 @@ input[type="search"]::-webkit-search-cancel-button {
}
.heading {
font-size: 2rem;
font-size: 1.5rem;
font-weight: bold;
}
@@ -202,7 +202,7 @@ button {
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10.1rem, 1fr));
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
overflow-x: auto;
scroll-snap-type: x mandatory;
flex-direction: row;

View File

@@ -56,15 +56,26 @@ $overshoot: cubic-bezier(0.68, -0.55, 0.265, 1.55);
$separator: $gray4;
$maxwidth: 1372px;
$margright: 0;
$padbottom: 4rem;
$maxwidth: 1438px;
$navheight: 5rem;
$cardwidth: 10.75rem;
$maxpadleft: 5rem;
$alt_layout_pad: max(2rem, calc((100% - 1280px) / 2));
$maxpadright: calc(100% - $maxwidth);
$padbottom: 4rem;
$padleft: clamp(2rem, $maxpadright, $maxpadleft);
$padright: clamp(
2rem,
max($maxpadright, 5rem),
calc($maxpadright + $maxpadleft)
);
// 👇 fixed width with content floating to the left.
// $padright: clamp(
// 1rem,
// max($maxpadright, 5rem),
// calc($maxpadright + $maxpadleft)
// );
$padright: $padleft;
$margright: calc(0rem - $padright);

View File

@@ -14,6 +14,7 @@ const props = defineProps<{
show_text?: boolean;
fetch_callback: () => Promise<void>;
reset_callback?: () => Promise<void>;
outside_route?: boolean;
}>();
const update = async () => {
@@ -26,8 +27,9 @@ onMounted(async () => {
props.fetch_callback().then(update);
});
onBeforeRouteUpdate(() => {
if (!props.reset_callback) return;
props.reset_callback().then(update);
});
!props.outside_route &&
onBeforeRouteUpdate(() => {
if (!props.reset_callback) return;
props.reset_callback().then(update);
});
</script>

View File

@@ -38,7 +38,12 @@
class="gradient"
:style="{
backgroundImage: colors.bg
? `linear-gradient(${gradientDirection}, transparent 20%,
? `linear-gradient(${gradientDirection}, transparent ${
isSmall
? 60
: gradientTransparentWidth -
(width < 700 ? 40 : width < 900 ? 20 : 10)
}%,
${colors.bg} ${gradientWidth}%,
${colors.bg} 100%)`
: '',
@@ -49,7 +54,7 @@
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { computed, onMounted, ref } from "vue";
import { Ref, computed, onMounted, ref } from "vue";
import { onBeforeRouteUpdate } from "vue-router";
import { useElementSize } from "@vueuse/core";
import useSettingsStore from "@/stores/settings";
@@ -60,7 +65,9 @@ import updatePageTitle from "@/utils/updatePageTitle";
import Info from "./HeaderComponents/Info.vue";
import useArtistStore from "@/stores/pages/artist";
import { getShift } from "@/utils/colortools/shift";
import { isSmall } from "@/stores/content-width";
const image_width_px = 450;
const store = useArtistStore();
const settings = useSettingsStore();
@@ -77,13 +84,16 @@ function updateTitle() {
onMounted(() => updateTitle());
onBeforeRouteUpdate(() => updateTitle());
const artistheader = ref(null);
const artistheader: Ref<HTMLElement | null> = ref(null);
const { width } = useElementSize(artistheader);
const gradientTransparentWidth = computed(() =>
Math.floor((image_width_px / (width.value || 1)) * 100)
);
const isSmallPhone = computed(() => width.value <= 550);
const useCircularImage = computed(
() =>
!isSmallPhone.value && (settings.useCircularArtistImg || width.value >= 995)
() => !isSmallPhone.value && settings.useCircularArtistImg
);
const gradientDirection = computed(() =>
@@ -91,7 +101,7 @@ const gradientDirection = computed(() =>
);
const gradientWidth = computed(() => {
return isSmallPhone.value ? "80" : "50";
return isSmallPhone.value ? "80" : Math.min(gradientTransparentWidth.value, 50);
});
const containerHeight = computed(() => {
@@ -109,7 +119,7 @@ const containerHeight = computed(() => {
.artist-page-header {
display: grid;
grid-template-columns: 1fr minmax(min-content, 50%);
grid-template-columns: 1fr 450px;
position: relative;
.artist-img {
@@ -123,7 +133,6 @@ const containerHeight = computed(() => {
aspect-ratio: 1;
object-fit: cover;
object-position: 0% 20%;
float: right;
}
}

View File

@@ -14,6 +14,7 @@
<div
class="artist-name"
:class="`${useCircularImage ? 'ellip' : 'ellip2'}`"
:title="artist.name"
>
{{ artist.name }}
</div>
@@ -77,7 +78,7 @@ defineProps<{
.artist-name {
font-size: 3.5rem;
font-weight: bold;
word-wrap: break-word;
word-wrap: break-all;
}
.stats {

View File

@@ -9,7 +9,7 @@
v-for="(song, index) in tracks"
:key="index"
:track="song"
:index="index + 1"
:index="total ? total - index : index + 1"
:source="source"
@playThis="playHandler(index)"
/>
@@ -31,6 +31,7 @@ defineProps<{
title: string;
playHandler: (index: number) => void;
source: dropSources;
total?: number;
}>();
</script>

View File

@@ -1,5 +1,10 @@
<template>
<div class="b-bar">
<div
class="b-bar"
:style="{
padding: `${settings.is_default_layout ? '0 1rem 0 1rem' : ''}`,
}"
>
<LeftGroup @handleFav="handleFav" />
<div class="center">
<div v-if="!isMobile" class="with-time">
@@ -32,6 +37,7 @@ import { isMobile } from "@/stores/content-width";
import { formatSeconds } from "@/utils";
import useQStore from "@/stores/queue";
import useSettings from "@/stores/settings";
import HotKeys from "@/components/LeftSidebar/NP/HotKeys.vue";
import Progress from "@/components/LeftSidebar/NP/Progress.vue";
@@ -41,6 +47,7 @@ import LeftGroup from "./Left.vue";
import RightGroup from "./Right.vue";
const queue = useQStore();
const settings = useSettings();
function handleFav() {
favoriteHandler(
@@ -60,7 +67,6 @@ function handleFav() {
grid-template-columns: 1fr max-content 1fr;
align-items: center;
z-index: 1;
padding: 0 1rem;
@include allPhones {
grid-template-columns: 1fr;
@@ -88,6 +94,7 @@ function handleFav() {
}
&:hover {
// INFO: Show the progress bar when hovering over the bottom bar
#progress::-moz-range-thumb {
height: 1rem;
width: 1rem;
@@ -102,6 +109,11 @@ function handleFav() {
height: 1rem;
width: 1rem;
}
// INFO: Also show the expand button
.np-image .expandicon {
opacity: 1;
}
}
.with-time {

View File

@@ -15,12 +15,12 @@
},
replace: true,
}"
class="np-image rounded-sm no-scroll"
>
<img
class="rounded-sm"
:src="paths.images.thumb.small + queue.currenttrack?.image"
alt=""
/>
<img :src="paths.images.thumb.small + queue.currenttrack?.image" alt="" />
<div class="expandicon">
<ExpandSvg />
</div>
</RouterLink>
<div
class="track-info"
@@ -62,6 +62,7 @@ import HeartSvg from "../shared/HeartSvg.vue";
import MasterFlag from "../shared/MasterFlag.vue";
import HotKeys from "../LeftSidebar/NP/HotKeys.vue";
import ArtistName from "@/components/shared/ArtistName.vue";
import ExpandSvg from "@/assets/icons/expand.svg";
const queue = useQStore();
const settings = useSettingsStore();
@@ -80,12 +81,43 @@ defineEmits<{
align-items: center;
font-size: small;
a {
font-size: small;
.np-image {
position: relative;
height: 3rem;
img {
height: 100%;
}
.expandicon {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(51, 51, 51, 0.575);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: all 0.2s;
svg {
transform: rotate(-90deg);
}
}
&:hover {
.expandicon {
transform: translateY(-$medium);
height: 130%;
}
}
}
img {
height: 3rem;
a {
font-size: small;
}
.heart-button {

View File

@@ -1,6 +1,6 @@
<template>
<div class="right-group">
<LyricsButton v-if="settings.use_lyrics_plugin || lyrics.exists" />
<LyricsButton />
<Volume />
<button
class="repeat"
@@ -17,8 +17,12 @@
<RepeatOneSvg v-if="settings.repeat_one" />
<RepeatAllSvg v-else />
</button>
<button title="Shuffle" @click="queue.shuffleQueue">
<ShuffleSvg />
</button>
<HeartSvg
v-if="!hideHeart"
title="Favorite"
:state="queue.currenttrack?.is_favorite"
@handleFav="() => $emit('handleFav')"
/>
@@ -28,16 +32,15 @@
<script setup lang="ts">
import useQueue from "@/stores/queue";
import useSettings from "@/stores/settings";
import useLyrics from "@/stores/lyrics";
import Volume from "./Volume.vue";
import HeartSvg from "../shared/HeartSvg.vue";
import LyricsButton from "../shared/LyricsButton.vue";
import RepeatAllSvg from "@/assets/icons/repeat.svg";
import RepeatOneSvg from "@/assets/icons/repeat-one.svg";
import Volume from "./Volume.vue";
import LyricsButton from "../shared/LyricsButton.vue";
import ShuffleSvg from "@/assets/icons/shuffle.svg";
const queue = useQueue();
const lyrics = useLyrics();
const settings = useSettings();
defineProps<{
@@ -53,7 +56,7 @@ defineEmits<{
.right-group {
display: grid;
justify-content: flex-end;
grid-template-columns: repeat(4, max-content);
grid-template-columns: repeat(5, max-content);
align-items: center;
height: 4rem;
@@ -85,5 +88,9 @@ defineEmits<{
opacity: 0.25;
}
}
.heart-button {
border: solid 1px $gray4 !important;
}
}
</style>

View File

@@ -34,7 +34,6 @@ const changeVolume = (event: Event) => {
};
const handleMouseWheel = (event: WheelEvent) => {
event.preventDefault();
const delta = event.deltaY / 1000;
let newVolume = settings.volume - delta / 3;

View File

@@ -70,7 +70,7 @@ context.$subscribe((mutation, state) => {
transform: scale(0);
height: min-content;
padding: $medium;
padding: $small;
background: $context;
transform-origin: top left;
font-size: 0.875rem;

View File

@@ -147,7 +147,7 @@ function runChildAction(action: () => void) {
transform: scale(0);
background-color: $context;
transform: scale(0);
padding: $medium;
padding: $small;
border: solid 1px $gray3;
max-height: calc(100vh / 2);
@@ -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);
}

View File

@@ -35,8 +35,8 @@
<script setup lang="ts">
import { RecentFavResult } from "@/interfaces";
import { paths } from "../../config";
import { Routes } from "@/router";
import { paths } from "../../config";
defineProps<{
fav: RecentFavResult;
@@ -45,7 +45,7 @@ defineProps<{
<style lang="scss">
.recent-fav-item {
flex: 0 0 10.1rem;
flex: 0 0 $cardwidth;
padding: $medium;
height: 100%;

View File

@@ -14,9 +14,14 @@
</template>
<script setup lang="ts">
import { onUpdated } from "vue";
import { subPath } from "@/interfaces";
import { focusElemByClass } from "@/utils";
import { onUpdated } from "vue";
import useSettings from "@/stores/settings";
const settings = useSettings();
defineProps<{
subPaths: subPath[];
@@ -27,7 +32,9 @@ defineEmits<{
}>();
onUpdated(() => {
focusElemByClass("inthisfolder");
if (settings.is_default_layout) {
focusElemByClass("inthisfolder");
}
});
</script>

View File

@@ -17,7 +17,7 @@
<div class="info">
<div class="f-item-text ellip">{{ folder.name }}</div>
<div v-if="folder.count" class="f-count">
{{ folder.count + ` File${folder.count == 1 ? "" : "s"}` }}
{{ folder.count.toLocaleString() + ` File${folder.count == 1 ? "" : "s"}` }}
</div>
</div>
<div v-if="!folder_page" class="check">

View File

@@ -35,13 +35,29 @@ const browselist = [
title: "Artists",
route: Routes.ArtistList,
},
{
title: "Playlists",
route: Routes.playlists,
},
{
title: "Favorites",
route: Routes.favorites,
},
{
title: "Favorite Tracks",
route: Routes.favoriteTracks,
},
{
title: "Favorite Artists",
route: Routes.favoriteArtists,
},
];
</script>
<style lang="scss">
.homebrowse {
padding: 1rem 0;
padding-left: $medium;
padding: 1.5rem 0;
padding-left: $small;
.btitle {
font-size: 1.15rem;

View 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>

View File

@@ -7,9 +7,7 @@
:max="time.full"
step="0.1"
:style="{
backgroundSize: `${
(time.current / (time.full || 0)) * 100
}% 100%`,
backgroundSize: `${(time.current / (time.full || 0)) * 100}% 100%`,
}"
@change="seek"
/>
@@ -22,10 +20,18 @@ const q = useQStore();
const { duration: time } = q;
let prevHash = "";
const seek = (e: Event) => {
if (prevHash && prevHash !== q.currenttrackhash) {
prevHash = "";
return;
}
const elem = e.target as HTMLInputElement;
const value = elem.value;
prevHash = q.currenttrackhash;
q.seek(value as unknown as number);
};
</script>

View File

@@ -8,18 +8,34 @@ import SearchSvg from "@/assets/icons/search.svg";
import SettingsSvg from "@/assets/icons/settings.svg";
import HomeSvg from "@/assets/icons/home.svg";
const folder = {
name: "folders",
route_name: Routes.folder,
params: { path: "$home" },
icon: FolderSvg,
};
const favorites = {
name: "favorites",
route_name: Routes.favorites,
icon: HeartSvg,
};
const playlists = {
name: "playlists",
route_name: Routes.playlists,
icon: PlaylistSvg,
};
const home = {
name: "home",
route_name: Routes.Home,
icon: HomeSvg,
};
export const menus = [
{
name: "home",
route_name: Routes.Home,
icon: HomeSvg,
},
{
name: "folders",
route_name: Routes.folder,
params: { path: "$home" },
icon: FolderSvg,
},
home,
folder,
{
name: "search",
route_name: Routes.search,
@@ -30,16 +46,8 @@ export const menus = [
{
separator: true,
},
{
name: "favorites",
route_name: Routes.favorites,
icon: HeartSvg,
},
{
name: "playlists",
route_name: Routes.playlists,
icon: PlaylistSvg,
},
favorites,
playlists,
{
separator: true,
},
@@ -50,3 +58,5 @@ export const menus = [
icon: SettingsSvg,
},
];
export const topnavitems = [home, folder, favorites, playlists];

View File

@@ -58,10 +58,10 @@ function getSvg(notif: NotifType) {
place-items: center;
box-shadow: 0px 0px 2rem rgba(0, 0, 0, 0.466);
font-size: 0.85rem;
padding: 1rem;
padding: 1rem $small;
grid-template-columns: 2rem 3fr;
gap: $small;
gap: $smaller;
.notif-text {
width: 100%;
@@ -74,14 +74,10 @@ function getSvg(notif: NotifType) {
}
.new-notif.info,
.new-notif.favorite {
$bg: rgb(28, 102, 238);
background-color: $bg;
}
.new-notif.success {
$bg: rgb(5, 167, 53);
.new-notif.favorite,.new-notif.success {
$bg: rgb(255, 255, 255);
background-color: $bg;
color: $black;
}
.new-notif.working {

View File

@@ -38,6 +38,7 @@
:source="dropSources.folder"
@play-this="queue.playNext"
/>
<h3>Queue</h3>
</div>
</template>

View File

@@ -8,29 +8,34 @@
<div class="from">
<img
v-if="
queue.from.type === FromOptions.album ||
queue.from.type === FromOptions.artist
tracklist.from.type === FromOptions.album ||
tracklist.from.type === FromOptions.artist
"
:src="data.image + '.webp'"
:alt="`Now Playing ${queue.from.type} image`"
:alt="`Now Playing ${tracklist.from.type} image`"
:class="`${
queue.from.type === FromOptions.artist ? 'circular' : 'rounded-sm'
tracklist.from.type === FromOptions.artist
? 'circular'
: 'rounded-sm'
}`"
/>
<div v-else class="from-icon border rounded-sm">
<component :is="data.icon"></component>
</div>
<div class="pad-sm">
<div class="type">{{ queue.from.type }}</div>
<div class="type">{{ tracklist.from.type }}</div>
<div class="ellip2">{{ data.name }}</div>
</div>
</div>
</router-link>
<button class="options" @click="showContextMenu">
<MoreSvg />
</button>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue";
import { computed, ref } from "vue";
import { RouteLocationRaw } from "vue-router";
import useTracklist from "@/stores/queue/tracklist";
@@ -38,12 +43,23 @@ import useTracklist from "@/stores/queue/tracklist";
import { FromOptions } from "@/enums";
import playingFrom from "@/utils/playingFrom";
const queue = useTracklist();
import MoreSvg from "@/assets/icons/more.svg";
import { showQueueContextMenu } from "@/helpers/contextMenuHandler";
const tracklist = useTracklist();
const context_showing = ref(false);
const data = computed(() => {
const { name, location, icon, image } = playingFrom(queue.from);
const { name, location, icon, image } = playingFrom(tracklist.from);
return { name, location, icon, image };
});
function showContextMenu(e: MouseEvent) {
if (!tracklist.tracklist.length) return;
showQueueContextMenu(e, context_showing);
}
</script>
<style lang="scss">
@@ -52,6 +68,14 @@ const data = computed(() => {
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
.options {
transform: rotate(90deg);
svg {
transform: scale(1.25);
}
}
}
.now-playling-from-link {

View File

@@ -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>

View File

@@ -11,7 +11,7 @@
:class="{ 'use-sqr_img': useSqrImg }"
>
<div
v-if="Number.isNaN"
v-if="Number.isInteger(info.id)"
class="float"
:style="{
color: textColor,

View File

@@ -3,8 +3,14 @@
<span v-if="!isHeaderSmall" class="status"
>Last updated {{ playlist.info.last_updated }} &#160;|&#160;&#160;</span
>
<div class="edit" @click="editPlaylist">Edit&#160;&#160;</div>
|
<div
v-if="Number.isInteger(playlist.info.id)"
class="edit"
@click="editPlaylist"
>
Edit&#160;&#160;
</div>
{{ Number.isInteger(playlist.info.id) ? " | " : "" }}
<DeleteSvg class="edit" @click="deletePlaylist" />
</div>
</template>
@@ -34,11 +40,8 @@ function deletePlaylist() {
bottom: 1rem;
right: 1rem;
padding: $smaller $small;
// background-color: $body;
// color: rgb(255, 255, 255);
font-size: 0.9rem;
border-radius: $smaller;
// box-shadow: 0 0 1rem rgba(0, 0, 0, 0.479);
z-index: 12;
display: flex;

View File

@@ -20,7 +20,10 @@
:class="{ border: !playlist.thumb }"
/>
<div class="overlay rounded">
<div v-if="playlist.help_text" class="rhelp playlist">{{ playlist.help_text }}</div>
<div v-if="playlist.help_text" class="rhelp playlist">
<span class="help">{{ playlist.help_text }}</span>
<span class="time">{{ playlist.time }}</span>
</div>
<div class="p-name ellip">{{ playlist.name }}</div>
<div class="p-count">
<b>{{

View File

@@ -1,10 +1,16 @@
<template>
<div class="queue-actions">
<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 />
<span>Shuffle</span>
</button>
<h2 v-else style="margin: 0">Now Playing</h2>
</div>
<div class="right">
<button
@@ -68,6 +74,7 @@ defineProps<{
.left {
display: flex;
align-items: center;
gap: $small;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -48,5 +48,7 @@ function switchTab(tab: string) {
align-items: center;
position: relative;
}
}
</style>

View File

@@ -3,10 +3,13 @@
</template>
<script setup lang="ts">
import ArtistGrid from "./ArtistGrid.vue";
import useSearch from "@/stores/search";
import TracksGrid from "./TracksGrid.vue";
import TopResults from "./TopResults.vue";
import CardPage from "@/views/SearchView/CardGridPage.vue";
const search = useSearch();
const props = defineProps<{
name: string;
}>();
@@ -24,15 +27,23 @@ function getComponent() {
};
case "albums":
return {
component: ArtistGrid,
component: CardPage,
props: {
album_grid: true,
page: "album",
items: search.albums.value,
outside_route: true,
fetch_callback: search.loadAlbums,
},
};
case "artists":
return {
component: ArtistGrid,
props: {},
component: CardPage,
props: {
page: "artist",
items: search.artists.value,
outside_route: true,
fetch_callback: search.loadArtists,
},
};
default:
return null;

View File

@@ -44,6 +44,14 @@ defineEmits<{
justify-content: center;
align-items: center;
}
.vue-recycle-scroller {
padding: 0 $small;
}
.cardlistrow {
grid-template-columns: repeat(auto-fill, minmax(8.1rem, 1fr));
}
}
#tab-content {

View File

@@ -1,37 +1,35 @@
<template>
<div id="tracks-results">
<div v-if="search.tracks.value.length">
<TrackItem
v-for="(track, index) in search.tracks.value"
:key="track.id"
:is-current="queue.currenttrackhash === track.trackhash"
:is-highlighted="false"
:is-current-playing="
queue.currenttrackhash === track.trackhash && queue.playing
"
:track="track"
:index="index + 1"
<div v-if="!search.tracks.value.length" class="t-center">
<h5>No tracks</h5>
</div>
<RecycleScroller
v-slot="{ item, index }"
class="scroller"
style="height: 100%"
:items="scrollerItems"
:item-size="64"
key-field="id"
>
<component
:is="item.component"
v-bind="item.props"
@playThis="updateQueue(index)"
/>
</div>
<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"
/>
</RecycleScroller>
</div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
import { computed, onMounted } from "vue";
import useQueue from "@/stores/queue";
import useSearch from "@/stores/search";
import useTracklist from "@/stores/queue/tracklist";
import LoadMore from "./LoadMore.vue";
import TrackItem from "@/components/shared/TrackItem.vue";
import AlbumsFetcher from "@/components/ArtistView/AlbumsFetcher.vue";
const queue = useQueue();
const search = useSearch();
@@ -42,6 +40,36 @@ function updateQueue(index: number) {
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(() => {
search.switchTab("tracks");
});

View File

@@ -1,5 +1,15 @@
<template>
<div class="gsearch-input">
<div
class="gsearch-input"
@click="
!settings.use_sidebar && $route.name !== Routes.search &&
$router.push({
name: Routes.search,
params: { page: 'top' },
query: { q: search.query },
})
"
>
<div id="ginner" ref="inputRef" tabindex="0">
<button
v-auto-animate
@@ -18,6 +28,7 @@
placeholder="Start typing to search"
type="search"
autocomplete="off"
spellcheck="false"
@blur.prevent="removeFocusedClass"
@focus.prevent="addFocusedClass"
/>
@@ -29,20 +40,26 @@
import { ref } from "vue";
import useTabStore from "@/stores/tabs";
import useSearchStore from "@/stores/search";
import useSearch from "@/stores/search";
import useSettings from "@/stores/settings";
import BackSvg from "@/assets/icons/arrow.svg";
import SearchSvg from "@/assets/icons/search.svg";
import { Routes } from "@/router";
const props = defineProps<{
on_nav?: boolean;
}>();
const tabs = useTabStore();
const search = useSearchStore();
const search = useSearch();
const settings = useSettings();
// HANDLE FOCUS
const inputRef = ref<HTMLElement>();
// NOTE: Functions are used because classes are added to the sorrounding element
// and not the input itself.
function addFocusedClass() {
inputRef.value?.classList.add("search-focused");
}
@@ -56,7 +73,7 @@ function removeFocusedClass() {
function handleButton() {
if (props.on_nav) return;
if (tabs.current === tabs.tabs.search || tabs.current === tabs.tabs.lyrics) {
if (tabs.current === tabs.tabs.search) {
tabs.switchToQueue();
} else {
tabs.switchToSearch();
@@ -73,9 +90,9 @@ function handleButton() {
width: 100%;
display: flex;
align-items: center;
gap: $small;
// gap: $small;
border-radius: 3rem;
outline: solid 1px $gray3;
background-color: $gray5;
button {
background: transparent;
@@ -101,13 +118,17 @@ function handleButton() {
border: none;
line-height: 2.25rem;
color: inherit;
font-size: 1rem;
font-size: 14px;
background-color: transparent;
outline: none;
@media screen and (max-width: 500px) {
width: 7rem;
}
}
}
}
.search-focused {
outline: solid $darkblue !important;
outline: solid 2px #fff !important;
}
</style>

View File

@@ -1,11 +0,0 @@
<script setup lang="ts">
import { onMounted } from "vue";
const props = defineProps<{
action: () => void;
}>();
onMounted(() => {
props.action();
});
</script>

View File

@@ -39,7 +39,22 @@
>
</div>
<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>
</template>
@@ -53,5 +68,25 @@
.flex {
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>

View 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>

View 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>

View File

@@ -6,7 +6,7 @@
<script setup lang="ts">
defineProps<{
state: undefined;
state: undefined | boolean;
}>();
</script>

View File

@@ -54,7 +54,7 @@ const currentTab = computed(() => {
<style lang="scss">
.settingscontent {
width: 35rem;
width: 31rem;
max-width: 100%;
.version {

View File

@@ -3,7 +3,7 @@
<div v-if="group.title || group.desc" class="info">
<h4 v-if="group.title">
{{ group.title
}}<span v-if="group.experimental" class="experimental circular">
}}<span v-if="group.experimental" class="badge experimental circular">
{{ group.experimental ? "experimental" : "" }}
</span>
</h4>
@@ -30,6 +30,15 @@
<div class="title">
<span class="ellip">
{{ 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>
<button
v-if="setting.type == SettingType.root_dirs"
@@ -60,8 +69,18 @@
>
{{ setting.button_text && setting.button_text() }}
</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>
<QuickActions v-if="setting.type == SettingType.quick_actions" />
<List
v-if="setting.type === SettingType.root_dirs"
icon="folder"
@@ -81,11 +100,13 @@
import { SettingType } from "@/settings/enums";
import { SettingGroup } from "@/interfaces/settings";
import List from "./Components/List.vue";
import Switch from "./Components/Switch.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 QuickActions from "./Components/QuickSettings.vue";
import SeparatorsInput from "./Components/SeparatorsInput.vue";
import LockedNumberInput from "./Components/LockedNumberInput.vue";
defineProps<{
group: SettingGroup;
@@ -101,13 +122,22 @@ defineProps<{
border-bottom: solid 1px $gray;
padding-bottom: 2rem;
.experimental {
font-size: 12px;
.badge {
margin-left: $small;
opacity: 0.5;
opacity: 0.75;
padding: 0 $smaller;
border-radius: $smaller;
font-size: 12px !important;
}
.experimental {
border: solid 1px $yellow;
color: $yellow;
padding: 0 $smaller;
}
.badge.new {
background-color: $blue;
opacity: 1;
}
&:first-child {
@@ -130,8 +160,6 @@ defineProps<{
.setting {
background-color: $gray;
// display: grid;
// gap: 1rem;
.inactive {
opacity: 0.5;

View File

@@ -17,8 +17,8 @@
<div class="creator t-center">
Designed and developed by
<span class="name"
><a target="_blank" href="https://github.com/mungai-njoroge"
>Mungai Njoroge</a
><a target="_blank" href="https://github.com/cwilvx"
>@cwilvx</a
>
</span>
</div>
@@ -44,12 +44,6 @@
color: $pink;
}
.release {
margin-left: $smaller;
font-size: 0.7rem;
color: $gray1;
}
.bottom-banner {
font-size: small;
margin-top: 1rem;

View File

@@ -10,9 +10,6 @@
}"
>
<div class="heading">{{ modal.title }}</div>
<div class="close circular" @click="modal.hideModal">
<PlusSvg />
</div>
<NewPlaylist
v-if="modal.component == modal.options.newPlaylist"
v-bind="modal.props"
@@ -20,7 +17,7 @@
@setTitle="setTitle"
/>
<UpdatePlaylist
v-if="modal.component == modal.options.updatePlaylist"
v-if="modal.component == modal.options.updatePlaylist"
v-bind="modal.props"
@hideModal="hideModal"
@setTitle="setTitle"
@@ -50,7 +47,6 @@ import { deletePlaylist as delPlaylist } from "@/requests/playlists";
import { useRouter } from "vue-router";
import useModalStore from "@/stores/modal";
import PlusSvg from "@/assets/icons/plus.svg";
import WelcomeModal from "./WelcomeModal.vue";
import ConfirmModal from "./modals/ConfirmModal.vue";
import NewPlaylist from "./modals/NewPlaylist.vue";
@@ -87,13 +83,15 @@ function deletePlaylist() {
input[type="search"] {
margin: $small 0;
border: 2px solid $gray3;
background-color: transparent;
border: none;
background-color: $gray5;
color: #fff;
width: 100%;
padding: 0.5rem;
font-size: 1rem;
font-size: 14px;
outline: none;
height: 2.75rem !important;
}
.bg {
@@ -116,28 +114,6 @@ function deletePlaylist() {
width: calc(100% - 2rem);
padding: 2rem 1rem;
}
.close {
position: absolute;
top: 1.5rem;
right: 1.25rem;
transform: rotate(45deg);
svg {
background-color: $gray3;
border-radius: 1rem;
}
cursor: pointer;
opacity: 0.75;
&:hover {
svg {
background-color: $red;
}
}
}
}
}
</style>

View File

@@ -7,6 +7,8 @@
type="search"
class="rounded-sm"
name="name"
placeholder="Type a name..."
spellcheck="false"
/>
<br /><br />
<button type="submit">Create</button>
@@ -15,17 +17,17 @@
<script setup lang="ts">
import { onMounted } from "vue";
import { useRoute } from "vue-router";
import {
saveAlbumAsPlaylist,
saveArtistAsPlaylist,
saveTrackAsPlaylist,
} from "@/requests/playlists";
import useQueueStore from "@/stores/queue";
import { NotifType, Notification } from "@/stores/notification";
import { createNewPlaylist, saveFolderAsPlaylist } from "@/requests/playlists";
import useTracklist from "@/stores/queue/tracklist";
import usePlaylistStore from "@/stores/pages/playlists";
import { NotifType, Notification } from "@/stores/notification";
const props = defineProps<{
trackhash?: string;
@@ -37,7 +39,6 @@ const props = defineProps<{
}>();
const store = usePlaylistStore();
const route = useRoute();
onMounted(() => {
const input_elem = document.getElementById(
@@ -112,8 +113,8 @@ function create(e: Event) {
};
const createQueuePlaylist = () => {
const queue = useQueueStore();
const trackhashes = queue.tracklist.map((track) => track.trackhash);
const { tracklist } = useTracklist();
const trackhashes = tracklist.map((track) => track.trackhash);
const itemhash = trackhashes.join(",");
saveTrackAsPlaylist(name, itemhash).then((res) => {
@@ -160,7 +161,6 @@ function create(e: Event) {
label {
font-size: 0.9rem;
color: $gray1;
font-weight: 700;
}
.submit {
@@ -171,8 +171,15 @@ function create(e: Event) {
button {
margin: 0 auto;
width: 8rem;
padding: 1.25rem;
// font-weight: normal;
transition: all 0.25s ease-out;
background-color: $pink;
background-color: $white;
color: $black;
&:hover {
color: $white;
}
}
}
</style>

View File

@@ -2,7 +2,7 @@
<br /><br />
<div style="position: relative">
<div id="bread-nav" class="bread-nav rounded-sm">
&nbsp;&nbsp;<span @click="fetchDirs('$root')">📁</span
&nbsp;&nbsp;<span @click="fetchDirs('$root')">$root</span
>&nbsp;&nbsp;<BreadCrumbNav :sub-paths="subPaths" @navigate="fetchDirs" />
</div>
<div class="set-root-dirs-browser">
@@ -26,10 +26,10 @@
</div>
<div class="buttons">
<button class="btn-active select-here" @click="selectHere">
Select here
Add this folder
</button>
<button class="btn-active finish" @click="submitFolders">
Select checked ({{ getNewDirs().length }})
Add all checked ({{ getNewDirs().length }})
</button>
</div>
</div>
@@ -40,9 +40,9 @@
import { onMounted, Ref, ref } from "vue";
import {
addRootDirs,
getFolders,
getRootDirs,
addRootDirs,
getFolders,
getRootDirs,
} from "@/requests/settings/rootdirs";
import { Folder, subPath } from "@/interfaces";
@@ -113,7 +113,7 @@ function submitFolders() {
}
function selectHere() {
if (current == "$root") return;
if (current == "$root" || current == "/") return;
addRootDirs([current], [])
.then((res) => settings.setRootDirs(res))
@@ -188,7 +188,7 @@ onMounted(() => {
.buttons {
display: flex;
justify-content: space-between;
justify-content: center;
gap: $medium;
margin-right: 1rem;
margin-bottom: -$medium;
@@ -199,17 +199,9 @@ onMounted(() => {
}
button {
font-weight: normal;
padding: 0 1rem;
}
button.select-here {
border: solid $darkestblue;
background: transparent;
&:hover {
background-color: $darkestblue;
}
}
}
.f-item {

View File

@@ -13,6 +13,7 @@
type="search"
class="rounded-sm"
name="name"
spellcheck="false"
@keypress.enter.prevent="update_playlist"
/>
@@ -81,7 +82,7 @@
</div>
<button type="submit">
{{ clicked ? "Saving" : "Save" }}
{{ clicked ? "Saving" : "Update" }}
</button>
</form>
</template>

View File

@@ -1,68 +1,74 @@
<template>
<div class="topnav">
<div
class="topnav"
:class="{
use_links: settings.is_alt_layout,
use_sidebar: settings.use_sidebar && isSmall,
}"
>
<div class="left">
<!-- back/forward -->
<NavButtons />
<div class="info">
<SettingsTitle
v-if="$route.name == Routes.settings"
:text="'Settings'"
/>
<FolderTitle
v-if="$route.name == Routes.folder"
:sub-paths="subPaths"
/>
<SearchTitle v-if="$route.name == Routes.search" />
<PlaylistsTitle v-if="$route.name == Routes.playlists" />
<QueueTitle v-if="$route.name == Routes.nowPlaying" />
<SimpleNav
v-if="$route.name == Routes.artistTracks"
:text="$route.query.artist as string || 'Artist Tracks'"
/>
<SimpleNav
v-if="$route.name === Routes.favoriteAlbums"
:text="'Favorite Albums'"
/>
<SimpleNav
v-if="$route.name === Routes.favoriteArtists"
:text="'Favorite Artists'"
/>
<SimpleNav
v-if="$route.name === Routes.favoriteTracks"
:text="'Favorite Tracks'"
/>
<NavLinks v-if="settings.is_alt_layout" />
<div
v-if="settings.is_default_layout && $route.name == Routes.folder"
class="info"
>
<Folder :sub-paths="subPaths" />
</div>
<NavTitles v-else-if="settings.is_default_layout && !isSmall" />
</div>
<RouterLink v-if="settings.is_alt_layout" to="/" class="logo rounded-sm"
><LogoSvg
/></RouterLink>
<div v-if="settings.is_alt_layout || !settings.use_sidebar" class="right">
<SearchInput :on_nav="true" />
<!-- v-if="settings.is_alt_layout" -->
<RouterLink
:to="{
name: Routes.settings,
params: {
tab: 'general',
},
}"
class="avatar"
>
<AvatarSvg />
</RouterLink>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from "vue";
import { Routes } from "@/router";
import { useRoute } from "vue-router";
import { computed, onMounted, ref, watch } from "vue";
import { subPath } from "@/interfaces";
import { Routes } from "@/router";
import { createSubPaths } from "@/utils";
import useSettings from "@/stores/settings";
import { content_width } from "@/stores/content-width";
import NavLinks from "./NavLinks.vue";
import NavTitles from "./NavTitles.vue";
import Folder from "./Titles/Folder.vue";
import NavButtons from "./NavButtons.vue";
import AvatarSvg from "@/assets/icons/settings.svg";
import SearchInput from "../RightSideBar/SearchInput.vue";
import LogoSvg from "@/assets/icons/logos/logo-fill.light.svg";
import FolderTitle from "./Titles/Folder.vue";
import PlaylistsTitle from "./Titles/PlaylistsTitle.vue";
import QueueTitle from "./Titles/QueueTitle.vue";
import SearchTitle from "./Titles/SearchTitle.vue";
import SettingsTitle from "./Titles/SettingsTitle.vue";
import SimpleNav from "./Titles/SimpleNav.vue";
const settings = useSettings();
const isSmall = computed(() => content_width.value < 800);
const route = useRoute();
const subPaths = ref<subPath[]>([]);
let oldpath = "";
watch(
() => route.name,
(newRoute) => {
switch (newRoute) {
case Routes.folder: {
let oldpath = "";
[oldpath, subPaths.value] = createSubPaths(
route.params.path as string,
oldpath
@@ -84,18 +90,42 @@ watch(
}
}
);
onMounted(() => {
if (route.name == Routes.folder) {
[oldpath, subPaths.value] = createSubPaths(
route.params.path as string,
oldpath
);
}
});
</script>
<style lang="scss">
.topnav {
display: grid;
grid-template-columns: 1fr min-content;
grid-template-columns: 1fr max-content;
input {
min-width: 6rem;
}
align-items: center;
gap: 1rem;
font-size: 14px;
&.use_links {
grid-template-columns: 1fr max-content 1fr;
}
&.use_sidebar {
gap: 0;
}
.left {
display: grid;
grid-template-columns: max-content 1fr;
gap: 1rem;
height: 2.25rem;
.info {
margin: auto 0;
@@ -109,10 +139,45 @@ watch(
}
}
.center {
display: grid;
place-items: center;
margin-right: 1rem;
.logo {
width: max-content;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
}
.right {
display: flex;
justify-content: flex-end;
gap: 1rem;
align-items: center;
width: 100%;
.avatar {
height: 2.25rem;
aspect-ratio: 1;
background-color: $gray4;
border-radius: 50%;
cursor: pointer;
display: grid;
place-items: center;
svg {
transform: scale(0.75);
color: $gray1;
}
&:hover {
background-color: #fff;
svg {
color: #000;
}
}
}
}
}
</style>

View File

@@ -19,17 +19,18 @@ import ArrowSvg from "../../assets/icons/right-arrow.svg";
grid-template-columns: 1fr 1fr;
gap: 1rem;
padding-right: 1rem;
border-right: 1px solid $gray3;
height: 100%;
border-right: 1px solid $gray5;
height: max-content;
& > * {
width: 2.25rem;
width: 2.5rem;
height: 2.5rem;
aspect-ratio: 1;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
border-radius: 0.65rem;
border-radius: 5rem;
svg {
transform: scale(1.25);

View 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>

View 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>

View File

@@ -1,48 +1,26 @@
<template>
<div id="folder-nav-title">
<div class="folder">
<div class="fname-wrapper">
<div class="fname">
<div
class="icon image"
@click="
$router.push({ name: Routes.folder, params: { path: '$home' } })
"
></div>
<BreadCrumbNav :sub-paths="subPaths" @navigate="navigate" />
</div>
</div>
<SearchInput v-if="!isSmallPhone" :page="Routes.folder" />
<button
class="options"
:class="{ 'btn-active': context_menu_showing }"
title="show more options"
@click="showContextMenu"
>
<MoreSvg />
</button>
<div class="fname">
<div
class="icon image"
@click="
$router.push({ name: Routes.folder, params: { path: '$home' } })
"
></div>
<BreadCrumbNav :sub-paths="subPaths" @navigate="navigate" />
</div>
</div>
</template>
<script setup lang="ts">
import { useRoute, useRouter } from "vue-router";
import { useRouter } from "vue-router";
import { Routes } from "@/router";
import { subPath } from "@/interfaces";
import { isSmallPhone } from "@/stores/content-width";
import SearchInput from "@/components/shared/NavSearchInput.vue";
import BreadCrumbNav from "@/components/FolderView/BreadCrumbNav.vue";
import MoreSvg from "@/assets/icons/more.svg";
import { showFolderContextMenu } from "@/helpers/contextMenuHandler";
import { ref } from "vue";
import { ContextSrc } from "@/enums";
const router = useRouter();
const route = useRoute();
const context_menu_showing = ref(false);
defineProps<{
subPaths: subPath[];
@@ -51,69 +29,36 @@ defineProps<{
function navigate(path: string) {
router.push({ name: Routes.folder, params: { path } });
}
function showContextMenu(e: MouseEvent) {
showFolderContextMenu(
e,
context_menu_showing,
ContextSrc.FolderNav,
route.params.path as string
);
}
</script>
<style lang="scss">
#folder-nav-title {
width: 100%;
display: grid;
overflow: hidden;
.folder {
display: grid;
grid-template-columns: 1fr max-content max-content;
.fname {
background-color: $gray4;
border-radius: $small;
height: 2.25rem;
display: flex;
align-items: center;
padding-right: $smaller;
width: fit-content;
max-width: 100%;
overflow: scroll;
scrollbar-width: none;
@include iphoneSE {
grid-template-columns: 1fr max-content;
&::-webkit-scrollbar {
display: none;
}
.fname-wrapper {
width: 100%;
overflow: hidden;
}
.fname {
background-color: $gray4;
border-radius: $small;
height: 2.25rem;
display: flex;
align-items: center;
padding-right: $smaller;
width: fit-content;
max-width: 100%;
overflow: scroll;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
.icon {
height: 2rem;
aspect-ratio: 1;
background-image: url("../../../assets/icons/folder.fill.svg");
background-size: 1.5rem;
margin-left: $smaller;
}
}
.options {
margin-left: 1rem;
height: 2.25rem;
width: 2.25rem;
.icon {
height: 2rem;
aspect-ratio: 1;
padding: 0;
svg {
transform: scale(1.2);
}
background-image: url("../../../assets/icons/folder.fill.svg");
background-size: 1.5rem;
margin-left: $smaller;
}
}
}

View File

@@ -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>

View File

@@ -1,8 +1,7 @@
<template>
<div class="nav-search-input">
<SearchInput :on_nav="true" />
<Tabs
v-if="!isMobile"
v-if="!(content_width < 800)"
:tabs="tabs"
:current-tab="($route.params.page as string)"
@switchTab="(tab: string) => {
@@ -18,10 +17,10 @@
<script setup lang="ts">
import { Routes } from "@/router";
import Tabs from "@/components/RightSideBar/Search/TabsWrapper.vue";
import SearchInput from "@/components/RightSideBar/SearchInput.vue";
import useSearchStore from "@/stores/search";
import { isMobile } from "@/stores/content-width";
import { content_width } from "@/stores/content-width";
import Tabs from "@/components/RightSideBar/Search/TabsWrapper.vue";
const search = useSearchStore();
const tabs = ["top", "tracks", "albums", "artists"];
@@ -29,17 +28,6 @@ const tabs = ["top", "tracks", "albums", "artists"];
<style lang="scss">
.nav-search-input {
align-items: center;
display: grid;
grid-template-columns: minmax(10rem, 20rem) max-content;
justify-content: space-between;
gap: 1rem;
@include allPhones {
grid-template-columns: 1fr;
gap: 0;
}
#right-tabs {
position: relative;
}

View File

@@ -23,7 +23,8 @@
</div>
<div>
<div v-if="album.help_text" class="rhelp album">
{{ album.help_text }}
<span class="help">{{ album.help_text }}</span>
<span class="time">{{ album.time }}</span>
</div>
<h4 v-tooltip class="title ellip">
{{ album.title }}

View File

@@ -23,7 +23,8 @@
/>
</div>
<div v-if="artist.help_text" class="rhelp t-center">
{{ artist.help_text }}
<span class="help">{{ artist.help_text }}</span>
<span class="time">{{ artist.time }}</span>
</div>
<div class="artist-name t-center">
{{ artist.name }}

View File

@@ -31,7 +31,7 @@ defineProps<{
<style lang="scss">
.cardlistrow {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10.1rem, 1fr));
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
padding-bottom: 2rem;
z-index: -1;
}

View File

@@ -29,8 +29,8 @@ import SeeAll from "../shared/SeeAll.vue";
import FolderCard from "./FolderCard.vue";
import AlbumCard from "./AlbumCard.vue";
import ArtistCard from "./ArtistCard.vue";
import PlaylistCard from "../PlaylistsList/PlaylistCard.vue";
import FavoritesCard from "./FavoritesCard.vue";
import PlaylistCard from "../PlaylistsList/PlaylistCard.vue";
const props = defineProps<{
title: string;
@@ -99,12 +99,12 @@ function getProps(item: { type: string; item: any }) {
<style lang="scss">
.cardscroller {
padding: 2rem 0;
padding: 1.5rem 0;
.recentitems {
gap: 1.5rem 0;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10.1rem, 1fr));
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
}
.p-card {
@@ -135,5 +135,26 @@ function getProps(item: { type: string; item: any }) {
padding: 1.25rem 2rem;
margin: 1rem;
}
.hlistitem {
// TODO: Handle when there's no time
// INFO: Set the time to display none by default
.rhelp .time {
display: none;
}
&:hover {
// INFO: Set the help text to display none on hover
.rhelp .help {
display: none;
}
// INFO: Set the time to display block on hover
.rhelp .time {
display: block;
}
}
}
}
</style>

View File

@@ -18,7 +18,10 @@
<PlayBtn :source="playSources.favorite" />
</div>
<div class="info">
<div class="rhelp playlist">PLAYLIST</div>
<div class="rhelp playlist">
<span class="help">PLAYLIST</span>
<span class="time">{{ item.time }}</span>
</div>
<div class="title">Favorite Tracks</div>
<div class="fcount">
<b>{{ item.count + ` Track${item.count == 1 ? "" : "s"}` }}</b>

View File

@@ -26,7 +26,8 @@
</div>
<div v-if="folder.help_text" class="rhelp folder">
{{ folder.help_text }}
<span class="help">{{ folder.help_text }}</span>
<span class="time">{{ folder.time }}</span>
</div>
<div class="ellip" :title="name(folder.path)">
{{ name(folder.path) }}
@@ -47,6 +48,7 @@ defineProps<{
path: string;
count: number;
help_text: string;
time?: string;
};
}>();

View File

@@ -1,5 +1,6 @@
<template>
<button
title="Lyrics"
class="lyrics"
:class="{ showStatus: lyrics.exists }"
@click="handleClick"
@@ -27,13 +28,12 @@ const lyrics = useLyrics();
let prevRoute = ref(route.name);
function handleClick() {
if (route.name === Routes.nowPlaying && route.params.tab === "lyrics") {
if (route.name === Routes.Lyrics) {
return router.back();
}
router.push({
name: Routes.nowPlaying,
params: { tab: "lyrics" },
name: Routes.Lyrics,
});
prevRoute.value = route.name;

View File

@@ -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>

View File

@@ -127,7 +127,7 @@ onBeforeUnmount(() => {
<style lang="scss">
.songlist-item {
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;
justify-content: flex-start;
height: $song-item-height;

View File

@@ -15,7 +15,7 @@
>
<SongItem
:track="item.track"
:index="index + 1"
:index="total ? total - index : index + 1"
:is_queue_track="is_queue"
:is_last="index == tracks.length - 1"
:droppable="false"
@@ -44,6 +44,7 @@ defineProps<{
oldIndex: number
) => void;
source: dropSources;
total?: number;
}>();
const itemHeight = 64;

View File

@@ -1,17 +1,26 @@
<template>
<div class="trackcard rounded">
<RouterLink
:to="{
name: Routes.album,
params: {
albumhash: track.albumhash,
},
}"
class="trackcard rounded"
>
<div class="image">
<img class="rounded-sm" :src="paths.images.thumb.large + track.image" />
<PlayBtn :source="playSource" :track="track" />
</div>
<div class="tinfo">
<div v-if="track.help_text" class="rhelp track">
{{ track.help_text }}
<span class="help">{{ track.help_text }}</span>
<span class="time">{{ track.time }}</span>
</div>
<div class="ttitle ellip">{{ track.title }}</div>
<ArtistName :albumartists="track.albumartists" :artists="track.artists" />
</div>
</div>
</RouterLink>
</template>
<script setup lang="ts">
@@ -21,6 +30,7 @@ import { playSources } from "@/enums";
import PlayBtn from "../shared/PlayBtn.vue";
import ArtistName from "../shared/ArtistName.vue";
import { Routes } from "@/router";
defineProps<{
track: Track;
@@ -69,6 +79,8 @@ defineEmits<{
img {
width: 100%;
aspect-ratio: 1;
object-fit: cover;
}
.artist {

View File

@@ -48,7 +48,7 @@
v-if="isQueueTrack"
class="remove-track"
title="Remove from queue"
@click.stop="queue.removeFromQueue(index)"
@click.stop="player.removeByIndex(index)"
>
<DelSvg />
</div>
@@ -60,10 +60,11 @@
import { useRoute } from "vue-router";
import { onBeforeUnmount, ref, watch } from "vue";
import useTracklist from "@/stores/queue/tracklist";
import { paths } from "@/config";
import { favType } from "@/enums";
import { Track } from "@/interfaces";
import useQueueStore from "@/stores/queue";
import favoriteHandler from "@/helpers/favoriteHandler";
import { showTrackContextMenu as showContext } from "@/helpers/contextMenuHandler";
@@ -79,10 +80,11 @@ const props = defineProps<{
index?: number;
}>();
const queue = useQueueStore();
const player = useTracklist();
const route = useRoute();
const context_on = ref(false);
const is_fav = ref(props.track.is_favorite);
const route = useRoute();
function showMenu(e: MouseEvent) {
showContext(e, props.track, context_on, route);

View File

@@ -4,26 +4,14 @@ import { Option, Playlist } from "@/interfaces";
import { getTracksInPath } from "@/requests/folders";
import useModal from "@/stores/modal";
import useSettings from "@/stores/settings";
import useTracklist from "@/stores/queue/tracklist";
import { addFolderToPlaylist } from "@/requests/playlists";
import { getAddToPlaylistOptions } from "./utils";
export default async (trigger_src: ContextSrc, path: string) => {
const settings = useSettings();
export default async (path: string) => {
const modal = useModal();
const getListModeOption = () =>
<Option>{
label: settings.folder_list_mode ? "Grid Mode" : "List Mode",
action: () => settings.toggleFolderListMode(),
icon: settings.folder_list_mode ? icons.GridIcon : icons.PlaylistIcon,
};
// if trigger source is folder nav, show list mode option
let items = trigger_src === ContextSrc.FolderNav ? [getListModeOption()] : [];
const play_next = <Option>{
label: "Play next",
action: () => {
@@ -65,5 +53,5 @@ export default async (trigger_src: ContextSrc, path: string) => {
icon: icons.PlaylistIcon,
};
return [play_next, add_to_queue, add_to_playlist, save_as_playlist, ...items];
return [play_next, add_to_queue, add_to_playlist, save_as_playlist];
};

View File

@@ -36,10 +36,11 @@ export default async () => {
useQueue().clearQueue();
},
icon: DeleteIcon,
critical: true,
};
const saveAsPlaylist: Option = {
label: "Save as playlist",
label: "Save queue as playlist",
action: () => {
useModalStore().showSaveQueueAsPlaylistModal(getQueueName(store.from));
},
@@ -51,7 +52,7 @@ export default async () => {
};
const addToPlaylist: Option = {
label: "Add to Playlist",
label: "Add queue to playlist",
children: await getAddToPlaylistOptions(AddToPlaylistAction, {
trackhash: store.tracklist.map((t) => t.trackhash).join(","),
}),

View File

@@ -19,7 +19,7 @@ import {
import usePlaylistStore from "@/stores/pages/playlist";
import useQueueStore from "@/stores/queue";
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.
@@ -190,11 +190,23 @@ export default async (
go_to_artist,
go_to_alb_artist,
open_in_explorer,
get_find_on_social("track", `${track.title} ${track.artists[0].name}`),
// del_track,
];
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;

View File

@@ -56,7 +56,7 @@ export async function getAddToPlaylistOptions(
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 getAlbumSearchTerm = () => {
const store = useAlbum();
@@ -65,7 +65,11 @@ export const get_find_on_social = (page = "album") => {
.map((a) => a.name)
.join(", ")}`;
};
const search_term = is_album ? getAlbumSearchTerm() : useArtist().info.name;
const search_term = query
? query
: is_album
? getAlbumSearchTerm()
: useArtist().info.name;
return <Option>{
label: "Search on",

View File

@@ -60,7 +60,7 @@ export const showFolderContextMenu = (
) => {
const menu = useContextStore();
const options = () => folderContextItems(source, path);
const options = () => folderContextItems(path);
menu.showContextMenu(e, options, source);
flagWatcher(menu, flag);

View File

@@ -1,8 +1,9 @@
import useQStore from "@/stores/queue";
import useModalStore from "@/stores/modal";
import useContextStore from "@/stores/context";
import useQueue from "@/stores/queue";
import useModal from "@/stores/modal";
import useContextMenu from "@/stores/context";
import useSettings from "@/stores/settings";
let key_down_fired = false;
let key_down_fired = "";
function focusPageSearchBox() {
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();
window.addEventListener("keydown", (e: KeyboardEvent) => {
const target = e.target as HTMLElement;
if (e.altKey) return;
if (e.altKey) return;
const ctrlKey = e.ctrlKey;
const shiftKey = e.shiftKey;
const no_text_selection = !window.getSelection()?.toString();
function FocusedOnInput(target: HTMLElement) {
function focusedOnInput() {
const targett = target as HTMLInputElement;
return (
@@ -39,7 +44,7 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
if (ctrlKey) return true;
}
if (FocusedOnInput(target)) {
if (focusedOnInput()) {
if (e.key == "Escape") {
target.blur();
}
@@ -47,10 +52,16 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
return;
}
if (key_down_fired) return;
if (key_down_fired == e.key.toLowerCase()) {
return;
}
switch (e.key) {
case "ArrowRight":
function setKeyFired() {
key_down_fired = e.key.toLowerCase();
}
switch (e.key.toLowerCase()) {
case "arrowright":
{
const doSeek = triggerSeek();
@@ -62,7 +73,7 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
setTimeout(() => {
// fire event after 1 second
key_down_fired = false;
resetKeyFired();
}, 1000);
if (no_text_selection) {
@@ -71,7 +82,7 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
}
break;
case "ArrowLeft":
case "arrowleft":
{
const doSeek = triggerSeek();
@@ -86,7 +97,7 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
}
setTimeout(() => {
key_down_fired = false;
resetKeyFired();
}, 1000);
}
@@ -95,7 +106,6 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
case " ": // space
{
e.preventDefault();
q.playPause();
}
@@ -108,9 +118,21 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
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 c = useContextStore();
const c = useContextMenu();
if (m.visible) {
m.hideModal();
@@ -122,10 +144,10 @@ export default function (queue: typeof useQStore, modal: typeof useModalStore) {
}
}
key_down_fired = true;
setKeyFired();
});
}
window.addEventListener("keyup", () => {
key_down_fired = false;
resetKeyFired();
});

View File

@@ -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 PlaylistIcon } from "@/assets/icons/playlist.svg?raw";
export { default as GridIcon } from "@/assets/icons/grid.svg?raw";
export { default as ReloadIcon } from "@/assets/icons/reload.svg?raw";

View File

@@ -30,6 +30,7 @@ export interface Track extends AlbumDisc {
copyright?: string;
master_index?: number;
help_text?: string;
time?: string;
}
export interface Folder {
@@ -56,6 +57,7 @@ export interface Album {
copyright?: string;
help_text?: string;
time?: string;
is_live: boolean;
is_compilation: boolean;
is_soundtrack: boolean;
@@ -76,6 +78,7 @@ export interface Artist {
colors: string[];
is_favorite?: boolean;
help_text?: string;
time?: string;
}
export interface Option {
@@ -107,6 +110,7 @@ export interface Playlist {
settings: PlaylistSettings;
pinned: boolean;
help_text?: string;
time?: string;
images:
| {
image: string;

View File

@@ -6,7 +6,7 @@ export interface SettingOption {
}
export interface Setting {
title: string;
title?: string;
desc?: string;
type: SettingType;
options?: SettingOption[];
@@ -16,6 +16,8 @@ export interface Setting {
button_text?: () => string;
defaultAction?: () => void;
show_if?: () => boolean;
experimental?: boolean;
new?: boolean;
}
export interface SettingGroup {

View File

@@ -33,9 +33,9 @@ app.use(MotionPlugin);
app.directive("tooltip", vTooltip);
app.component("WrapBalancer", WrapBalancer);
app.component("RecycleScroller", RecycleScroller);
app.component("DynamicScroller", DynamicScroller);
app.component("DynamicScrollerItem", DynamicScrollerItem);
app.component("WrapBalancer", WrapBalancer);
app.mount("#app");

View File

@@ -7,24 +7,24 @@ import usePlaylistPageStore from "@/stores/pages/playlist";
import usePlaylistListPageStore from "@/stores/pages/playlists";
import useArtistPageStore from "@/stores/pages/artist";
import HomeView from "@/views/HomeView";
const FolderView = () => import("@/views/FolderView.vue");
const PlaylistListView = () => import("@/views/PlaylistList.vue");
const PlaylistView = () => import("@/views/PlaylistView/index.vue");
const AlbumView = () => import("@/views/AlbumView/index.vue");
import HomeView from "@/views/HomeView";
const Lyrics = () => import("@/views/LyricsView");
const ArtistView = () => import("@/views/ArtistView");
const ArtistTracksView = () => import("@/views/ArtistTracks.vue");
const ArtistDiscographyView = () => import("@/views/ArtistDiscography.vue");
const SettingsView = () => import("@/views/SettingsView.vue");
const SearchView = () => import("@/views/SearchView");
const FavoritesView = () => import("@/views/Favorites.vue");
const FavoriteAlbums = () => import("@/views/FavoriteAlbums.vue");
const FavoriteTracks = () => import("@/views/FavoriteTracks.vue");
const FavoriteArtists = () => import("@/views/FavoriteArtists.vue");
const NotFound = () => import("@/views/NotFound.vue");
const NowPlaying = () => import("@/views/NowPlaying");
const SearchView = () => import("@/views/SearchView");
const AlbumList = () => import("@/views/AlbumListView");
const FolderView = () => import("@/views/FolderView.vue");
const FavoritesView = () => import("@/views/Favorites.vue");
const SettingsView = () => import("@/views/SettingsView.vue");
const AlbumView = () => import("@/views/AlbumView/index.vue");
const ArtistTracksView = () => import("@/views/ArtistTracks.vue");
const PlaylistListView = () => import("@/views/PlaylistList.vue");
const FavoriteTracks = () => import("@/views/FavoriteTracks.vue");
const PlaylistView = () => import("@/views/PlaylistView/index.vue");
const ArtistDiscographyView = () => import("@/views/ArtistDiscography.vue");
const FavoriteCardScroller = () => import("@/views/FavoriteCardScroller.vue");
const folder = {
path: "/folder/:path",
@@ -103,6 +103,12 @@ const NowPlayingView = {
component: NowPlaying,
};
const LyricsView = {
path: "/lyrics",
name: "LyricsView",
component: Lyrics,
};
const ArtistTracks = {
path: "/artists/:hash/tracks",
name: "ArtistTracks",
@@ -136,7 +142,13 @@ const favorites = {
const favoriteAlbums = {
path: "/favorites/albums",
name: "FavoriteAlbums",
component: FavoriteAlbums,
component: FavoriteCardScroller,
};
const favoriteArtists = {
path: "/favorites/artists",
name: "FavoriteArtists",
component: FavoriteCardScroller,
};
const favoriteTracks = {
@@ -145,12 +157,6 @@ const favoriteTracks = {
component: FavoriteTracks,
};
const favoriteArtists = {
path: "/favorites/artists",
name: "FavoriteArtists",
component: FavoriteArtists,
};
const notFound = {
name: "NotFound",
path: "/:pathMatch(.*)",
@@ -194,9 +200,10 @@ const routes = [
Home,
AlbumListView,
ArtistListView,
LyricsView,
];
export const Routes = {
const Routes = {
folder: folder.name,
playlists: playlists.name,
playlist: playlistView.name,
@@ -215,6 +222,7 @@ export const Routes = {
Home: Home.name,
AlbumList: AlbumListView.name,
ArtistList: ArtistListView.name,
Lyrics: LyricsView.name,
};
const router = createRouter({
@@ -223,4 +231,4 @@ const router = createRouter({
routes,
} as RouterOptions);
export { router };
export { router, Routes };

View 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];

View 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;

View File

@@ -5,4 +5,6 @@ export enum SettingType {
binary,
button,
root_dirs,
locked_number_input,
quick_actions,
}

View 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];

View File

@@ -11,6 +11,9 @@ import albums from "./albums";
import separators from "./separators";
import tracks from "./tracks";
import circularArtistImg from "./circular-artist-img";
import quickactions from "./quickactions";
import layout from "./layout";
import folderlistmode from "./folderlistmode";
const npStrings = strings.nowPlayingStrings;
const rootRootStrings = strings.manageRootDirsStrings;
@@ -18,20 +21,26 @@ const rootRootStrings = strings.manageRootDirsStrings;
export default {
title: "General",
groups: [
{
settings: quickactions,
},
{
title: "UI Settings",
desc: "Settings for various parts of the user interface.",
settings: [
...layout,
...extendWidth,
...sidebarSettings,
circularArtistImg,
...contextChildrenShowMode,
...folderlistmode,
],
},
{
title: npStrings.title,
desc: npStrings.desc,
settings: [...nowPlaying],
show_if: () => !useSettingsStore().is_alt_layout,
},
{
title: rootRootStrings.title,
@@ -61,3 +70,5 @@ export default {
},
],
} as SettingCategory;
// ENHANCEMENT: Decouple components from Group.vue and pass them as part of the Setting interface (maybe?)

View 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];

View 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];

View File

@@ -6,10 +6,12 @@ import useSettingsStore from "@/stores/settings";
const settings = useSettingsStore;
const use_sidebar: Setting = {
title: "Hide right sidebar",
title: "Toggle right sidebar",
desc: "CTRL + B",
type: SettingType.binary,
state: () => !settings().use_sidebar,
state: () => settings().use_sidebar,
action: () => settings().toggleDisableSidebar(),
show_if: () => !settings().is_alt_layout,
};
export default [use_sidebar];

View File

@@ -1,5 +1,6 @@
import audio from "./audio";
import about from "./about";
import general from "./general";
import plugins from "./plugins";
import about from "./about";
export default [general, plugins, about];
export default [general, audio, plugins, about];

View File

@@ -1 +0,0 @@

View File

@@ -3,6 +3,7 @@ import { defineStore } from "pinia";
import { getRecentlyAdded, getRecentlyPlayed } from "@/requests/home";
import { maxAbumCards } from "./content-width";
import { Routes, router } from "@/router";
export default defineStore("homepage", () => {
const recentlyAddedCutoff = ref(0);
@@ -22,11 +23,19 @@ export default defineStore("homepage", () => {
recentlyPlayed.value = data.items;
}
function resetAll() {
setTimeout(() => {
if (router.currentRoute.value.name == Routes.Home) return;
[recentlyAdded.value, recentlyPlayed.value] = [[], []];
}, 5000);
}
return {
recentlyAddedCutoff,
recentlyAdded,
recentlyPlayed,
fetchRecentlyAdded,
fetchRecentlyPlayed,
resetAll,
};
});

View File

@@ -1,12 +1,12 @@
import { defineStore } from "pinia";
import useQueue from "./queue";
import useTabs from "./tabs";
import useLyricsPlugin from "./plugins/lyrics";
import useQueue from "./queue";
import useSettings from "./settings";
import { LyricsLine } from "@/interfaces";
import { checkExists, getLyrics } from "@/requests/lyrics";
import { Routes, router } from "@/router";
// a custom error class called HasNoSyncedLyricsError
class HasUnSyncedLyricsError extends Error {
@@ -87,7 +87,7 @@ export default defineStore("lyrics", {
});
},
scrollToContainerTop() {
const container = document.getElementById("sidelyrics");
const container = document.getElementById("lyricscontent");
if (container) {
container.scroll({
@@ -97,9 +97,7 @@ export default defineStore("lyrics", {
}
},
checkExists(filepath: string, trackhash: string) {
const tabs = useTabs();
if (tabs.nowplaying !== tabs.tabs.lyrics) {
if (router.currentRoute.value.name !== Routes.Lyrics) {
this.lyrics = <LyricsLine[]>[];
}

View File

@@ -15,6 +15,7 @@ import { maxAbumCards } from "@/stores/content-width";
import { useFuse } from "@/utils";
import setColorsToStore from "@/utils/colortools/setColorsToStore";
import { useNotifStore } from "../notification";
import { router, Routes } from "@/router";
interface Disc {
[key: string]: Track[];
@@ -136,13 +137,19 @@ export default defineStore("album", {
this.query = "";
},
resetAlbumArtists() {
this.albumArtists = [];
setTimeout(() => {
if (router.currentRoute.value.name == Routes.album) return;
this.albumArtists = [];
this.fetched_other_hash = "";
}, 10000);
},
resetOtherVersions() {
this.otherVersions = [];
this.fetched_version_hash = "";
},
resetSimilarAlbums() {
this.similarAlbums = [];
this.fetched_similar_hash = "";
},
makeFavorite() {
this.info.is_favorite = true;
@@ -150,6 +157,14 @@ export default defineStore("album", {
removeFavorite() {
this.info.is_favorite = false;
},
resetAll() {
setTimeout(() => {
if (router.currentRoute.value.name == Routes.album) return;
this.resetAlbumArtists();
this.resetOtherVersions();
this.resetSimilarAlbums();
}, 5000);
},
},
getters: {
filteredTracks(): ComputedRef<FuseResult[]> {

View File

@@ -11,6 +11,7 @@ import { Album, Artist, Track } from "@/interfaces";
import { maxAbumCards } from "@/stores/content-width";
import useSettingsStore from "@/stores/settings";
import setColorsToStore from "@/utils/colortools/setColorsToStore";
import { Routes, router } from "@/router";
export default defineStore("artistPage", {
state: () => ({
@@ -27,7 +28,6 @@ export default defineStore("artistPage", {
btn: "",
},
genres: <string[]>[],
fetched_similar_hash: "",
}),
actions: {
@@ -60,7 +60,6 @@ export default defineStore("artistPage", {
this.info.artisthash,
maxAbumCards.value
);
},
extractColors() {
const url = paths.images.artist.large + this.info.image;
@@ -79,6 +78,14 @@ export default defineStore("artistPage", {
resetSimilarArtists() {
this.similar_artists = [];
},
resetAll() {
setTimeout(() => {
if (router.currentRoute.value.name == Routes.artist) return;
this.resetAlbums();
this.resetSimilarArtists();
this.fetched_similar_hash = "";
}, 5000);
},
makeFavorite() {
this.info.is_favorite = true;
},

View File

@@ -1,4 +1,4 @@
import { router } from "@/router";
import { Routes, router } from "@/router";
import { defineStore } from "pinia";
import { discographyAlbumTypes } from "@/enums";
@@ -71,5 +71,11 @@ export default defineStore("artistDiscography", {
this.toShow = [];
this.artistname = "";
},
resetAll() {
setTimeout(() => {
if (router.currentRoute.value.name == Routes.artistDiscography) return;
this.resetStore();
}, 5000);
},
},
});

Some files were not shown because too many files have changed in this diff Show More