Compare commits
89 Commits
handle-alb
...
no-sidebar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
feb35afa9a | ||
|
|
6b925a0434 | ||
|
|
4211ccc685 | ||
|
|
9c982283fb | ||
|
|
888958db54 | ||
|
|
cb3e9ec2cd | ||
|
|
f7e604f11b | ||
|
|
1b36b96239 | ||
|
|
ee3ef6c1e6 | ||
|
|
83bd063bea | ||
|
|
12531edec9 | ||
|
|
7edd501d0f | ||
|
|
c7036b3a5d | ||
|
|
3075517af9 | ||
|
|
e8f0bc6b8b | ||
|
|
ded3a48e0b | ||
|
|
a04a3c4fe4 | ||
|
|
4aaf70ba68 | ||
|
|
fa176cb027 | ||
|
|
c4ce344487 | ||
|
|
f2c7cccba1 | ||
|
|
901406a337 | ||
|
|
4b48b13561 | ||
|
|
10b29f6349 | ||
|
|
b24d833d12 | ||
|
|
9004c02898 | ||
|
|
409edba74c | ||
|
|
2f1c07f786 | ||
|
|
278e73745a | ||
|
|
3652432e0b | ||
|
|
67ad2ef206 | ||
|
|
f2e157a746 | ||
|
|
606515ffd5 | ||
|
|
6ff67e5f94 | ||
|
|
5f037fc647 | ||
|
|
d1c62f701e | ||
|
|
b2bef03373 | ||
|
|
49fe308da0 | ||
|
|
bce2772dcb | ||
|
|
dd0b9d6d61 | ||
|
|
1e90298f72 | ||
|
|
7f4385f8cb | ||
|
|
48f2a73291 | ||
|
|
8cf33089a2 | ||
|
|
c16be33d40 | ||
|
|
a9691a2a25 | ||
|
|
e88e6dcc2d | ||
|
|
00ffbdbc42 | ||
|
|
97f348daf2 | ||
|
|
d0f47b4504 | ||
|
|
2db6bfebcf | ||
|
|
286003cf27 | ||
|
|
d27c61c7ce | ||
|
|
c56ee65a73 | ||
|
|
d3c0c7c596 | ||
|
|
e7fec30b7c | ||
|
|
da36f8d7dd | ||
|
|
63de7a6613 | ||
|
|
b14c814c55 | ||
|
|
7f8293e691 | ||
|
|
59a27d4489 | ||
|
|
4e59e73ec5 | ||
|
|
df1c909fce | ||
|
|
3aa0aebfc6 | ||
|
|
afdbb0dbb5 | ||
|
|
9fc37034a6 | ||
|
|
53fc0c6656 | ||
|
|
cfe57b788b | ||
|
|
3b55cc1c2c | ||
|
|
47c41be79a | ||
|
|
cfc9c2632b | ||
|
|
c0cb2791d0 | ||
|
|
6520b686a3 | ||
|
|
4041c8f588 | ||
|
|
da63f481c6 | ||
|
|
a5bcaadafb | ||
|
|
19142b284a | ||
|
|
511fa58d66 | ||
|
|
2fbac120b2 | ||
|
|
dea36af5cc | ||
|
|
81d28461f6 | ||
|
|
92302e87e5 | ||
|
|
1fd30b4ac3 | ||
|
|
f751bbac97 | ||
|
|
feb99103b8 | ||
|
|
0851c76e65 | ||
|
|
bf1f3bf00a | ||
|
|
c0dd04bc94 | ||
|
|
c2aba79db7 |
@@ -21,9 +21,7 @@
|
||||
"motion": "^10.15.5",
|
||||
"node-vibrant": "3.1.6",
|
||||
"pinia": "^2.0.17",
|
||||
"pinia-plugin-persistedstate": "^2.1.1",
|
||||
"sass": "^1.56.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"v-wave": "^1.5.0",
|
||||
"vue": "^v3.2.45",
|
||||
"vue-debounce": "^3.0.2",
|
||||
@@ -40,6 +38,8 @@
|
||||
"eslint": "^8.46.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"sass": "^1.56.1",
|
||||
"sass-loader": "^13.2.0",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^3.0.4",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
|
||||
15
public/workers/logtrack.js
Normal file
@@ -0,0 +1,15 @@
|
||||
onmessage = (e) => {
|
||||
const { trackhash, duration, source, timestamp } = e.data;
|
||||
|
||||
const is_dev = location.port === "5173";
|
||||
const base_url = is_dev ? "http://localhost:1980" : location.origin;
|
||||
const url = base_url + "/logger/track/log";
|
||||
|
||||
fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ trackhash, duration, source, timestamp }),
|
||||
});
|
||||
};
|
||||
18
public/workers/silence.js
Normal file
@@ -0,0 +1,18 @@
|
||||
onmessage = async (e) => {
|
||||
const { ending_file, starting_file } = e.data;
|
||||
|
||||
const is_dev = location.port === "5173";
|
||||
const base_url = is_dev ? "http://localhost:1980" : location.origin;
|
||||
const url = base_url + "/file/silence";
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ end: ending_file, start: starting_file }),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
postMessage(data);
|
||||
};
|
||||
89
src/App.vue
@@ -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' : '1720px'}` }"
|
||||
>
|
||||
<LeftSidebar v-if="!isMobile" />
|
||||
<LeftSidebar v-if="settings.is_default_layout && !isMobile" />
|
||||
<NavBar />
|
||||
<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" />
|
||||
@@ -27,10 +37,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
// @libraries
|
||||
import { onMounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { onStartTyping } from "@vueuse/core";
|
||||
import { vElementSize } from "@vueuse/components";
|
||||
import { onStartTyping } from "@vueuse/core";
|
||||
import { onMounted, Ref, ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { BalancerProvider } from "vue-wrap-balancer";
|
||||
|
||||
// @stores
|
||||
@@ -38,11 +48,13 @@ import {
|
||||
content_height,
|
||||
content_width,
|
||||
isMobile,
|
||||
updateCardWidth,
|
||||
} from "@/stores/content-width";
|
||||
import useModalStore from "@/stores/modal";
|
||||
import useQStore from "@/stores/queue";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
import useQueueStore from "@/stores/queue";
|
||||
import useLyrics from "@/stores/lyrics";
|
||||
import useModal from "@/stores/modal";
|
||||
import useQueue from "@/stores/queue";
|
||||
import useSettings from "@/stores/settings";
|
||||
import useTracker from "@/stores/tracker";
|
||||
|
||||
// @utils
|
||||
import handleShortcuts from "@/helpers/useKeyboard";
|
||||
@@ -56,33 +68,42 @@ import Notification from "@/components/Notification.vue";
|
||||
|
||||
// @app-grid-components
|
||||
import BottomBar from "@/components/BottomBar/BottomBar.vue";
|
||||
import LeftSidebar from "@/components/LeftSidebar/index.vue";
|
||||
import NavBar from "@/components/nav/NavBar.vue";
|
||||
import RightSideBar from "@/components/RightSideBar/Main.vue";
|
||||
import LeftSidebar from "@/components/LeftSidebar/index.vue";
|
||||
import { getAllSettings } from "@/requests/settings";
|
||||
|
||||
import { getRootDirs } from "@/requests/settings/rootdirs";
|
||||
import { baseApiUrl } from "@/config";
|
||||
// import BubbleManager from "./components/bubbles/BinManager.vue";
|
||||
|
||||
const appcontent: Ref<HTMLLegendElement | null> = ref(null);
|
||||
const queue = useQueue();
|
||||
const modal = useModal();
|
||||
const lyrics = useLyrics();
|
||||
const router = useRouter();
|
||||
const queue = useQueueStore();
|
||||
const modal = useModalStore();
|
||||
const settings = useSettingsStore();
|
||||
const settings = useSettings();
|
||||
useTracker();
|
||||
|
||||
handleShortcuts(useQStore, useModalStore);
|
||||
handleShortcuts(useQueue, useModal);
|
||||
|
||||
router.afterEach(() => {
|
||||
(document.getElementById("acontent") as HTMLElement).scrollTo(0, 0);
|
||||
});
|
||||
|
||||
onStartTyping(() => {
|
||||
if (isMobile.value) return;
|
||||
|
||||
const elem = document.getElementById("globalsearch") as HTMLInputElement;
|
||||
elem.focus();
|
||||
elem.value = "";
|
||||
});
|
||||
|
||||
function getContentSize() {
|
||||
const elem = document.getElementById("acontent") as HTMLElement;
|
||||
return {
|
||||
width: elem.offsetWidth,
|
||||
height: elem.offsetHeight,
|
||||
};
|
||||
}
|
||||
|
||||
function updateContentElemSize({
|
||||
width,
|
||||
height,
|
||||
@@ -90,8 +111,12 @@ function updateContentElemSize({
|
||||
width: number;
|
||||
height: number;
|
||||
}) {
|
||||
content_width.value = width;
|
||||
// 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();
|
||||
}
|
||||
|
||||
function handleWelcomeModal() {
|
||||
@@ -118,16 +143,26 @@ function handleRootDirsPrompt() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
queue.startBufferingStatusWatcher();
|
||||
const { width, height } = getContentSize();
|
||||
updateContentElemSize({ width, height });
|
||||
|
||||
handleWelcomeModal();
|
||||
settings.initializeVolume();
|
||||
|
||||
if (baseApiUrl.value === null) {
|
||||
modal.showSetIPModal();
|
||||
return;
|
||||
}
|
||||
|
||||
handleRootDirsPrompt();
|
||||
|
||||
getAllSettings()
|
||||
.then(({ settings: data }) => {
|
||||
settings.mapDbSettings(data);
|
||||
})
|
||||
.then(() => {
|
||||
if (queue.currenttrack && !settings.use_lyrics_plugin) {
|
||||
lyrics.checkExists(
|
||||
queue.currenttrack.filepath,
|
||||
queue.currenttrack.trackhash
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 13.8477C16.127 13.8477 17.8496 11.9668 17.8496 9.66406C17.8496 7.39648 16.127 5.59473 14 5.59473C11.8818 5.59473 10.1416 7.42285 10.1504 9.68164C10.1592 11.9756 11.873 13.8477 14 13.8477ZM14 12.3096C12.7871 12.3096 11.7588 11.1582 11.7588 9.68164C11.75 8.24023 12.7783 7.13281 14 7.13281C15.2305 7.13281 16.2412 8.22266 16.2412 9.66406C16.2412 11.1406 15.2217 12.3096 14 12.3096ZM8.51562 22.0215H19.4756C20.9961 22.0215 21.7256 21.5381 21.7256 20.501C21.7256 18.084 18.7109 14.8672 14 14.8672C9.28906 14.8672 6.26562 18.084 6.26562 20.501C6.26562 21.5381 6.99512 22.0215 8.51562 22.0215ZM8.24316 20.4834C8.03223 20.4834 7.95312 20.4131 7.95312 20.2549C7.95312 18.9102 10.124 16.4053 14 16.4053C17.8672 16.4053 20.0381 18.9102 20.0381 20.2549C20.0381 20.4131 19.959 20.4834 19.748 20.4834H8.24316Z" fill="#F2F2F2"/>
|
||||
<path d="M14 13.8477C16.127 13.8477 17.8496 11.9668 17.8496 9.66406C17.8496 7.39648 16.127 5.59473 14 5.59473C11.8818 5.59473 10.1416 7.42285 10.1504 9.68164C10.1592 11.9756 11.873 13.8477 14 13.8477ZM14 12.3096C12.7871 12.3096 11.7588 11.1582 11.7588 9.68164C11.75 8.24023 12.7783 7.13281 14 7.13281C15.2305 7.13281 16.2412 8.22266 16.2412 9.66406C16.2412 11.1406 15.2217 12.3096 14 12.3096ZM8.51562 22.0215H19.4756C20.9961 22.0215 21.7256 21.5381 21.7256 20.501C21.7256 18.084 18.7109 14.8672 14 14.8672C9.28906 14.8672 6.26562 18.084 6.26562 20.501C6.26562 21.5381 6.99512 22.0215 8.51562 22.0215ZM8.24316 20.4834C8.03223 20.4834 7.95312 20.4131 7.95312 20.2549C7.95312 18.9102 10.124 16.4053 14 16.4053C17.8672 16.4053 20.0381 18.9102 20.0381 20.2549C20.0381 20.4131 19.959 20.4834 19.748 20.4834H8.24316Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 931 B After Width: | Height: | Size: 936 B |
3
src/assets/icons/avatar.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.10515 24.5753H21.6977C23.0148 24.5753 23.8007 23.9513 23.8007 22.9154C23.8007 19.9004 19.9776 15.7538 13.8955 15.7538C7.82523 15.7538 4 19.9004 4 22.9154C4 23.9513 4.78797 24.5753 6.10515 24.5753ZM13.9052 13.6731C16.4594 13.6731 18.6327 11.4019 18.6327 8.46591C18.6327 5.58403 16.453 3.39099 13.9052 3.39099C11.3573 3.39099 9.17547 5.62458 9.17969 8.48724C9.1818 11.4019 11.3434 13.6731 13.9052 13.6731Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 542 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 1.875C7.75195 1.875 1.875 7.75195 1.875 15C1.875 22.248 7.75195 28.125 15 28.125C22.248 28.125 28.125 22.248 28.125 15C28.125 7.75195 22.248 1.875 15 1.875ZM15 25.8984C8.98242 25.8984 4.10156 21.0176 4.10156 15C4.10156 8.98242 8.98242 4.10156 15 4.10156C21.0176 4.10156 25.8984 8.98242 25.8984 15C25.8984 21.0176 21.0176 25.8984 15 25.8984Z" fill="white"/>
|
||||
<path d="M13.5938 9.84375C13.5938 10.2167 13.7419 10.5744 14.0056 10.8381C14.2694 11.1018 14.627 11.25 15 11.25C15.373 11.25 15.7306 11.1018 15.9944 10.8381C16.2581 10.5744 16.4062 10.2167 16.4062 9.84375C16.4062 9.47079 16.2581 9.1131 15.9944 8.84938C15.7306 8.58566 15.373 8.4375 15 8.4375C14.627 8.4375 14.2694 8.58566 14.0056 8.84938C13.7419 9.1131 13.5938 9.47079 13.5938 9.84375V9.84375ZM15.7031 13.125H14.2969C14.168 13.125 14.0625 13.2305 14.0625 13.3594V21.3281C14.0625 21.457 14.168 21.5625 14.2969 21.5625H15.7031C15.832 21.5625 15.9375 21.457 15.9375 21.3281V13.3594C15.9375 13.2305 15.832 13.125 15.7031 13.125Z" fill="white"/>
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 1.875C7.75195 1.875 1.875 7.75195 1.875 15C1.875 22.248 7.75195 28.125 15 28.125C22.248 28.125 28.125 22.248 28.125 15C28.125 7.75195 22.248 1.875 15 1.875ZM15 25.8984C8.98242 25.8984 4.10156 21.0176 4.10156 15C4.10156 8.98242 8.98242 4.10156 15 4.10156C21.0176 4.10156 25.8984 8.98242 25.8984 15C25.8984 21.0176 21.0176 25.8984 15 25.8984Z" fill="currentColor"/>
|
||||
<path d="M13.5938 9.84375C13.5938 10.2167 13.7419 10.5744 14.0056 10.8381C14.2694 11.1018 14.627 11.25 15 11.25C15.373 11.25 15.7306 11.1018 15.9944 10.8381C16.2581 10.5744 16.4062 10.2167 16.4062 9.84375C16.4062 9.47079 16.2581 9.1131 15.9944 8.84938C15.7306 8.58566 15.373 8.4375 15 8.4375C14.627 8.4375 14.2694 8.58566 14.0056 8.84938C13.7419 9.1131 13.5938 9.47079 13.5938 9.84375V9.84375ZM15.7031 13.125H14.2969C14.168 13.125 14.0625 13.2305 14.0625 13.3594V21.3281C14.0625 21.457 14.168 21.5625 14.2969 21.5625H15.7031C15.832 21.5625 15.9375 21.457 15.9375 21.3281V13.3594C15.9375 13.2305 15.832 13.125 15.7031 13.125Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
4
src/assets/icons/lyrics.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.39687 27.1131C9.04913 27.1131 9.53405 26.7937 10.3459 26.0807L14.3572 22.5294H21.4504C24.9784 22.5294 26.9547 20.503 26.9547 17.0251V7.93679C26.9547 4.45891 24.9784 2.4325 21.4504 2.4325H6.50218C2.97625 2.4325 1 4.4493 1 7.93679V17.0251C1 20.5126 3.02476 22.5294 6.42836 22.5294H6.9107V25.4181C6.9107 26.4533 7.45422 27.1131 8.39687 27.1131ZM8.96593 24.532V21.1863C8.96593 20.4986 8.67132 20.2335 8.01319 20.2335H6.56757C4.35225 20.2335 3.29592 19.1066 3.29592 16.9523V8.00007C3.29592 5.84569 4.35225 4.72842 6.56757 4.72842H21.3871C23.5928 4.72842 24.6588 5.84569 24.6588 8.00007V16.9523C24.6588 19.1066 23.5928 20.2335 21.3871 20.2335H14.2321C13.521 20.2335 13.1823 20.3484 12.6864 20.8497L8.96593 24.532Z" fill="currentColor"/>
|
||||
<path d="M8.2771 11.2555C8.2771 12.6189 9.13022 13.6523 10.4502 13.6523C10.9762 13.6523 11.4768 13.547 11.7878 13.1582H11.9174C11.5281 14.0908 10.6426 14.7372 9.78389 14.9491C9.37561 15.0578 9.23194 15.2495 9.23194 15.5329C9.23194 15.8427 9.4928 16.0804 9.83147 16.0804C11.0784 16.0804 13.4024 14.5982 13.4024 11.7334C13.4024 10.0933 12.3606 8.83142 10.776 8.83142C9.34444 8.83142 8.2771 9.8315 8.2771 11.2555ZM14.6327 11.2555C14.6327 12.6189 15.4837 13.6523 16.8058 13.6523C17.3317 13.6523 17.8302 13.547 18.1412 13.1582H18.273C17.8837 14.0908 16.9982 14.7372 16.1394 14.9491C15.7387 15.0578 15.5875 15.2495 15.5875 15.5329C15.5875 15.8427 15.8462 16.0804 16.1849 16.0804C17.4318 16.0804 19.7559 14.5982 19.7559 11.7334C19.7559 10.0933 18.7141 8.83142 17.1316 8.83142C15.6979 8.83142 14.6327 9.8315 14.6327 11.2555Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
4
src/assets/icons/lyrics2.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8.39687 27.1131C9.04913 27.1131 9.53405 26.7937 10.3459 26.0807L14.3572 22.5294H21.4504C24.9784 22.5294 26.9547 20.503 26.9547 17.0251V7.93679C26.9547 4.45891 24.9784 2.4325 21.4504 2.4325H6.50218C2.97625 2.4325 1 4.4493 1 7.93679V17.0251C1 20.5126 3.02476 22.5294 6.42836 22.5294H6.9107V25.4181C6.9107 26.4533 7.45422 27.1131 8.39687 27.1131ZM8.96593 24.532V21.1863C8.96593 20.4986 8.67132 20.2335 8.01319 20.2335H6.56757C4.35225 20.2335 3.29592 19.1066 3.29592 16.9523V8.00007C3.29592 5.84569 4.35225 4.72842 6.56757 4.72842H21.3871C23.5928 4.72842 24.6588 5.84569 24.6588 8.00007V16.9523C24.6588 19.1066 23.5928 20.2335 21.3871 20.2335H14.2321C13.521 20.2335 13.1823 20.3484 12.6864 20.8497L8.96593 24.532Z" fill="currentColor"/>
|
||||
<path d="M8.2771 11.2555C8.2771 12.6189 9.13022 13.6523 10.4502 13.6523C10.9762 13.6523 11.4768 13.547 11.7878 13.1582H11.9174C11.5281 14.0908 10.6426 14.7372 9.78389 14.9491C9.37561 15.0578 9.23194 15.2495 9.23194 15.5329C9.23194 15.8427 9.4928 16.0804 9.83147 16.0804C11.0784 16.0804 13.4024 14.5982 13.4024 11.7334C13.4024 10.0933 12.3606 8.83142 10.776 8.83142C9.34444 8.83142 8.2771 9.8315 8.2771 11.2555ZM14.6327 11.2555C14.6327 12.6189 15.4837 13.6523 16.8058 13.6523C17.3317 13.6523 17.8302 13.547 18.1412 13.1582H18.273C17.8837 14.0908 16.9982 14.7372 16.1394 14.9491C15.7387 15.0578 15.5875 15.2495 15.5875 15.5329C15.5875 15.8427 15.8462 16.0804 16.1849 16.0804C17.4318 16.0804 19.7559 14.5982 19.7559 11.7334C19.7559 10.0933 18.7141 8.83142 17.1316 8.83142C15.6979 8.83142 14.6327 9.8315 14.6327 11.2555Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 28 28" width="28px" height="28px">
|
||||
<g id="surface79432257">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 14 5.214844 C 13.210938 5.214844 12.5 5.625 12.105469 6.308594 L 4.253906 19.90625 C 3.859375 20.589844 3.859375 21.410156 4.253906 22.09375 C 4.652344 22.777344 5.359375 23.1875 6.148438 23.1875 L 21.851562 23.1875 C 22.640625 23.1875 23.347656 22.777344 23.746094 22.09375 C 24.140625 21.410156 24.140625 20.589844 23.746094 19.90625 L 15.894531 6.308594 C 15.5 5.625 14.789062 5.214844 14 5.214844 Z M 14 6.964844 C 14.074219 6.964844 14.265625 6.988281 14.378906 7.183594 L 22.230469 20.78125 C 22.34375 20.980469 22.269531 21.152344 22.230469 21.21875 C 22.191406 21.285156 22.078125 21.4375 21.851562 21.4375 L 6.148438 21.4375 C 5.921875 21.4375 5.808594 21.285156 5.769531 21.21875 C 5.730469 21.152344 5.65625 20.980469 5.769531 20.78125 L 13.621094 7.183594 C 13.734375 6.988281 13.925781 6.964844 14 6.964844 Z M 13.992188 10.058594 C 13.691406 10.058594 13.445312 10.152344 13.253906 10.335938 C 13.066406 10.523438 12.976562 10.753906 12.984375 11.03125 L 13.125 16.214844 C 13.140625 16.792969 13.433594 17.085938 14.007812 17.085938 C 14.5625 17.085938 14.84375 16.792969 14.851562 16.214844 L 15.015625 11.042969 C 15.023438 10.765625 14.929688 10.53125 14.730469 10.34375 C 14.535156 10.152344 14.289062 10.058594 13.992188 10.058594 Z M 14 18.238281 C 13.328125 18.238281 12.902344 18.808594 12.902344 19.289062 C 12.902344 19.769531 13.3125 20.339844 14 20.339844 C 14.6875 20.339844 15.097656 19.800781 15.097656 19.289062 C 15.097656 18.777344 14.671875 18.238281 14 18.238281 Z M 14 18.238281 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:currentColor;fill-opacity:1;" d="M 14 5.214844 C 13.210938 5.214844 12.5 5.625 12.105469 6.308594 L 4.253906 19.90625 C 3.859375 20.589844 3.859375 21.410156 4.253906 22.09375 C 4.652344 22.777344 5.359375 23.1875 6.148438 23.1875 L 21.851562 23.1875 C 22.640625 23.1875 23.347656 22.777344 23.746094 22.09375 C 24.140625 21.410156 24.140625 20.589844 23.746094 19.90625 L 15.894531 6.308594 C 15.5 5.625 14.789062 5.214844 14 5.214844 Z M 14 6.964844 C 14.074219 6.964844 14.265625 6.988281 14.378906 7.183594 L 22.230469 20.78125 C 22.34375 20.980469 22.269531 21.152344 22.230469 21.21875 C 22.191406 21.285156 22.078125 21.4375 21.851562 21.4375 L 6.148438 21.4375 C 5.921875 21.4375 5.808594 21.285156 5.769531 21.21875 C 5.730469 21.152344 5.65625 20.980469 5.769531 20.78125 L 13.621094 7.183594 C 13.734375 6.988281 13.925781 6.964844 14 6.964844 Z M 13.992188 10.058594 C 13.691406 10.058594 13.445312 10.152344 13.253906 10.335938 C 13.066406 10.523438 12.976562 10.753906 12.984375 11.03125 L 13.125 16.214844 C 13.140625 16.792969 13.433594 17.085938 14.007812 17.085938 C 14.5625 17.085938 14.84375 16.792969 14.851562 16.214844 L 15.015625 11.042969 C 15.023438 10.765625 14.929688 10.53125 14.730469 10.34375 C 14.535156 10.152344 14.289062 10.058594 13.992188 10.058594 Z M 14 18.238281 C 13.328125 18.238281 12.902344 18.808594 12.902344 19.289062 C 12.902344 19.769531 13.3125 20.339844 14 20.339844 C 14.6875 20.339844 15.097656 19.800781 15.097656 19.289062 C 15.097656 18.777344 14.671875 18.238281 14 18.238281 Z M 14 18.238281 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 28 28" width="28px" height="28px">
|
||||
<g id="surface79671233">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 14 4.375 C 8.683594 4.375 4.375 8.683594 4.375 14 C 4.375 19.316406 8.683594 23.625 14 23.625 C 19.316406 23.625 23.625 19.316406 23.625 14 C 23.625 8.683594 19.316406 4.375 14 4.375 Z M 14 6.125 C 18.347656 6.125 21.875 9.652344 21.875 14 C 21.875 18.347656 18.347656 21.875 14 21.875 C 9.652344 21.875 6.125 18.347656 6.125 14 C 6.125 9.652344 9.652344 6.125 14 6.125 Z M 14 9.1875 C 13.273438 9.1875 12.6875 9.773438 12.6875 10.5 C 12.6875 11.226562 13.273438 11.8125 14 11.8125 C 14.726562 11.8125 15.3125 11.226562 15.3125 10.5 C 15.3125 9.773438 14.726562 9.1875 14 9.1875 Z M 14 13.125 C 13.515625 13.125 13.125 13.515625 13.125 14 L 13.125 18.375 C 13.125 18.859375 13.515625 19.25 14 19.25 C 14.484375 19.25 14.875 18.859375 14.875 18.375 L 14.875 14 C 14.875 13.515625 14.484375 13.125 14 13.125 Z M 14 13.125 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:currentColor;fill-opacity:1;" d="M 14 4.375 C 8.683594 4.375 4.375 8.683594 4.375 14 C 4.375 19.316406 8.683594 23.625 14 23.625 C 19.316406 23.625 23.625 19.316406 23.625 14 C 23.625 8.683594 19.316406 4.375 14 4.375 Z M 14 6.125 C 18.347656 6.125 21.875 9.652344 21.875 14 C 21.875 18.347656 18.347656 21.875 14 21.875 C 9.652344 21.875 6.125 18.347656 6.125 14 C 6.125 9.652344 9.652344 6.125 14 6.125 Z M 14 9.1875 C 13.273438 9.1875 12.6875 9.773438 12.6875 10.5 C 12.6875 11.226562 13.273438 11.8125 14 11.8125 C 14.726562 11.8125 15.3125 11.226562 15.3125 10.5 C 15.3125 9.773438 14.726562 9.1875 14 9.1875 Z M 14 13.125 C 13.515625 13.125 13.125 13.515625 13.125 14 L 13.125 18.375 C 13.125 18.859375 13.515625 19.25 14 19.25 C 14.484375 19.25 14.875 18.859375 14.875 18.375 L 14.875 14 C 14.875 13.515625 14.484375 13.125 14 13.125 Z M 14 13.125 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 28 28" width="28px" height="28px">
|
||||
<g id="surface79451713">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 22.703125 15.355469 C 22.628906 15.773438 22.449219 16.152344 22.203125 16.476562 C 22.667969 17.058594 22.851562 17.796875 22.671875 18.554688 C 22.550781 19.082031 22.265625 19.539062 21.886719 19.886719 C 22.15625 20.265625 22.3125 20.71875 22.3125 21.203125 C 22.3125 22.542969 21.121094 23.59375 19.597656 23.59375 L 13.121094 23.613281 C 11.023438 23.613281 9.042969 22.796875 7.558594 21.316406 C 6.070312 19.828125 5.25 17.851562 5.25 15.75 C 5.25 11.828125 6.660156 10.425781 7.59375 9.5 L 7.695312 9.398438 C 7.988281 9.101562 8.527344 8.707031 9.210938 8.203125 C 10.59375 7.179688 12.914062 5.464844 13.339844 4.308594 C 13.683594 3.382812 14.109375 2.230469 15.75 2.230469 C 16.480469 2.230469 17.152344 2.683594 17.550781 3.445312 C 17.929688 4.175781 18.496094 6.132812 16.472656 9.625 L 19.757812 9.625 C 21.167969 9.625 22.3125 10.699219 22.3125 12.015625 C 22.3125 12.4375 22.191406 12.832031 21.984375 13.175781 C 22.574219 13.746094 22.851562 14.519531 22.703125 15.355469 Z M 19.597656 21.84375 C 20.121094 21.84375 20.5625 21.550781 20.5625 21.203125 C 20.5625 20.851562 20.203125 20.5625 19.757812 20.5625 L 17.9375 20.5625 C 17.453125 20.5625 17.0625 20.171875 17.0625 19.6875 C 17.0625 19.203125 17.453125 18.8125 17.9375 18.8125 L 20.226562 18.8125 C 20.609375 18.8125 20.894531 18.472656 20.96875 18.15625 C 21.015625 17.949219 21 17.679688 20.71875 17.441406 C 20.558594 17.476562 20.394531 17.5 20.226562 17.5 L 17.9375 17.5 C 17.453125 17.5 17.0625 17.109375 17.0625 16.625 C 17.0625 16.140625 17.453125 15.75 17.9375 15.75 L 20.226562 15.75 C 20.617188 15.75 20.917969 15.394531 20.980469 15.050781 C 21.046875 14.667969 20.820312 14.417969 20.542969 14.261719 C 20.25 14.355469 19.933594 14.40625 19.597656 14.40625 L 17.945312 14.4375 C 17.46875 14.4375 17.070312 14.054688 17.0625 13.578125 C 17.054688 13.09375 17.4375 12.695312 17.921875 12.6875 L 19.582031 12.65625 C 20.121094 12.65625 20.5625 12.363281 20.5625 12.015625 C 20.5625 11.664062 20.203125 11.375 19.757812 11.375 L 14.875 11.375 C 14.550781 11.375 14.25 11.195312 14.097656 10.902344 C 13.949219 10.613281 13.972656 10.265625 14.15625 10 C 16.121094 7.1875 16.417969 5.234375 16.039062 4.339844 C 15.921875 4.058594 15.777344 3.984375 15.742188 3.976562 C 15.386719 3.976562 15.328125 3.976562 14.984375 4.917969 C 14.375 6.5625 11.992188 8.324219 10.25 9.609375 C 9.675781 10.035156 9.136719 10.433594 8.929688 10.636719 L 8.828125 10.738281 C 8.015625 11.550781 7 12.554688 7 15.75 C 7 17.386719 7.636719 18.921875 8.792969 20.078125 C 9.949219 21.230469 11.488281 21.863281 13.117188 21.863281 Z M 19.597656 21.84375 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:currentColor;fill-opacity:1;" d="M 22.703125 15.355469 C 22.628906 15.773438 22.449219 16.152344 22.203125 16.476562 C 22.667969 17.058594 22.851562 17.796875 22.671875 18.554688 C 22.550781 19.082031 22.265625 19.539062 21.886719 19.886719 C 22.15625 20.265625 22.3125 20.71875 22.3125 21.203125 C 22.3125 22.542969 21.121094 23.59375 19.597656 23.59375 L 13.121094 23.613281 C 11.023438 23.613281 9.042969 22.796875 7.558594 21.316406 C 6.070312 19.828125 5.25 17.851562 5.25 15.75 C 5.25 11.828125 6.660156 10.425781 7.59375 9.5 L 7.695312 9.398438 C 7.988281 9.101562 8.527344 8.707031 9.210938 8.203125 C 10.59375 7.179688 12.914062 5.464844 13.339844 4.308594 C 13.683594 3.382812 14.109375 2.230469 15.75 2.230469 C 16.480469 2.230469 17.152344 2.683594 17.550781 3.445312 C 17.929688 4.175781 18.496094 6.132812 16.472656 9.625 L 19.757812 9.625 C 21.167969 9.625 22.3125 10.699219 22.3125 12.015625 C 22.3125 12.4375 22.191406 12.832031 21.984375 13.175781 C 22.574219 13.746094 22.851562 14.519531 22.703125 15.355469 Z M 19.597656 21.84375 C 20.121094 21.84375 20.5625 21.550781 20.5625 21.203125 C 20.5625 20.851562 20.203125 20.5625 19.757812 20.5625 L 17.9375 20.5625 C 17.453125 20.5625 17.0625 20.171875 17.0625 19.6875 C 17.0625 19.203125 17.453125 18.8125 17.9375 18.8125 L 20.226562 18.8125 C 20.609375 18.8125 20.894531 18.472656 20.96875 18.15625 C 21.015625 17.949219 21 17.679688 20.71875 17.441406 C 20.558594 17.476562 20.394531 17.5 20.226562 17.5 L 17.9375 17.5 C 17.453125 17.5 17.0625 17.109375 17.0625 16.625 C 17.0625 16.140625 17.453125 15.75 17.9375 15.75 L 20.226562 15.75 C 20.617188 15.75 20.917969 15.394531 20.980469 15.050781 C 21.046875 14.667969 20.820312 14.417969 20.542969 14.261719 C 20.25 14.355469 19.933594 14.40625 19.597656 14.40625 L 17.945312 14.4375 C 17.46875 14.4375 17.070312 14.054688 17.0625 13.578125 C 17.054688 13.09375 17.4375 12.695312 17.921875 12.6875 L 19.582031 12.65625 C 20.121094 12.65625 20.5625 12.363281 20.5625 12.015625 C 20.5625 11.664062 20.203125 11.375 19.757812 11.375 L 14.875 11.375 C 14.550781 11.375 14.25 11.195312 14.097656 10.902344 C 13.949219 10.613281 13.972656 10.265625 14.15625 10 C 16.121094 7.1875 16.417969 5.234375 16.039062 4.339844 C 15.921875 4.058594 15.777344 3.984375 15.742188 3.976562 C 15.386719 3.976562 15.328125 3.976562 14.984375 4.917969 C 14.375 6.5625 11.992188 8.324219 10.25 9.609375 C 9.675781 10.035156 9.136719 10.433594 8.929688 10.636719 L 8.828125 10.738281 C 8.015625 11.550781 7 12.554688 7 15.75 C 7 17.386719 7.636719 18.921875 8.792969 20.078125 C 9.949219 21.230469 11.488281 21.863281 13.117188 21.863281 Z M 19.597656 21.84375 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15.6328 35L8.625 10.3203H8.41406C8.74219 14.5234 8.90625 17.7891 8.90625 20.1172V35H0.703125V0.734375H13.0312L20.1797 25.0625H20.3672L27.375 0.734375H39.7266V35H31.2188V19.9766C31.2188 19.1953 31.2266 18.3281 31.2422 17.375C31.2734 16.4219 31.3828 14.0859 31.5703 10.3672H31.3594L24.4453 35H15.6328ZM76.2422 0.734375V21.3594C76.2422 25.8438 74.9688 29.3203 72.4219 31.7891C69.8906 34.2422 66.2344 35.4688 61.4531 35.4688C56.7812 35.4688 53.1875 34.2734 50.6719 31.8828C48.1719 29.4922 46.9219 26.0547 46.9219 21.5703V0.734375H56.2266V20.8438C56.2266 23.2656 56.6797 25.0234 57.5859 26.1172C58.4922 27.2109 59.8281 27.7578 61.5938 27.7578C63.4844 27.7578 64.8516 27.2188 65.6953 26.1406C66.5547 25.0469 66.9844 23.2656 66.9844 20.7969V0.734375H76.2422ZM105.844 24.5938C105.844 26.7188 105.305 28.6094 104.227 30.2656C103.148 31.9062 101.594 33.1875 99.5625 34.1094C97.5312 35.0156 95.1484 35.4688 92.4141 35.4688C90.1328 35.4688 88.2188 35.3125 86.6719 35C85.125 34.6719 83.5156 34.1094 81.8438 33.3125V25.0625C83.6094 25.9688 85.4453 26.6797 87.3516 27.1953C89.2578 27.6953 91.0078 27.9453 92.6016 27.9453C93.9766 27.9453 94.9844 27.7109 95.625 27.2422C96.2656 26.7578 96.5859 26.1406 96.5859 25.3906C96.5859 24.9219 96.4531 24.5156 96.1875 24.1719C95.9375 23.8125 95.5234 23.4531 94.9453 23.0938C94.3828 22.7344 92.8672 22 90.3984 20.8906C88.1641 19.875 86.4844 18.8906 85.3594 17.9375C84.25 16.9844 83.4219 15.8906 82.875 14.6562C82.3438 13.4219 82.0781 11.9609 82.0781 10.2734C82.0781 7.11719 83.2266 4.65625 85.5234 2.89062C87.8203 1.125 90.9766 0.242188 94.9922 0.242188C98.5391 0.242188 102.156 1.0625 105.844 2.70312L103.008 9.85156C99.8047 8.38281 97.0391 7.64844 94.7109 7.64844C93.5078 7.64844 92.6328 7.85938 92.0859 8.28125C91.5391 8.70312 91.2656 9.22656 91.2656 9.85156C91.2656 10.5234 91.6094 11.125 92.2969 11.6562C93 12.1875 94.8906 13.1562 97.9688 14.5625C100.922 15.8906 102.969 17.3203 104.109 18.8516C105.266 20.3672 105.844 22.2812 105.844 24.5938ZM111.141 35V0.734375H120.445V35H111.141ZM143.133 7.83594C140.93 7.83594 139.211 8.74219 137.977 10.5547C136.742 12.3516 136.125 14.8359 136.125 18.0078C136.125 24.6016 138.633 27.8984 143.648 27.8984C145.164 27.8984 146.633 27.6875 148.055 27.2656C149.477 26.8438 150.906 26.3359 152.344 25.7422V33.5703C149.484 34.8359 146.25 35.4688 142.641 35.4688C137.469 35.4688 133.5 33.9688 130.734 30.9688C127.984 27.9688 126.609 23.6328 126.609 17.9609C126.609 14.4141 127.273 11.2969 128.602 8.60938C129.945 5.92188 131.867 3.85938 134.367 2.42188C136.883 0.96875 139.836 0.242188 143.227 0.242188C146.93 0.242188 150.469 1.04688 153.844 2.65625L151.008 9.94531C149.742 9.35156 148.477 8.85156 147.211 8.44531C145.945 8.03906 144.586 7.83594 143.133 7.83594Z" fill="white"/>
|
||||
<path d="M160.289 14.4453C159.008 12.5859 158.367 9.9375 158.367 6.5V3.10156H167.602L185.039 22.2031C186.023 23.2969 186.68 24.2188 187.008 24.9688C187.336 25.7188 187.578 26.4062 187.734 27.0312C188.078 28.2812 188.25 29.8203 188.25 31.6484V35H179.273L160.359 14.4453H160.289ZM178.102 13.1094C178.102 8.8125 178.766 6.01562 180.094 4.71875C180.797 4.03125 181.711 3.59375 182.836 3.40625C183.977 3.20312 185.359 3.10156 186.984 3.10156H188.25V6.80469C188.25 10.1328 187.375 12.3594 185.625 13.4844C184.375 14.2969 182.273 14.7031 179.32 14.7031H178.102V13.1094ZM158.367 31.3438C158.367 28.0156 159.242 25.7812 160.992 24.6406C162.242 23.8438 164.344 23.4453 167.297 23.4453H168.516V25.0391C168.516 29.3828 167.852 32.1719 166.523 33.4062C165.617 34.25 164.297 34.75 162.562 34.9062C161.688 34.9688 160.711 35 159.633 35H158.367V31.3438Z" fill="#4AD168"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.6 KiB |
@@ -5,7 +5,6 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
|
||||
padding: 0 1rem;
|
||||
padding-bottom: 4rem;
|
||||
overflow: auto;
|
||||
max-height: 100%;
|
||||
gap: 2rem 1rem;
|
||||
|
||||
@@ -2,92 +2,26 @@ $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;
|
||||
}
|
||||
}
|
||||
|
||||
#acontent {
|
||||
width: 100%;
|
||||
grid-area: content;
|
||||
padding-right: calc($medium);
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vue-recycle-scroller__item-wrapper {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.vue-recycle-scroller {
|
||||
padding-left: 1.25rem;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.r-sidebar {
|
||||
grid-area: r-sidebar;
|
||||
border-left: $g-border;
|
||||
}
|
||||
|
||||
.topnav {
|
||||
grid-area: nav;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.b-bar {
|
||||
grid-area: bottombar;
|
||||
border-top: $g-border;
|
||||
}
|
||||
|
||||
.content-page {
|
||||
margin-left: 1.25rem;
|
||||
margin-right: -$medium;
|
||||
padding-right: $medium;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
// ====== MODIFIERS =======
|
||||
|
||||
#app-grid.extendWidth {
|
||||
padding-right: 0;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
#app-grid.noSidebar {
|
||||
grid-template-columns: min-content 1fr;
|
||||
grid-template-areas:
|
||||
"l-sidebar nav"
|
||||
"l-sidebar content"
|
||||
"bottombar bottombar";
|
||||
|
||||
#acontent {
|
||||
margin-right: 0 !important;
|
||||
padding-right: $medium !important;
|
||||
}
|
||||
|
||||
.topnav {
|
||||
//reduce width to match #acontent
|
||||
width: calc(100% - 1rem);
|
||||
padding-right: 0;
|
||||
#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"
|
||||
@@ -96,25 +30,177 @@ $g-border: solid 1px $gray5;
|
||||
}
|
||||
}
|
||||
|
||||
#app-grid.NoSideBorders {
|
||||
border-right: none;
|
||||
#acontent {
|
||||
width: 100%;
|
||||
grid-area: content;
|
||||
overflow: hidden;
|
||||
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;
|
||||
padding-right: $padright;
|
||||
padding-bottom: $padbottom;
|
||||
}
|
||||
|
||||
.vue-recycle-scroller__item-wrapper {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.vue-recycle-scroller {
|
||||
scrollbar-gutter: stable;
|
||||
padding-left: $padleft;
|
||||
}
|
||||
|
||||
.r-sidebar {
|
||||
grid-area: r-sidebar;
|
||||
border-left: $g-border;
|
||||
|
||||
.vue-recycle-scroller {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ====== 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;
|
||||
border-right: none;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
#app-grid.useSidebar {
|
||||
grid-template-columns: min-content 1fr 28rem;
|
||||
grid-template-areas:
|
||||
"l-sidebar nav r-sidebar"
|
||||
"l-sidebar content r-sidebar"
|
||||
"bottombar bottombar bottombar";
|
||||
|
||||
@include for-desktop-down {
|
||||
grid-template-columns: min-content 1fr 24rem;
|
||||
}
|
||||
|
||||
#acontent {
|
||||
// margin-right: 0 !important;
|
||||
// padding-right: $medium !important;
|
||||
}
|
||||
}
|
||||
|
||||
#app-grid.NoSideBorders {
|
||||
border-right: none !important;
|
||||
border-left: none !important;
|
||||
}
|
||||
|
||||
.v-scroll-page {
|
||||
width: calc(100% + $medium) !important;
|
||||
|
||||
.scroller {
|
||||
padding-right: $padright;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding-right: 1.25rem;
|
||||
padding-bottom: $content-padding-bottom;
|
||||
padding-bottom: $padbottom;
|
||||
}
|
||||
}
|
||||
|
||||
.isSmall {
|
||||
.songlist-item {
|
||||
grid-template-columns: 2fr 5.5rem;
|
||||
grid-template-columns: 2fr 5.5rem !important;
|
||||
}
|
||||
|
||||
.song-artists,
|
||||
|
||||
@@ -28,7 +28,7 @@ input[type="search"]::-webkit-search-cancel-button {
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-size: 2rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -140,7 +140,6 @@ button {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
// NO THIS, NO THAT (OVERRIDES)
|
||||
.no-border {
|
||||
border: none;
|
||||
}
|
||||
@@ -170,3 +169,75 @@ button {
|
||||
position: absolute;
|
||||
left: -20rem;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: solid 3px rgb(0, 0, 0);
|
||||
border-top: solid 3px transparent;
|
||||
border-left: solid 3px transparent;
|
||||
border-radius: 50%;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
animation: spin 0.45s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.card-list-scroll-x {
|
||||
overflow: hidden;
|
||||
|
||||
h3 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
align-items: baseline;
|
||||
padding: 0 $medium;
|
||||
margin-bottom: $medium;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
flex-direction: row;
|
||||
padding-bottom: 2rem;
|
||||
|
||||
@include hideScrollbars;
|
||||
}
|
||||
|
||||
.album-card {
|
||||
&:hover {
|
||||
background-color: $gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rhelp {
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
color: $purple;
|
||||
font-weight: bold;
|
||||
margin: $smaller 0;
|
||||
|
||||
&.album {
|
||||
color: $orange;
|
||||
}
|
||||
|
||||
&.track {
|
||||
color: $pink;
|
||||
}
|
||||
|
||||
&.folder {
|
||||
color: $teal;
|
||||
}
|
||||
|
||||
&.playlist {
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
|
||||
@mixin tablet-portrait {
|
||||
@media (max-width: 810) {
|
||||
@media (max-width: 810px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,14 +26,14 @@ $gray2: rgb(99, 99, 102);
|
||||
$gray3: rgb(72, 72, 74);
|
||||
$gray4: rgb(58, 58, 60);
|
||||
$gray5: rgb(44, 44, 46);
|
||||
$body: rgba(0, 0, 0, 0.95);
|
||||
$body: #111111;
|
||||
|
||||
$red: #ff453a;
|
||||
$blue: #0a84ff;
|
||||
$darkblue: #055ee2;
|
||||
$green: rgb(20, 160, 55);
|
||||
$green: rgb(94, 247, 132);
|
||||
$yellow: rgb(255, 214, 10);
|
||||
$orange: rgb(255, 159, 10);
|
||||
$orange: #ff9f0a;
|
||||
$pink: rgb(255, 55, 95);
|
||||
$purple: #bf5af2;
|
||||
$brown: rgb(172, 142, 104);
|
||||
@@ -55,3 +55,27 @@ $side-nav-svg: $red;
|
||||
$overshoot: cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
|
||||
$separator: $gray4;
|
||||
|
||||
$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);
|
||||
|
||||
$padleft: clamp(2rem, $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);
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
"./variables",
|
||||
"./ProgressBar.scss",
|
||||
"./BottomBar/BottomBar.scss",
|
||||
"./Global",
|
||||
"./moz.scss"
|
||||
"./Global"
|
||||
;
|
||||
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// Styles that only apply to our dear Firefox
|
||||
|
||||
@-moz-document url-prefix() {
|
||||
#acontent {
|
||||
margin-right: calc(-1rem + 1px);
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
// applies to playlist list page
|
||||
.content-page {
|
||||
margin-right: calc(0rem - ($medium + 4px));
|
||||
}
|
||||
|
||||
// virtual scroller pages: folder, playlist, album
|
||||
.header-list-layout {
|
||||
margin-right: calc(0rem - ($medium + 4px)) !important;
|
||||
|
||||
.scrollable {
|
||||
padding-right: calc(1rem - 3px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.v-scroll-page {
|
||||
width: calc(100% + 1rem) !important;
|
||||
|
||||
.scroller {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
// padding-right: 1.25rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
#app-grid.noSidebar > #acontent {
|
||||
padding-right: 1rem !important;
|
||||
}
|
||||
|
||||
.search-view {
|
||||
margin-right: -1rem !important;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="album_disc_header no-select"
|
||||
v-if="album_disc.is_album_disc_number"
|
||||
class="album_disc_header no-select"
|
||||
>
|
||||
<div class="disc_number">Disc {{ album_disc.album_page_disc_number }}</div>
|
||||
<div></div>
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<div class="card-list-scroll-x">
|
||||
<h3>
|
||||
<span>{{ title }}</span>
|
||||
<SeeAll
|
||||
v-if="route && maxAbumCards - 1 <= albums.length"
|
||||
:route="route"
|
||||
@click="
|
||||
!favorites ? useArtistDiscographyStore().setPage(albumType) : null
|
||||
"
|
||||
/>
|
||||
</h3>
|
||||
<div ref="artistItemsWrappers" class="cards">
|
||||
<AlbumCard
|
||||
v-for="a in albums"
|
||||
:key="a.albumhash"
|
||||
:album="a"
|
||||
:show_date="show_date"
|
||||
:artist_page="artist_page"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
import { Album } from "@/interfaces";
|
||||
import { discographyAlbumTypes } from "@/enums";
|
||||
import { maxAbumCards } from "@/stores/content-width";
|
||||
import useArtistDiscographyStore from "@/stores/pages/artistDiscog";
|
||||
|
||||
import AlbumCard from "../shared/AlbumCard.vue";
|
||||
import SeeAll from "../shared/SeeAll.vue";
|
||||
|
||||
defineProps<{
|
||||
title: string;
|
||||
albums: Album[];
|
||||
albumType?: discographyAlbumTypes;
|
||||
favorites?: boolean;
|
||||
route?: string;
|
||||
show_date?: boolean;
|
||||
artist_page?: boolean;
|
||||
}>();
|
||||
|
||||
const artistItemsWrappers = ref<HTMLElement | null>(null);
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.card-list-scroll-x {
|
||||
overflow: hidden;
|
||||
|
||||
h3 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
align-items: baseline;
|
||||
padding: 0 $medium;
|
||||
margin-bottom: $medium;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow-x: auto;
|
||||
scroll-snap-type: x mandatory;
|
||||
flex-direction: row;
|
||||
padding-bottom: 2rem;
|
||||
|
||||
@include hideScrollbars;
|
||||
}
|
||||
|
||||
.album-card {
|
||||
&:hover {
|
||||
background-color: $gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
<HeartSvg
|
||||
:state="album.is_favorite"
|
||||
@handleFav="handleFav"
|
||||
:color="colors.bg ? colors.bg : ''"
|
||||
@handleFav="handleFav"
|
||||
/>
|
||||
<button
|
||||
class="options"
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
|
||||
import { Album } from "@/interfaces";
|
||||
import { formatSeconds } from "@/utils";
|
||||
import { isSmallPhone } from "@/stores/content-width";
|
||||
|
||||
import ArtistName from "@/components/shared/ArtistName.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
album: Album;
|
||||
@@ -33,7 +34,9 @@ const statsText = computed(() => {
|
||||
// hide track count if it's a single, also add an s to track if it's plural
|
||||
return `• ${props.album.date} ${
|
||||
!is_single
|
||||
? `• ${props.album.count} Track${props.album.count > 1 ? "s" : ""}`
|
||||
? `• ${props.album.count.toLocaleString()} Track${
|
||||
props.album.count > 1 ? "s" : ""
|
||||
}`
|
||||
: ""
|
||||
} • ${formatSeconds(props.album.duration, true)}`;
|
||||
});
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
import { paths } from "@/config";
|
||||
import { isHeaderSmall } from "@/stores/content-width";
|
||||
|
||||
@@ -1,21 +1,35 @@
|
||||
<template>
|
||||
<div style="height: 1px"></div>
|
||||
<div style="height: 1px">
|
||||
<button v-if="show_text" @click="fetch_callback">Load More</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
import { nextTick, onMounted } from "vue";
|
||||
import { onBeforeRouteUpdate } from "vue-router";
|
||||
|
||||
import { updateCardWidth } from "@/stores/content-width";
|
||||
|
||||
const props = defineProps<{
|
||||
fetch_callback: () => void;
|
||||
reset_callback: () => void;
|
||||
show_text?: boolean;
|
||||
fetch_callback: () => Promise<void>;
|
||||
reset_callback?: () => Promise<void>;
|
||||
outside_route?: boolean;
|
||||
}>();
|
||||
|
||||
const update = async () => {
|
||||
await nextTick();
|
||||
|
||||
updateCardWidth();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
props.fetch_callback();
|
||||
props.fetch_callback().then(update);
|
||||
});
|
||||
|
||||
onBeforeRouteUpdate(() => {
|
||||
props.reset_callback();
|
||||
});
|
||||
!props.outside_route &&
|
||||
onBeforeRouteUpdate(() => {
|
||||
if (!props.reset_callback) return;
|
||||
props.reset_callback().then(update);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -38,7 +38,12 @@
|
||||
class="gradient"
|
||||
:style="{
|
||||
backgroundImage: colors.bg
|
||||
? `linear-gradient(${gradientDirection}, transparent 20%,
|
||||
? `linear-gradient(${gradientDirection}, transparent ${
|
||||
isSmall
|
||||
? 60
|
||||
: gradientTransparentWidth -
|
||||
(width < 700 ? 40 : width < 900 ? 20 : 10)
|
||||
}%,
|
||||
${colors.bg} ${gradientWidth}%,
|
||||
${colors.bg} 100%)`
|
||||
: '',
|
||||
@@ -49,7 +54,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
import { Ref, computed, onMounted, ref } from "vue";
|
||||
import { onBeforeRouteUpdate } from "vue-router";
|
||||
import { useElementSize } from "@vueuse/core";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
@@ -60,7 +65,9 @@ import updatePageTitle from "@/utils/updatePageTitle";
|
||||
import Info from "./HeaderComponents/Info.vue";
|
||||
import useArtistStore from "@/stores/pages/artist";
|
||||
import { getShift } from "@/utils/colortools/shift";
|
||||
import { isSmall } from "@/stores/content-width";
|
||||
|
||||
const image_width_px = 450;
|
||||
const store = useArtistStore();
|
||||
const settings = useSettingsStore();
|
||||
|
||||
@@ -77,13 +84,16 @@ function updateTitle() {
|
||||
onMounted(() => updateTitle());
|
||||
onBeforeRouteUpdate(() => updateTitle());
|
||||
|
||||
const artistheader = ref(null);
|
||||
const artistheader: Ref<HTMLElement | null> = ref(null);
|
||||
const { width } = useElementSize(artistheader);
|
||||
|
||||
const gradientTransparentWidth = computed(() =>
|
||||
Math.floor((image_width_px / (width.value || 1)) * 100)
|
||||
);
|
||||
|
||||
const isSmallPhone = computed(() => width.value <= 550);
|
||||
const useCircularImage = computed(
|
||||
() =>
|
||||
!isSmallPhone.value && (settings.useCircularArtistImg || width.value >= 995)
|
||||
() => !isSmallPhone.value && settings.useCircularArtistImg
|
||||
);
|
||||
|
||||
const gradientDirection = computed(() =>
|
||||
@@ -91,7 +101,7 @@ const gradientDirection = computed(() =>
|
||||
);
|
||||
|
||||
const gradientWidth = computed(() => {
|
||||
return isSmallPhone.value ? "80" : "50";
|
||||
return isSmallPhone.value ? "80" : Math.min(gradientTransparentWidth.value, 50);
|
||||
});
|
||||
|
||||
const containerHeight = computed(() => {
|
||||
@@ -109,7 +119,7 @@ const containerHeight = computed(() => {
|
||||
|
||||
.artist-page-header {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr minmax(min-content, 50%);
|
||||
grid-template-columns: 1fr 450px;
|
||||
position: relative;
|
||||
|
||||
.artist-img {
|
||||
@@ -123,7 +133,6 @@ const containerHeight = computed(() => {
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
object-position: 0% 20%;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,18 +14,19 @@
|
||||
<div
|
||||
class="artist-name"
|
||||
:class="`${useCircularImage ? 'ellip' : 'ellip2'}`"
|
||||
:title="artist.name"
|
||||
>
|
||||
{{ artist.name }}
|
||||
</div>
|
||||
<div class="stats">
|
||||
<span v-if="artist.trackcount">
|
||||
{{ artist.trackcount }} Track{{
|
||||
{{ artist.trackcount.toLocaleString() }} Track{{
|
||||
`${artist.trackcount == 1 ? "" : "s"}`
|
||||
}}
|
||||
</span>
|
||||
{{ artist.albumcount && artist.trackcount ? "•" : "" }}
|
||||
{{ artist.albumcount && artist.trackcount.toLocaleString() ? "•" : "" }}
|
||||
<span v-if="artist.albumcount">
|
||||
{{ artist.albumcount }} Album{{
|
||||
{{ artist.albumcount.toLocaleString() }} Album{{
|
||||
`${artist.albumcount == 1 ? "" : "s"}`
|
||||
}}
|
||||
</span>
|
||||
@@ -77,7 +78,7 @@ defineProps<{
|
||||
.artist-name {
|
||||
font-size: 3.5rem;
|
||||
font-weight: bold;
|
||||
word-wrap: break-word;
|
||||
word-wrap: break-all;
|
||||
}
|
||||
|
||||
.stats {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
<template>
|
||||
<div class="artists-view rounded">
|
||||
<div class="al-header">
|
||||
<div class="heading">ALL ARTISTS</div>
|
||||
<div class="search">
|
||||
<input type="search" placeholder="Search artists" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="all-albums">
|
||||
<div class="item rounded" v-for="artist in artists" :key="artist">
|
||||
<div class="album-art image rounded"></div>
|
||||
<div class="name t-center ellip">{{ artist.name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
setup() {
|
||||
const artists = [
|
||||
{
|
||||
name: "Juice Wrld",
|
||||
},
|
||||
{
|
||||
name: "Eminem",
|
||||
},
|
||||
{
|
||||
name: "Sting",
|
||||
},
|
||||
{
|
||||
name: "Juice Wrld",
|
||||
},
|
||||
{
|
||||
name: "Eminem",
|
||||
},
|
||||
{
|
||||
name: "Sting",
|
||||
},
|
||||
{
|
||||
name: "Juice Wrld",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
artists,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.artists-view {
|
||||
height: calc(100% - 14.5rem);
|
||||
padding: $small;
|
||||
margin-top: $small;
|
||||
overflow: hidden;
|
||||
|
||||
.all-albums {
|
||||
height: calc(100% - 4rem);
|
||||
border-top: 1px solid $separator;
|
||||
padding: $small 0 0 0;
|
||||
overflow-y: auto;
|
||||
|
||||
.item {
|
||||
position: relative;
|
||||
width: 10.9rem;
|
||||
height: 12rem;
|
||||
padding: $small 0.95rem $small 0.95rem;
|
||||
margin: $smaller;
|
||||
transition: all 0.2s ease-in-out;
|
||||
cursor: default;
|
||||
float: left;
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(74, 84, 87);
|
||||
// border: solid
|
||||
}
|
||||
|
||||
.album-art {
|
||||
height: 9em;
|
||||
width: 9em;
|
||||
border-radius: 50%;
|
||||
background-image: url(../../assets/images/null.webp);
|
||||
}
|
||||
|
||||
.name {
|
||||
margin-top: $small;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.al-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
height: 4em;
|
||||
padding: 0 $small 0 $small;
|
||||
|
||||
.search {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
padding-right: $small;
|
||||
|
||||
input {
|
||||
width: 30rem;
|
||||
border: none;
|
||||
border-radius: 2rem;
|
||||
padding-left: 1rem;
|
||||
background-color: #4645456c;
|
||||
color: rgba(255, 255, 255, 0.521);
|
||||
font-size: 1rem;
|
||||
line-height: 3rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input::-webkit-search-cancel-button {
|
||||
position: relative;
|
||||
right: 20px;
|
||||
cursor: default;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,160 +0,0 @@
|
||||
<template>
|
||||
<div class="top-artists">
|
||||
<div class="heading">TOP ARTISTS</div>
|
||||
<div class="items">
|
||||
<div class="item rounded" v-for="artist in artists" :key="artist">
|
||||
<div class="image"></div>
|
||||
<div class="info">
|
||||
<div class="name ellip">{{ artist.name }}</div>
|
||||
<div class="artist ellip">{{ artist.album_count }} Albums</div>
|
||||
<div class="separator"></div>
|
||||
<div class="top">
|
||||
<div class="play-icon"></div>
|
||||
<div class="text ellip">{{ artist.top_track }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script >
|
||||
export default {
|
||||
setup() {
|
||||
const artists = [
|
||||
{
|
||||
name: "Sting",
|
||||
album_count: "12",
|
||||
top_track: "Alien in Newyork",
|
||||
},
|
||||
{
|
||||
name: "Juice Wrld",
|
||||
album_count: "4",
|
||||
top_track: "Girl Of My Dreams",
|
||||
},
|
||||
{
|
||||
name: "Lil Peep",
|
||||
album_count: "6",
|
||||
top_track: "Haunt U",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
artists,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.top-artists {
|
||||
height: 14rem;
|
||||
border-radius: $small;
|
||||
padding: $small;
|
||||
|
||||
.heading {
|
||||
margin: $small 0 1.5em $small;
|
||||
}
|
||||
|
||||
.items {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-gap: $small;
|
||||
|
||||
.item {
|
||||
height: 10rem;
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
background-color: rgb(7, 6, 6);
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: 7.5rem 1fr;
|
||||
padding: $small;
|
||||
cursor: default;
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-.5em);
|
||||
}
|
||||
|
||||
.image {
|
||||
height: 7rem;
|
||||
width: 7rem;
|
||||
// background-image: url("../../assets/images/null.webp");
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.info .name {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.info .artist {
|
||||
font-size: small;
|
||||
color: rgba(255, 255, 255, 0.699);
|
||||
}
|
||||
|
||||
.info .top {
|
||||
height: 2.5rem;
|
||||
background-color: rgb(51, 129, 20);
|
||||
border-radius: $small;
|
||||
margin-left: auto;
|
||||
display: grid;
|
||||
grid-template-columns: 2.5rem 1fr;
|
||||
align-items: center;
|
||||
transition: all 0.2s ease-in-out;
|
||||
user-select: none;
|
||||
|
||||
.play-icon {
|
||||
margin: 0 0 0 $small;
|
||||
height: 1.2rem;
|
||||
width: 1.2rem;
|
||||
background-image: url(../../assets/icons/play.svg);
|
||||
background-size: 96%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgb(0, 134, 89);
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
.play-icon {
|
||||
transform: scale(1.2);
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
}
|
||||
&:first-child {
|
||||
background-color: rgb(177, 116, 2);
|
||||
|
||||
.image {
|
||||
background-image: url("../../assets/images/null.webp");
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(2){
|
||||
background-color: rgb(0, 74, 117);
|
||||
|
||||
.image {
|
||||
background-image: url("../../assets/images/null.webp");
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3){
|
||||
background-color: rgb(161, 106, 106);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -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 {
|
||||
|
||||
@@ -10,16 +10,17 @@
|
||||
title="Go to Now Playing"
|
||||
:to="{
|
||||
name: Routes.nowPlaying,
|
||||
query: {
|
||||
tab: 'queue',
|
||||
params: {
|
||||
tab: 'home',
|
||||
},
|
||||
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"
|
||||
@@ -61,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();
|
||||
@@ -79,12 +81,36 @@ 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: opacity 0.2s;
|
||||
|
||||
svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
height: 3rem;
|
||||
a {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.heart-button {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="right-group">
|
||||
<LyricsButton v-if="settings.use_lyrics_plugin || lyrics.exists" />
|
||||
<Volume />
|
||||
<button
|
||||
class="repeat"
|
||||
@@ -16,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')"
|
||||
/>
|
||||
@@ -25,16 +30,20 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useQStore from "@/stores/queue";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
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 ShuffleSvg from "@/assets/icons/shuffle.svg";
|
||||
|
||||
const queue = useQStore();
|
||||
const settings = useSettingsStore();
|
||||
const queue = useQueue();
|
||||
const lyrics = useLyrics();
|
||||
const settings = useSettings();
|
||||
|
||||
defineProps<{
|
||||
hideHeart?: boolean;
|
||||
@@ -49,7 +58,7 @@ defineEmits<{
|
||||
.right-group {
|
||||
display: grid;
|
||||
justify-content: flex-end;
|
||||
grid-template-columns: repeat(3, max-content);
|
||||
grid-template-columns: repeat(5, max-content);
|
||||
align-items: center;
|
||||
height: 4rem;
|
||||
|
||||
@@ -69,9 +78,8 @@ defineEmits<{
|
||||
}
|
||||
}
|
||||
|
||||
button.repeat {
|
||||
background-color: transparent;
|
||||
|
||||
.lyrics,
|
||||
.repeat {
|
||||
svg {
|
||||
transform: scale(0.75);
|
||||
}
|
||||
@@ -82,5 +90,9 @@ defineEmits<{
|
||||
opacity: 0.25;
|
||||
}
|
||||
}
|
||||
|
||||
.heart-button {
|
||||
border: solid 1px $gray4 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -34,7 +34,6 @@ const changeVolume = (event: Event) => {
|
||||
};
|
||||
|
||||
const handleMouseWheel = (event: WheelEvent) => {
|
||||
event.preventDefault();
|
||||
const delta = event.deltaY / 1000;
|
||||
let newVolume = settings.volume - delta / 3;
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ context.$subscribe((mutation, state) => {
|
||||
transform: scale(0);
|
||||
height: min-content;
|
||||
|
||||
padding: $medium;
|
||||
padding: $small;
|
||||
background: $context;
|
||||
transform-origin: top left;
|
||||
font-size: 0.875rem;
|
||||
|
||||
@@ -147,7 +147,7 @@ function runChildAction(action: () => void) {
|
||||
transform: scale(0);
|
||||
background-color: $context;
|
||||
transform: scale(0);
|
||||
padding: $medium;
|
||||
padding: $small;
|
||||
border: solid 1px $gray3;
|
||||
|
||||
max-height: calc(100vh / 2);
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<template>
|
||||
<div class="card-list-scroll-x">
|
||||
<h3>Recent</h3>
|
||||
<div ref="recentitemswrappers" class="cards">
|
||||
<Recentsitemcard
|
||||
v-for="fav in favs.slice(0, maxAbumCards)"
|
||||
:key="JSON.stringify(fav)"
|
||||
:fav="fav"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
import Recentsitemcard from "@/components/Favorites/RecentsItemCard.vue";
|
||||
import { RecentFavResult } from "@/interfaces";
|
||||
import { maxAbumCards } from "@/stores/content-width";
|
||||
|
||||
defineProps<{
|
||||
favs: RecentFavResult[];
|
||||
}>();
|
||||
|
||||
const recentitemswrappers = ref<HTMLElement | null>(null);
|
||||
</script>
|
||||
@@ -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%;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="breadcrumb-nav">
|
||||
<div
|
||||
class="path"
|
||||
v-for="path in subPaths"
|
||||
:key="path.path"
|
||||
class="path"
|
||||
:class="{ inthisfolder: path.active }"
|
||||
@click.prevent="$emit('navigate', path.path)"
|
||||
>
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
<template>
|
||||
<router-link :to="{ name: Routes.folder, params: { path: folder.path } }">
|
||||
<div
|
||||
v-auto-animate
|
||||
class="f-item"
|
||||
@click="(e) => (folder_page ? null : handleClick(e))"
|
||||
@mouseover="mouse_over = true"
|
||||
@mouseleave="mouse_over = false"
|
||||
:style="{
|
||||
backgroundColor: is_checked ? '#234ece' : '',
|
||||
}"
|
||||
:class="{ context_menu_showing }"
|
||||
@click="(e) => (folder_page ? null : handleClick(e))"
|
||||
@mouseover="mouse_over = true"
|
||||
@mouseleave="mouse_over = false"
|
||||
@contextmenu.prevent="(e) => (!folder_page ? null : showContextMenu(e))"
|
||||
v-auto-animate
|
||||
>
|
||||
<SymLinkSvg v-if="folder.is_sym" />
|
||||
<FolderSvg v-else />
|
||||
<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 v-if="folder.count" class="f-count">
|
||||
{{ folder.count.toLocaleString() + ` File${folder.count == 1 ? "" : "s"}` }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="check" v-if="!folder_page">
|
||||
<div v-if="!folder_page" class="check">
|
||||
<CheckSvg v-if="!is_checked && mouse_over" />
|
||||
<CheckFilledSvg v-if="is_checked" />
|
||||
</div>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<h1>This is your Homepage</h1>
|
||||
</template>
|
||||
84
src/components/HomeView/Browse.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div class="homebrowse">
|
||||
<div class="btitle"><b>Browse Library</b></div>
|
||||
<div class="browselist">
|
||||
<RouterLink
|
||||
v-for="i in browselist"
|
||||
:key="i.title"
|
||||
class="browseitem rounded-sm t-center"
|
||||
:to="{ name: i.route, params: i.params }"
|
||||
:style="{ width: `${album_card_with - 24}px` }"
|
||||
>
|
||||
{{ i.title }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Routes } from "@/router";
|
||||
import { album_card_with } from "@/stores/content-width";
|
||||
|
||||
const browselist = [
|
||||
{
|
||||
title: "Folders",
|
||||
route: Routes.folder,
|
||||
params: {
|
||||
path: "$home",
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Albums",
|
||||
route: Routes.AlbumList,
|
||||
},
|
||||
{
|
||||
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: 1.5rem 0;
|
||||
padding-left: $small;
|
||||
|
||||
.btitle {
|
||||
font-size: 1.15rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.browselist {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
margin-top: $small;
|
||||
}
|
||||
|
||||
.browseitem {
|
||||
padding: 1.5rem 0;
|
||||
background-color: $gray;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.browseitem:hover {
|
||||
background-color: $gray5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
66
src/components/HomeView/Numbers.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div class="home-count">
|
||||
<div
|
||||
class="count-item"
|
||||
:title="count.count.toLocaleString()"
|
||||
v-for="count in parseCount()"
|
||||
:key="count.text"
|
||||
>
|
||||
<div>{{ count.formatted }}</div>
|
||||
<div class="text">{{ count.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
counts: { [key: string]: number }[];
|
||||
}>();
|
||||
|
||||
function formatNumber(num: number) {
|
||||
if (num >= 1e6) {
|
||||
return [(num / 1e6).toFixed(1), "m"];
|
||||
} else if (num >= 1e3) {
|
||||
return [(num / 1e3).toFixed(1), "k"];
|
||||
} else {
|
||||
return [num.toString(), ""];
|
||||
}
|
||||
}
|
||||
|
||||
function parseCount() {
|
||||
return props.counts.map((count) => {
|
||||
const num = count[Object.keys(count)[0]];
|
||||
const formatted = formatNumber(count[Object.keys(count)[0]]);
|
||||
|
||||
return {
|
||||
count: num,
|
||||
formatted: formatted[0] + formatted[1],
|
||||
text: Object.keys(count)[0],
|
||||
};
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.home-count {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
.count-item {
|
||||
font-size: 3rem;
|
||||
font-weight: bolder;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: solid 1px $gray5;
|
||||
padding-right: 1rem;
|
||||
|
||||
.text {
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
text-transform: uppercase;
|
||||
margin-left: $smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -17,10 +17,10 @@
|
||||
}"
|
||||
></div>
|
||||
<div
|
||||
v-else
|
||||
id="home-button"
|
||||
class="nav-button"
|
||||
:class="{ active: $route.name === menu.route_name }"
|
||||
id="home-button"
|
||||
v-else
|
||||
>
|
||||
<div class="in">
|
||||
<component :is="menu.icon"></component>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="bitrate"
|
||||
v-if="q.currenttrack?.bitrate"
|
||||
class="bitrate"
|
||||
title="file type • bitrate"
|
||||
>
|
||||
{{ q.currenttrack.filepath?.split('.').pop() }} • {{ q.currenttrack.bitrate }}
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
<template>
|
||||
<div class="hotkeys no-scroll">
|
||||
<button @click.prevent="q.playPrev">
|
||||
<button @click.prevent="queue.playPrev">
|
||||
<PrevSvg />
|
||||
</button>
|
||||
<button @click.prevent="q.playPause">
|
||||
<Spinner v-if="q.buffering && q.playing" />
|
||||
<PauseSvg v-else-if="q.playing" />
|
||||
<PlaySvg v-else/>
|
||||
<button @click.prevent="queue.playPause">
|
||||
<Spinner v-if="buffering && queue.playing" />
|
||||
<PauseSvg v-else-if="queue.playing" />
|
||||
<PlaySvg v-else />
|
||||
</button>
|
||||
<button @click.prevent="q.playNext">
|
||||
<button @click.prevent="queue.playNext">
|
||||
<NextSvg />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { usePlayer } from "@/stores/player";
|
||||
import useQStore from "@/stores/queue";
|
||||
|
||||
import {
|
||||
default as NextSvg,
|
||||
default as PrevSvg,
|
||||
default as NextSvg,
|
||||
default as PrevSvg,
|
||||
} from "@/assets/icons/next.svg";
|
||||
import PauseSvg from "@/assets/icons/pause.svg";
|
||||
import PlaySvg from "@/assets/icons/play.svg";
|
||||
import Spinner from "@/components/shared/Spinner.vue";
|
||||
|
||||
const q = useQStore();
|
||||
const queue = useQStore();
|
||||
const { buffering } = usePlayer();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -68,4 +70,4 @@ const q = useQStore();
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
@@ -7,9 +7,7 @@
|
||||
:max="time.full"
|
||||
step="0.1"
|
||||
:style="{
|
||||
backgroundSize: `${
|
||||
(time.current / (time.full || 0)) * 100
|
||||
}% 100%`,
|
||||
backgroundSize: `${(time.current / (time.full || 0)) * 100}% 100%`,
|
||||
}"
|
||||
@change="seek"
|
||||
/>
|
||||
@@ -22,10 +20,18 @@ const q = useQStore();
|
||||
|
||||
const { duration: time } = q;
|
||||
|
||||
let prevHash = "";
|
||||
|
||||
const seek = (e: Event) => {
|
||||
if (prevHash && prevHash !== q.currenttrackhash) {
|
||||
prevHash = "";
|
||||
return;
|
||||
}
|
||||
|
||||
const elem = e.target as HTMLInputElement;
|
||||
const value = elem.value;
|
||||
|
||||
prevHash = q.currenttrackhash;
|
||||
q.seek(value as unknown as number);
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
<router-link
|
||||
:to="{
|
||||
name: Routes.nowPlaying,
|
||||
params: {
|
||||
tab: 'home',
|
||||
},
|
||||
}"
|
||||
>
|
||||
<img
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<template>
|
||||
<div class="side-nav-container">
|
||||
<router-link
|
||||
v-for="(menu, index) in menus.filter((i) =>
|
||||
i.unlock_key ? i.unlock_key() : true
|
||||
)"
|
||||
v-for="(menu, index) in menus"
|
||||
:key="index"
|
||||
v-wave
|
||||
:to="{
|
||||
@@ -18,7 +16,7 @@
|
||||
}"
|
||||
>
|
||||
<div v-if="!menu.separator">
|
||||
<component :is="menu.icon" :class="menu.class" />
|
||||
<component :is="menu.icon" />
|
||||
<span>{{ menu.name }}</span>
|
||||
</div>
|
||||
</router-link>
|
||||
@@ -86,7 +84,7 @@ import { menus } from "./navitems";
|
||||
transform: scale(0.9);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
|
||||
svg.radiosvg {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
|
||||
@@ -6,14 +6,36 @@ import HeartSvg from "@/assets/icons/heart.svg";
|
||||
import PlaylistSvg from "@/assets/icons/playlist-1.svg";
|
||||
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: "folders",
|
||||
route_name: Routes.folder,
|
||||
params: { path: "$home" },
|
||||
icon: FolderSvg,
|
||||
},
|
||||
home,
|
||||
folder,
|
||||
{
|
||||
name: "search",
|
||||
route_name: Routes.search,
|
||||
@@ -24,22 +46,17 @@ 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,
|
||||
},
|
||||
{
|
||||
name: "settings",
|
||||
route_name: Routes.settings,
|
||||
params: { tab: "general" },
|
||||
icon: SettingsSvg,
|
||||
},
|
||||
];
|
||||
|
||||
export const topnavitems = [home, folder, favorites, playlists];
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
:class="notif.type"
|
||||
>
|
||||
<component :is="getSvg(notif.type)" class="notif-icon" />
|
||||
<div class="notif-text ellip">{{ notif.text }}</div>
|
||||
<div class="notif-text">{{ notif.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -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 {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
/>
|
||||
</RouterLink>
|
||||
<NowPlayingInfo @handle-fav="handleFav" />
|
||||
<Progress v-if="isSmallPhone"/>
|
||||
<Progress v-if="isSmallPhone" />
|
||||
<div v-if="isSmallPhone" class="below-progress">
|
||||
<div class="time">
|
||||
{{ formatSeconds(queue.duration.current) }}
|
||||
@@ -30,18 +30,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 v-if="queue.currenttrack">Now Playing</h3>
|
||||
<h3 v-if="queue.next">Up Next</h3>
|
||||
<SongItem
|
||||
v-if="queue.currenttrack"
|
||||
:track="queue.currenttrack"
|
||||
:index="queue.currentindex + 1"
|
||||
v-if="queue.next"
|
||||
:track="queue.next"
|
||||
:index="queue.nextindex + 1"
|
||||
:source="dropSources.folder"
|
||||
:style="{
|
||||
backgroundColor: colors.theme1,
|
||||
color: getTextColor(colors.theme1),
|
||||
}"
|
||||
@play-this="() => {}"
|
||||
@play-this="queue.playNext"
|
||||
/>
|
||||
<h3>Queue</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -49,21 +46,18 @@
|
||||
import { paths } from "@/config";
|
||||
import { Routes } from "@/router";
|
||||
import useQueueStore from "@/stores/queue";
|
||||
|
||||
import Progress from "@/components/LeftSidebar/NP/Progress.vue";
|
||||
import { formatSeconds } from "@/utils";
|
||||
import { dropSources, favType } from "@/enums";
|
||||
import { isSmallPhone } from "@/stores/content-width";
|
||||
import favoriteHandler from "@/helpers/favoriteHandler";
|
||||
import useColorStore from "@/stores/colors";
|
||||
import { getTextColor } from "@/utils/colortools/shift";
|
||||
import SongItem from "../shared/SongItem.vue";
|
||||
import NowPlayingInfo from "./NowPlayingInfo.vue";
|
||||
|
||||
import PlayingFrom from "./PlayingFrom.vue";
|
||||
import Buttons from "../BottomBar/Right.vue";
|
||||
import { isSmallPhone } from "@/stores/content-width";
|
||||
import { formatSeconds } from "@/utils";
|
||||
import SongItem from "../shared/SongItem.vue";
|
||||
import NowPlayingInfo from "./NowPlayingInfo.vue";
|
||||
import Progress from "@/components/LeftSidebar/NP/Progress.vue";
|
||||
|
||||
const queue = useQueueStore();
|
||||
const colors = useColorStore();
|
||||
|
||||
function handleFav() {
|
||||
favoriteHandler(
|
||||
|
||||
@@ -8,25 +8,29 @@
|
||||
<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>
|
||||
|
||||
@@ -34,19 +38,28 @@
|
||||
import { computed, ref } from "vue";
|
||||
import { RouteLocationRaw } from "vue-router";
|
||||
|
||||
import useTracklist from "@/stores/queue/tracklist";
|
||||
|
||||
import { FromOptions } from "@/enums";
|
||||
import useQueueStore from "@/stores/queue";
|
||||
import playingFrom from "@/utils/playingFrom";
|
||||
|
||||
const queue = useQueueStore();
|
||||
|
||||
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">
|
||||
@@ -56,7 +69,13 @@ const data = computed(() => {
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.options {
|
||||
transform: rotate(90deg);
|
||||
|
||||
svg {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.now-playling-from-link {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<template>
|
||||
<div class="now-playing-tabs">
|
||||
<div class="tab-items">
|
||||
<h3>Queue</h3>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.now-playing-tabs {
|
||||
.tab-items {
|
||||
border: solid transparent;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<div class="card-list-scroll-x">
|
||||
<h3>{{ title }} <SeeAll v-if="route" :route="route" /></h3>
|
||||
<div ref="artistItemswrappers" class="cards">
|
||||
<ArtistCard
|
||||
v-for="artist in artists.slice(0, maxAbumCards)"
|
||||
:key="artist.image"
|
||||
:artist="artist"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
import { Artist } from "@/interfaces";
|
||||
import { maxAbumCards } from "@/stores/content-width";
|
||||
|
||||
import ArtistCard from "@/components/shared/ArtistCard.vue";
|
||||
import SeeAll from "../shared/SeeAll.vue";
|
||||
|
||||
defineProps<{
|
||||
artists: Artist[];
|
||||
title: string;
|
||||
route?: string;
|
||||
}>();
|
||||
|
||||
const artistItemswrappers = ref<HTMLElement | null>(null);
|
||||
</script>
|
||||
@@ -11,6 +11,7 @@
|
||||
:class="{ 'use-sqr_img': useSqrImg }"
|
||||
>
|
||||
<div
|
||||
v-if="Number.isInteger(info.id)"
|
||||
class="float"
|
||||
:style="{
|
||||
color: textColor,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="duration">
|
||||
{{
|
||||
playlist.info.count +
|
||||
playlist.info.count.toLocaleString() +
|
||||
` ${playlist.info.count == 1 ? "Track" : "Tracks"}`
|
||||
}}
|
||||
•
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<template>
|
||||
<div class="last-updated">
|
||||
<span class="status" v-if="!isHeaderSmall"
|
||||
<span v-if="!isHeaderSmall" class="status"
|
||||
>Last updated {{ playlist.info.last_updated }}  |  </span
|
||||
>
|
||||
<div class="edit" @click="editPlaylist">Edit  </div>
|
||||
|
|
||||
<div
|
||||
v-if="Number.isInteger(playlist.info.id)"
|
||||
class="edit"
|
||||
@click="editPlaylist"
|
||||
>
|
||||
Edit  
|
||||
</div>
|
||||
{{ Number.isInteger(playlist.info.id) ? " | " : "" }}
|
||||
<DeleteSvg class="edit" @click="deletePlaylist" />
|
||||
</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;
|
||||
|
||||
@@ -20,9 +20,16 @@
|
||||
:class="{ border: !playlist.thumb }"
|
||||
/>
|
||||
<div class="overlay rounded">
|
||||
<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">
|
||||
{{ playlist.count + ` ${playlist.count === 1 ? "Track" : "Tracks"}` }}
|
||||
<b>{{
|
||||
playlist.count.toLocaleString() +
|
||||
` Track${playlist.count === 1 ? "" : "s"}`
|
||||
}}</b>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
@@ -33,8 +40,7 @@ import { paths } from "../../config";
|
||||
import { Playlist } from "../../interfaces";
|
||||
|
||||
const imguri = paths.images.playlist;
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
playlist: Playlist;
|
||||
}>();
|
||||
</script>
|
||||
@@ -44,9 +50,10 @@ const props = defineProps<{
|
||||
background-color: #2c2c2e45;
|
||||
display: grid;
|
||||
grid-template-rows: 1fr max-content;
|
||||
padding: 1rem;
|
||||
padding: $medium;
|
||||
gap: $small;
|
||||
user-select: none;
|
||||
height: max-content;
|
||||
|
||||
.image-grid {
|
||||
display: grid;
|
||||
@@ -55,7 +62,7 @@ const props = defineProps<{
|
||||
|
||||
&:hover {
|
||||
transition: all 0.25s ease;
|
||||
background-color: $gray3;
|
||||
background-color: $gray4 !important;
|
||||
background-blend-mode: screen;
|
||||
}
|
||||
|
||||
@@ -65,6 +72,7 @@ const props = defineProps<{
|
||||
object-fit: cover;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -74,25 +82,7 @@ const props = defineProps<{
|
||||
.p-count {
|
||||
opacity: 0.75;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.p-name {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
margin-top: $smaller;
|
||||
|
||||
.name {
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: $medium;
|
||||
opacity: 0.5;
|
||||
margin-top: $smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
<template>
|
||||
<div class="r-home">
|
||||
<UpNext :track="queue.tracklist[queue.next]" :playNext="queue.playNext" />
|
||||
<UpNext :track="queue.tracklist[queue.next]" :play-next="queue.playNext" />
|
||||
<!-- <Recommendations /> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.r-home {
|
||||
height: calc(100% - 1rem);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useQStore from "../../../stores/queue";
|
||||
import UpNext from "../Queue/upNext.vue";
|
||||
const queue = useQStore();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.r-home {
|
||||
height: calc(100% - 1rem);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="r-tracks rounded bg-primary">
|
||||
<div class="heading">Similar tracks</div>
|
||||
<div class="tracks">
|
||||
<div class="song-item" v-for="song in songs" :key="song.artist">
|
||||
<div v-for="song in songs" :key="song.artist" class="song-item">
|
||||
<img src="" class="rounded" loading="lazy"/>
|
||||
<div class="tags">
|
||||
<div class="title">{{ song.title }}</div>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<template>
|
||||
<div class="r-sidebar">
|
||||
<SearchInput />
|
||||
<div v-auto-animate class="r-content no-scroll" >
|
||||
<div class="r-dash" v-if="tabs.current === tabs.tabs.home">
|
||||
<div v-auto-animate class="r-content no-scroll">
|
||||
<div v-if="tabs.current === tabs.tabs.home" class="r-dash">
|
||||
<DashBoard />
|
||||
</div>
|
||||
<div class="r-search" v-if="tabs.current === tabs.tabs.search">
|
||||
<div v-if="tabs.current === tabs.tabs.search" class="r-search">
|
||||
<Search />
|
||||
</div>
|
||||
<div class="r-queue" v-if="tabs.current === tabs.tabs.queue">
|
||||
<div v-if="tabs.current === tabs.tabs.queue" class="r-queue">
|
||||
<Queue />
|
||||
</div>
|
||||
</div>
|
||||
@@ -16,10 +16,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useTabStore from "../../stores/tabs";
|
||||
import DashBoard from "./Home/Main.vue";
|
||||
import useTabStore from "@/stores/tabs";
|
||||
|
||||
import Queue from "./Queue.vue";
|
||||
import Search from "./Search/Main.vue";
|
||||
import DashBoard from "./Home/Main.vue";
|
||||
import SearchInput from "./SearchInput.vue";
|
||||
|
||||
const tabs = useTabStore();
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
@mouseout="mouseover = false"
|
||||
>
|
||||
<NoItems
|
||||
:flag="!queue.tracklist.length"
|
||||
:flag="!store.tracklist.length"
|
||||
:title="'No songs in queue'"
|
||||
:description="'When you start playing songs, they will appear here.'"
|
||||
:icon="QueueSvg"
|
||||
@@ -36,18 +36,23 @@
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
||||
|
||||
import useQStore from "@/stores/queue";
|
||||
import useInterface from "@/stores/interface";
|
||||
import useTracklist from "@/stores/queue/tracklist";
|
||||
|
||||
import TrackItem from "@/components/shared/TrackItem.vue";
|
||||
import QueueActions from "./Queue/QueueActions.vue";
|
||||
import NoItems from "../shared/NoItems.vue";
|
||||
import QueueActions from "./Queue/QueueActions.vue";
|
||||
import TrackItem from "@/components/shared/TrackItem.vue";
|
||||
import QueueSvg from "@/assets/icons/queue.svg";
|
||||
|
||||
const itemHeight = 64;
|
||||
const queue = useQStore();
|
||||
const store = useTracklist();
|
||||
const mouseover = ref(false);
|
||||
|
||||
const { focusCurrentInSidebar, setScrollFunction } = useInterface();
|
||||
|
||||
const scrollerItems = computed(() => {
|
||||
return queue.tracklist.map((track, index) => ({
|
||||
return store.tracklist.map((track, index) => ({
|
||||
track,
|
||||
id: index,
|
||||
}));
|
||||
@@ -70,12 +75,12 @@ function scrollToCurrent() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
queue.setScrollFunction(scrollToCurrent, mouseover);
|
||||
queue.focusCurrentInSidebar();
|
||||
setScrollFunction(scrollToCurrent, mouseover);
|
||||
focusCurrentInSidebar();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
queue.setScrollFunction(() => {}, null);
|
||||
setScrollFunction(() => {}, null);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -83,9 +88,5 @@ onBeforeUnmount(() => {
|
||||
.queue-virtual-scroller {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
.vue-recycle-scroller {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
<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
|
||||
class="menu"
|
||||
:class="{ 'btn-active': context_showing }"
|
||||
@click="showContextMenu"
|
||||
>
|
||||
@@ -19,20 +26,28 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import useQueueStore from "@/stores/queue";
|
||||
import useQueue from "@/stores/queue";
|
||||
import useTracklist from "@/stores/queue/tracklist";
|
||||
|
||||
import { showQueueContextMenu } from "@/helpers/contextMenuHandler";
|
||||
|
||||
import OptionsSvg from "@/assets/icons/more.svg";
|
||||
import ShuffleSvg from "@/assets/icons/shuffle.svg";
|
||||
import { showQueueContextMenu } from "@/helpers/contextMenuHandler";
|
||||
|
||||
const queue = useQueueStore();
|
||||
const queue = useQueue();
|
||||
const { tracklist } = useTracklist();
|
||||
|
||||
const context_showing = ref(false);
|
||||
|
||||
function showContextMenu(e: MouseEvent) {
|
||||
if (!queue.tracklist.length) return;
|
||||
if (!tracklist.length) return;
|
||||
|
||||
showQueueContextMenu(e, context_showing);
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
onNowPlaying?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@@ -43,8 +58,23 @@ function showContextMenu(e: MouseEvent) {
|
||||
margin: 1rem;
|
||||
margin-bottom: 0;
|
||||
|
||||
.lyricsversion {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
.save {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
// hide on screens less than 600px
|
||||
@media screen and (max-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $small;
|
||||
}
|
||||
|
||||
@@ -56,11 +86,16 @@ function showContextMenu(e: MouseEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
.right > button {
|
||||
padding: 0 $smaller;
|
||||
.right {
|
||||
display: flex;
|
||||
gap: $medium;
|
||||
|
||||
svg {
|
||||
transform: scale(1.2) rotate(90deg);
|
||||
.menu {
|
||||
padding: 0 $smaller;
|
||||
|
||||
svg {
|
||||
transform: scale(1.2) rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<template>
|
||||
<div v-auto-animate class="artists-results">
|
||||
<div>
|
||||
<div
|
||||
v-if="album_grid == true && search.albums.value.length"
|
||||
class="search-results-grid"
|
||||
>
|
||||
<AlbumCard
|
||||
v-for="a in search.albums.value"
|
||||
:key="a.albumid"
|
||||
:album="a"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="!album_grid && search.artists.value.length"
|
||||
class="search-results-grid"
|
||||
>
|
||||
<ArtistCard
|
||||
v-for="artist in search.artists.value"
|
||||
:key="artist.image"
|
||||
:artist="artist"
|
||||
:alt="true"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="t-center">
|
||||
<h5>No {{ album_grid ? "albums" : "artists" }}</h5>
|
||||
</div>
|
||||
</div>
|
||||
<LoadMore
|
||||
v-if="search.albums.value.length || search.artists.value.length"
|
||||
:loader="album_grid ? search.loadAlbums : search.loadArtists"
|
||||
:can_load_more="album_grid ? search.albums.more : search.artists.more"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useSearchStore from "../../../stores/search";
|
||||
|
||||
import AlbumCard from "@/components/shared/AlbumCard.vue";
|
||||
import ArtistCard from "../../shared/ArtistCard.vue";
|
||||
import LoadMore from "./LoadMore.vue";
|
||||
|
||||
const search = useSearchStore();
|
||||
|
||||
defineProps<{
|
||||
album_grid?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.artists-results {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
margin: 0 1rem;
|
||||
grid-template-rows: 1fr max-content;
|
||||
}
|
||||
|
||||
.search-results-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
|
||||
min-height: 10rem;
|
||||
padding-bottom: $small;
|
||||
}
|
||||
</style>
|
||||
@@ -1,41 +0,0 @@
|
||||
<template>
|
||||
<div class="morexx">
|
||||
<button
|
||||
@click.prevent="loader()"
|
||||
:class="{
|
||||
load_disabled: !can_load_more,
|
||||
}"
|
||||
>
|
||||
<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>
|
||||
@@ -2,9 +2,9 @@
|
||||
<div class="right-search">
|
||||
<TabsWrapper
|
||||
:tabs="tabs"
|
||||
:current-tab="currentTab"
|
||||
:tab-content="true"
|
||||
@switchTab="switchTab"
|
||||
:currentTab="currentTab"
|
||||
:tabContent="true"
|
||||
>
|
||||
<Tab :name="currentTab" />
|
||||
</TabsWrapper>
|
||||
@@ -48,5 +48,7 @@ function switchTab(tab: string) {
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<div class="right-search-top-albums-or-artists">
|
||||
<AlbumCard
|
||||
v-for="album in search.top_results.albums.slice(
|
||||
0,
|
||||
!onSearchPage ? 3 : search.top_results.albums.length
|
||||
)"
|
||||
v-for="album in search.top_results.albums.slice(0, 3)"
|
||||
:key="album.albumhash"
|
||||
:album="album"
|
||||
/>
|
||||
@@ -16,10 +13,6 @@ import useSearchstore from "@/stores/search";
|
||||
import AlbumCard from "@/components/shared/AlbumCard.vue";
|
||||
|
||||
const search = useSearchstore();
|
||||
|
||||
defineProps<{
|
||||
onSearchPage?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<div class="right-search-top-albums-or-artists">
|
||||
<ArtistCard
|
||||
v-for="artist in search.top_results.artists.slice(
|
||||
0,
|
||||
!onSearchPage ? 3 : search.top_results.artists.length
|
||||
)"
|
||||
v-for="artist in search.top_results.artists.slice(0, 3)"
|
||||
:key="artist.artisthash"
|
||||
:artist="artist"
|
||||
/>
|
||||
@@ -12,12 +9,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ArtistCard from "@/components/shared/ArtistCard.vue";
|
||||
import useSearchStore from "@/stores/search";
|
||||
import ArtistCard from "@/components/shared/ArtistCard.vue";
|
||||
|
||||
const search = useSearchStore();
|
||||
|
||||
defineProps<{
|
||||
onSearchPage?: boolean;
|
||||
}>();
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -17,15 +17,17 @@ import { Track } from "@/interfaces";
|
||||
|
||||
import useQueueStore from "@/stores/queue";
|
||||
import useSearchStore from "@/stores/search";
|
||||
import useTracklist from "@/stores/queue/tracklist";
|
||||
|
||||
import TrackItem from "@/components/shared/TrackItem.vue";
|
||||
|
||||
const search = useSearchStore();
|
||||
const queue = useQueueStore();
|
||||
const tracklist = useTracklist();
|
||||
|
||||
function handlePlay(track: Track) {
|
||||
queue.clearQueue();
|
||||
queue.playFromSearch(search.query, [track]);
|
||||
tracklist.setFromSearch(search.query, [track]);
|
||||
queue.play(0);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,44 +1,75 @@
|
||||
<template>
|
||||
<div id="tracks-results">
|
||||
<div v-if="search.tracks.value.length">
|
||||
<TrackItem
|
||||
v-for="(track, index) in search.tracks.value"
|
||||
:key="track.id"
|
||||
:isCurrent="queue.currenttrackhash === track.trackhash"
|
||||
:isHighlighted="false"
|
||||
:isCurrentPlaying="
|
||||
queue.currenttrackhash === track.trackhash && queue.playing
|
||||
"
|
||||
:track="track"
|
||||
@playThis="updateQueue(index)"
|
||||
:index="index + 1"
|
||||
/>
|
||||
<div v-if="!search.tracks.value.length" class="t-center">
|
||||
<h5>No tracks</h5>
|
||||
</div>
|
||||
<div v-else class="t-center"><h5>No tracks</h5></div>
|
||||
<LoadMore
|
||||
:loader="search.loadTracks"
|
||||
:can_load_more="search.tracks.more"
|
||||
v-if="search.tracks.value.length"
|
||||
/>
|
||||
|
||||
<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)"
|
||||
/>
|
||||
</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 TrackItem from "@/components/shared/TrackItem.vue";
|
||||
import useQStore from "@/stores/queue";
|
||||
import useSearchStore from "@/stores/search";
|
||||
import LoadMore from "./LoadMore.vue";
|
||||
import AlbumsFetcher from "@/components/ArtistView/AlbumsFetcher.vue";
|
||||
|
||||
const queue = useQStore();
|
||||
const search = useSearchStore();
|
||||
const queue = useQueue();
|
||||
const search = useSearch();
|
||||
const tracklist = useTracklist();
|
||||
|
||||
function updateQueue(index: number) {
|
||||
queue.playFromSearch(search.query, search.tracks.value);
|
||||
tracklist.setFromSearch(search.query, search.tracks.value);
|
||||
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");
|
||||
});
|
||||
|
||||
@@ -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
|
||||
@@ -10,7 +20,7 @@
|
||||
@click.prevent="handleButton"
|
||||
>
|
||||
<SearchSvg v-if="on_nav || tabs.current === tabs.tabs.queue" />
|
||||
<BackSvg v-else-if="tabs.current === tabs.tabs.search" />
|
||||
<BackSvg v-else />
|
||||
</button>
|
||||
<input
|
||||
id="globalsearch"
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<template></template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
action: () => void;
|
||||
}>();
|
||||
|
||||
onMounted(() => {
|
||||
props.action();
|
||||
});
|
||||
</script>
|
||||
92
src/components/SettingsView/About.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="aboutswingmusic">
|
||||
Swing Music is a labor of love developed by
|
||||
<a href="https://github.com/cwilvx" target="_blank">@<u>cwilvx</u></a> on
|
||||
GitHub. If you like this software, please consider donating to support
|
||||
development and giving it a star on GitHub. <br /><br /><br />
|
||||
<div class="flex">
|
||||
<a href="https://swingmusic.vercel.app/support-us.html" target="_blank">
|
||||
<button>Donate</button></a
|
||||
><a href="https://github.com/cwilvx/swingmusic" target="_blank"
|
||||
><button>Star on Github</button></a
|
||||
>
|
||||
<a href="https://github.com/cwilvx" target="_blank"
|
||||
><button>Follow @cwilvx on Github</button></a
|
||||
>
|
||||
</div>
|
||||
|
||||
<br /><br />
|
||||
If you encounter any bugs, please open an issue on GitHub. If you would like
|
||||
to get involved in development, start with the
|
||||
<a
|
||||
href="https://github.com/cwilvx/swingmusic/blob/master/.github/contributing.md"
|
||||
target="_blank"
|
||||
><u>contribution guidelines</u></a
|
||||
>.
|
||||
|
||||
<br /><br /><br />
|
||||
<div class="flex">
|
||||
<a
|
||||
href="https://github.com/cwilvx/swingmusic/issues/new/choose"
|
||||
target="_blank"
|
||||
>
|
||||
<button>Open an Issue</button>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/cwilvx/swingmusic/blob/master/.github/contributing.md"
|
||||
target="_blank"
|
||||
><button>Contribute</button></a
|
||||
>
|
||||
</div>
|
||||
<br /><br />
|
||||
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>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.aboutswingmusic {
|
||||
padding: $small;
|
||||
margin-top: 2rem;
|
||||
|
||||
.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>
|
||||
40
src/components/SettingsView/Components/LockedNumberInput.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<div class="lockernumberinput">
|
||||
<button class="minus" @click="submit('minus')">-</button>
|
||||
<div class="number">{{ value }}{{ unit }}</div>
|
||||
<button class="plus" @click="submit('plus')">+</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
value: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
unit: string;
|
||||
onChange: (value: number) => void;
|
||||
}>();
|
||||
|
||||
function submit(action: "plus" | "minus") {
|
||||
const newValue = props.value + (action === "plus" ? props.step : -props.step);
|
||||
if (newValue < props.min || newValue > props.max) return;
|
||||
props.onChange(newValue);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.lockernumberinput {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
align-items: center;
|
||||
|
||||
.number {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button {
|
||||
width: 2.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
80
src/components/SettingsView/Components/QuickSettings.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="settings-quickactions grid">
|
||||
<button
|
||||
v-for="a in actions"
|
||||
:key="a.label"
|
||||
class="qaction rounded-sm"
|
||||
@click="a.action"
|
||||
>
|
||||
<component :is="a.icon" v-if="a.icon" />
|
||||
<div class="label">{{ a.label }}</div>
|
||||
<Switch v-if="a.state" :state="a.state()" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useSettings from "@/stores/settings";
|
||||
import { triggerScan } from "@/requests/settings/rootdirs";
|
||||
|
||||
import Switch from "./Switch.vue";
|
||||
import ReloadSvg from "@/assets/icons/reload.svg";
|
||||
|
||||
const settings = useSettings();
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: "Rescan library",
|
||||
action: triggerScan,
|
||||
icon: ReloadSvg,
|
||||
},
|
||||
{
|
||||
label: "Sidebar",
|
||||
action: settings.toggleDisableSidebar,
|
||||
state: () => settings.use_sidebar,
|
||||
},
|
||||
{
|
||||
label: "Silence skip",
|
||||
action: settings.toggleUseSilenceSkip,
|
||||
state: () => settings.use_silence_skip,
|
||||
},
|
||||
{
|
||||
label: "Crossfade",
|
||||
action: settings.toggleCrossfade,
|
||||
state: () => settings.use_crossfade,
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.settings-quickactions.grid {
|
||||
gap: 1.25rem;
|
||||
padding-top: $medium;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
|
||||
.qaction {
|
||||
background-color: $gray5;
|
||||
padding: 1.5rem $small;
|
||||
font-size: 14px;
|
||||
border: solid 1px $gray4;
|
||||
// justify-content: space-between;
|
||||
|
||||
gap: $smaller;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray4;
|
||||
}
|
||||
}
|
||||
|
||||
.switch {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
.qaction svg {
|
||||
transform: scale(0.75);
|
||||
}
|
||||
.qaction:nth-child(2) svg {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<div class="setting-select rounded-sm no-scroll">
|
||||
<div
|
||||
class="option"
|
||||
v-for="option in optionsWithActive"
|
||||
:key="option.title"
|
||||
class="option"
|
||||
:class="{ active: option.active }"
|
||||
@click="setterFn(option.value)"
|
||||
>
|
||||
|
||||
@@ -24,13 +24,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Ref, computed, ref, watch } from "vue";
|
||||
import { Ref, computed, onMounted, ref } from "vue";
|
||||
|
||||
const separatorinput: Ref<HTMLInputElement | null> = ref(null);
|
||||
const input = ref("");
|
||||
|
||||
const props = defineProps<{
|
||||
default: () => string[];
|
||||
default: string[];
|
||||
submit: (input: string) => void;
|
||||
}>();
|
||||
|
||||
@@ -69,14 +69,16 @@ function submitInput() {
|
||||
|
||||
const preview_items = computed(() => splitInput(input.value));
|
||||
const default_input = computed(() =>
|
||||
props.default() ? props.default().join(", ") : ""
|
||||
props.default ? props.default.join(", ") : ""
|
||||
);
|
||||
|
||||
watch(props.default, (newval, _) => {
|
||||
const text = newval.join(", ");
|
||||
|
||||
if (separatorinput.value) separatorinput.value.value = text;
|
||||
onMounted(() => {
|
||||
const text = props.default.join(", ");
|
||||
input.value = text;
|
||||
|
||||
if (separatorinput.value) {
|
||||
separatorinput.value.value = text;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
state: boolean | null;
|
||||
state: undefined | boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,44 +6,62 @@
|
||||
Customize your settings and preferences
|
||||
</template>
|
||||
</GenericHeader>
|
||||
<GenericTabs
|
||||
:items="
|
||||
settingGroups.map((g) => ({
|
||||
title: g.title,
|
||||
params: {
|
||||
tab: g.title.toLowerCase(),
|
||||
},
|
||||
}))
|
||||
"
|
||||
:active="(item) => item.title === currentTab?.title"
|
||||
:route="Routes.settings"
|
||||
/>
|
||||
<Group
|
||||
v-for="(group, index) in settingGroups[current].groups"
|
||||
v-for="(group, index) in currentTab?.groups"
|
||||
:key="index"
|
||||
:group="group"
|
||||
/>
|
||||
<About v-if="currentTab?.title === 'About'" />
|
||||
<div class="version t-center">
|
||||
<LogoSvg /> <span>Swing Music - v{{ VERSION }}</span>
|
||||
<b>Swing Music - v{{ settings.version }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { VERSION } from "@/config";
|
||||
import { computed } from "vue";
|
||||
import { Routes } from "@/router";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import settingGroups from "@/settings";
|
||||
import useSettings from "@/stores/settings";
|
||||
|
||||
import Group from "./Group.vue";
|
||||
import About from "./About.vue";
|
||||
import GenericTabs from "@/components/shared/GenericTabs.vue";
|
||||
import GenericHeader from "@/components/shared/GenericHeader.vue";
|
||||
import LogoSvg from "@/assets/icons/logos/logo-light.svg";
|
||||
|
||||
defineProps<{
|
||||
current: number;
|
||||
}>();
|
||||
const route = useRoute();
|
||||
const settings = useSettings();
|
||||
|
||||
const currentTab = computed(() => {
|
||||
const tab = route.params.tab;
|
||||
return settingGroups.find((group) => group.title.toLowerCase() === tab);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.settingscontent {
|
||||
width: 35rem;
|
||||
width: 31rem;
|
||||
max-width: 100%;
|
||||
padding-bottom: 2rem;
|
||||
|
||||
.version {
|
||||
margin: 2rem auto;
|
||||
color: $gray1;
|
||||
height: 3rem;
|
||||
width: max-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
opacity: 0.5;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div v-if="group.show_if ? group.show_if() : true" class="settingsgroup">
|
||||
<div v-if="group.title || group.desc" class="info">
|
||||
<h4 v-if="group.title">{{ group.title }}</h4>
|
||||
<h4 v-if="group.title">
|
||||
{{ group.title
|
||||
}}<span v-if="group.experimental" class="badge experimental circular">
|
||||
{{ group.experimental ? "experimental" : "" }}
|
||||
</span>
|
||||
</h4>
|
||||
<div v-if="group.desc" class="desc">{{ group.desc }}</div>
|
||||
</div>
|
||||
<div class="setting rounded pad-lg">
|
||||
@@ -25,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"
|
||||
@@ -55,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"
|
||||
@@ -65,7 +89,7 @@
|
||||
<SeparatorsInput
|
||||
v-if="setting.type === SettingType.separators_input && setting.action"
|
||||
:submit="setting.action"
|
||||
:default="setting.state ? setting.state : () => []"
|
||||
:default="setting.state ? setting.state() : []"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -76,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;
|
||||
@@ -96,6 +122,24 @@ defineProps<{
|
||||
border-bottom: solid 1px $gray;
|
||||
padding-bottom: 2rem;
|
||||
|
||||
.badge {
|
||||
margin-left: $small;
|
||||
opacity: 0.75;
|
||||
padding: 0 $smaller;
|
||||
border-radius: $smaller;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
.experimental {
|
||||
border: solid 1px $yellow;
|
||||
color: $yellow;
|
||||
}
|
||||
|
||||
.badge.new {
|
||||
background-color: $blue;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
@@ -116,8 +160,6 @@ defineProps<{
|
||||
|
||||
.setting {
|
||||
background-color: $gray;
|
||||
// display: grid;
|
||||
// gap: 1rem;
|
||||
|
||||
.inactive {
|
||||
opacity: 0.5;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
<template>
|
||||
<div class="modal" v-if="modal.visible">
|
||||
<div v-if="modal.visible" class="modal">
|
||||
<div class="bg" @click="modal.hideModal"></div>
|
||||
<div
|
||||
v-motion-slide-top
|
||||
class="m-content rounded"
|
||||
:style="{
|
||||
maxWidth:
|
||||
modal.component == modal.options.setRootDirs ? '56rem' : '30rem',
|
||||
}"
|
||||
v-motion-slide-top
|
||||
>
|
||||
<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,8 +17,8 @@
|
||||
@setTitle="setTitle"
|
||||
/>
|
||||
<UpdatePlaylist
|
||||
v-bind="modal.props"
|
||||
v-if="modal.component == modal.options.updatePlaylist"
|
||||
v-bind="modal.props"
|
||||
@hideModal="hideModal"
|
||||
@setTitle="setTitle"
|
||||
/>
|
||||
@@ -29,14 +26,10 @@
|
||||
<div v-if="modal.component == modal.options.deletePlaylist">
|
||||
<ConfirmModal
|
||||
:text="'Are you sure you want to permanently delete this playlist?'"
|
||||
:cancelAction="modal.hideModal"
|
||||
:confirmAction="deletePlaylist"
|
||||
:cancel-action="modal.hideModal"
|
||||
:confirm-action="deletePlaylist"
|
||||
/>
|
||||
</div>
|
||||
<SetIP
|
||||
v-if="modal.component == modal.options.SetIP"
|
||||
@setTitle="setTitle"
|
||||
/>
|
||||
<SetRootDirs
|
||||
v-if="modal.component == modal.options.setRootDirs"
|
||||
@hideModal="hideModal"
|
||||
@@ -54,12 +47,10 @@ 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";
|
||||
import RootDirsPrompt from "./modals/RootDirsPrompt.vue";
|
||||
import SetIP from "./modals/SetIP.vue";
|
||||
import SetRootDirs from "./modals/SetRootDirs.vue";
|
||||
import UpdatePlaylist from "./modals/updatePlaylist.vue";
|
||||
|
||||
@@ -92,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 {
|
||||
@@ -121,28 +114,6 @@ function deletePlaylist() {
|
||||
width: calc(100% - 2rem);
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 1.5rem;
|
||||
right: 1.25rem;
|
||||
|
||||
transform: rotate(45deg);
|
||||
|
||||
svg {
|
||||
background-color: $gray3;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
opacity: 0.75;
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
background-color: $red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
type="search"
|
||||
class="rounded-sm"
|
||||
name="name"
|
||||
placeholder="Type a name..."
|
||||
spellcheck="false"
|
||||
/>
|
||||
<br /><br />
|
||||
<button type="submit">Create</button>
|
||||
@@ -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>
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<template>
|
||||
<div class="set-ip-modal">
|
||||
<label for="">Change the </label>
|
||||
<input
|
||||
id="modal-input"
|
||||
type="text"
|
||||
class="rounded-sm"
|
||||
v-model="ip_address"
|
||||
/>
|
||||
<button
|
||||
class="circular btn-active"
|
||||
@click.prevent="setBaseApiUrl(ip_address)"
|
||||
>
|
||||
Set address
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
import { setBaseApiUrl, baseApiUrl } from "@/config";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: "setTitle", title: string): void;
|
||||
}>();
|
||||
const title = "Set backend address";
|
||||
|
||||
const ip_address = ref("http://localhost:1970");
|
||||
|
||||
onMounted(() => {
|
||||
emit("setTitle", title);
|
||||
document.getElementById("modal-input")?.focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.set-ip-modal {
|
||||
#modal-input {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0 auto;
|
||||
margin-top: $small;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<br /><br />
|
||||
<div style="position: relative">
|
||||
<div class="bread-nav rounded-sm" id="bread-nav">
|
||||
<span @click="fetchDirs('$root')">📁</span
|
||||
> <BreadCrumbNav :subPaths="subPaths" @navigate="fetchDirs" />
|
||||
<div id="bread-nav" class="bread-nav rounded-sm">
|
||||
<span @click="fetchDirs('$root')">$root</span
|
||||
> <BreadCrumbNav :sub-paths="subPaths" @navigate="fetchDirs" />
|
||||
</div>
|
||||
<div class="set-root-dirs-browser">
|
||||
<h4 v-if="no_more_dirs">
|
||||
@@ -16,20 +16,20 @@
|
||||
v-for="dir in dirs"
|
||||
:key="dir.name"
|
||||
:folder="dir"
|
||||
@navigate="fetchDirs(dir.path)"
|
||||
@check="handleCheck(dir.path)"
|
||||
:is_checked="
|
||||
selected.filter((p) => p == dir.path).length > 0 ? true : false
|
||||
"
|
||||
@navigate="fetchDirs(dir.path)"
|
||||
@check="handleCheck(dir.path)"
|
||||
/>
|
||||
</div>
|
||||
</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 {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
type="search"
|
||||
class="rounded-sm"
|
||||
name="name"
|
||||
spellcheck="false"
|
||||
@keypress.enter.prevent="update_playlist"
|
||||
/>
|
||||
|
||||
@@ -81,7 +82,7 @@
|
||||
</div>
|
||||
|
||||
<button type="submit">
|
||||
{{ clicked ? "Saving" : "Save" }}
|
||||
{{ clicked ? "Saving" : "Update" }}
|
||||
</button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
@@ -1,76 +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" />
|
||||
<ArtistDiscographyTitle
|
||||
v-if="$route.name == Routes.artistDiscography"
|
||||
/>
|
||||
<SimpleNav
|
||||
v-if="$route.name == Routes.artistTracks"
|
||||
:text="$route.query.artist as string || 'Artist Tracks'"
|
||||
/>
|
||||
<SimpleNav
|
||||
v-if="$route.name === Routes.favorites"
|
||||
:text="'Favorites ❤️'"
|
||||
/>
|
||||
<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 ArtistDiscographyTitle from "./Titles/ArtistDiscographyTitle.vue";
|
||||
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
|
||||
@@ -92,20 +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;
|
||||
width: 100%;
|
||||
padding: 0 1.25rem;
|
||||
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;
|
||||
@@ -119,10 +139,45 @@ watch(
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin-right: 1rem;
|
||||
.logo {
|
||||
width: max-content;
|
||||
margin: 0 auto;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.avatar {
|
||||
height: 2.25rem;
|
||||
aspect-ratio: 1;
|
||||
background-color: $gray4;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
svg {
|
||||
transform: scale(0.75);
|
||||
color: $gray1;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
|
||||
svg {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,17 +19,18 @@ import ArrowSvg from "../../assets/icons/right-arrow.svg";
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
padding-right: 1rem;
|
||||
border-right: 1px solid $gray3;
|
||||
height: 100%;
|
||||
border-right: 1px solid $gray5;
|
||||
height: max-content;
|
||||
|
||||
& > * {
|
||||
width: 2.25rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
border-radius: 0.65rem;
|
||||
border-radius: 5rem;
|
||||
|
||||
svg {
|
||||
transform: scale(1.25);
|
||||
|
||||
73
src/components/nav/NavLinks.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div class="topnavlinks">
|
||||
<RouterLink
|
||||
v-for="link in topnavitems"
|
||||
:key="link.name"
|
||||
class="link"
|
||||
:to="{
|
||||
name: link.route_name,
|
||||
params: link?.params,
|
||||
}"
|
||||
:class="{
|
||||
active: $route.name && $route.name === link.route_name,
|
||||
}"
|
||||
>
|
||||
{{ link.name || "|" }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { topnavitems } from "../LeftSidebar/navitems";
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.topnavlinks {
|
||||
display: flex;
|
||||
gap: $smaller;
|
||||
align-items: center;
|
||||
|
||||
width: max-content;
|
||||
user-select: none;
|
||||
|
||||
.link {
|
||||
text-transform: capitalize;
|
||||
padding: 0.25rem $medium;
|
||||
cursor: pointer;
|
||||
border-radius: 2rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
transform: scale(0.75);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $primary;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #fff;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.link.home {
|
||||
padding: 0;
|
||||
padding-right: $small;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: $smaller;
|
||||
}
|
||||
|
||||
.separator_ {
|
||||
padding: 0;
|
||||
pointer-events: none;
|
||||
background-color: transparent;
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
33
src/components/nav/NavTitles.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<div class="info">
|
||||
<SettingsTitle v-if="$route.name == Routes.settings" :text="'Settings'" />
|
||||
<SearchTitle v-if="$route.name == Routes.search" />
|
||||
<PlaylistsTitle v-if="$route.name == Routes.playlists" />
|
||||
<SimpleNav
|
||||
v-if="$route.name == Routes.artistTracks"
|
||||
:text="$route.query.artist as string || 'Artist Tracks'"
|
||||
/>
|
||||
<SimpleNav
|
||||
v-if="$route.name === Routes.favoriteAlbums"
|
||||
:text="'Favorite Albums'"
|
||||
/>
|
||||
<SimpleNav
|
||||
v-if="$route.name === Routes.favoriteArtists"
|
||||
:text="'Favorite Artists'"
|
||||
/>
|
||||
<SimpleNav
|
||||
v-if="$route.name === Routes.favoriteTracks"
|
||||
:text="'Favorite Tracks'"
|
||||
/>
|
||||
<SimpleNav v-if="$route.name === Routes.nowPlaying" :text="'Now Playing'" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Routes } from "@/router";
|
||||
|
||||
import SimpleNav from "./Titles/SimpleNav.vue";
|
||||
import SearchTitle from "./Titles/SearchTitle.vue";
|
||||
import SettingsTitle from "./Titles/SettingsTitle.vue";
|
||||
import PlaylistsTitle from "./Titles/PlaylistsTitle.vue";
|
||||
</script>
|
||||
@@ -1,118 +0,0 @@
|
||||
<template>
|
||||
<div class="artist-discography-nav">
|
||||
<h1 class="ellip">{{ store.artistname }}</h1>
|
||||
<div class="buttons">
|
||||
<div class="select rounded-sm" v-auto-animate="{ duration: 10 }">
|
||||
<button class="selected" @click.prevent="showDropDown = !showDropDown">
|
||||
<span class="ellip">{{ store.page }}</span>
|
||||
<ArrowSvg />
|
||||
</button>
|
||||
<div
|
||||
ref="dropOptionsRef"
|
||||
class="options rounded-sm shadow-lg"
|
||||
v-if="showDropDown"
|
||||
>
|
||||
<div
|
||||
class="option"
|
||||
v-for="a in albums"
|
||||
@click.prevent="switchView(a)"
|
||||
>
|
||||
{{ a }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
|
||||
import ArrowSvg from "@/assets/icons/expand.svg";
|
||||
import { discographyAlbumTypes as albums } from "@/enums";
|
||||
import { Ref, ref } from "vue";
|
||||
|
||||
import useArtistDiscogStore from "@/stores/pages/artistDiscog";
|
||||
|
||||
const store = useArtistDiscogStore();
|
||||
|
||||
const showDropDown = ref(false);
|
||||
const dropOptionsRef: Ref<HTMLElement | undefined> = ref();
|
||||
|
||||
function hideDropDown() {
|
||||
showDropDown.value = false;
|
||||
}
|
||||
|
||||
function switchView(album: albums) {
|
||||
store.setAlbums(album);
|
||||
hideDropDown();
|
||||
}
|
||||
|
||||
onClickOutside(dropOptionsRef, (e) => {
|
||||
// @ts-ignore
|
||||
e.stopImmediatePropagation();
|
||||
hideDropDown();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.artist-discography-nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.selected {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2rem;
|
||||
gap: $smaller;
|
||||
width: 100%;
|
||||
padding-right: 0;
|
||||
|
||||
svg {
|
||||
transform: rotate(90deg) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.select {
|
||||
position: relative;
|
||||
width: 8rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: calc($medium + 2px);
|
||||
z-index: 10;
|
||||
|
||||
.options {
|
||||
background-color: $gray;
|
||||
position: absolute;
|
||||
top: 120%;
|
||||
padding: $small $smaller;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.option {
|
||||
padding: $small;
|
||||
border-bottom: 1px solid $gray4;
|
||||
width: 7.5rem;
|
||||
|
||||
&:hover {
|
||||
border-radius: $smaller;
|
||||
border-bottom: 1px solid transparent;
|
||||
background-color: $darkestblue;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,48 +1,26 @@
|
||||
<template>
|
||||
<div id="folder-nav-title">
|
||||
<div class="folder">
|
||||
<div class="fname-wrapper">
|
||||
<div class="fname">
|
||||
<div
|
||||
class="icon image"
|
||||
@click="
|
||||
$router.push({ name: Routes.folder, params: { path: '$home' } })
|
||||
"
|
||||
></div>
|
||||
<BreadCrumbNav :sub-paths="subPaths" @navigate="navigate" />
|
||||
</div>
|
||||
</div>
|
||||
<SearchInput v-if="!isSmallPhone" :page="Routes.folder" />
|
||||
<button
|
||||
class="options"
|
||||
:class="{ 'btn-active': context_menu_showing }"
|
||||
title="show more options"
|
||||
@click="showContextMenu"
|
||||
>
|
||||
<MoreSvg />
|
||||
</button>
|
||||
<div class="fname">
|
||||
<div
|
||||
class="icon image"
|
||||
@click="
|
||||
$router.push({ name: Routes.folder, params: { path: '$home' } })
|
||||
"
|
||||
></div>
|
||||
<BreadCrumbNav :sub-paths="subPaths" @navigate="navigate" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
import { Routes } from "@/router";
|
||||
import { subPath } from "@/interfaces";
|
||||
import { isSmallPhone } from "@/stores/content-width";
|
||||
|
||||
import SearchInput from "@/components/shared/NavSearchInput.vue";
|
||||
import BreadCrumbNav from "@/components/FolderView/BreadCrumbNav.vue";
|
||||
import MoreSvg from "@/assets/icons/more.svg";
|
||||
import { showFolderContextMenu } from "@/helpers/contextMenuHandler";
|
||||
import { ref } from "vue";
|
||||
import { ContextSrc } from "@/enums";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const context_menu_showing = ref(false);
|
||||
|
||||
defineProps<{
|
||||
subPaths: subPath[];
|
||||
@@ -51,69 +29,36 @@ defineProps<{
|
||||
function navigate(path: string) {
|
||||
router.push({ name: Routes.folder, params: { path } });
|
||||
}
|
||||
|
||||
function showContextMenu(e: MouseEvent) {
|
||||
showFolderContextMenu(
|
||||
e,
|
||||
context_menu_showing,
|
||||
ContextSrc.FolderNav,
|
||||
route.params.path as string
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#folder-nav-title {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
overflow: hidden;
|
||||
|
||||
.folder {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content max-content;
|
||||
.fname {
|
||||
background-color: $gray4;
|
||||
border-radius: $small;
|
||||
height: 2.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: $smaller;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
overflow: scroll;
|
||||
scrollbar-width: none;
|
||||
|
||||
@include iphoneSE {
|
||||
grid-template-columns: 1fr max-content;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fname-wrapper {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fname {
|
||||
background-color: $gray4;
|
||||
border-radius: $small;
|
||||
height: 2.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-right: $smaller;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
overflow: scroll;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 2rem;
|
||||
aspect-ratio: 1;
|
||||
background-image: url("../../../assets/icons/folder.fill.svg");
|
||||
background-size: 1.5rem;
|
||||
margin-left: $smaller;
|
||||
}
|
||||
}
|
||||
|
||||
.options {
|
||||
margin-left: 1rem;
|
||||
height: 2.25rem;
|
||||
width: 2.25rem;
|
||||
.icon {
|
||||
height: 2rem;
|
||||
aspect-ratio: 1;
|
||||
padding: 0;
|
||||
|
||||
svg {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
background-image: url("../../../assets/icons/folder.fill.svg");
|
||||
background-size: 1.5rem;
|
||||
margin-left: $smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<template>
|
||||
<div class="nav-queue-title">
|
||||
<QueueActions />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import QueueActions from "@/components/RightSideBar/Queue/QueueActions.vue";
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.nav-queue-title {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
align-items: center;
|
||||
|
||||
.queue-actions {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,28 +1,26 @@
|
||||
<template>
|
||||
<div class="nav-search-input">
|
||||
<SearchInput :on_nav="true" />
|
||||
<div v-if="!isMobile" class="buttons-area">
|
||||
<Tabs
|
||||
:tabs="tabs"
|
||||
:current-tab="($route.params.page as string)"
|
||||
@switchTab="(tab: string) => {
|
||||
<Tabs
|
||||
v-if="!(content_width < 800)"
|
||||
:tabs="tabs"
|
||||
:current-tab="($route.params.page as string)"
|
||||
@switchTab="(tab: string) => {
|
||||
$router.replace({ name: Routes.search, params: { page: tab }, query: {
|
||||
q: search.query,
|
||||
} });
|
||||
search.switchTab(tab);
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<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"];
|
||||
@@ -30,35 +28,8 @@ const tabs = ["top", "tracks", "albums", "artists"];
|
||||
|
||||
<style lang="scss">
|
||||
.nav-search-input {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(14rem, 20rem) max-content;
|
||||
justify-content: space-between;
|
||||
gap: 2rem;
|
||||
|
||||
@include allPhones {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.buttons-area {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 18rem;
|
||||
}
|
||||
|
||||
#right-tabs {
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
|
||||
.tabheaders {
|
||||
height: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
.tabheaders {
|
||||
height: 2.25rem;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div v-if="album.help_text" class="rhelp album">
|
||||
<span class="help">{{ album.help_text }}</span>
|
||||
<span class="time">{{ album.time }}</span>
|
||||
</div>
|
||||
<h4 v-tooltip class="title ellip">
|
||||
{{ album.title }}
|
||||
</h4>
|
||||
@@ -38,30 +42,6 @@
|
||||
{{ `${artists[0].name}` }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<!-- when showing other versions -->
|
||||
<!-- <div
|
||||
v-if="
|
||||
album.versions.length === 0 &&
|
||||
hide_artists &&
|
||||
$route.name === Routes.album
|
||||
"
|
||||
class="artist ellip"
|
||||
>
|
||||
{{ album.date }}
|
||||
{{
|
||||
album.albumartists.length > 1
|
||||
? ` • ${album.albumartists[1].name}`
|
||||
: ""
|
||||
}}
|
||||
</div> -->
|
||||
<!-- <div v-else>
|
||||
<div class="artist ellip">
|
||||
{{ album.date }}
|
||||
</div>
|
||||
</div> -->
|
||||
<!-- end -->
|
||||
|
||||
<div v-if="album.versions.length" class="versions">
|
||||
<MasterFlag
|
||||
v-for="v in getVersions(
|
||||
@@ -121,8 +101,6 @@ const artists = computed(() => {
|
||||
|
||||
<style lang="scss">
|
||||
.album-card {
|
||||
flex: 0 0 10.1rem;
|
||||
|
||||
display: grid;
|
||||
gap: $small;
|
||||
padding: $medium;
|
||||
|
||||
@@ -8,17 +8,40 @@
|
||||
}"
|
||||
class="artist-card"
|
||||
>
|
||||
<img class="artist-image circular" :src="imguri + artist.image" />
|
||||
<div class="image circular">
|
||||
<img class="artist-image circular" :src="imguri + artist.image" />
|
||||
<div
|
||||
class="overlay circular"
|
||||
:style="{
|
||||
background: `linear-gradient(to top, ${artist.colors[0]} 20%, transparent)`,
|
||||
}"
|
||||
></div>
|
||||
<PlayBtn
|
||||
:artisthash="artist.artisthash"
|
||||
:artistname="artist.name"
|
||||
:source="playSources.artist"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="artist.help_text" class="rhelp t-center">
|
||||
<span class="help">{{ artist.help_text }}</span>
|
||||
<span class="time">{{ artist.time }}</span>
|
||||
</div>
|
||||
<div class="artist-name t-center">
|
||||
{{ artist.name }}
|
||||
</div>
|
||||
<div v-if="artist.help_text && artist.trackcount" class="racount t-center">
|
||||
{{ artist.trackcount }} Track{{ artist.trackcount == 1 ? "" : "s" }}
|
||||
</div>
|
||||
</RouterLink>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Artist } from "@/interfaces";
|
||||
import { Routes } from "@/router";
|
||||
import { paths } from "../../config";
|
||||
import { paths } from "@/config";
|
||||
|
||||
import PlayBtn from "./PlayBtn.vue";
|
||||
import { playSources } from "@/enums";
|
||||
|
||||
const imguri = paths.images.artist.large;
|
||||
|
||||
@@ -29,32 +52,67 @@ defineProps<{
|
||||
|
||||
<style lang="scss">
|
||||
.artist-card {
|
||||
flex: 0 0 10.1rem;
|
||||
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
border-radius: $medium;
|
||||
display: grid;
|
||||
gap: $small;
|
||||
justify-content: center;
|
||||
padding: 1.2rem 1rem !important;
|
||||
font-size: 0.9rem;
|
||||
font-weight: bolder;
|
||||
height: max-content;
|
||||
|
||||
.image {
|
||||
position: relative;
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: calc(100% - $small + 1px);
|
||||
top: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
$btnwidth: 4rem;
|
||||
|
||||
.play-btn {
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
width: 4rem;
|
||||
bottom: 0;
|
||||
left: calc(50% - ($btnwidth / 2));
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: $gray4;
|
||||
|
||||
.play-btn {
|
||||
opacity: 1;
|
||||
transform: translateY(-1.25rem);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.artist-image {
|
||||
width: 100%;
|
||||
transition: all 0.5s ease-in-out;
|
||||
object-fit: cover;
|
||||
margin-bottom: $smaller;
|
||||
}
|
||||
|
||||
.artist-name {
|
||||
margin-bottom: $smaller;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.racount {
|
||||
font-size: 12px;
|
||||
color: #ffffffbf;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||