Compare commits

...

19 Commits

Author SHA1 Message Date
geoffrey45
f6e135a28c make songcard less rounded 2023-04-17 02:25:59 +03:00
geoffrey45
e8b7db4d98 add setting to hide master flag
+ show track bitrate on master flag hover
2023-04-14 20:37:12 +03:00
geoffrey45
731d06a35e minor tweaks 2023-04-06 11:59:00 +03:00
geoffrey45
7eba9fe307 remove console.logs 2023-03-27 08:01:05 +03:00
geoffrey45
fdb8b9fa31 remove drag and drop code: prep for next release 2023-03-27 03:35:04 +03:00
geoffrey45
766b705f07 remove console.log 2023-03-26 18:35:50 +03:00
geoffrey45
b4e72c77bc fix ub40 tracks not playing due to use of decodeURIComponent instead of encodeURIComponent 2023-03-26 18:34:56 +03:00
geoffrey45
640a7d7830 fix artist page appearances see all bug 2023-03-26 18:06:15 +03:00
geoffrey45
9f8847823e add button to toggle list mode 2023-03-26 11:07:57 +03:00
geoffrey45
93e4db218f paint bottom bar artist name with thumbnail cover
+ break bottom bar and playlist header into components
2023-03-19 23:36:09 +03:00
geoffrey45
101d9496ca add playlist image grid to playlist card
+ add wave animations on nav items, and songcard
2023-03-18 00:39:41 +03:00
geoffrey45
710c6ca91d use @nextcss/colortools/shift to make colored headers balanced 2023-03-17 00:55:54 +03:00
geoffrey45
8beed7f45c add and hide trackbin 2023-03-15 10:02:01 +03:00
geoffrey45
9cb3820c96 redesign playlist header
~ to show the playlist contents images in header
2023-03-14 23:32:47 +03:00
geoffrey45
ed2e1f11e4 fix favorite tracks page behaving similar to queue page
+ add icons to root dirs list in settings page
2023-03-14 20:04:49 +03:00
geoffrey45
8a4b9b1024 add v-wave
+ fix console errrors on queue clear
2023-03-12 10:35:50 +03:00
geoffrey45
bc58a738da add padding to context menu
+ misc
2023-03-04 21:25:28 +03:00
geoffrey45
cf57f915ba a gazzilion refactors 2023-02-26 09:46:41 +03:00
geoffrey45
a7bfb8fe51 add draggables and drop zones to songitem component 2023-02-25 15:24:54 +03:00
106 changed files with 1215 additions and 467 deletions

View File

@@ -4,7 +4,6 @@
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="./src/assets/icons/logo-small.svg" />
<title>Swing Music</title>
</head>
<body>

View File

@@ -21,6 +21,7 @@
"pinia-plugin-persistedstate": "^2.1.1",
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"v-wave": "^1.5.0",
"vue": "^v3.2.45",
"vue-debounce": "^3.0.2",
"vue-router": "^4.1.3",
@@ -29,11 +30,13 @@
"webpack": "^5.74.0"
},
"devDependencies": {
"@nextcss/color-tools": "^1.0.7",
"@vitejs/plugin-vue": "^3.2.0",
"vite-svg-loader": "^3.4.0",
"eslint": "^8.7.0",
"eslint-plugin-vue": "^8.3.0",
"vite": "^3.0.4"
"vite": "^3.0.4",
"vite-svg-loader": "^3.4.0",
"vue-virtual-draglist": "^3.0.4"
},
"packageManager": "yarn@1.22.19"
}

BIN
public/favicon.ico Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

4
public/logo-fill.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="118" height="118" viewBox="0 0 118 118" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="59" cy="59" r="57.5" fill="white" stroke="#CBCBCB" stroke-width="3"/>
<path d="M59.5145 62.9498C65.9658 62.9498 72.2826 62.5664 77.4604 61.8C81.5668 61.179 82.853 58.1484 81.4581 53.8231L73.7457 29.5215C72.3476 25.0713 68.7697 23 63.8316 23H55.1684C50.2303 23 46.6523 25.0713 45.2544 29.5215L37.5419 53.8231C36.1469 58.1484 37.4332 61.179 41.5397 61.8C46.7174 62.5664 53.0395 62.9498 59.5145 62.9498ZM56.8245 60.9007V93.2057H62.1859V60.9007H56.8245ZM46.3261 95.2486H72.6739C73.9328 95.2486 74.7905 94.3463 74.7905 93.0343C74.7905 90.6048 72.8101 88.9172 69.8281 88.9172H49.172C46.19 88.9172 44.2385 90.6048 44.2385 93.0343C44.2385 94.3463 45.0671 95.2486 46.3261 95.2486Z" fill="#FF2171"/>
</svg>

After

Width:  |  Height:  |  Size: 809 B

4
public/logo.svg Normal file
View File

@@ -0,0 +1,4 @@
<svg width="118" height="118" viewBox="0 0 118 118" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="59" cy="59" r="57.5" stroke="#FF2171" stroke-width="3"/>
<path d="M59.5118 62.9828C65.9124 62.9828 72.2055 62.6018 77.3811 61.8344C81.5487 61.2062 82.8644 58.1137 81.4561 53.7443L73.7855 29.5747C72.3743 25.1017 68.7741 23 63.8107 23H55.1892C50.2259 23 46.6257 25.1017 45.2145 29.5747L37.5439 53.7443C36.1356 58.1137 37.4513 61.2062 41.6189 61.8344C46.7945 62.6018 53.0875 62.9828 59.5118 62.9828ZM59.5118 58.0762C54.1754 58.0762 49.0021 57.7818 43.5558 57.1664C42.1608 57.0191 41.9873 56.1769 42.4268 54.7242L49.6984 31.4809C50.4479 29.0507 52.4363 27.8778 55.1892 27.8778H63.8107C66.5636 27.8778 68.552 29.0507 69.3253 31.4809L76.5732 54.7242C77.0127 56.1769 76.8392 57.0191 75.4443 57.1664C69.9977 57.7818 64.8246 58.0762 59.5118 58.0762ZM56.8364 60.82V92.9501H62.1687V60.82H56.8364ZM46.3949 94.9817H72.605C73.8519 94.9817 74.705 94.0842 74.705 92.7793C74.705 90.363 72.7351 88.6848 69.7747 88.6848H49.2253C46.2648 88.6848 44.3238 90.363 44.3238 92.7793C44.3238 94.0842 45.148 94.9817 46.3949 94.9817Z" fill="#FF2171"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -2,6 +2,7 @@
<ContextMenu />
<Modal />
<Notification />
<div id="drag-img" class="ellip2" style=""></div>
<section
id="app-grid"
:class="{
@@ -18,6 +19,7 @@
</div>
<RightSideBar v-if="settings.use_sidebar && xl" />
<BottomBar />
<!-- <BubbleManager /> -->
</section>
</template>
@@ -45,12 +47,13 @@ import Modal from "@/components/modal.vue";
import Notification from "@/components/Notification.vue";
// @app-grid-components
import BottomBar from "@/components/BottomBar.vue";
import BottomBar from "@/components/BottomBar/BottomBar.vue";
import NavBar from "@/components/nav/NavBar.vue";
import RightSideBar from "@/components/RightSideBar/Main.vue";
import LeftSidebar from "./components/LeftSidebar/index.vue";
import { baseApiUrl } from "./config";
import { getRootDirs } from "./composables/fetch/settings/rootdirs";
// import BubbleManager from "./components/bubbles/BinManager.vue";
const queue = useQStore();
const router = useRouter();
@@ -65,10 +68,6 @@ router.afterEach(() => {
});
onStartTyping((e) => {
if (e.ctrlKey) {
console.log("ctrl pressed");
}
const elem = document.getElementById("globalsearch") as HTMLInputElement;
elem.focus();
elem.value = "";

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="M6.81055 21.7666H21.3916C23.0879 21.7666 24.0723 20.7822 24.0723 18.9014V9.85742C24.0723 7.97656 23.0791 6.99219 21.1807 6.99219H13.0332C12.4004 6.99219 12.0225 6.85156 11.5303 6.44727L11.0381 6.04297C10.4141 5.5332 9.95703 5.36621 9.03418 5.36621H6.54688C4.89453 5.36621 3.91895 6.33301 3.91895 8.1875V18.9014C3.91895 20.791 4.91211 21.7666 6.81055 21.7666ZM5.66797 8.33691C5.66797 7.53711 6.11621 7.11523 6.89844 7.11523H8.56836C9.19238 7.11523 9.56152 7.26465 10.0625 7.66895L10.5547 8.08203C11.1699 8.57422 11.6445 8.75 12.5674 8.75H21.0664C21.875 8.75 22.3232 9.17188 22.3232 10.0156V10.5342H5.66797V8.33691ZM6.9248 20.0176C6.11621 20.0176 5.66797 19.5957 5.66797 18.7432V12.0723H22.3232V18.752C22.3232 19.5957 21.875 20.0176 21.0664 20.0176H6.9248Z" fill="#F2F2F2"/>
<svg width="28" height="28" viewBox="0 0 28 28" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M6.81055 21.7666H21.3916C23.0879 21.7666 24.0723 20.7822 24.0723 18.9014V9.85742C24.0723 7.97656 23.0791 6.99219 21.1807 6.99219H13.0332C12.4004 6.99219 12.0225 6.85156 11.5303 6.44727L11.0381 6.04297C10.4141 5.5332 9.95703 5.36621 9.03418 5.36621H6.54688C4.89453 5.36621 3.91895 6.33301 3.91895 8.1875V18.9014C3.91895 20.791 4.91211 21.7666 6.81055 21.7666ZM5.66797 8.33691C5.66797 7.53711 6.11621 7.11523 6.89844 7.11523H8.56836C9.19238 7.11523 9.56152 7.26465 10.0625 7.66895L10.5547 8.08203C11.1699 8.57422 11.6445 8.75 12.5674 8.75H21.0664C21.875 8.75 22.3232 9.17188 22.3232 10.0156V10.5342H5.66797V8.33691ZM6.9248 20.0176C6.11621 20.0176 5.66797 19.5957 5.66797 18.7432V12.0723H22.3232V18.752C22.3232 19.5957 21.875 20.0176 21.0664 20.0176H6.9248Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 885 B

After

Width:  |  Height:  |  Size: 898 B

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 24 24" width="24px" height="24px">
<g id="surface88165820">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 5 3 C 3.90625 3 3 3.90625 3 5 L 3 9 C 3 10.09375 3.90625 11 5 11 L 9 11 C 10.09375 11 11 10.09375 11 9 L 11 5 C 11 3.90625 10.09375 3 9 3 Z M 15 3 C 13.90625 3 13 3.90625 13 5 L 13 9 C 13 10.09375 13.90625 11 15 11 L 19 11 C 20.09375 11 21 10.09375 21 9 L 21 5 C 21 3.90625 20.09375 3 19 3 Z M 5 5 L 9 5 L 9 9 L 5 9 Z M 15 5 L 19 5 L 19 9 L 15 9 Z M 5 13 C 3.90625 13 3 13.90625 3 15 L 3 19 C 3 20.09375 3.90625 21 5 21 L 9 21 C 10.09375 21 11 20.09375 11 19 L 11 15 C 11 13.90625 10.09375 13 9 13 Z M 15 13 C 13.90625 13 13 13.90625 13 15 L 13 19 C 13 20.09375 13.90625 21 15 21 L 19 21 C 20.09375 21 21 20.09375 21 19 L 21 15 C 21 13.90625 20.09375 13 19 13 Z M 5 15 L 9 15 L 9 19 L 5 19 Z M 15 15 L 19 15 L 19 19 L 15 19 Z M 15 15 "/>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:0.8;" d="M 5 3 C 3.90625 3 3 3.90625 3 5 L 3 9 C 3 10.09375 3.90625 11 5 11 L 9 11 C 10.09375 11 11 10.09375 11 9 L 11 5 C 11 3.90625 10.09375 3 9 3 Z M 15 3 C 13.90625 3 13 3.90625 13 5 L 13 9 C 13 10.09375 13.90625 11 15 11 L 19 11 C 20.09375 11 21 10.09375 21 9 L 21 5 C 21 3.90625 20.09375 3 19 3 Z M 5 5 L 9 5 L 9 9 L 5 9 Z M 15 5 L 19 5 L 19 9 L 15 9 Z M 5 13 C 3.90625 13 3 13.90625 3 15 L 3 19 C 3 20.09375 3.90625 21 5 21 L 9 21 C 10.09375 21 11 20.09375 11 19 L 11 15 C 11 13.90625 10.09375 13 9 13 Z M 15 13 C 13.90625 13 13 13.90625 13 15 L 13 19 C 13 20.09375 13.90625 21 15 21 L 19 21 C 20.09375 21 21 20.09375 21 19 L 21 15 C 21 13.90625 20.09375 13 19 13 Z M 5 15 L 9 15 L 9 19 L 5 19 Z M 15 15 L 19 15 L 19 19 L 15 19 Z M 15 15 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="375" viewBox="0 0 375 375" height="375" version="1.0"><defs><clipPath id="a"><path d="M 73.445312 66 L 301 66 L 301 308.839844 L 73.445312 308.839844 Z M 73.445312 66"/></clipPath></defs><path fill="#FFF" d="M 187.5 0 C 83.945312 0 0 83.945312 0 187.5 C 0 291.054688 83.945312 375 187.5 375 C 291.054688 375 375 291.054688 375 187.5 C 375 83.945312 291.054688 0 187.5 0 Z M 187.5 0"/><g clip-path="url(#a)"><path fill="#4AD168" d="M 88.226562 152.753906 C 78.492188 138.632812 73.621094 118.523438 73.621094 92.417969 L 73.621094 66.613281 L 143.804688 66.613281 L 276.328125 211.664062 C 283.808594 219.96875 288.796875 226.96875 291.289062 232.664062 C 293.785156 238.359375 295.625 243.578125 296.8125 248.324219 C 299.425781 257.816406 300.730469 269.503906 300.730469 283.386719 L 300.730469 308.835938 L 232.507812 308.835938 L 88.761719 152.753906 Z M 223.601562 142.609375 C 223.601562 109.980469 228.648438 88.742188 238.742188 78.894531 C 244.085938 73.671875 251.035156 70.351562 259.585938 68.925781 C 268.253906 67.382812 278.761719 66.613281 291.113281 66.613281 L 300.730469 66.613281 L 300.730469 94.734375 C 300.730469 120.003906 294.082031 136.914062 280.78125 145.457031 C 271.28125 151.625 255.308594 154.710938 232.867188 154.710938 L 223.601562 154.710938 Z M 73.621094 281.074219 C 73.621094 255.800781 80.273438 238.832031 93.570312 230.171875 C 103.070312 224.121094 119.042969 221.09375 141.488281 221.09375 L 150.75 221.09375 L 150.75 233.199219 C 150.75 266.183594 145.703125 287.363281 135.609375 296.734375 C 128.722656 303.140625 118.6875 306.9375 105.507812 308.125 C 98.855469 308.601562 91.433594 308.835938 83.242188 308.835938 L 73.621094 308.835938 Z M 73.621094 281.074219"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,3 +0,0 @@
<svg width="60" height="50" viewBox="0 0 60 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M57.6854 3.09975C56.5653 1.69314 54.4815 0 50.7566 0C46.146 0 40.2851 3.0737 35.1276 8.20522C34.3722 8.93457 33.6949 9.66392 33.0437 10.4193V5.20966C33.0437 4.38065 32.7144 3.58559 32.1282 2.99939C31.542 2.41319 30.7469 2.08386 29.9179 2.08386C29.0889 2.08386 28.2938 2.41319 27.7076 2.99939C27.1214 3.58559 26.7921 4.38065 26.7921 5.20966V10.4193C26.1409 9.66392 25.4636 8.93457 24.7082 8.20522C19.5507 3.0737 13.6898 0 9.07926 0C5.35435 0 3.27048 1.69314 2.15041 3.09975C-1.28797 7.50191 -0.0116033 15.082 1.88992 22.636C3.32258 28.4187 6.31814 31.1538 9.13135 32.4041C8.40766 34.0087 8.03468 35.7493 8.03733 37.5096C8.03992 40.0481 8.81518 42.5258 10.26 44.6131C11.7049 46.7004 13.7509 48.2984 16.1259 49.1947C18.501 50.091 21.0927 50.2432 23.5563 49.6308C26.0199 49.0185 28.2388 47.6708 29.9179 45.7669C31.8971 48.0002 34.6097 49.4521 37.5658 49.8601C40.5219 50.2681 43.5265 49.6054 46.0366 47.9918C48.5468 46.3781 50.3971 43.9198 51.2531 41.0611C52.1092 38.2024 51.9146 35.1318 50.7045 32.4041C53.5177 31.1538 56.5132 28.4187 57.9459 22.636C59.8474 15.082 61.1238 7.50191 57.6854 3.09975V3.09975ZM20.5405 43.7612C18.9172 43.8013 17.3419 43.2082 16.1482 42.1074C14.9544 41.0066 14.2358 39.4846 14.1444 37.8633C14.053 36.2421 14.596 34.6489 15.6585 33.4209C16.721 32.1929 18.2197 31.4266 19.8372 31.284C20.2452 31.2419 20.6408 31.1192 21.0011 30.9231C21.3613 30.727 21.6791 30.4613 21.9359 30.1415C22.1927 29.8217 22.3835 29.4541 22.4972 29.06C22.6109 28.6659 22.6453 28.2531 22.5983 27.8456C22.5066 27.0244 22.0934 26.2728 21.449 25.7554C20.8047 25.238 19.9817 24.9968 19.16 25.0845C17.12 25.2999 15.1671 26.0255 13.4814 27.1944C11.4497 27.0121 9.10531 25.7097 7.93313 21.1252C6.0316 13.4409 5.74507 8.67409 7.09959 6.9549C7.25588 6.74651 7.62055 6.25159 9.07926 6.25159C11.9446 6.25159 16.477 8.83038 20.3061 12.6334C24.1352 16.4365 26.7921 20.9949 26.7921 23.9644V37.5096C26.7853 39.1655 26.1244 40.7516 24.9535 41.9225C23.7826 43.0935 22.1964 43.7543 20.5405 43.7612ZM51.9027 21.1252C50.7305 25.7097 48.3862 27.0121 46.3544 27.1944C44.6687 26.0255 42.7158 25.2999 40.6759 25.0845C40.2538 25.0083 39.8206 25.0198 39.4032 25.1182C38.9858 25.2166 38.5932 25.3999 38.2496 25.6566C37.9061 25.9133 37.619 26.2379 37.4063 26.6103C37.1936 26.9827 37.0598 27.3948 37.0133 27.8212C36.9667 28.2475 37.0084 28.6788 37.1356 29.0883C37.2629 29.4978 37.4731 29.8768 37.7531 30.2016C38.0331 30.5264 38.377 30.7901 38.7633 30.9763C39.1496 31.1626 39.5701 31.2673 39.9986 31.284C41.1968 31.4197 42.3302 31.899 43.2622 32.6642C44.1942 33.4294 44.885 34.4478 45.2514 35.5966C45.6177 36.7455 45.644 37.9758 45.327 39.1393C45.01 40.3027 44.3633 41.3497 43.4648 42.154C42.5663 42.9582 41.4544 43.4855 40.2631 43.6722C39.0718 43.8588 37.8519 43.697 36.7504 43.2061C35.649 42.7152 34.713 41.9163 34.0553 40.9055C33.3977 39.8948 33.0463 38.7154 33.0437 37.5096V23.9644C33.0437 20.9949 35.5964 16.5407 39.5297 12.6334C43.463 8.72618 47.8912 6.25159 50.7566 6.25159C52.2153 6.25159 52.5799 6.74651 52.7362 6.9549C54.0907 8.67409 53.8042 13.4409 51.9027 21.1252Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,12 +1,12 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="28" height="28" viewBox="0 0 28 28" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_308_586)">
<path d="M23.1318 14.1377C25.5928 14.1377 27.6582 12.0811 27.6582 9.61133C27.6582 7.12402 25.6104 5.08496 23.1318 5.08496C20.6533 5.08496 18.6055 7.13281 18.6055 9.61133C18.6055 12.0986 20.6533 14.1377 23.1318 14.1377ZM6.81055 21.7666H21.3916C23.0879 21.7666 24.0723 20.7822 24.0723 18.9014V15.3066C23.5977 15.4121 22.8857 15.4209 22.3232 15.3066V18.752C22.3232 19.5957 21.875 20.0176 21.0664 20.0176H6.9248C6.11621 20.0176 5.66797 19.5957 5.66797 18.7432V12.0723H17.9551C17.709 11.5977 17.5244 11.0791 17.4365 10.5342H5.66797V8.33691C5.66797 7.53711 6.11621 7.11523 6.89844 7.11523H8.56836C9.19238 7.11523 9.56152 7.26465 10.0625 7.66895L10.5547 8.08203C11.1699 8.57422 11.6445 8.75 12.5674 8.75H17.4453C17.5156 8.12598 17.6562 7.58105 17.9551 6.99219H13.0332C12.4004 6.99219 12.0225 6.85156 11.5303 6.44727L11.0381 6.04297C10.4141 5.5332 9.95703 5.36621 9.03418 5.36621H6.54688C4.89453 5.36621 3.91895 6.33301 3.91895 8.1875V18.9014C3.91895 20.791 4.91211 21.7666 6.81055 21.7666ZM20.8643 10.2178C20.5391 10.2178 20.2578 9.92773 20.2578 9.61133C20.2578 9.28613 20.5391 9.00488 20.8643 9.00488H25.3994C25.7334 9.00488 26.0059 9.28613 26.0059 9.61133C26.0059 9.92773 25.7334 10.2178 25.3994 10.2178H20.8643Z" fill="#fff"/>
<path d="M26.7314 6.32392C28.484 8.0765 28.4777 10.9912 26.7376 12.7314C24.9788 14.4902 22.0827 14.4902 20.3239 12.7314C18.5713 10.9788 18.5713 8.08271 20.3301 6.32392C22.0827 4.57135 24.9788 4.57135 26.7314 6.32392Z" fill="#fff"/>
<path d="M21.4986 10.6246C21.2438 10.8794 21.2438 11.2896 21.5048 11.5506C21.7721 11.8178 22.1885 11.8241 22.4433 11.5693L23.9783 10.0342L24.662 9.27599V9.95962L24.6247 11.0783C24.6185 11.2461 24.6806 11.4201 24.8111 11.5382C25.0535 11.7806 25.4326 11.7992 25.6688 11.5506C25.793 11.4139 25.849 11.2585 25.8366 11.0721L25.7371 8.01438C25.7309 7.77201 25.6812 7.62285 25.5569 7.49856C25.4388 7.38047 25.2834 7.33697 25.0535 7.33076L21.9834 7.21889C21.8031 7.21268 21.6478 7.26861 21.5173 7.39912C21.2749 7.6415 21.2749 8.02681 21.5297 8.25676C21.6478 8.37484 21.8218 8.43699 21.9834 8.43699L23.1083 8.40592L23.7919 8.39349L23.0337 9.08955L21.4986 10.6246Z" fill="black"/>
<path d="M23.1318 14.1377C25.5928 14.1377 27.6582 12.0811 27.6582 9.61133C27.6582 7.12402 25.6104 5.08496 23.1318 5.08496C20.6533 5.08496 18.6055 7.13281 18.6055 9.61133C18.6055 12.0986 20.6533 14.1377 23.1318 14.1377ZM6.81055 21.7666H21.3916C23.0879 21.7666 24.0723 20.7822 24.0723 18.9014V15.3066C23.5977 15.4121 22.8857 15.4209 22.3232 15.3066V18.752C22.3232 19.5957 21.875 20.0176 21.0664 20.0176H6.9248C6.11621 20.0176 5.66797 19.5957 5.66797 18.7432V12.0723H17.9551C17.709 11.5977 17.5244 11.0791 17.4365 10.5342H5.66797V8.33691C5.66797 7.53711 6.11621 7.11523 6.89844 7.11523H8.56836C9.19238 7.11523 9.56152 7.26465 10.0625 7.66895L10.5547 8.08203C11.1699 8.57422 11.6445 8.75 12.5674 8.75H17.4453C17.5156 8.12598 17.6562 7.58105 17.9551 6.99219H13.0332C12.4004 6.99219 12.0225 6.85156 11.5303 6.44727L11.0381 6.04297C10.4141 5.5332 9.95703 5.36621 9.03418 5.36621H6.54688C4.89453 5.36621 3.91895 6.33301 3.91895 8.1875V18.9014C3.91895 20.791 4.91211 21.7666 6.81055 21.7666ZM20.8643 10.2178C20.5391 10.2178 20.2578 9.92773 20.2578 9.61133C20.2578 9.28613 20.5391 9.00488 20.8643 9.00488H25.3994C25.7334 9.00488 26.0059 9.28613 26.0059 9.61133C26.0059 9.92773 25.7334 10.2178 25.3994 10.2178H20.8643Z" fill="currentColor"/>
<path d="M26.7314 6.32392C28.484 8.0765 28.4777 10.9912 26.7376 12.7314C24.9788 14.4902 22.0827 14.4902 20.3239 12.7314C18.5713 10.9788 18.5713 8.08271 20.3301 6.32392C22.0827 4.57135 24.9788 4.57135 26.7314 6.32392Z" fill="green"/>
<path d="M21.4986 10.6246C21.2438 10.8794 21.2438 11.2896 21.5048 11.5506C21.7721 11.8178 22.1885 11.8241 22.4433 11.5693L23.9783 10.0342L24.662 9.27599V9.95962L24.6247 11.0783C24.6185 11.2461 24.6806 11.4201 24.8111 11.5382C25.0535 11.7806 25.4326 11.7992 25.6688 11.5506C25.793 11.4139 25.849 11.2585 25.8366 11.0721L25.7371 8.01438C25.7309 7.77201 25.6812 7.62285 25.5569 7.49856C25.4388 7.38047 25.2834 7.33697 25.0535 7.33076L21.9834 7.21889C21.8031 7.21268 21.6478 7.26861 21.5173 7.39912C21.2749 7.6415 21.2749 8.02681 21.5297 8.25676C21.6478 8.37484 21.8218 8.43699 21.9834 8.43699L23.1083 8.40592L23.7919 8.39349L23.0337 9.08955L21.4986 10.6246Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_308_586">
<rect width="28" height="28" fill="white"/>
<rect width="28" height="28" fill="currentColor"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -3,11 +3,11 @@
.scrollable {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
padding: 0 1rem;
padding-bottom: 4rem;
overflow: auto;
max-height: 100%;
gap: 2rem $smaller;
gap: 2rem 1rem;
}
}

View File

@@ -81,6 +81,8 @@ button {
background-color: $gray4;
border: solid 1px $gray3;
font-weight: 700;
cursor: pointer;
svg {
transition: all 0.2s;
@@ -153,3 +155,14 @@ button {
border-color: $gray5 !important;
opacity: 1;
}
#drag-img {
width: max-content;
max-width: 15rem;
background-color: $darkblue;
padding: $smaller $small;
border-radius: $smaller;
opacity: 1;
position: absolute;
left: -20rem;
}

View File

@@ -13,7 +13,7 @@
html {
cursor: default !important;
overflow: hidden;
& > * {
overflow: visible !important;
}
@@ -41,8 +41,4 @@ body {
width: 100%;
height: 100%;
}
a {
cursor: default !important;
}
}

View File

@@ -4,7 +4,7 @@ input[type="range"] {
width: calc(100% - 2px);
height: 0.3rem;
border-radius: 5px;
background: $gray4 linear-gradient(90deg, $darkblue, $darkestblue) no-repeat;
background: $gray4 linear-gradient(37deg, $pink, $pink) no-repeat;
background-size: 100% 100%;
&::-webkit-slider-thumb {
@@ -13,7 +13,7 @@ input[type="range"] {
height: 0;
width: 0.8rem;
border-radius: 50%;
background: $darkestblue;
background: $pink;
}
&::-moz-range-thumb {
@@ -21,7 +21,7 @@ input[type="range"] {
height: 0;
border-radius: 50%;
background: $darkestblue;
background: $pink;
border: none;
}
@@ -31,7 +31,7 @@ input[type="range"] {
height: 0;
width: 0.8rem;
border-radius: 50%;
background: $darkestblue;
background: $pink;
border: none;
}
}

View File

@@ -53,7 +53,7 @@ defineProps<{
h3 {
display: grid;
grid-template-columns: 1fr max-content;
align-items: center;
align-items: baseline;
padding: 0 $medium;
margin-bottom: $small;
}

View File

@@ -1,17 +1,24 @@
<template>
<div
class="genres-banner"
:class="{
nocontrast: album.info.colors ? isLight(album.info.colors[0]) : false,
:style="{
color: album.info.colors ? getTextColor(album.info.colors[0]) : '',
}"
>
<!-- :class="{
nocontrast: album.info.colors ? isLight(album.info.colors[0]) : false,
}" -->
<div class="rounded pad-sm">
{{ album.info.genres.length ? "Genres" : "No genres" }}
</div>
<div
v-for="genre in album.info.genres"
class="rounded pad-sm"
:style="{ backgroundColor: album.info.colors[0] }"
:style="{
backgroundColor: album.info.colors
? getBackgroundColor(album.info.colors[0])
: '',
}"
>
{{ genre }}
</div>
@@ -23,6 +30,8 @@ import { onMounted } from "vue";
import useAlbumStore from "@/stores/pages/album";
import { isLight } from "@/composables/colors/album";
import { getTextColor, getBackgroundColor } from "@/utils/colortools/shift";
const album = useAlbumStore();
onMounted(async () => {
@@ -33,10 +42,6 @@ onMounted(async () => {
</script>
<style lang="scss">
.genres-banner.nocontrast {
color: $black;
}
.genres-banner {
display: flex;
gap: 1rem;
@@ -52,6 +57,7 @@ onMounted(async () => {
text-align: center;
outline: solid 1px $gray;
padding: $small 1rem;
font-weight: 700;
&:first-child {
background-color: white;
@@ -61,8 +67,8 @@ onMounted(async () => {
}
&:hover {
background-color: $darkestblue !important;
outline-color: $darkestblue;
background-color: $pink !important;
outline-color: $pink;
color: $white;
}
}

View File

@@ -3,14 +3,18 @@
class="album-header-ambient rounded"
style="height: 100%; width: 100%"
:style="{
boxShadow: album.colors ? `0 .5rem 2rem ${album.colors[0]}` : '',
boxShadow: album.colors[0]
? `0 .5rem 2rem ${album.colors[0]}`
: '0 .5rem 2rem black',
}"
></div>
<div
class="a-header rounded"
ref="albumheaderthing"
:style="{
backgroundColor: album.colors ? album.colors[0] : '',
backgroundColor: album.colors[0]
? getBackgroundColor(album.colors[0])
: '',
height: `${heightLarge ? '24rem' : '18rem'}`,
}"
>
@@ -22,8 +26,9 @@
</div>
<div
class="info"
:class="{ nocontrast: album.colors ? isLight(album.colors[0]) : false }"
:style="{ color: album.colors[0] ? getTextColor(album.colors[0]) : '' }"
>
<!-- :class="{ nocontrast: album.colors ? isLight(album.colors[0]) : false }" -->
<div class="album-info">
<div class="top">
<div v-auto-animate class="h">
@@ -58,7 +63,7 @@
</div>
<Motion
class="art"
v-if="!isMedium"
v-if="!isMedium && !isSmall"
:initial="{ opacity: 0, x: 10 }"
:animate="{
opacity: 1,
@@ -77,10 +82,14 @@
>
<img
:src="imguri.artist.small + a.image"
class="shadow-lg circular"
class="circular"
loading="lazy"
:title="a.name"
:style="{ border: `solid 2px ${album.colors[0]}` }"
:style="{
border: `solid 4px ${
album.colors[0] ? getBackgroundColor(album.colors[0]) : ''
}`,
}"
/>
</RouterLink>
</Motion>
@@ -89,9 +98,9 @@
</template>
<script setup lang="ts">
import { ref } from "vue";
import { storeToRefs } from "pinia";
import { Routes } from "@/router";
import { storeToRefs } from "pinia";
import { ref } from "vue";
import { paths } from "@/config";
import useNavStore from "@/stores/nav";
@@ -100,20 +109,18 @@ import {
albumHeaderSmall,
heightLarge,
isMedium,
isSmall,
} from "@/stores/content-width";
import { isLight } from "@/composables/colors/album";
import { formatSeconds, useVisibility } from "@/utils";
import { favType, playSources } from "@/composables/enums";
import { getBackgroundColor, getTextColor } from "@/utils/colortools/shift";
import { favType, playSources } from "@/composables/enums";
import { formatSeconds, useVisibility } from "@/utils";
import ArtistName from "@/components/shared/ArtistName.vue";
import favoriteHandler from "@/composables/favoriteHandler";
import HeartSvg from "../shared/HeartSvg.vue";
import PlayBtnRect from "../shared/PlayBtnRect.vue";
import favoriteHandler from "@/composables/favoriteHandler";
import ArtistName from "@/components/shared/ArtistName.vue";
// const props = defineProps<{
// album: Album;
// }>();
const albumheaderthing = ref<any>(null);
const imguri = paths.images;
@@ -199,6 +206,12 @@ function handleFav() {
.nocontrast {
color: $black;
.top {
.h {
color: $pink;
}
}
}
.info {
@@ -214,9 +227,14 @@ function handleFav() {
max-width: 10rem;
flex-wrap: wrap;
.shadow-inset {
height: max-content;
}
img {
height: 3rem;
background-color: $gray;
margin-left: -1.5rem;
}
a {
@@ -225,7 +243,10 @@ function handleFav() {
a:hover {
img {
border: solid 2px white !important;
z-index: 100;
border-color: $pink !important;
// margin-right: 1.5rem;
// border: solid 2px white !important;
}
}
}
@@ -240,12 +261,12 @@ function handleFav() {
.top {
.h {
font-size: 14px;
opacity: 0.5;
font-weight: 700;
}
.title {
font-size: 2.5rem;
font-weight: 600;
font-size: 2.75rem;
font-weight: 700;
width: fit-content;
cursor: text;
}
@@ -280,8 +301,6 @@ function handleFav() {
font-size: 0.8rem;
display: flex;
flex-wrap: wrap;
// width: fit-content;
// cursor: text;
}
}
}

View File

@@ -16,10 +16,15 @@
>
<div
class="artist-info"
:class="{
nocontrast: artist.info.colors ? isLight(artist.info.colors[0]) : false,
:style="{
color: artist.info.colors[0]
? getTextColor(artist.info.colors[0])
: undefined,
}"
>
<!-- :class="{
nocontrast: artist.info.colors ? isLight(artist.info.colors[0]) : false,
}" -->
<section class="text">
<div class="card-title">Artist</div>
<div class="artist-name ellip2">{{ artist.info.name }}</div>
@@ -67,12 +72,13 @@ import formatSeconds from "@/utils/useFormatSeconds";
import { isLight } from "@/composables/colors/album";
import { favType, playSources } from "@/composables/enums";
import favoriteHandler from "@/composables/favoriteHandler";
import { heightLarge } from "@/stores/content-width";
import { getTextColor } from "@/utils/colortools/shift";
import PlayBtnRect from "../shared/PlayBtnRect.vue";
import HeartSvg from "@/components/shared/HeartSvg.vue";
import { heightLarge } from "@/stores/content-width";
const artist = useArtistPageStore();
function handleFav() {
@@ -96,11 +102,11 @@ function handleFav() {
.artist-page-header {
display: grid;
grid-template-columns: 50% 50%;
grid-template-columns: 1fr minmax(min-content, 50%);
position: relative;
.artist-img {
width: 100%;
// width: 100%;
img {
height: 100%;
@@ -113,12 +119,7 @@ function handleFav() {
.gradient {
position: absolute;
background-image: linear-gradient(
to left,
transparent 10%,
$gray 50%,
$gray 100%
);
background-image: linear-gradient(to left, transparent 10%, $gray 50%, $gray 100%);
height: 100%;
width: 100%;
}
@@ -141,18 +142,19 @@ function handleFav() {
}
.card-title {
opacity: 0.5;
font-size: small;
font-weight: 700;
}
.artist-name {
font-size: 3rem;
font-size: 3.5rem;
font-weight: bold;
word-wrap: break-word;
}
.stats {
font-size: small;
font-weight: 700;
}
}
@@ -165,9 +167,9 @@ function handleFav() {
gap: $small;
.heart-button {
background-color: pink !important;
border-color: pink;
}
background-color: pink !important;
border-color: pink;
}
}
}
</style>

View File

@@ -10,6 +10,7 @@
:track="song"
:index="index + 1"
@playThis="playHandler(index)"
:source="source"
/>
</div>
<div class="error" v-if="!tracks.length">No tracks</div>
@@ -21,12 +22,14 @@ import SongItem from "../shared/SongItem.vue";
import { Track } from "@/interfaces";
import { isMedium, isSmall } from "@/stores/content-width";
import SeeAll from "../shared/SeeAll.vue";
import { dropSources } from "@/composables/enums";
defineProps<{
tracks: Track[];
route: string;
title: string;
playHandler: (index: number) => void;
source: dropSources;
}>();
</script>
@@ -36,6 +39,7 @@ defineProps<{
.section-title {
margin-left: 0;
align-items: baseline;
}
.error {

View File

@@ -1,40 +1,6 @@
<template>
<div class="b-bar">
<div class="left-group" v-auto-animate>
<HeartSvg
v-if="settings.use_np_img"
:state="queue.currenttrack?.is_favorite"
@handleFav="handleFav"
/>
<RouterLink
v-else
title="go to album"
:to="{
name: Routes.album,
params: {
hash: queue.currenttrack?.albumhash || ' ',
},
}"
>
<img
class="rounded-sm"
:src="paths.images.thumb.small + queue.currenttrack?.image"
alt=""
/>
</RouterLink>
<div class="track-info">
<ArtistName
:artists="queue.currenttrack?.artist || []"
:albumartists="
queue.currenttrack?.albumartist || 'Welcome to Swing Music'
"
class="artist"
/>
<div v-tooltip class="title ellip">
{{ queue.currenttrack?.title || "Hello there" }}
</div>
</div>
</div>
<LeftGroup @handleFav="handleFav" />
<div class="center">
<div class="with-time">
<div class="time time-current">
@@ -84,8 +50,6 @@
</template>
<script setup lang="ts">
import { Routes } from "@/router";
import { paths } from "@/config";
import { formatSeconds } from "@/utils";
import { favType } from "@/composables/enums";
import favoriteHandler from "@/composables/favoriteHandler";
@@ -95,11 +59,11 @@ import useSettingsStore from "@/stores/settings";
import HotKeys from "@/components/LeftSidebar/NP/HotKeys.vue";
import Progress from "@/components/LeftSidebar/NP/Progress.vue";
import ArtistName from "@/components/shared/ArtistName.vue";
import HeartSvg from "./shared/HeartSvg.vue";
import HeartSvg from "../shared/HeartSvg.vue";
import RepeatAllSvg from "@/assets/icons/repeat.svg";
import RepeatOneSvg from "@/assets/icons/repeat-one.svg";
import LeftGroup from "./Left.vue";
const queue = useQStore();
const settings = useSettingsStore();
@@ -137,44 +101,6 @@ function handleFav() {
gap: 1rem;
.left-group {
display: grid;
padding-left: 1rem;
grid-template-columns: max-content 1fr;
gap: $small;
align-items: center;
font-size: small;
a {
font-size: small;
}
img {
height: 3rem;
}
button {
height: 3rem;
width: 3rem;
border: solid 1px $gray4;
padding: 0;
}
.track-info {
.artistname {
font-size: 0.9rem;
opacity: 0.75;
font-weight: bold;
margin-bottom: 2px;
}
.title {
color: $white;
font-weight: bold;
}
}
}
&:hover {
::-moz-range-thumb {
height: 0.8rem;
@@ -208,7 +134,7 @@ function handleFav() {
width: 30rem;
@media (max-width: 833px) {
@media (max-width: 1080px) {
width: 20rem !important;
}

View File

@@ -0,0 +1,108 @@
<template>
<div class="left-group" v-auto-animate>
<HeartSvg
v-if="settings.use_np_img"
:state="queue.currenttrack?.is_favorite"
@handleFav="emit('handleFav')"
/>
<RouterLink
v-else
title="go to album"
:to="{
name: Routes.album,
params: {
hash: queue.currenttrack?.albumhash || ' ',
},
}"
>
<img
class="rounded-sm"
:src="paths.images.thumb.small + queue.currenttrack?.image"
alt=""
/>
</RouterLink>
<div
class="track-info"
:style="{
color: getShift(colors.theme1, [0, -170]),
}"
>
<ArtistName
:artists="queue.currenttrack?.artist || []"
:albumartists="
queue.currenttrack?.albumartist || 'Welcome to Swing Music'
"
class="artist"
/>
<div v-tooltip class="title">
<span class="ellip">
{{ queue.currenttrack?.title || "Hello there" }}
</span>
<MasterFlag :bitrate="queue.currenttrack?.bitrate || 0" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Routes } from "@/router";
import { paths } from "@/config";
import ArtistName from "@/components/shared/ArtistName.vue";
import { getShift } from "@/utils/colortools/shift";
import useColorStore from "@/stores/colors";
import useSettingsStore from "@/stores/settings";
import useQStore from "@/stores/queue";
import HeartSvg from "../shared/HeartSvg.vue";
import MasterFlag from "../shared/MasterFlag.vue";
const queue = useQStore();
const settings = useSettingsStore();
const colors = useColorStore();
const emit = defineEmits<{
(e: "handleFav"): void;
}>();
</script>
<style lang="scss">
.left-group {
display: grid;
padding-left: 1rem;
grid-template-columns: max-content 1fr;
gap: $small;
align-items: center;
font-size: small;
a {
font-size: small;
}
img {
height: 3rem;
}
.heart-button {
height: 3rem;
width: 3rem;
border: solid 1px $gray4;
padding: 0;
}
.track-info {
.artistname {
font-size: 0.9rem;
opacity: 0.75;
font-weight: bold;
margin-bottom: 2px;
}
.title {
color: $white;
font-weight: bold;
display: flex;
align-items: center;
}
}
}
</style>

View File

@@ -43,7 +43,6 @@ context.$subscribe((mutation, state) => {
watcher = onClickOutside(
contextMenuRef,
(e) => {
e.stopImmediatePropagation();
context.hideContextMenu();
},
{
@@ -57,8 +56,6 @@ context.$subscribe((mutation, state) => {
if (watcher !== null) {
watcher();
}
// wat();
});
</script>
@@ -72,7 +69,7 @@ context.$subscribe((mutation, state) => {
transform: scale(0);
height: min-content;
padding: $small 0;
padding: $medium;
background: $context;
transform-origin: top left;
font-size: 0.875rem;

View File

@@ -121,8 +121,9 @@ function runChildAction(action: () => void) {
width: 100%;
display: flex;
align-items: center;
padding: 0.4rem 1rem;
padding: 0.4rem;
position: relative;
border-radius: $small;
.more {
height: 1.5rem;
@@ -134,14 +135,16 @@ function runChildAction(action: () => void) {
.children {
position: absolute;
width: 13rem;
width: 12rem;
background-color: $context;
transform: scale(0);
padding: $small 0;
padding: $medium;
border: solid 1px $gray;
.context-item {
padding: $small 1rem;
padding: 0.4rem;
}
.separator {
@@ -171,6 +174,7 @@ function runChildAction(action: () => void) {
.folder {
background-image: url("../../assets/icons/folder.svg");
filter: invert(100%);
}
.artist {

View File

@@ -43,6 +43,7 @@ onUpdated(() => {
.path {
white-space: nowrap;
margin: auto 0;
cursor: pointer;
.text {
padding: $smaller;

View File

@@ -8,6 +8,7 @@
:style="{
backgroundColor: is_checked ? '#234ece' : '',
}"
v-auto-animate
>
<div class="check" v-if="!folder_page">
<CheckSvg v-if="!is_checked && mouse_over" />
@@ -18,6 +19,9 @@
<SymLinkSvg v-if="folder.is_sym" />
<div class="info">
<div class="f-item-text ellip">{{ folder.name }}</div>
<div class="f-count" v-if="folder.count">
{{ folder.count + ` File${folder.count == 1 ? "" : "s"}` }}
</div>
</div>
</div>
</router-link>
@@ -69,10 +73,16 @@ function handleClick(e: MouseEvent) {
grid-template-columns: max-content 1fr;
align-items: center;
background-color: $gray;
transition: all 0.2s ease;
border-radius: $medium;
position: relative;
.f-count {
font-size: $medium;
font-weight: 700;
color: $gray1;
margin-top: $smaller;
}
.check {
z-index: 10;
position: absolute;
@@ -91,6 +101,7 @@ function handleClick(e: MouseEvent) {
svg {
margin: 0 $small 0 1rem;
color: $gray1;
}
.f-item-text {

View File

@@ -1,5 +1,8 @@
<template>
<div class="f-container rounded">
<div
class="f-container rounded-sm"
:class="{ 'list-mode': settings.folder_list_mode }"
>
<div id="f-items" class="rounded">
<FolderItem
v-for="folder in folders"
@@ -14,34 +17,55 @@
<script setup lang="ts">
import { Folder } from "@/interfaces";
import FolderItem from "./FolderItem.vue";
import useSettingsStore from "@/stores/settings";
defineProps<{
folders: Folder[];
}>();
const settings = useSettingsStore();
</script>
<style lang="scss">
.f-container {
padding-bottom: 1.25rem;
}
#f-items {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
gap: 1.5rem;
padding-bottom: 1.25rem;
}
#f-items.list-mode {
.f-item:hover {
// cursor: pointer;
background-color: $gray5;
}
.f-container.list-mode > #f-items {
grid-template-columns: 1fr;
gap: 0;
// border: solid 1px $gray;
// padding: 1rem;
.f-item {
transition: none;
height: 3.25rem;
border-radius: $small;
background-color: transparent;
}
.f-item:hover {
background-color: $gray !important;
.f-count {
position: absolute;
right: 1.25rem;
bottom: 50%;
translate: 0 50%;
}
&:hover {
background-color: $gray !important;
}
}
}
</style>
<!-- TODO: ADD BUTTON TO TOGGLE LIST MODE -->
<!-- TODO: ADD BUTTON TO TOGGLE LIST MODE -->

View File

@@ -1,5 +1,8 @@
<template>
<div class="hotkeys rounded-sm no-scroll">
<div
class="hotkeys rounded-sm no-scroll"
>
<button @click.prevent="q.playPrev">
<PrevSvg />
</button>
@@ -24,6 +27,7 @@ import PauseSvg from "../../../assets/icons/pause.svg";
import PlaySvg from "../../../assets/icons/play.svg";
const q = useQStore();
</script>
<style lang="scss">

View File

@@ -9,7 +9,7 @@
}"
:exit="{ opacity: 0, scale: 0.9 }"
>
<div class="sidebar-songcard">
<div class="sidebar-songcard rounded-sm" v-wave>
<router-link
:to="{
name: 'AlbumView',
@@ -21,7 +21,7 @@
<img
:src="imguri + q.currenttrack?.image"
alt=""
class="l-image rounded force-lm"
class="l-image rounded-sm force-lm"
/>
</router-link>
<div
@@ -48,6 +48,8 @@ const q = useQueueStore();
.sidebar-songcard {
width: 100%;
position: relative;
width: 13rem;
height: 13rem;
img {
cursor: pointer;

View File

@@ -9,21 +9,23 @@
query: menu.query && menu.query(),
}"
>
<div
v-if="menu.separator"
:class="{
separator: menu.separator,
}"
></div>
<div
class="nav-button"
:class="{ active: $route.name === menu.route_name }"
id="home-button"
v-else
>
<div class="in">
<component :is="menu.icon"></component>
<span>{{ menu.name }}</span>
<div v-wave class="rounded-sm">
<div
v-if="menu.separator"
:class="{
separator: menu.separator,
}"
></div>
<div
class="nav-button"
:class="{ active: $route.name === menu.route_name }"
id="home-button"
v-else
>
<div class="in">
<component :is="menu.icon"></component>
<span>{{ menu.name }}</span>
</div>
</div>
</div>
</router-link>
@@ -48,7 +50,7 @@ const menus = [
},
{
name: "folders",
route_name: "FolderView",
route_name: Routes.folder,
params: { path: "$home" },
icon: FolderSvg,
},
@@ -90,23 +92,35 @@ const menus = [
margin-top: 1rem;
.nav-button {
border-radius: $medium;
border-radius: $small;
display: flex;
align-items: flex-start;
justify-content: flex-start;
padding: $small 0;
position: relative;
font-size: 14px;
font-weight: 700;
&.active::before {
content: "";
content: " ";
position: absolute;
left: -$small;
top: $medium;
top: 50%;
transform: translateY(-50%);
opacity: 0.75;
height: 40%;
width: 4px;
background-color: $pink;
border-radius: 1rem;
}
&:hover {
background-color: $darkestblue;
&::before {
background-color: $white;
}
}
.in {

View File

@@ -1,24 +1,42 @@
<template>
<div id="logo-container"
>
<router-link :to="{ name: 'Home' }">
<div id="logo"></div
></router-link>
</div>
<router-link class="swing-logo" :to="{ name: 'Home' }">
<div class="link">
<img src="/logo-fill.svg" alt="" />
<div>Swing <br />Music</div>
</div>
</router-link>
</template>
<style lang="scss">
@import "../assets/scss/mixins.scss";
.swing-logo {
background-color: $gray4;
padding: $medium;
display: flex;
align-items: center;
justify-content: space-between;
gap: $small;
cursor: pointer !important;
line-height: 1rem;
border-radius: 12px;
#logo-container {
overflow: hidden;
}
svg {
scale: 1.5;
}
#logo {
height: 4.5rem !important;
background-image: url(./../assets/images/logo.webp);
background-size: contain;
@include ximage;
border-radius: $medium;
.link {
display: flex;
align-items: center;
gap: $small;
}
span {
font-size: 1rem;
font-weight: 600;
color: $white;
}
img {
height: 2.25rem;
}
}
</style>

View File

@@ -36,7 +36,7 @@ defineProps<{
display: flex;
justify-content: space-between;
padding-left: $medium;
align-items: center;
align-items: baseline;
margin-bottom: $small;
}

View File

@@ -1,76 +1,54 @@
<template>
<div
class="p-header image rounded no-scroll"
ref="playlistheader"
:style="[
{
backgroundImage: !(info.image as string).endsWith('None') ? `url(${imguri + info.image})` : undefined,
background: bg,
backgroundPosition: `center ${bannerPos}%`,
height: `${heightLarge ? '24rem' : '18rem'}`,
},
]"
:class="{ border: (info.image as string).endsWith('None') }"
:class="{ border: !info.images.length }"
>
<div class="gradient" v-if="!(info.image as string).endsWith('None')"></div>
<div class="carddd">
<div class="info">
<div class="btns">
<PlayBtnRect :source="playSources.playlist" :store="usePStore" />
</div>
<div class="duration">
{{ info.count + ` ${info.count == 1 ? "Track" : "Tracks"}` }}
{{ formatSeconds(info.duration, true) }}
</div>
<div class="title ellip">{{ info.name }}</div>
<div class="type">Playlist</div>
</div>
</div>
<div class="last-updated" :class="{ lightbg: !info.image }">
<span class="status"
>Last updated {{ info.last_updated }} &#160;|&#160;&#160;</span
>
<div class="edit" @click="editPlaylist">Edit&#160;&#160;</div>
|
<DeleteSvg class="edit" @click="deletePlaylist" />
</div>
<BannerImages />
<Info />
<LastUpdated />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { onMounted, ref } from "vue";
import { storeToRefs } from "pinia";
import useNavStore from "@/stores/nav";
import useModalStore from "@/stores/modal";
import usePStore from "@/stores/pages/playlist";
import { heightLarge } from "@/stores/content-width";
import { paths } from "@/config";
import { playSources } from "@/composables/enums";
import { formatSeconds, useVisibility } from "@/utils";
import BannerImages from "./Header/BannerImages.vue";
import PlayBtnRect from "../shared/PlayBtnRect.vue";
import DeleteSvg from "@/assets/icons/delete.svg";
import Info from "./Header/Info.vue";
import { getBackgroundColor } from "@/utils/colortools/shift";
import LastUpdated from "./Header/LastUpdated.vue";
const modal = useModalStore();
const nav = useNavStore();
const playlist = usePStore();
const imguri = paths.images.playlist;
const playlistheader = ref<HTMLElement | null>(null);
const { info, bannerPos } = storeToRefs(playlist);
const bg = ref("");
useVisibility(playlistheader, nav.toggleShowPlay);
function getBg() {
if (playlist.info.has_image) {
return `url(${imguri + info.value.image})`;
}
function editPlaylist() {
modal.showEditPlaylistModal(info.value);
if (info.value.images.length > 2) {
return getBackgroundColor(info.value.images[2].color);
}
}
function deletePlaylist() {
modal.showDeletePlaylistModal(parseInt(playlist.info.id));
}
onMounted(() => (bg.value = getBg()));
</script>
<style lang="scss">
@@ -91,6 +69,24 @@ function deletePlaylist() {
opacity: 0.5;
}
.playlist-banner-images {
width: 21rem;
position: absolute;
right: 0;
top: -10rem;
rotate: -40deg;
display: flex;
flex-wrap: wrap;
gap: $medium;
transition: all 0.2s ease-in-out;
img {
height: 9rem;
transition: all 0.2s ease-in-out;
}
}
.last-updated {
position: absolute;
bottom: 1rem;
@@ -135,8 +131,9 @@ function deletePlaylist() {
.type {
font-size: small;
font-weight: bold;
color: rgba(255, 255, 255, 0.692);
font-weight: 700;
// color: rgb(218, 218, 218);
opacity: 0.85;
}
.title {
@@ -145,13 +142,22 @@ function deletePlaylist() {
cursor: text;
}
.info.is_light {
color: $gray5;
.type {
color: $pink;
// opacity: 0.5;
}
}
.duration {
font-size: 0.8rem;
color: $white;
padding: $smaller;
padding-left: 0;
font-weight: 900;
cursor: text;
opacity: 0.85;
}
.btns {

View File

@@ -0,0 +1,26 @@
<template>
<div class="playlist-banner-images" v-if="!playlist.info.has_image">
<img
v-for="(img, index) in playlist.info.images"
:key="index"
:src="paths.images.thumb.large + img.image"
alt=""
class="rounded-sm"
:style="{
boxShadow: `0 0 1rem ${
!playlist.info.has_image && playlist.info.images.length > 2
? getShift(playlist.info.images[2].color, [40, 60])
: ''
}`,
}"
/>
</div>
</template>
<script setup lang="ts">
import usePStore from "@/stores/pages/playlist";
import { getShift } from "@/utils/colortools/shift";
import { paths } from "@/config";
const playlist = usePStore();
</script>

View File

@@ -0,0 +1,37 @@
<template>
<div class="carddd">
<div
class="info"
:style="{
color:
!playlist.info.has_image && playlist.info.images.length > 2
? getTextColor(playlist.info.images[2].color)
: '',
}"
>
<div class="btns">
<PlayBtnRect :source="playSources.playlist" :store="usePStore" />
</div>
<div class="duration">
{{
playlist.info.count +
` ${playlist.info.count == 1 ? "Track" : "Tracks"}`
}}
{{ formatSeconds(playlist.info.duration, true) }}
</div>
<div class="title ellip">{{ playlist.info.name }}</div>
<div class="type">Playlist</div>
</div>
</div>
</template>
<script setup lang="ts">
import { formatSeconds, useVisibility } from "@/utils";
import { playSources } from "@/composables/enums";
import { getTextColor } from "@/utils/colortools/shift";
import PlayBtnRect from "@/components/shared/PlayBtnRect.vue";
import usePStore from "@/stores/pages/playlist";
const playlist = usePStore();
</script>

View File

@@ -0,0 +1,27 @@
<template>
<div class="last-updated" :class="{ lightbg: !playlist.info.image }">
<span class="status"
>Last updated {{ playlist.info.last_updated }} &#160;|&#160;&#160;</span
>
<div class="edit" @click="editPlaylist">Edit&#160;&#160;</div>
|
<DeleteSvg class="edit" @click="deletePlaylist" />
</div>
</template>
<script setup lang="ts">
import DeleteSvg from "@/assets/icons/delete.svg";
import usePStore from "@/stores/pages/playlist";
import useModalStore from "@/stores/modal";
const playlist = usePStore();
const modal = useModalStore();
function editPlaylist() {
modal.showEditPlaylistModal(playlist.info);
}
function deletePlaylist() {
modal.showDeletePlaylistModal(parseInt(playlist.info.id));
}
</script>

View File

@@ -3,9 +3,19 @@
:to="{ name: 'PlaylistView', params: { pid: playlist.id } }"
class="p-card rounded no-scroll"
>
<div
class="image-grid rounded-sm no-scroll"
v-if="!playlist.has_image && playlist.images.length"
>
<img
v-for="img in playlist.images"
:src="paths.images.thumb.large + img"
/>
</div>
<img
v-else
:src="imguri + playlist.thumb"
class="rounded"
class="rounded-sm"
:class="{ border: !playlist.thumb }"
/>
<div class="overlay rounded">
@@ -37,6 +47,11 @@ const props = defineProps<{
gap: $small;
border: solid 1px $gray5;
.image-grid {
display: grid;
grid: repeat(2, 1fr) / repeat(2, 1fr);
}
&:hover {
transition: all 0.25s ease;
background-color: $gray3;

View File

@@ -45,6 +45,10 @@ import QueueSvg from "@/assets/icons/queue.svg";
const itemHeight = 64;
const queue = useQStore();
const mouseover = ref(false);
const items = ref([
{ id: "1", text: "abc" },
{ id: "2", text: "def" },
]);
const scrollerItems = computed(() => {
return queue.tracklist.map((track) => ({

View File

@@ -1,15 +1,21 @@
<template>
<div class="queue-actions">
<div class="left">
<button class="clear-queue action" @click="queue.clearQueue">
<ClearSvg />
<span>Clear</span>
</button>
<button class="shuffle-queue action" @click="queue.shuffleQueue">
<button v-wave class="shuffle-queue action" @click="queue.shuffleQueue">
<ShuffleSvg />
<span>Shuffle</span>
</button>
</div>
<div class="right">
<button
v-wave
class="go-to-source action"
href="#"
@click="queue.clearQueue"
>
<ClearSvg />
</button>
</div>
</div>
</template>
@@ -36,11 +42,17 @@ const queue = useQueueStore();
}
.action {
padding-left: $smaller;
padding: 0 $medium;
svg {
transform: scale(0.8);
}
}
.right {
.go-to-source {
padding: 0 $smaller;
}
}
}
</style>

View File

@@ -6,7 +6,7 @@
:currentTab="currentTab"
:tabContent="true"
>
<Tab :name="currentTab" :isOnSearchPage="isOnSearchPage" />
<Tab :name="currentTab" />
</TabsWrapper>
</div>
</template>
@@ -19,9 +19,6 @@ import Tab from "./Tab.vue";
import TabsWrapper from "./TabsWrapper.vue";
const search = useSearchStore();
defineProps<{
isOnSearchPage?: boolean;
}>();
const tabs = ["tracks", "albums", "artists"];

View File

@@ -8,7 +8,6 @@ import TracksGrid from "./TracksGrid.vue";
const props = defineProps<{
name: string;
isOnSearchPage?: boolean;
}>();
function getComponent() {
@@ -16,9 +15,6 @@ function getComponent() {
case "tracks":
return {
component: TracksGrid,
props: {
isOnSearchPage: props.isOnSearchPage,
},
};
case "albums":
return {

View File

@@ -1,7 +1,7 @@
<template>
<div id="tracks-results">
<div v-if="search.tracks.value.length">
<TrackComponent
<TrackItem
v-for="(track, index) in search.tracks.value"
:key="track.id"
:isCurrent="queue.currenttrackhash === track.trackhash"
@@ -24,9 +24,8 @@
</template>
<script setup lang="ts">
import { computed, onMounted } from "vue";
import { onMounted } from "vue";
import SongItem from "@/components/shared/SongItem.vue";
import TrackItem from "@/components/shared/TrackItem.vue";
import useQStore from "@/stores/queue";
import useSearchStore from "@/stores/search";
@@ -40,24 +39,6 @@ function updateQueue(index: number) {
queue.play(index);
}
const props = defineProps<{
isOnSearchPage?: boolean;
}>();
const TrackComponent = computed(() => {
if (props.isOnSearchPage) {
return SongItem;
}
return TrackItem;
});
let use_song_item: boolean = false;
if (props.isOnSearchPage) {
use_song_item = true;
}
onMounted(() => {
search.switchTab("tracks");
});

View File

@@ -1,14 +1,17 @@
<template>
<div class="list-items">
<div class="option-list" v-for="i in items" :key="i.title">
<div class="ellip">
{{ i.title }}
<div class="option-list-item" v-for="i in items" :key="i.title">
<div class="with-icon">
<component :is="icon" />
<div class="text ellip">
{{ i.title }}
</div>
</div>
<div class="icon" @click="i.action">
<span>{{ i.buttontext }}</span>
</div>
</div>
<div v-if="!items.length" class="option-list" style="opacity: 0.5">
<div v-if="!items.length" class="option-list-item" style="opacity: 0.5">
Root directories not configured. Use the "modify" button above to
configure
</div>
@@ -16,13 +19,28 @@
</template>
<script setup lang="ts">
defineProps<{
import FolderSvg from "../../../assets/icons/folder.svg";
const props = defineProps<{
items: {
title: string;
buttontext: string;
action: () => void;
}[];
icon: "folder";
}>();
function getIcon() {
switch (props.icon) {
case "folder":
return FolderSvg;
default:
return FolderSvg;
}
}
const icon = getIcon();
</script>
<style lang="scss">
@@ -34,27 +52,35 @@ defineProps<{
border-radius: $small;
margin-top: 1rem;
overflow: hidden;
padding: 1rem 0;
}
.option-list {
.option-list-item {
padding: $small 1rem;
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
&:nth-child(even) {
background-color: $gray4;
.with-icon {
display: flex;
gap: $small;
align-items: center;
font-family: "SF Mono", monospace;
font-size: 0.9rem;
}
&:hover {
background-color: rgba(255, 255, 255, 0.178);
background-color: $gray4;
}
span {
color: $red;
color: white;
cursor: pointer;
text-decoration: underline;
background-color: $red;
padding: $smaller $small;
border-radius: 6px;
z-index: 20;
}
}
}

View File

@@ -20,7 +20,7 @@ defineProps<{
<style lang="scss">
.settingscontent {
width: 100%;
// max-width: 40rem;
margin: 0 auto;
padding-bottom: 2rem;
}
</style>

View File

@@ -26,12 +26,12 @@
<Switch
v-if="setting.type == SettingType.binary"
@click="setting.action()"
:state="setting.source && setting.source()"
:state="setting.state && setting.state()"
/>
<Select
v-if="setting.type === SettingType.select"
:options="setting.options"
:source="setting.source !== null ? setting.source : () => ''"
:source="setting.state !== null ? setting.state : () => ''"
:setterFn="setting.action"
/>
<button
@@ -43,7 +43,8 @@
</div>
<List
:items="setting.source !== null ? setting.source() : []"
icon="folder"
:items="setting.state !== null ? setting.state() : []"
v-if="setting.type === SettingType.list"
/>
</div>

View File

@@ -0,0 +1,24 @@
<template>
<div id="bubble-manager" class="rounded">
<DropArea />
</div>
</template>
<script setup lang="ts">
import DropArea from "./DropArea.vue";
</script>
<style lang="scss">
#bubble-manager {
position: fixed;
bottom: 6rem;
width: 15rem;
background-color: $gray;
padding: $small;
left: 50%;
transform: translateX(-50%);
z-index: 100;
}
</style>

View File

@@ -0,0 +1,44 @@
<template>
<div class="bin-drop-area t-center rounded-sm">
<div class="bin-count circular">10</div>
DROP TRACKS HERE
<br />
</div>
</template>
<style lang="scss">
.bin-drop-area {
// background-color: $gray;
color: $pink;
padding: $medium;
border-radius: $small;
height: 5rem;
position: relative;
display: grid;
align-items: center;
justify-content: center;
font-weight: 700;
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='10' ry='10' stroke='%23989696FF' stroke-width='4' stroke-dasharray='6%2c 14' stroke-dashoffset='100' stroke-linecap='square'/%3e%3c/svg%3e");
border-radius: 10px;
.bin-count {
position: absolute;
top: -$medium;
right: -$medium;
font-size: 14px;
width: 2rem;
aspect-ratio: 1;
font-weight: 600;
background-color: $pink;
color: white;
padding: $smaller;
margin-bottom: $small;
display: flex;
align-items: center;
justify-content: center;
}
}
</style>

View File

@@ -74,6 +74,7 @@ function create(e: Event) {
label {
font-size: 0.9rem;
color: $gray1;
font-weight: 700;
}
.submit {
@@ -85,6 +86,8 @@ function create(e: Event) {
margin: 0 auto;
width: 8rem;
transition: all 0.25s ease-out;
background-color: $pink;
border: solid 1px $pink;
}
}
</style>

View File

@@ -213,10 +213,10 @@ onMounted(() => {
}
.f-item {
background-color: $gray3;
background-color: $gray5;
&:hover {
background-color: $brown;
background-color: $gray3;
}
}
}

View File

@@ -1,8 +1,10 @@
<template>
<form
@submit.prevent="update_playlist"
id="playlist-update-modal"
class="playlist-modal"
enctype="multipart/form-data"
autocomplete="off"
>
<label for="name">Playlist name</label>
<br />
@@ -11,7 +13,8 @@
class="rounded-sm"
name="name"
id="modal-playlist-name-input"
:value="props.playlist.name"
v-model="pname"
@keypress.enter="playlist.has_image && update_playlist"
/>
<br />
<input
@@ -41,15 +44,20 @@
}"
/>
</div>
<div class="boxed banner-position-adjust rounded-sm">
<div class="t-center">Adjust image position</div>
<div
class="boxed banner-position-adjust rounded-sm"
v-if="playlist.has_image"
>
<div class="t-center">
Adjust image position - {{ pStore.bannerPos }}%
</div>
<div class="buttons">
<button @click.prevent="pStore.minusBannerPos">
<div @click.prevent="pStore.minusBannerPos">
<ExpandSvg />
</button>
<button @click.prevent="pStore.plusBannerPos">
</div>
<div @click.prevent="pStore.plusBannerPos">
<ExpandSvg />
</button>
</div>
</div>
</div>
@@ -61,7 +69,6 @@
<script setup lang="ts">
import { onMounted, ref } from "vue";
// import { useDropZone } from "@vueuse/core";
import { updatePlaylist } from "@/composables/fetch/playlists";
import { paths } from "@/config";
@@ -75,8 +82,7 @@ const props = defineProps<{
playlist: Playlist;
}>();
// const dropZoneRef = ref<HTMLDivElement>();
// const { isOverDropZone } = useDropZone(dropZoneRef, handleDrop);
const pname = ref(props.playlist.name);
onMounted(() => {
(document.getElementById("modal-playlist-name-input") as HTMLElement).focus();
@@ -108,12 +114,6 @@ function handleUpload() {
}
}
// function handleDrop(files: File[] | null) {
// if (files) {
// handleFile(files[0]);
// }
// }
function handleFile(file: File) {
if (!file || !file.type.startsWith("image/")) {
return;
@@ -132,7 +132,9 @@ function handleFile(file: File) {
let clicked = ref(false);
function update_playlist(e: Event) {
const form = e.target as HTMLFormElement;
const form = document.getElementById(
"playlist-update-modal"
) as HTMLFormElement;
const formData = new FormData(form);
const name = formData.get("name") as string;
@@ -155,9 +157,16 @@ function update_playlist(e: Event) {
});
}
}
// Future TODO: Implement drag and drop for images here
</script>
<style lang="scss">
#playlist-update-modal {
input {
height: 3rem !important;
}
}
.playlist-modal {
.boxed {
border: solid 2px $gray3;
@@ -172,6 +181,7 @@ function update_playlist(e: Event) {
width: 100%;
padding: $small;
cursor: pointer;
margin-bottom: 1rem;
#update-pl-img-preview {
width: 4.5rem;
@@ -191,26 +201,32 @@ function update_playlist(e: Event) {
position: relative;
}
button {
aspect-ratio: 1;
height: 2rem;
width: 2rem;
padding: 0;
background: transparent;
border: solid 1px transparent;
.buttons {
display: grid;
gap: $small;
&:hover {
border: solid 1px $gray4;
div {
aspect-ratio: 1;
height: 2rem;
background-color: $gray4;
border-radius: $small;
display: grid;
place-content: center;
&:first-child {
rotate: -90deg;
}
&:last-child {
rotate: 90deg;
}
&:hover {
background-color: $pink;
}
}
}
button:last-child {
transform: rotate(90deg);
}
button:first-child {
transform: rotate(-90deg);
}
}
}
</style>

View File

@@ -13,6 +13,15 @@
</div>
</div>
<SearchInput :page="Routes.folder" />
<button
class="toggle-list-mode"
@click="settings.toggleFolderListMode"
title="toggle list mode for folders"
v-auto-animate
>
<GridSvg v-if="settings.folder_list_mode" />
<ListSvg v-else />
</button>
</div>
</div>
</template>
@@ -25,8 +34,12 @@ import { subPath } from "@/interfaces";
import SearchInput from "@/components/shared/NavSearchInput.vue";
import BreadCrumbNav from "@/components/FolderView/BreadCrumbNav.vue";
import GridSvg from "@/assets/icons/grid.svg";
import ListSvg from "@/assets/icons/playlist.svg";
import useSettingsStore from "@/stores/settings";
const router = useRouter();
const settings = useSettingsStore();
defineProps<{
subPaths: subPath[];
@@ -74,6 +87,14 @@ function navigate(path: string) {
margin-left: $smaller;
}
}
.toggle-list-mode {
margin-left: 1rem;
height: 2.25rem;
width: 2.25rem;
aspect-ratio: 1;
padding: 0;
}
}
}
</style>

View File

@@ -19,6 +19,7 @@ const { showNewPlaylistModal } = useModalStore();
.playlists-nav {
display: grid;
grid-template-columns: 1fr max-content;
align-items: center;
button {
padding-right: $small;

View File

@@ -33,6 +33,7 @@ const tabs = ["tracks", "albums", "artists"];
display: grid;
grid-template-columns: 1fr max-content;
gap: 1rem;
.buttons-area {
position: relative;

View File

@@ -14,5 +14,6 @@ import Nav from "@/components/SettingsView/Nav.vue";
.settings-nav {
display: grid;
grid-template-columns: 1fr max-content;
align-items: center;
}
</style>

View File

@@ -145,6 +145,7 @@ defineProps<{
font-size: 0.8rem;
text-align: left;
opacity: 0.75;
font-weight: 700;
a {
cursor: pointer !important;

View File

@@ -1,5 +1,6 @@
<template>
<button
v-wave
class="heart-button circular"
@click="!no_emit && emit('handleFav')"
:class="{
@@ -29,8 +30,8 @@ import HeartSvg from "@/assets/icons/heart.svg";
import HeartFillSvg from "@/assets/icons/heart.fill.svg";
defineProps<{
state: boolean | undefined;
no_emit?: boolean;
state: Boolean | undefined;
no_emit?: Boolean;
}>();
const emit = defineEmits<{

View File

@@ -1,7 +1,22 @@
<template>
<span class="master-flag" title="Master audio file">M</span>
<span
v-if="store.show_master_quality_flag && bitrate > 1024"
class="master-flag"
:title="'Master audio bitrate - ' + `${bitrate} Kbps`"
>M</span
>
</template>
<script setup lang="ts">
import useSettingsStore from "@/stores/settings";
defineProps<{
bitrate: number;
}>();
const store = useSettingsStore();
</script>
<style lang="scss">
.master-flag {
font-size: 10px;

View File

@@ -1,17 +1,18 @@
<template>
<div class="header-input-wrapper rounded-sm" :class="{ showInput: clicked }">
<button
class="search-btn circular"
class="search-btn"
id="page-search-trigger"
:class="{ 'btn-active': clicked }"
@click="handleFocus"
v-wave
>
<SearchSvg /> Search
<SearchSvg />
</button>
<input
class="header-input pad-sm circular"
class="header-input pad-sm rounded-sm"
:class="{ showInput: clicked }"
placeholder="type to search"
:placeholder="currentEmoji"
v-model.trim="query"
id="page-search"
ref="inputRef"
@@ -20,7 +21,7 @@
</template>
<script setup lang="ts">
import { ref } from "vue";
import { onMounted, ref } from "vue";
import { storeToRefs } from "pinia";
import useAlbumStore from "@/stores/pages/album";
@@ -47,15 +48,15 @@ const props = defineProps<{
const inputRef = ref<HTMLElement>();
function handleFocus() {
// if input is not focused, focus it
// if input is focused, blur it
clicked.value = !clicked.value;
if (clicked.value) {
inputRef.value?.focus();
setRandomEmoji();
} else {
inputRef.value?.blur();
resetQuery();
}
}
function getRef() {
@@ -82,17 +83,27 @@ 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: 22rem;
width: 19rem;
}
display: flex;
flex-direction: row-reverse;
width: 7rem;
width: 5rem;
gap: $small;
transition: all 0.25s;
transition-delay: 0.1s;
@@ -101,18 +112,18 @@ if (source) {
.header-input {
background-color: transparent;
border: none;
color: inherit;
font-size: 1rem;
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: $gray5;
background-color: $gray3;
&:focus {
background-color: $gray3;
background-color: $darkestblue;
}
&.showInput {
@@ -125,6 +136,6 @@ if (source) {
.search-btn {
cursor: pointer;
padding: 0 $small;
padding-right: 1rem;
// padding-right: 1rem;
}
</style>

View File

@@ -1,5 +1,9 @@
<template>
<button class="playbtnrect shadow-sm circular btn-active" @click="usePlayFrom(source, useQStore, store)">
<button
v-wave
class="playbtnrect shadow-sm circular btn-active"
@click="usePlayFrom(source, useQStore, store)"
>
<playBtnSvg />
<div class="text">Play</div>
</button>
@@ -34,5 +38,7 @@ defineProps<{
justify-content: center;
transition: all 0.5s ease-in-out;
color: $white;
background-color: $pink !important;
border: solid 1px $pink !important;
}
</style>

View File

@@ -33,7 +33,7 @@
<span class="title ellip">
{{ track.title }}
</span>
<MasterFlag v-if="track.bitrate > 1024" />
<MasterFlag :bitrate="track.bitrate" />
</div>
<div class="isSmallArtists" style="display: none">
<ArtistName
@@ -85,6 +85,7 @@ import OptionSvg from "@/assets/icons/more.svg";
import ArtistName from "./ArtistName.vue";
import HeartSvg from "./HeartSvg.vue";
import MasterFlag from "./MasterFlag.vue";
import { dropSources } from "@/composables/enums";
const imguri = paths.images.thumb.small;
const context_menu_showing = ref(false);
@@ -93,14 +94,24 @@ const queue = useQueueStore();
const props = defineProps<{
track: Track;
index: number | string;
hide_album?: Boolean;
is_queue_track?: Boolean;
hide_album?: boolean;
is_queue_track?: boolean;
droppable?: boolean;
is_last?: boolean;
source: dropSources;
}>();
const is_fav = ref(props.track.is_favorite);
const emit = defineEmits<{
(e: "playThis"): void;
(
e: "trackDropped",
source: dropSources,
track: Track,
newIndex: number,
oldIndex: number
): void;
}>();
function emitUpdate() {
@@ -156,6 +167,39 @@ onBeforeUnmount(() => {
user-select: none;
padding-left: $small;
border: solid 1px transparent;
position: relative;
// .top-drop,
// .bottom-drop {
// display: flex;
// align-items: center;
// z-index: 20;
// position: absolute;
// height: 32px;
// width: 100%;
// left: 0;
// &.active {
// &::before {
// content: "";
// position: absolute;
// height: 1px;
// width: 100%;
// background-color: $red;
// left: 0;
// top: 50%;
// }
// }
// }
// .top-drop {
// top: -16px;
// }
// .bottom-drop {
// bottom: -16px;
// }
.song-title {
.with-flag {
@@ -256,15 +300,10 @@ onBeforeUnmount(() => {
}
&:hover {
background-color: $darkestblue;
background-color: $gray3;
}
}
// .context_menu_showing {
// background-color: $red;
// opacity: 1;
// }
.flex {
position: relative;
align-items: center;
@@ -278,12 +317,14 @@ onBeforeUnmount(() => {
width: 3rem;
height: 3rem;
cursor: pointer;
z-index: 20;
}
.now-playing-track-indicator {
position: absolute;
left: $small;
top: $small;
z-index: 20;
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div
class="queue-view-virtual-scroller v-scroll-page"
:class="{ isSmall, isMedium }"
:class="{ isSmall, isMedium, is_queue }"
style="height: 100%"
>
<RecycleScroller
@@ -16,8 +16,12 @@
<SongItem
:track="item.track"
:index="index + 1"
:is_queue_track="true"
:is_queue_track="is_queue"
@playThis="handlePlay(index)"
:is_last="index == tracks.length - 1"
:droppable="false"
@trackDropped="dropHandler"
:source="source"
/>
</RecycleScroller>
</div>
@@ -25,6 +29,7 @@
<script setup lang="ts">
import SongItem from "@/components/shared/SongItem.vue";
import { dropSources } from "@/composables/enums";
import { Track } from "@/interfaces";
import { isMedium, isSmall } from "@/stores/content-width";
@@ -32,7 +37,24 @@ defineProps<{
tracks: Track[];
is_queue?: boolean;
handlePlay: (index: number) => void;
dropHandler: (
source: dropSources,
track: Track,
newIndex: number,
oldIndex: number
) => void;
source: dropSources;
}>();
const itemHeight = 64;
</script>
<style lang="scss">
.queue-view-virtual-scroller.is_queue {
.songlist-item.current {
background-color: $darkestblue !important;
border: none;
}
}
</style>

View File

@@ -10,6 +10,9 @@
{ contexton: context_on },
]"
@contextmenu.prevent="showMenu"
v-wave="{
duration: 0.35,
}"
>
<div class="album-art">
<img :src="paths.images.thumb.small + track.image" class="rounded-sm" />
@@ -20,8 +23,10 @@
></div>
</div>
<div class="tags">
<div class="title ellip" v-tooltip>
{{ track.title }}
<div class="title" v-tooltip>
<span class="ellip">
{{ track.title }}
</span>
</div>
<hr />
<div class="artist">
@@ -34,6 +39,7 @@
</div>
<div class="float-buttons flex">
<div
class="fav-icon"
:title="is_fav ? 'Add to favorites' : 'Remove from favorites'"
@click.stop="() => addToFav(track.trackhash)"
>
@@ -54,15 +60,17 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import DelSvg from "@/assets/icons/plus.svg";
import { showTrackContextMenu as showContext } from "@/composables/context";
import { paths } from "@/config";
import { Track } from "@/interfaces";
import useQueueStore from "@/stores/queue";
import ArtistName from "./ArtistName.vue";
import HeartSvg from "./HeartSvg.vue";
import favoriteHandler from "@/composables/favoriteHandler";
import { favType } from "@/composables/enums";
import favoriteHandler from "@/composables/favoriteHandler";
import { showTrackContextMenu as showContext } from "@/composables/context";
import HeartSvg from "./HeartSvg.vue";
import MasterFlag from "./MasterFlag.vue";
import ArtistName from "./ArtistName.vue";
import DelSvg from "@/assets/icons/plus.svg";
const props = defineProps<{
track: Track;
@@ -107,7 +115,7 @@ watch(
</script>
<style lang="scss">
.currentInQueue {
.track-item.currentInQueue {
background-color: $gray4;
}
@@ -130,19 +138,31 @@ watch(
.float-buttons {
opacity: 0;
gap: $small;
.heart-button {
width: 2rem;
height: 2rem;
padding: 0;
border: none;
transition: all 0.25s ease;
transform: scale(1) translateY(-1rem);
&:hover {
background-color: pink;
}
}
.remove-track {
margin-top: $smaller;
transition: all 0.25s ease;
transform: scale(1) translateY(1rem) rotate(45deg);
transform: rotate(45deg);
height: 2rem;
width: 2rem;
display: grid;
place-items: center;
&:hover {
border-radius: 1rem;
background-color: $red;
}
}
&:hover {
@@ -155,12 +175,8 @@ watch(
opacity: 1;
}
.heart-button {
transform: scale(1) translateY(0);
}
.remove-track {
transform: scale(1) translateY(0) rotate(45deg);
transform: translateY(0) rotate(45deg);
}
background-color: $gray5;
@@ -192,6 +208,7 @@ watch(
.artist {
opacity: 0.67;
width: fit-content;
font-weight: bold;
}
}
</style>

View File

@@ -7,10 +7,14 @@
export function isLight(rgb: string): boolean {
if (rgb == null || undefined) return false;
const [r, g, b] = rgb.match(/\d+/g)!.map(Number);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
try {
const [r, g, b] = rgb.match(/\d+/g)!.map(Number);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
return brightness > 165;
return brightness > 165;
} catch (error) {
return false;
}
}
interface BtnColor {

View File

@@ -60,3 +60,13 @@ export enum favType {
album = "album",
track = "track",
}
export enum dropSources {
queue = "queue",
playlist = "playlist",
folder = "folder",
album = "album",
search = "search",
artist = "artist",
favorite = "favorite",
}

View File

@@ -14,6 +14,8 @@ export default async function favoriteHandler(
setter: (x?: unknown) => void,
remover: (x?: unknown) => void
) {
if (itemhash == "") return;
const queue = useQueueStore();
const is_current =
type === favType.track && itemhash === queue.currenttrackhash;

View File

@@ -0,0 +1,14 @@
import useAxios from "./useAxios";
import { paths } from "@/config";
export async function fetchAlbumColor(
albumhash: string | undefined
): Promise<string> {
const { data } = await useAxios({
url: paths.api.colors.album + `/${albumhash}`,
get: true,
});
return data.color;
}

View File

@@ -55,9 +55,9 @@ export async function createNewPlaylist(playlist_name: string, track?: Track) {
* Fetches all playlists from the server.
* @returns {Promise<Playlist[]>} A promise that resolves to an array of playlists.
*/
export async function getAllPlaylists(): Promise<Playlist[]> {
export async function getAllPlaylists(no_images = false): Promise<Playlist[]> {
const { data, error } = await useAxios({
url: allPlaylistsUrl,
url: allPlaylistsUrl + (no_images ? "?no_images=true" : ""),
get: true,
});

View File

@@ -87,6 +87,12 @@ export const paths = {
return this.base + "/loadmore";
},
},
colors: {
base: baseApiUrl.value + "/colors",
get album() {
return this.base + "/album";
},
},
settings: {
base: baseApiUrl.value + "/settings",
get get_root_dirs() {

View File

@@ -59,7 +59,7 @@ export default async (
};
let playlists = <Option[]>[];
const p = await getAllPlaylists();
const p = await getAllPlaylists(true);
if (p.length === 0) {
no_playlists = true;

View File

@@ -33,8 +33,8 @@ export interface Folder {
name: string;
path: string;
has_tracks: number;
subdircount: number;
is_sym: boolean;
count: number;
}
export interface Album {
@@ -83,6 +83,7 @@ export interface Playlist {
id: string;
name: string;
image: string | FormData;
has_image: boolean;
tracks: Track[];
count: number;
last_updated: string;
@@ -90,6 +91,12 @@ export interface Playlist {
duration: number;
has_gif: boolean;
banner_pos: number;
images:
| {
image: string;
color: string;
}[] // for playlist page
| string[]; // for playlist list page
}
export interface Notif {

View File

@@ -11,7 +11,7 @@ export interface Setting {
options?: SettingOption[];
inactive?: () => boolean;
action: (arg0?: any) => void;
source: (() => any) | null;
state: (() => any) | null;
button_text?: () => string;
defaultAction?: () => void;
}

View File

@@ -7,9 +7,9 @@ import {
DynamicScrollerItem,
// @ts-ignore
} from "vue-virtual-scroller";
import { autoAnimatePlugin } from "@formkit/auto-animate/vue";
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
import VWave from "v-wave";
import App from "./App.vue";
import { router } from "./router";
@@ -25,6 +25,7 @@ pinia.use(piniaPluginPersistedstate);
app.use(pinia);
app.use(router);
app.use(autoAnimatePlugin);
app.use(VWave);
app.directive("tooltip", vTooltip);
app.component("RecycleScroller", RecycleScroller);

View File

@@ -20,7 +20,7 @@ const context_children_show_mode: Setting = {
value: mode.hover,
},
],
source: () => settings().contextChildrenShowMode,
state: () => settings().contextChildrenShowMode,
action: (value: mode) => settings().setContextChildrenShowMode(value),
defaultAction: () => settings().toggleContextChildrenShowMode(),
};

View File

@@ -9,7 +9,7 @@ const settings = useSettingsStore;
const extend_to_full_width: Setting = {
title: appWidthStrings.settings.extend,
type: SettingType.binary,
source: () => settings().extend_width,
state: () => settings().extend_width,
action: () => settings().toggleExtendWidth(),
inactive: () => !settings().can_extend_width,
};

View File

@@ -5,6 +5,7 @@ import extendWidth from "./extend-width";
import nowPlaying from "./now-playing-group";
import sidebarSettings from "./sidebar";
import rootDirSettings from "./root-dirs";
import masterFlag from "./master-flag";
const npStrings = strings.nowPlayingStrings;
const rootRootStrings = strings.manageRootDirsStrings;
@@ -16,6 +17,7 @@ export default {
settings: [
...sidebarSettings,
...extendWidth,
...masterFlag,
...contextChildrenShowMode,
],
},

View File

@@ -0,0 +1,16 @@
import { SettingType } from "../enums";
import { Setting } from "@/interfaces/settings";
import { showMasterFlag } from "./../strings";
import useSettingsStore from "@/stores/settings";
const settings = useSettingsStore;
const show_master_flag: Setting = {
title: showMasterFlag.settings.show_flag,
type: SettingType.binary,
state: () => settings().show_master_quality_flag,
action: () => settings().toggleShowMasterQualityFlag(),
};
export default [show_master_flag];

View File

@@ -9,7 +9,7 @@ const settings = useSettingsStore;
const disable_np_img: Setting = {
title: data.settings.album_art,
type: SettingType.binary,
source: () => settings().use_np_img,
state: () => settings().use_np_img,
action: () => settings().toggleUseNPImg(),
};

View File

@@ -11,7 +11,7 @@ const text = data.settings;
const change_root_dirs: Setting = {
title: text.change,
type: SettingType.button,
source: null,
state: null,
button_text: () =>
`\xa0 \xa0 ${
useSettingsStore().root_dirs.length ? "Modify" : "Configure"
@@ -22,7 +22,7 @@ const change_root_dirs: Setting = {
const list_root_dirs: Setting = {
title: text.list_root_dirs,
type: SettingType.list,
source: () =>
state: () =>
useSettingsStore().root_dirs.map((d) => ({
title: d,
buttontext: "remove",

View File

@@ -9,7 +9,7 @@ const settings = useSettingsStore;
const use_sidebar: Setting = {
title: sidebarStrings.settings.use_sidebar,
type: SettingType.binary,
source: () => settings().use_sidebar,
state: () => settings().use_sidebar,
action: () => settings().toggleDisableSidebar(),
};

View File

@@ -35,6 +35,12 @@ export const contextChildrenShowModeStrings = <S>{
},
};
export const showMasterFlag = <S>{
settings: {
show_flag: "Show M (Master) flag on track with bitrate higher than 1024 Kbps",
},
};
export const showFoldersAsStrings = <S>{
settings: { show_folders_as: "Show folders as" },
};

16
src/stores/colors.ts Normal file
View File

@@ -0,0 +1,16 @@
import { defineStore } from "pinia";
// @ts-ignore
import { brightness } from "@nextcss/color-tools";
import rgb2Hex from "@/utils/colortools/rgb2Hex";
export default defineStore("SwingMusicColors", {
state: () => ({
theme1: "",
}),
actions: {
setTheme1Color(color: string) {
this.theme1 = color;
},
},
persist: true,
});

View File

@@ -102,26 +102,6 @@ export default defineStore("album", {
},
},
getters: {
// discs(): Disc {
// return createDiscs(this.srcTracks);
// },
/**
* All tracks ordered by disc and track number.
*/
// allTracks(): Track[] {
// const tracks = Object.keys(this.discs).reduce((tracks: Track[], disc) => {
// const disc_tracks = this.discs[disc];
// return [...tracks, ...disc_tracks];
// }, []);
// tracks.map((t, index) => {
// t.master_index = index;
// return t;
// });
// return tracks;
// },
filteredTracks(): ComputedRef<FuseResult[]> {
const discs = this.discs;
let tracks: Track[] | AlbumDisc[] = [];

View File

@@ -35,15 +35,6 @@ export default defineStore("artistPage", {
this.singles = singles;
this.appearances = appearances;
this.compilations = compilations;
// if (albums.length > 0) {
// }
// if (eps.length > 0) {
// }
// if (singles.length > 0) {
// }
},
resetAlbums() {
this.albums = [];

View File

@@ -39,18 +39,17 @@ export default defineStore("playlist-tracks", {
* @param info Playlist info
*/
updatePInfo(info: Playlist) {
const { duration, count } = this.info;
const { duration, count, images } = this.info;
this.info = info;
this.info = { ...this.info, duration, count };
this.info = { ...this.info, duration, count, images };
this.bannerPos = this.info.banner_pos;
},
plusBannerPos() {
this.bannerPos !== 100 ? (this.bannerPos += 10) : null;
this.bannerPos !== 100 ? (this.bannerPos += 5) : null;
},
minusBannerPos() {
this.bannerPos !== 0 ? (this.bannerPos -= 10) : null;
this.bannerPos !== 0 ? (this.bannerPos -= 5) : null;
},
resetArtists() {
this.artists = [];

View File

@@ -3,10 +3,11 @@ import { defineStore } from "pinia";
import { Ref } from "vue";
import { NotifType, useNotifStore } from "./notification";
import { favType, FromOptions } from "../composables/enums";
import { dropSources, favType, FromOptions } from "../composables/enums";
import updateMediaNotif from "../composables/mediaNotification";
import { isFavorite } from "@/composables/fetch/favorite";
import useSettingsStore from "./settings";
import useColorStore from "./colors";
import {
fromAlbum,
@@ -17,6 +18,7 @@ import {
fromSearch,
Track,
} from "../interfaces";
import { fetchAlbumColor } from "@/composables/fetch/colors";
function shuffle(tracks: Track[]) {
const shuffled = tracks.slice();
@@ -65,7 +67,9 @@ export default defineStore("Queue", {
this.focusCurrentInSidebar();
const track = this.tracklist[index];
const uri = `${paths.api.files}/${track.trackhash}`;
const uri = `${paths.api.files}/${
track.trackhash
}?filepath=${encodeURIComponent(track.filepath as string)}`;
new Promise((resolve, reject) => {
audio.autoplay = true;
@@ -79,6 +83,10 @@ export default defineStore("Queue", {
this.playing = true;
updateMediaNotif();
fetchAlbumColor(track.albumhash).then((color) => {
useColorStore().setTheme1Color(color);
});
audio.ontimeupdate = () => {
this.duration.current = audio.currentTime;
};
@@ -141,7 +149,7 @@ export default defineStore("Queue", {
audio.src = "";
this.playing = false;
updateMediaNotif()
updateMediaNotif();
this.focusCurrentInSidebar();
};
@@ -270,6 +278,22 @@ export default defineStore("Queue", {
NotifType.Success
);
},
addTrackToIndex(
source: dropSources,
track: Track,
newIndex: number,
oldIndex: number
) {
console.log(source);
if (source === dropSources.queue) {
this.tracklist.splice(oldIndex, 1);
this.tracklist.splice(newIndex, 0, track);
return;
}
// else, just insert track at newIndex
this.tracklist.splice(newIndex, 0, track);
},
clearQueue() {
this.tracklist = [] as Track[];
this.currentindex = 0;
@@ -298,6 +322,7 @@ export default defineStore("Queue", {
// insert current track at beginning of queue
this.tracklist.unshift(current as Track);
this.currentindex = 0;
this.focusCurrentInSidebar();
return;
}
@@ -319,8 +344,13 @@ export default defineStore("Queue", {
if (was_playing) {
this.playPause();
}
} else {
this.tracklist.splice(index, 1);
return;
}
this.tracklist.splice(index, 1);
if (index < this.currentindex) {
this.currentindex -= 1;
}
},
setScrollFunction(

View File

@@ -8,12 +8,14 @@ export default defineStore("settings", {
use_np_img: true,
use_sidebar: true,
extend_width: false,
contextChildrenShowMode: contextChildrenShowMode.click,
contextChildrenShowMode: contextChildrenShowMode.hover,
artist_top_tracks_count: 5,
repeat_all: true,
repeat_one: false,
root_dir_set: false,
root_dirs: <string[]>[],
folder_list_mode: false,
show_master_quality_flag: true,
}),
actions: {
toggleUseNPImg() {
@@ -57,6 +59,12 @@ export default defineStore("settings", {
setRootDirs(dirs: string[]) {
this.root_dirs = dirs;
},
toggleFolderListMode() {
this.folder_list_mode = !this.folder_list_mode;
},
toggleShowMasterQualityFlag(){
this.show_master_quality_flag = !this.show_master_quality_flag
}
},
getters: {
can_extend_width(): boolean {

View File

@@ -0,0 +1,22 @@
function rgbToArray(rgb: string): number[] | null {
try {
return rgb.match(/\d+/g)!.map(Number);
} catch (error) {
return null;
}
}
export default function rgb2Hex(rgb: string): string {
const rgb_array = rgbToArray(rgb);
if (!rgb_array) return "";
const [r, g, b] = rgb_array;
const toHex = (num: number) => {
const hex = num.toString(16);
return hex.length === 1 ? "0" + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}

View File

@@ -0,0 +1,27 @@
// @ts-ignore
import { colorShift, brightness } from "@nextcss/color-tools";
import rgb2Hex from "./rgb2Hex";
/**
* Shifts a color by a multiplier to get a lighter or darker color.
* @param color rgb color
* @param multipliers Two multipliers for the shift. First one is used when the color is light, and the other when color is dark
*/
export function getShift(color: string, multipliers: number[]) {
color = rgb2Hex(color);
const is_light = brightness(color) > 50;
return is_light
? colorShift(color, multipliers[0])
: colorShift(color, multipliers[1]);
}
export function getTextColor(color: string) {
return getShift(color, [80, -80]);
}
export function getBackgroundColor(color: string) {
return getShift(color, [-50, 50]);
}
// TODO: Support more levels of brightness. ie. slightly light, light, slightly dark, dark

View File

@@ -0,0 +1,39 @@
import { dropSources } from "@/composables/enums";
import { Track } from "@/interfaces";
export function showDragStart(
e: DragEvent,
track: Track,
oldIndex: number,
source: dropSources = dropSources.folder
) {
console.log("drag start");
console.log(source);
const dragDiv = document.getElementById("drag-img") as HTMLDivElement;
dragDiv.innerText = track.title;
e.dataTransfer?.setDragImage(dragDiv, -15, 0);
// add track object to dataTransfer
e.dataTransfer?.clearData();
e.dataTransfer?.setData(
"swing-track",
JSON.stringify({ track, source, oldIndex })
);
}
export function handleDrop(e: DragEvent, index: number, top: boolean) {
const data = e.dataTransfer?.getData("swing-track");
if (!data) return;
const drop_data = JSON.parse(data) as {
track: Track;
oldIndex: number;
source: dropSources;
};
console.log(drop_data);
// find dropped index
const newIndex = top ? index - 1 : index + 1;
console.log(newIndex + 1);
return { ...drop_data, newIndex };
}

View File

@@ -34,7 +34,7 @@ import Header from "@/components/AlbumView/Header.vue";
import SongItem from "@/components/shared/SongItem.vue";
import { isSmall, heightLarge } from "@/stores/content-width";
import { discographyAlbumTypes } from "@/composables/enums";
import { discographyAlbumTypes, dropSources } from "@/composables/enums";
const album = useAlbumStore();
const queue = useQueueStore();
@@ -60,7 +60,12 @@ class songItem {
this.id = track.filepath || Math.random();
this.props = track.is_album_disc_number
? { album_disc: track }
: { track, hide_album: true, index: track.track };
: {
track,
hide_album: true,
index: track.track,
source: dropSources.album,
};
this.component = track.is_album_disc_number ? AlbumDiscBar : SongItem;
}
}

View File

@@ -16,6 +16,7 @@
:track="item.track"
:index="index + 1"
@playThis="playFromPage(index)"
:source="dropSources.artist"
/>
</RecycleScroller>
</div>
@@ -32,7 +33,7 @@ import { getArtistTracks } from "@/composables/fetch/artists";
import useQueueStore from "@/stores/queue";
import SongItem from "@/components/shared/SongItem.vue";
import { FromOptions } from "@/composables/enums";
import { dropSources, FromOptions } from "@/composables/enums";
const itemHeight = 64;
const route = useRoute();

View File

@@ -33,7 +33,11 @@ import ArtistAlbumsFetcher from "@/components/ArtistView/ArtistAlbumsFetcher.vue
import { computed } from "vue";
import { onBeforeRouteLeave, onBeforeRouteUpdate, useRoute } from "vue-router";
import { Album } from "@/interfaces";
import { discographyAlbumTypes, FromOptions } from "@/composables/enums";
import {
discographyAlbumTypes,
dropSources,
FromOptions,
} from "@/composables/enums";
import useQueueStore from "@/stores/queue";
import { getArtistTracks } from "@/composables/fetch/artists";
@@ -82,11 +86,11 @@ function createAbumComponent(
case AlbumType.SINGLES:
albumType = discographyAlbumTypes.singles;
break;
case AlbumType.APPEARANCES:
albumType = discographyAlbumTypes.appearances;
case AlbumType.COMPILATIONS:
albumType = discographyAlbumTypes.compilations;
break;
case AlbumType.APPEARANCES:
albumType = discographyAlbumTypes.appearances;
default:
break;
@@ -115,6 +119,7 @@ function getTopTracksComponent(): ScrollerItem {
title: "Tracks",
route: `/artists/${store.info.artisthash}/tracks?artist=${store.info.name}`,
playHandler: handlePlay,
source: dropSources.artist,
},
};
}

View File

@@ -23,3 +23,4 @@ onMounted(() => {
getFavArtists(0).then((data) => (artists.value = data));
});
</script>

View File

@@ -1,5 +1,11 @@
<template>
<SongList :tracks="tracks" :handlePlay="handlePlay" />
<SongList
:tracks="tracks"
:handlePlay="handlePlay"
:is_queue="false"
:dropHandler="() => {}"
:source="dropSources.favorite"
/>
</template>
<script setup lang="ts">
@@ -10,6 +16,7 @@ import useQueueStore from "@/stores/queue";
import { getFavTracks } from "@/composables/fetch/favorite";
import SongList from "@/components/shared/SongList.vue";
import { dropSources } from "@/composables/enums";
const tracks: Ref<Track[]> = ref([]);
const queue = useQueueStore();

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