Compare commits
44 Commits
no-sidebar
...
introducin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab0c566c92 | ||
|
|
e0654b182a | ||
|
|
06de7a26e1 | ||
|
|
176b38ffa5 | ||
|
|
12e7db7310 | ||
|
|
32ac77c426 | ||
|
|
bce03af941 | ||
|
|
f6921fedc9 | ||
|
|
3477397072 | ||
|
|
0bb7bf56d2 | ||
|
|
d2b0e28751 | ||
|
|
2d0146cfc3 | ||
|
|
cfcfd4d7d7 | ||
|
|
92c6e76ad5 | ||
|
|
5a4efb41bc | ||
|
|
211bab805a | ||
|
|
b7583ac889 | ||
|
|
a63717961b | ||
|
|
9a7596a6ba | ||
|
|
62c65873f5 | ||
|
|
821b0029dd | ||
|
|
336a9755b8 | ||
|
|
f9a522240e | ||
|
|
94af90e481 | ||
|
|
85dfb5700e | ||
|
|
45010fe4d1 | ||
|
|
3948deb165 | ||
|
|
e5f73d8495 | ||
|
|
18afef151c | ||
|
|
80dbd98beb | ||
|
|
a12d1a86e5 | ||
|
|
449e7b4bb7 | ||
|
|
5ee0b81ed9 | ||
|
|
3496fde28c | ||
|
|
58b2a02989 | ||
|
|
eb69d78a38 | ||
|
|
b7a9a35c4f | ||
|
|
8de2a701ea | ||
|
|
681ccaba8c | ||
|
|
6d3f64ffc7 | ||
|
|
aa780977f1 | ||
|
|
4cb77334fb | ||
|
|
293e95c5b0 | ||
|
|
302095ef80 |
2
.gitignore
vendored
@@ -24,7 +24,7 @@ pnpm-debug.log*
|
||||
*.sw?
|
||||
|
||||
# TODO
|
||||
TODO.md
|
||||
# TODO.md
|
||||
|
||||
__pycache__
|
||||
dev-dist/*
|
||||
2
TODO.md
Normal file
@@ -0,0 +1,2 @@
|
||||
- Remove welcome dialog
|
||||
- Update folder page breadcrumb when response has skipped empty folders
|
||||
@@ -3,15 +3,15 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1" />
|
||||
<title>Swing Music</title>
|
||||
<base href="./">
|
||||
<base href="./" />
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong
|
||||
>We're sorry but this app doesn't work properly without JavaScript
|
||||
enabled. Please enable it to continue.</strong
|
||||
>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to
|
||||
continue.</strong
|
||||
>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"v-wave": "^1.5.0",
|
||||
"vue": "^v3.2.45",
|
||||
"vue-boring-avatars": "^1.4.0",
|
||||
"vue-debounce": "^3.0.2",
|
||||
"vue-router": "^4.1.3",
|
||||
"vue-template-compiler": "^2.0.0",
|
||||
|
||||
@@ -11,5 +11,6 @@ onmessage = (e) => {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ trackhash, duration, source, timestamp }),
|
||||
credentials: "include"
|
||||
});
|
||||
};
|
||||
|
||||
@@ -10,7 +10,8 @@ onmessage = async (e) => {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ end: ending_file, start: starting_file }),
|
||||
body: JSON.stringify({ ending_file, starting_file }),
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
|
||||
276
src/App.vue
@@ -1,177 +1,199 @@
|
||||
<template>
|
||||
<ContextMenu />
|
||||
<Modal />
|
||||
<Notification />
|
||||
<div id="drag-img" class="ellip2" style=""></div>
|
||||
<section
|
||||
id="app-grid"
|
||||
:class="{
|
||||
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'
|
||||
: ''
|
||||
}`,
|
||||
}"
|
||||
>
|
||||
<LeftSidebar v-if="settings.is_default_layout && !isMobile" />
|
||||
<NavBar />
|
||||
<div id="acontent" v-element-size="updateContentElemSize">
|
||||
<div id="contentresizer" ref="appcontent"></div>
|
||||
<BalancerProvider>
|
||||
<RouterView />
|
||||
</BalancerProvider>
|
||||
</div>
|
||||
<RightSideBar v-if="settings.use_sidebar && xl" />
|
||||
<BottomBar />
|
||||
<!-- <BubbleManager /> -->
|
||||
</section>
|
||||
<ContextMenu />
|
||||
<Modal />
|
||||
<Notification />
|
||||
<div
|
||||
id="drag-img"
|
||||
class="ellip2"
|
||||
style=""
|
||||
></div>
|
||||
<section
|
||||
id="app-grid"
|
||||
:class="{
|
||||
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'
|
||||
: ''
|
||||
}`,
|
||||
}"
|
||||
>
|
||||
<LeftSidebar v-if="settings.is_default_layout && !isMobile" />
|
||||
<NavBar />
|
||||
<div
|
||||
id="acontent"
|
||||
v-element-size="updateContentElemSize"
|
||||
>
|
||||
<div
|
||||
id="contentresizer"
|
||||
ref="appcontent"
|
||||
></div>
|
||||
<BalancerProvider>
|
||||
<RouterView />
|
||||
</BalancerProvider>
|
||||
</div>
|
||||
<RightSideBar v-if="settings.use_sidebar && xl" />
|
||||
<BottomBar />
|
||||
<!-- <BubbleManager /> -->
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// @libraries
|
||||
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";
|
||||
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
|
||||
import {
|
||||
content_height,
|
||||
content_width,
|
||||
isMobile,
|
||||
content_height,
|
||||
content_width,
|
||||
isMobile,
|
||||
resizer_height,
|
||||
resizer_width,
|
||||
updateCardWidth,
|
||||
} from "@/stores/content-width";
|
||||
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";
|
||||
} from '@/stores/content-width'
|
||||
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'
|
||||
import useAuth from '@/stores/auth'
|
||||
|
||||
// @utils
|
||||
import handleShortcuts from "@/helpers/useKeyboard";
|
||||
import { readLocalStorage, writeLocalStorage } from "@/utils";
|
||||
import { xl, xxl } from "./composables/useBreakpoints";
|
||||
import handleShortcuts from '@/helpers/useKeyboard'
|
||||
import { readLocalStorage, writeLocalStorage } from '@/utils'
|
||||
import { xl, xxl } from './composables/useBreakpoints'
|
||||
|
||||
// @small-components
|
||||
import ContextMenu from "@/components/ContextMenu.vue";
|
||||
import Modal from "@/components/modal.vue";
|
||||
import Notification from "@/components/Notification.vue";
|
||||
import ContextMenu from '@/components/ContextMenu.vue'
|
||||
import Modal from '@/components/modal.vue'
|
||||
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 { getAllSettings } from "@/requests/settings";
|
||||
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 { getRootDirs } from "@/requests/settings/rootdirs";
|
||||
import { getLoggedInUser } from './requests/auth'
|
||||
import { getAllSettings } from '@/requests/settings'
|
||||
import { getRootDirs } from '@/requests/settings/rootdirs'
|
||||
// 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 settings = useSettings();
|
||||
useTracker();
|
||||
const appcontent: Ref<HTMLLegendElement | null> = ref(null)
|
||||
const auth = useAuth()
|
||||
const resizercontent: Ref<HTMLLegendElement | null> = ref(null);
|
||||
const queue = useQueue()
|
||||
const modal = useModal()
|
||||
const lyrics = useLyrics()
|
||||
const router = useRouter()
|
||||
const settings = useSettings()
|
||||
useTracker()
|
||||
|
||||
handleShortcuts(useQueue, useModal);
|
||||
handleShortcuts(useQueue, useModal)
|
||||
|
||||
router.afterEach(() => {
|
||||
(document.getElementById("acontent") as HTMLElement).scrollTo(0, 0);
|
||||
});
|
||||
;(document.getElementById('acontent') as HTMLElement).scrollTo(0, 0)
|
||||
})
|
||||
|
||||
onStartTyping(() => {
|
||||
const elem = document.getElementById("globalsearch") as HTMLInputElement;
|
||||
elem.focus();
|
||||
elem.value = "";
|
||||
});
|
||||
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,
|
||||
};
|
||||
const elem = document.getElementById('acontent') as HTMLElement
|
||||
return {
|
||||
width: elem.offsetWidth,
|
||||
height: elem.offsetHeight,
|
||||
}
|
||||
}
|
||||
|
||||
function updateContentElemSize({
|
||||
width,
|
||||
height,
|
||||
}: {
|
||||
width: number;
|
||||
height: number;
|
||||
}) {
|
||||
function updateContentElemSize({ width, height }: { width: number; height: number }) {
|
||||
// 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;
|
||||
|
||||
const elem_resizer_width = resizercontent.value?.offsetWidth || 0;
|
||||
resizer_width.value = elem_resizer_width;
|
||||
resizer_height.value = height;
|
||||
|
||||
updateCardWidth();
|
||||
}
|
||||
|
||||
function handleWelcomeModal() {
|
||||
let welcomeShowCount = readLocalStorage("shown-welcome-message");
|
||||
let welcomeShowCount = readLocalStorage('shown-welcome-message')
|
||||
|
||||
if (!welcomeShowCount) {
|
||||
welcomeShowCount = 0;
|
||||
}
|
||||
if (!welcomeShowCount) {
|
||||
welcomeShowCount = 0
|
||||
}
|
||||
|
||||
if (welcomeShowCount < 2) {
|
||||
modal.showWelcomeModal();
|
||||
writeLocalStorage("shown-welcome-message", welcomeShowCount + 1);
|
||||
}
|
||||
if (welcomeShowCount < 2) {
|
||||
modal.showWelcomeModal()
|
||||
writeLocalStorage('shown-welcome-message', welcomeShowCount + 1)
|
||||
}
|
||||
}
|
||||
|
||||
function handleRootDirsPrompt() {
|
||||
getRootDirs().then((dirs) => {
|
||||
if (dirs.length === 0) {
|
||||
modal.showRootDirsPromptModal();
|
||||
} else {
|
||||
settings.setRootDirs(dirs);
|
||||
}
|
||||
});
|
||||
getRootDirs().then((dirs) => {
|
||||
if (dirs.length === 0) {
|
||||
modal.showRootDirsPromptModal()
|
||||
} else {
|
||||
settings.setRootDirs(dirs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const { width, height } = getContentSize();
|
||||
updateContentElemSize({ width, height });
|
||||
onMounted(async () => {
|
||||
const { width, height } = getContentSize()
|
||||
updateContentElemSize({ width, height })
|
||||
|
||||
handleWelcomeModal();
|
||||
settings.initializeVolume();
|
||||
const res = await getLoggedInUser()
|
||||
|
||||
handleRootDirsPrompt();
|
||||
if (res.status == 200) {
|
||||
auth.setUser(res.data)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
getAllSettings()
|
||||
.then(({ settings: data }) => {
|
||||
settings.mapDbSettings(data);
|
||||
})
|
||||
.then(() => {
|
||||
if (queue.currenttrack && !settings.use_lyrics_plugin) {
|
||||
lyrics.checkExists(
|
||||
queue.currenttrack.filepath,
|
||||
queue.currenttrack.trackhash
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
handleWelcomeModal()
|
||||
settings.initializeVolume()
|
||||
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./assets/scss/mixins.scss";
|
||||
|
||||
.r-sidebar {
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
@import './assets/scss/mixins.scss';
|
||||
.designatedOS .r-sidebar {
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
BIN
src/assets/fonts/SFCompactDisplay-Bold.woff2
Normal file
BIN
src/assets/fonts/SFCompactDisplay-Medium.woff2
Normal file
BIN
src/assets/fonts/SFCompactDisplay-Regular.woff2
Normal file
BIN
src/assets/fonts/SFCompactDisplay-Semibold.woff2
Normal file
BIN
src/assets/fonts/sf-mono-medium.woff2
Normal file
@@ -1,3 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.9912 22.7422C18.9746 22.7422 23.0879 18.6289 23.0879 13.6543C23.0879 8.67969 18.9658 4.56641 13.9824 4.56641C9.00781 4.56641 4.90332 8.67969 4.90332 13.6543C4.90332 18.6289 9.0166 22.7422 13.9912 22.7422ZM13.9912 20.9316C9.95703 20.9316 6.73145 17.6885 6.73145 13.6543C6.73145 9.62012 9.95703 6.38574 13.9824 6.38574C18.0166 6.38574 21.2598 9.62012 21.2686 13.6543C21.2773 17.6885 18.0254 20.9316 13.9912 20.9316ZM14 17.0996C15.9072 17.0996 17.4453 15.5615 17.4453 13.6455C17.4453 11.7471 15.9072 10.2002 14 10.2002C12.084 10.2002 10.5459 11.7471 10.5459 13.6455C10.5459 15.5615 12.084 17.0996 14 17.0996Z" fill="#fff"/>
|
||||
<svg viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.9912 22.7422C18.9746 22.7422 23.0879 18.6289 23.0879 13.6543C23.0879 8.67969 18.9658 4.56641 13.9824 4.56641C9.00781 4.56641 4.90332 8.67969 4.90332 13.6543C4.90332 18.6289 9.0166 22.7422 13.9912 22.7422ZM13.9912 20.9316C9.95703 20.9316 6.73145 17.6885 6.73145 13.6543C6.73145 9.62012 9.95703 6.38574 13.9824 6.38574C18.0166 6.38574 21.2598 9.62012 21.2686 13.6543C21.2773 17.6885 18.0254 20.9316 13.9912 20.9316ZM14 17.0996C15.9072 17.0996 17.4453 15.5615 17.4453 13.6455C17.4453 11.7471 15.9072 10.2002 14 10.2002C12.084 10.2002 10.5459 11.7471 10.5459 13.6455C10.5459 15.5615 12.084 17.0996 14 17.0996Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 737 B After Width: | Height: | Size: 722 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg 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="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 936 B After Width: | Height: | Size: 913 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg 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>
|
||||
|
||||
|
Before Width: | Height: | Size: 542 B After Width: | Height: | Size: 519 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 28 28" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M9.96582 22.7686H18.043C19.3965 22.7686 20.2666 21.9512 20.3369 20.5977L20.9258 7.94141H21.8926C22.3408 7.94141 22.6836 7.58984 22.6836 7.15039C22.6836 6.71094 22.332 6.37695 21.8926 6.37695H17.9902V5.05859C17.9902 3.70508 17.1289 2.91406 15.6611 2.91406H12.3213C10.8535 2.91406 9.99219 3.70508 9.99219 5.05859V6.37695H6.10742C5.66797 6.37695 5.31641 6.71973 5.31641 7.15039C5.31641 7.59863 5.66797 7.94141 6.10742 7.94141H7.07422L7.66309 20.5977C7.7334 21.96 8.59473 22.7686 9.96582 22.7686ZM11.6357 5.1377C11.6357 4.68945 11.9521 4.39941 12.4355 4.39941H15.5469C16.0303 4.39941 16.3467 4.68945 16.3467 5.1377V6.37695H11.6357V5.1377ZM10.1416 21.1953C9.6582 21.1953 9.30664 20.835 9.28027 20.3164L8.69141 7.94141H19.2822L18.7109 20.3164C18.6934 20.8438 18.3506 21.1953 17.8496 21.1953H10.1416ZM11.4072 19.7803C11.7852 19.7803 12.0225 19.543 12.0137 19.1914L11.75 9.99805C11.7412 9.64648 11.4951 9.41797 11.1348 9.41797C10.7656 9.41797 10.5283 9.65527 10.5371 10.0068L10.8008 19.2002C10.8096 19.5518 11.0557 19.7803 11.4072 19.7803ZM14 19.7803C14.3691 19.7803 14.624 19.5518 14.624 19.2002V10.0068C14.624 9.65527 14.3691 9.41797 14 9.41797C13.6309 9.41797 13.3848 9.65527 13.3848 10.0068V19.2002C13.3848 19.5518 13.6309 19.7803 14 19.7803ZM16.5928 19.7891C16.9443 19.7891 17.1904 19.5518 17.1992 19.2002L17.4629 10.0068C17.4717 9.65527 17.2344 9.42676 16.8652 9.42676C16.5049 9.42676 16.2588 9.65527 16.25 10.0068L15.9863 19.2002C15.9775 19.543 16.2148 19.7891 16.5928 19.7891Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
4
src/assets/icons/eye.slash.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 33 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.1039 24.2276C18.2667 24.2276 20.2462 23.8513 22.0458 23.2428L20.1795 21.3723C18.9009 21.7511 17.5488 21.9793 16.1039 21.9793C8.60694 21.9793 2.65404 15.7188 2.65404 14.0854C2.65404 13.2989 4.65326 10.7519 7.8614 8.78101L6.10032 7.02204C2.26547 9.45297 0 12.6801 0 14.0854C0 16.4824 6.62578 24.2276 16.1039 24.2276ZM16.1039 3.94751C14.0627 3.94751 12.2027 4.29696 10.4822 4.86696L12.3581 6.73539C13.5459 6.39296 14.771 6.19562 16.1039 6.19562C23.5913 6.19562 29.5463 12.7162 29.5463 14.0854C29.5463 15.0029 27.6516 17.3973 24.6312 19.2925L26.37 21.0376C30.0415 18.6163 32.2003 15.4619 32.2003 14.0854C32.2003 11.6831 25.7119 3.94751 16.1039 3.94751ZM16.1039 20.4032C17.0128 20.4032 17.8695 20.1833 18.6375 19.8247L10.3399 11.5272C9.9696 12.2931 9.76147 13.1593 9.76147 14.0875C9.77108 17.5246 12.5794 20.4032 16.1039 20.4032ZM21.9532 16.3501C22.2638 15.6536 22.4313 14.8801 22.4313 14.0833C22.4313 10.5609 19.6134 7.77179 16.106 7.77179C15.292 7.77179 14.5282 7.93937 13.8316 8.22859L21.9532 16.3501Z" fill="currentColor"/>
|
||||
<path d="M25.3391 24.4257C25.7033 24.7996 26.2869 24.8188 26.6586 24.4257C27.0496 24.0327 27.0207 23.48 26.6586 23.1062L6.81388 3.27316C6.44967 2.90895 5.85435 2.90895 5.48264 3.27316C5.13014 3.62566 5.13014 4.2423 5.48264 4.59269L25.3391 24.4257Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
3
src/assets/icons/eye.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 33 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.1039 24.3001C25.7119 24.3001 32.2003 16.555 32.2003 14.158C32.2003 11.7557 25.7023 4.02014 16.1039 4.02014C6.63539 4.02014 0 11.7557 0 14.158C0 16.555 6.62578 24.3001 16.1039 24.3001ZM16.1039 22.052C8.60694 22.052 2.65404 15.7914 2.65404 14.158C2.65404 12.7888 8.60694 6.26825 16.1039 6.26825C23.572 6.26825 29.5463 12.7888 29.5463 14.158C29.5463 15.7914 23.572 22.052 16.1039 22.052ZM16.106 20.4759C19.6177 20.4759 22.4313 17.5973 22.4313 14.158C22.4313 10.6335 19.6177 7.84654 16.106 7.84654C12.5794 7.84654 9.75186 10.6314 9.76147 14.158C9.78069 17.5973 12.5794 20.4759 16.106 20.4759ZM16.1018 16.2048C14.9632 16.2048 14.0442 15.2774 14.0442 14.158C14.0442 13.029 14.9632 12.1154 16.1018 12.1154C17.2371 12.1154 18.1582 13.029 18.1582 14.158C18.1582 15.2774 17.2371 16.2048 16.1018 16.2048Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 910 B |
3
src/assets/icons/file.fill.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.80577 26.3655H20.2891C22.8013 26.3655 24.0948 25.0506 24.0948 22.5287V12.0379H15.3341C13.8679 12.0379 13.1552 11.3251 13.1552 9.85679V1H7.80577C5.30523 1 4 2.32445 4 4.84632V22.5287C4 25.0602 5.29562 26.3655 7.80577 26.3655ZM15.5423 10.3248H23.9669C23.905 9.80008 23.5166 9.29336 22.9077 8.67273L16.5098 2.17656C15.921 1.58031 15.4026 1.19406 14.8682 1.12047V9.66039C14.8682 10.1034 15.0897 10.3248 15.5423 10.3248Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 531 B |
@@ -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="M21.1807 6.99219H13.0332C12.4004 6.99219 12.0225 6.85156 11.5303 6.44727L11.0381 6.04297C10.4141 5.5332 9.95703 5.36621 9.03418 5.36621H6.54688C4.89453 5.36621 3.91895 6.33301 3.91895 8.1875V10.6484H24.0723V9.85742C24.0723 7.97656 23.0791 6.99219 21.1807 6.99219ZM6.81055 21.7666H21.3916C23.0879 21.7666 24.0723 20.7822 24.0723 18.9014V11.9141H3.91895V18.9014C3.91895 20.791 4.91211 21.7666 6.81055 21.7666Z" fill="#F2F2F2"/>
|
||||
<svg viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.1807 6.99219H13.0332C12.4004 6.99219 12.0225 6.85156 11.5303 6.44727L11.0381 6.04297C10.4141 5.5332 9.95703 5.36621 9.03418 5.36621H6.54688C4.89453 5.36621 3.91895 6.33301 3.91895 8.1875V10.6484H24.0723V9.85742C24.0723 7.97656 23.0791 6.99219 21.1807 6.99219ZM6.81055 21.7666H21.3916C23.0879 21.7666 24.0723 20.7822 24.0723 18.9014V11.9141H3.91895V18.9014C3.91895 20.791 4.91211 21.7666 6.81055 21.7666Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 538 B After Width: | Height: | Size: 520 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 28 28" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.81055 21.7666H21.3916C23.0879 21.7666 24.0723 20.7822 24.0723 18.9014V9.85742C24.0723 7.97656 23.0791 6.99219 21.1807 6.99219H13.0332C12.4004 6.99219 12.0225 6.85156 11.5303 6.44727L11.0381 6.04297C10.4141 5.5332 9.95703 5.36621 9.03418 5.36621H6.54688C4.89453 5.36621 3.91895 6.33301 3.91895 8.1875V18.9014C3.91895 20.791 4.91211 21.7666 6.81055 21.7666ZM5.66797 8.33691C5.66797 7.53711 6.11621 7.11523 6.89844 7.11523H8.56836C9.19238 7.11523 9.56152 7.26465 10.0625 7.66895L10.5547 8.08203C11.1699 8.57422 11.6445 8.75 12.5674 8.75H21.0664C21.875 8.75 22.3232 9.17188 22.3232 10.0156V10.5342H5.66797V8.33691ZM6.9248 20.0176C6.11621 20.0176 5.66797 19.5957 5.66797 18.7432V12.0723H22.3232V18.752C22.3232 19.5957 21.875 20.0176 21.0664 20.0176H6.9248Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 898 B After Width: | Height: | Size: 875 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="28" fill="currentColor" height="28" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg fill="currentColor" viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5.09668 11.1846C5.09668 14.9375 8.25195 18.6465 13.1562 21.8105C13.4287 21.9863 13.7627 22.1621 13.9912 22.1621C14.2197 22.1621 14.5537 21.9863 14.8262 21.8105C19.7393 18.6465 22.8857 14.9375 22.8857 11.1846C22.8857 7.94141 20.6445 5.69141 17.7705 5.69141C16.0918 5.69141 14.7822 6.45605 13.9912 7.61621C13.2178 6.46484 11.8994 5.69141 10.2207 5.69141C7.33789 5.69141 5.09668 7.94141 5.09668 11.1846ZM6.90723 11.1758C6.90723 8.96094 8.36621 7.45801 10.3262 7.45801C11.9082 7.45801 12.7959 8.41602 13.3496 9.25098C13.5957 9.61133 13.7627 9.72559 13.9912 9.72559C14.2285 9.72559 14.3779 9.60254 14.6328 9.25098C15.2305 8.43359 16.083 7.45801 17.6562 7.45801C19.625 7.45801 21.084 8.96094 21.084 11.1758C21.084 14.2695 17.8672 17.6973 14.1582 20.1582C14.0791 20.2109 14.0264 20.2461 13.9912 20.2461C13.9561 20.2461 13.9033 20.2109 13.833 20.1582C10.124 17.6973 6.90723 14.2695 6.90723 11.1758Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1016 B After Width: | Height: | Size: 993 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 30 30" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M15 1.875C7.75195 1.875 1.875 7.75195 1.875 15C1.875 22.248 7.75195 28.125 15 28.125C22.248 28.125 28.125 22.248 28.125 15C28.125 7.75195 22.248 1.875 15 1.875ZM15 25.8984C8.98242 25.8984 4.10156 21.0176 4.10156 15C4.10156 8.98242 8.98242 4.10156 15 4.10156C21.0176 4.10156 25.8984 8.98242 25.8984 15C25.8984 21.0176 21.0176 25.8984 15 25.8984Z" fill="currentColor"/>
|
||||
<path d="M13.5938 9.84375C13.5938 10.2167 13.7419 10.5744 14.0056 10.8381C14.2694 11.1018 14.627 11.25 15 11.25C15.373 11.25 15.7306 11.1018 15.9944 10.8381C16.2581 10.5744 16.4062 10.2167 16.4062 9.84375C16.4062 9.47079 16.2581 9.1131 15.9944 8.84938C15.7306 8.58566 15.373 8.4375 15 8.4375C14.627 8.4375 14.2694 8.58566 14.0056 8.84938C13.7419 9.1131 13.5938 9.47079 13.5938 9.84375V9.84375ZM15.7031 13.125H14.2969C14.168 13.125 14.0625 13.2305 14.0625 13.3594V21.3281C14.0625 21.457 14.168 21.5625 14.2969 21.5625H15.7031C15.832 21.5625 15.9375 21.457 15.9375 21.3281V13.3594C15.9375 13.2305 15.832 13.125 15.7031 13.125Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -1,4 +1,4 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg 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>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
3
src/assets/icons/mic.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 12.6082C5 17.4276 8.21234 20.7368 12.8291 21.1729V23.5828H8.52569C7.9196 23.5828 7.41359 24.074 7.41359 24.6822C7.41359 25.2841 7.9196 25.77 8.52569 25.77H19.2732C19.8814 25.77 20.3874 25.2841 20.3874 24.6822C20.3874 24.074 19.8814 23.5828 19.2732 23.5828H14.9719V21.1729C19.5982 20.7368 22.801 17.4276 22.801 12.6082V10.2862C22.801 9.68015 22.313 9.20906 21.7027 9.20906C21.0945 9.20906 20.5927 9.68015 20.5927 10.2862V12.5294C20.5927 16.5234 17.8983 19.1487 13.9053 19.1487C9.90263 19.1487 7.20826 16.5234 7.20826 12.5294V10.2862C7.20826 9.68015 6.71608 9.20906 6.09616 9.20906C5.48586 9.20906 5 9.68015 5 10.2862V12.6082ZM9.42241 12.0935C9.42241 14.9091 11.2665 16.8855 13.9053 16.8855C16.5324 16.8855 18.3786 14.9091 18.3786 12.0935V4.78991C18.3786 1.97109 16.5324 0 13.9053 0C11.2665 0 9.42241 1.97109 9.42241 4.78991V12.0935ZM11.6391 12.1315V4.75406C11.6391 3.19053 12.5234 2.12788 13.9053 2.12788C15.2872 2.12788 16.1598 3.18631 16.1598 4.75406V12.1315C16.1598 13.6971 15.2872 14.7555 13.9053 14.7555C12.5234 14.7555 11.6391 13.6971 11.6391 12.1315Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
src/assets/icons/paintbrush.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 28 29" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.53691 26.8314C4.56988 28.8665 6.94784 28.8899 8.94987 26.8921C10.3446 25.4974 11.8097 22.2588 12.8986 20.9125L15.3357 23.3613C16.3151 24.3524 17.5158 24.3567 18.4697 23.3985L20.0067 21.8498C20.9649 20.8799 20.9585 19.7165 19.9695 18.7275L10.6526 9.39675C9.66143 8.40348 8.48628 8.39387 7.51644 9.36371L5.98152 10.9082C5.02129 11.8685 5.01379 13.049 6.00918 14.0423L8.45371 16.4793C7.11918 17.5682 3.88058 19.0258 2.48793 20.4185C0.490115 22.4205 0.501833 24.8081 2.53691 26.8314ZM7.86565 12.179L8.7769 11.2849C9.04151 11.0224 9.32956 11.0106 9.59417 11.2753L18.0985 19.7699C18.3535 20.0346 18.3535 20.3247 18.0792 20.5989L17.1968 21.491C16.9301 21.7769 16.6249 21.7865 16.3561 21.5006L13.5427 18.6775C13.0648 18.1975 12.4306 18.2444 11.8878 18.7734C10.9737 19.6736 9.383 23.5038 7.51339 25.3596C6.44112 26.4414 5.14714 26.4393 4.03221 25.3361C2.93862 24.2308 2.92479 22.923 4.00666 21.8507C5.87627 19.9971 9.70432 18.4042 10.6067 17.4881C11.1239 16.9378 11.1804 16.3035 10.7004 15.8332L7.86565 13.0059C7.59893 12.7413 7.59893 12.4457 7.86565 12.179ZM5.74503 25.0429C6.51448 25.0429 7.13581 24.412 7.13581 23.6446C7.13581 22.8773 6.51448 22.256 5.74503 22.256C4.97768 22.256 4.34674 22.8773 4.34674 23.6446C4.34674 24.412 4.97768 25.0429 5.74503 25.0429ZM19.6507 20.1238L25.3142 14.4625C26.6719 13.1027 26.6442 11.466 25.2514 10.071L16.0395 0.845349C14.7078 -0.486366 12.4632 0.0552723 12.0043 2.07511C10.8722 6.96979 10.8612 7.36987 9.02605 9.93464L10.5741 11.4785C12.7514 8.67885 12.8764 7.35159 13.9971 3.22283C14.1305 2.70111 14.5594 2.56564 14.9114 2.90807L23.6128 11.5999C24.0992 12.0862 24.0971 12.6433 23.6501 13.0924L18.1357 18.6089L19.6507 20.1238ZM17.7654 13.2839C18.3844 13.9049 21.8084 12.0065 22.9899 10.5182L20.4139 7.95182C20.1912 10.026 19.1131 11.526 17.8006 12.8385C17.6482 12.9909 17.6599 13.1805 17.7654 13.2839Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -1 +1 @@
|
||||
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="28px" height="28px" baseProfile="basic"><path d="M13,46h16.205c0.338,1.549,1.093,2.903,2.174,4H13c-1.104,0-2-0.895-2-2C11,46.895,11.896,46,13,46z"/><path d="M12.999,38l18.714,0c-1.142,0.918-2.077,2.195-2.486,4L13,42c-1.104,0-2-0.895-2-2C11,38.895,11.895,38,12.999,38z"/><path d="M13,30h28v4H13c-1.104,0-2-0.895-2-2C11,30.895,11.896,30,13,30z"/><path d="M13,22h28v4H13c-1.104,0-2-0.895-2-2C11,22.895,11.896,22,13,22z"/><path d="M13,14h28v4H13c-1.104,0-2-0.895-2-2C11,14.895,11.896,14,13,14z"/><path d="M54.026,9.158C54.997,8.834,56,9.557,56,10.581v7.484c0,0.829-0.511,1.572-1.286,1.868l-5.75,2.199 C48.384,22.353,48,22.911,48,23.532V39c0,8-4.083,11-8.561,11C35.026,50,32,47.754,32,44.079c0-3.39,2.07-4.633,6.224-5.553 c4.067-0.9,5.776-1.327,5.776-4.142V13.942c0-0.861,0.551-1.625,1.368-1.897L54.026,9.158z" /></svg>
|
||||
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" baseProfile="basic"><path d="M13,46h16.205c0.338,1.549,1.093,2.903,2.174,4H13c-1.104,0-2-0.895-2-2C11,46.895,11.896,46,13,46z"/><path d="M12.999,38l18.714,0c-1.142,0.918-2.077,2.195-2.486,4L13,42c-1.104,0-2-0.895-2-2C11,38.895,11.895,38,12.999,38z"/><path d="M13,30h28v4H13c-1.104,0-2-0.895-2-2C11,30.895,11.896,30,13,30z"/><path d="M13,22h28v4H13c-1.104,0-2-0.895-2-2C11,22.895,11.896,22,13,22z"/><path d="M13,14h28v4H13c-1.104,0-2-0.895-2-2C11,14.895,11.896,14,13,14z"/><path d="M54.026,9.158C54.997,8.834,56,9.557,56,10.581v7.484c0,0.829-0.511,1.572-1.286,1.868l-5.75,2.199 C48.384,22.353,48,22.911,48,23.532V39c0,8-4.083,11-8.561,11C35.026,50,32,47.754,32,44.079c0-3.39,2.07-4.633,6.224-5.553 c4.067-0.9,5.776-1.327,5.776-4.142V13.942c0-0.861,0.551-1.625,1.368-1.897L54.026,9.158z" /></svg>
|
||||
|
Before Width: | Height: | Size: 902 B After Width: | Height: | Size: 875 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.16016 9.50586C6.85449 9.50586 7.4082 8.95215 7.4082 8.25781C7.4082 7.57227 6.85449 7.00977 6.16016 7.00977C5.47461 7.00977 4.91211 7.57227 4.91211 8.25781C4.91211 8.95215 5.47461 9.50586 6.16016 9.50586ZM10.291 9.10156H22.2266C22.7012 9.10156 23.0791 8.73242 23.0791 8.25781C23.0791 7.7832 22.71 7.41406 22.2266 7.41406H10.291C9.8252 7.41406 9.44727 7.7832 9.44727 8.25781C9.44727 8.73242 9.81641 9.10156 10.291 9.10156ZM6.16016 14.9111C6.85449 14.9111 7.4082 14.3574 7.4082 13.6631C7.4082 12.9775 6.85449 12.415 6.16016 12.415C5.47461 12.415 4.91211 12.9775 4.91211 13.6631C4.91211 14.3574 5.47461 14.9111 6.16016 14.9111ZM10.291 14.5068H22.2266C22.7012 14.5068 23.0791 14.1377 23.0791 13.6631C23.0791 13.1885 22.71 12.8193 22.2266 12.8193H10.291C9.8252 12.8193 9.44727 13.1885 9.44727 13.6631C9.44727 14.1377 9.81641 14.5068 10.291 14.5068ZM6.16016 20.3164C6.85449 20.3164 7.4082 19.7627 7.4082 19.0684C7.4082 18.3828 6.85449 17.8203 6.16016 17.8203C5.47461 17.8203 4.91211 18.3828 4.91211 19.0684C4.91211 19.7627 5.47461 20.3164 6.16016 20.3164ZM10.291 19.9121H22.2266C22.7012 19.9121 23.0791 19.543 23.0791 19.0684C23.0791 18.5938 22.71 18.2246 22.2266 18.2246H10.291C9.8252 18.2246 9.44727 18.5938 9.44727 19.0684C9.44727 19.543 9.81641 19.9121 10.291 19.9121Z" fill="#F2F2F2"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -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="M7.63672 14.6562H12.998V20.0176C12.998 20.5625 13.4463 21.0195 14 21.0195C14.5537 21.0195 15.002 20.5625 15.002 20.0176V14.6562H20.3633C20.9082 14.6562 21.3652 14.208 21.3652 13.6543C21.3652 13.1006 20.9082 12.6523 20.3633 12.6523H15.002V7.29102C15.002 6.74609 14.5537 6.28906 14 6.28906C13.4463 6.28906 12.998 6.74609 12.998 7.29102V12.6523H7.63672C7.0918 12.6523 6.63477 13.1006 6.63477 13.6543C6.63477 14.208 7.0918 14.6562 7.63672 14.6562Z" fill="#F2F2F2"/>
|
||||
<svg viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.63672 14.6562H12.998V20.0176C12.998 20.5625 13.4463 21.0195 14 21.0195C14.5537 21.0195 15.002 20.5625 15.002 20.0176V14.6562H20.3633C20.9082 14.6562 21.3652 14.208 21.3652 13.6543C21.3652 13.1006 20.9082 12.6523 20.3633 12.6523H15.002V7.29102C15.002 6.74609 14.5537 6.28906 14 6.28906C13.4463 6.28906 12.998 6.74609 12.998 7.29102V12.6523H7.63672C7.0918 12.6523 6.63477 13.1006 6.63477 13.6543C6.63477 14.208 7.0918 14.6562 7.63672 14.6562Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 574 B After Width: | Height: | Size: 556 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 28 28" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.58773 19.4704H5.91726C6.01218 19.4704 6.09749 19.4992 6.17531 19.5695L11.3234 24.1773C11.9323 24.7213 12.4017 24.9747 13.0169 24.9747C13.8724 24.9747 14.5289 24.3395 14.5289 23.4744V5.57171C14.5289 4.70664 13.8724 4.01172 12.9977 4.01172C12.3909 4.01172 11.977 4.28008 11.3234 4.86883L6.17531 9.4361C6.09538 9.5043 6.01218 9.53524 5.91726 9.53524H2.58773C0.869528 9.53524 0 10.4366 0 12.2561V16.7612C0 18.5786 0.879137 19.4704 2.58773 19.4704ZM2.78366 17.4072C2.39647 17.4072 2.20991 17.2185 2.20991 16.8313V12.1839C2.20991 11.7892 2.39647 11.6005 2.78366 11.6005H6.44929C6.76663 11.6005 7.00733 11.5356 7.27686 11.2895L11.9695 7.01803C12.0239 6.96154 12.0825 6.9306 12.1624 6.9306C12.2466 6.9306 12.319 6.99131 12.319 7.09888V21.88C12.319 21.9876 12.2466 22.0621 12.1624 22.0621C12.1017 22.0621 12.0335 22.027 11.9695 21.9705L7.27686 17.7182C7.00733 17.4796 6.76663 17.4072 6.44929 17.4072H2.78366Z" fill="currentColor"/>
|
||||
<path d="M18.5131 19.6804C18.9969 20.0005 19.6489 19.8932 20.0075 19.3792C20.952 18.1218 21.5122 16.3335 21.5122 14.4815C21.5122 12.6295 20.952 10.8508 20.0075 9.57953C19.6489 9.06977 18.9969 8.95071 18.5131 9.28258C17.942 9.65711 17.84 10.3464 18.2877 11.0019C18.9357 11.9226 19.3013 13.1748 19.3013 14.4815C19.3013 15.7881 18.924 17.0287 18.2877 17.961C17.8496 18.6241 17.942 19.2962 18.5131 19.6804Z" fill="currentColor"/>
|
||||
<path d="M23.1864 22.8128C23.7128 23.1479 24.3615 23.0193 24.7264 22.4875C26.2517 20.3322 27.137 17.4477 27.137 14.4815C27.137 11.5152 26.2613 8.61156 24.7264 6.47336C24.3615 5.94368 23.7128 5.81501 23.1864 6.15016C22.6346 6.49797 22.5559 7.18375 22.9611 7.79077C24.2089 9.61023 24.9282 12.0065 24.9282 14.4815C24.9282 16.9565 24.1897 19.3314 22.9611 21.1701C22.5655 21.7771 22.6346 22.465 23.1864 22.8128Z" fill="currentColor"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -6,7 +6,7 @@
|
||||
height: 100%;
|
||||
padding-right: $small;
|
||||
|
||||
@include largePhones {
|
||||
@include allPhones {
|
||||
grid-template-columns: 1fr 9.2rem;
|
||||
}
|
||||
|
||||
@@ -42,11 +42,5 @@
|
||||
align-items: center;
|
||||
gap: $small;
|
||||
}
|
||||
|
||||
.volume-group {
|
||||
@include tablet-portrait {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
|
||||
padding: 0 1rem;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
max-height: 100%;
|
||||
gap: 2rem 1rem;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ $g-border: solid 1px $gray5;
|
||||
display: grid;
|
||||
// grid-template-columns: min-content 1fr 29rem;
|
||||
grid-template-columns: min-content 1fr;
|
||||
grid-template-rows: $navheight 1fr $navheight;
|
||||
grid-template-rows: $navheight 1fr 5rem;
|
||||
grid-template-areas:
|
||||
"l-sidebar nav"
|
||||
"l-sidebar content"
|
||||
@@ -14,6 +14,7 @@ $g-border: solid 1px $gray5;
|
||||
border-top: none;
|
||||
border-bottom: none;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
|
||||
#contentresizer {
|
||||
margin: 0 $padright 0 $padleft;
|
||||
@@ -21,12 +22,11 @@ $g-border: solid 1px $gray5;
|
||||
|
||||
@include allPhones {
|
||||
grid-template-columns: 1fr;
|
||||
// grid-template-columns: 1fr;
|
||||
grid-template-rows: max-content 1fr 9.5rem;
|
||||
grid-template-areas:
|
||||
"nav"
|
||||
"content"
|
||||
"bottombar";
|
||||
grid-template-rows: auto 1fr auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,16 @@ $g-border: solid 1px $gray5;
|
||||
height: $navheight;
|
||||
padding: 1rem $padleft;
|
||||
padding-right: $padright;
|
||||
|
||||
@include allPhones {
|
||||
gap: $small;
|
||||
height: unset;
|
||||
padding: 6px 8px;
|
||||
margin: $medium 1rem;
|
||||
border-radius: 1rem;
|
||||
border: solid 1px $gray5;
|
||||
background-image: linear-gradient(37deg, rgb(29, 28, 28), transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.b-bar {
|
||||
@@ -54,6 +64,12 @@ $g-border: solid 1px $gray5;
|
||||
padding-left: $padleft;
|
||||
padding-right: $padright;
|
||||
padding-bottom: $padbottom;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
@include allPhones {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.vue-recycle-scroller__item-wrapper {
|
||||
@@ -84,6 +100,15 @@ $g-border: solid 1px $gray5;
|
||||
"content"
|
||||
"bottombar";
|
||||
|
||||
@include allPhones {
|
||||
grid-template-columns: 1fr !important;
|
||||
grid-template-rows: max-content 1fr 9.5rem !important;
|
||||
grid-template-areas:
|
||||
"nav"
|
||||
"content"
|
||||
"bottombar" !important;
|
||||
}
|
||||
|
||||
.vue-recycle-scroller,
|
||||
.content-page,
|
||||
.topnav,
|
||||
@@ -128,11 +153,7 @@ $g-border: solid 1px $gray5;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
.nolyrics {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1980px) {
|
||||
@media only screen and (min-width: 1980px) {
|
||||
// NOTE: Styles for 1680px and below
|
||||
$alt_layout_pad: max(2rem, calc((100% - 1680px) / 2));
|
||||
|
||||
@@ -195,12 +216,27 @@ $g-border: solid 1px $gray5;
|
||||
width: 100%;
|
||||
padding-bottom: $content-padding-bottom;
|
||||
padding-bottom: $padbottom;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
@include allPhones {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.song-title > .isSmallArtists {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.isSmall {
|
||||
.songlist-item {
|
||||
grid-template-columns: 2fr 5.5rem !important;
|
||||
|
||||
@include mediumPhones {
|
||||
grid-template-columns: 2fr 2.5rem !important;
|
||||
gap: $small !important;
|
||||
}
|
||||
}
|
||||
|
||||
.song-artists,
|
||||
@@ -209,7 +245,9 @@ $g-border: solid 1px $gray5;
|
||||
}
|
||||
|
||||
.isSmallArtists {
|
||||
display: unset !important;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: small;
|
||||
color: $white;
|
||||
opacity: 0.67;
|
||||
|
||||
@@ -29,7 +29,7 @@ input[type="search"]::-webkit-search-cancel-button {
|
||||
|
||||
.heading {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -72,18 +72,22 @@ a {
|
||||
|
||||
// BUTTONS
|
||||
button {
|
||||
border: none;
|
||||
font-family: "SF Compact Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 0.9rem !important;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: inherit;
|
||||
border-radius: $small;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2.25rem;
|
||||
padding: 0 $small;
|
||||
transition: background-color 0.2s ease-out, color 0.2s ease-out, border 0.2s ease-out;
|
||||
|
||||
background-color: $gray4;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
|
||||
svg {
|
||||
@@ -222,7 +226,7 @@ button {
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
color: $purple;
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
margin: $smaller 0;
|
||||
|
||||
&.album {
|
||||
@@ -240,4 +244,23 @@ button {
|
||||
&.playlist {
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
|
||||
// Badges used in settings
|
||||
.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;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
@import "./app-grid.scss", "./controls.scss", "./inputs.scss",
|
||||
"./scrollbars.scss", "./state.scss", "./variants.scss", "./basic.scss",
|
||||
"./search-tabheaders.scss", "./album-grid.scss";
|
||||
@import "./app-grid.scss", "./controls.scss", "./inputs.scss", "./scrollbars.scss", "./state.scss", "./variants.scss",
|
||||
"./basic.scss", "./search-tabheaders.scss", "./album-grid.scss";
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
@@ -13,6 +12,8 @@
|
||||
html {
|
||||
cursor: default !important;
|
||||
overflow: hidden;
|
||||
color: $white;
|
||||
background-color: $body;
|
||||
|
||||
& > * {
|
||||
overflow: visible !important;
|
||||
@@ -28,20 +29,60 @@ html.loading * {
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: $body;
|
||||
color: $white;
|
||||
font-family: "SFCompactDisplay", -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
||||
"Segoe UI Symbol";
|
||||
font-family: "SF Compact Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: $white;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
text-rendering: optimizeLegibility;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
background-color: $body;
|
||||
color-scheme: dark;
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.noSelect {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.dimmer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@include allPhones {
|
||||
.dimmer {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1001;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
background-color: rgb(0, 0, 0, 0.5);
|
||||
transition: opacity 300ms ease, visibility 300ms ease;
|
||||
}
|
||||
|
||||
.dimmer.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,5 +5,7 @@ input[type="number"] {
|
||||
}
|
||||
|
||||
input[type="search"] {
|
||||
font-family: "SF Compact Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
height: 2.25rem !important;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,58 @@
|
||||
::-webkit-scrollbar {
|
||||
width: 3px;
|
||||
/* Total width */
|
||||
.designatedOS ::-webkit-scrollbar {
|
||||
background-color: $body;
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
border-radius: 1rem;
|
||||
/* Background of the scrollbar except button or resizer */
|
||||
.designatedOS ::-webkit-scrollbar-track {
|
||||
background-color: $body;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: $gray1;
|
||||
border-radius: 1rem;
|
||||
/* Scrollbar itself */
|
||||
.designatedOS ::-webkit-scrollbar-thumb {
|
||||
background-color: $gray2;
|
||||
border-radius: 16px;
|
||||
border: 4px solid $body;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: $blue;
|
||||
.designatedOS ::-webkit-scrollbar-thumb:hover {
|
||||
background-color: $gray1;
|
||||
}
|
||||
|
||||
/* Custom scrollbar version for dropdowns etc */
|
||||
/* Context dropdown menus */
|
||||
.designatedOS .context-item .children > .wrapper::-webkit-scrollbar {
|
||||
width: 6px !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.designatedOS .context-item .children > .wrapper::-webkit-scrollbar-track {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.designatedOS .context-item .children > .wrapper::-webkit-scrollbar-thumb {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.designatedOS .context-item .children > .wrapper::-webkit-scrollbar-thumb:hover {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
/* Scrollable divs */
|
||||
.designatedOS .scrollable::-webkit-scrollbar {
|
||||
width: 6px !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.designatedOS .scrollable::-webkit-scrollbar-track {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.designatedOS .scrollable::-webkit-scrollbar-thumb {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.designatedOS .scrollable::-webkit-scrollbar-thumb:hover {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
background-color: $gray5;
|
||||
border: none;
|
||||
|
||||
transition: all 0.3s ease;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
padding: 0 $medium;
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.activetab {
|
||||
|
||||
@@ -1,16 +1,61 @@
|
||||
|
||||
.now-playing-track-indicator {
|
||||
background-image: url(../../assets/icons/playing.gif);
|
||||
height: 2rem;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
background-color: $white;
|
||||
background-size: 1.5rem !important;
|
||||
transition: opacity 0.2s ease-out, visibility 0.2s ease-out;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#wave {
|
||||
fill: #000000;
|
||||
width: 28px;
|
||||
height: auto;
|
||||
|
||||
@for $i from 1 through 9 {
|
||||
#Line_#{$i} {
|
||||
animation: pulse 0.4s infinite;
|
||||
animation-delay: $i * 0.12s;
|
||||
transform: scaleY(0.8);
|
||||
transform-origin: center;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scaleY(0.8);
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scaleY(0.6);
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scaleY(0.8);
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.last_played {
|
||||
background-image: url(../../assets/icons/playing.webp);
|
||||
transition: all 0.3s ease-in-out;
|
||||
.now-playing-track-indicator.last_played {
|
||||
#wave {
|
||||
@for $i from 1 through 9 {
|
||||
#Line_#{$i} {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hidden {
|
||||
|
||||
@@ -5,51 +5,47 @@
|
||||
}
|
||||
|
||||
// media query mixins
|
||||
@mixin smallPhone {
|
||||
@media (max-width: 550px) {
|
||||
@mixin smallestPhones {
|
||||
@media only screen and (max-width: 320px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin iphoneSE {
|
||||
@media (max-width: 386px) {
|
||||
@mixin smallerPhones {
|
||||
@media only screen and (max-width: 360px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin allPhones {
|
||||
@media (max-width: 900px) {
|
||||
@mixin smallPhones {
|
||||
@media only screen and (max-width: 420px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/* This query is "synced" with content-width.ts values */
|
||||
@mixin mediumPhones {
|
||||
@media only screen and (max-width: 460px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
/* This query is "synced" with content-width.ts values */
|
||||
@mixin largePhones {
|
||||
// 550px <= width < 900px
|
||||
@media (min-width: 550px) {
|
||||
@media only screen and (max-width: 660px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin tablet-landscape {
|
||||
@media (max-width: 1080px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin tablet-portrait {
|
||||
@media (max-width: 810px) {
|
||||
/* This query is "synced" with content-width.ts and index.ts values */
|
||||
@mixin allPhones {
|
||||
@media only screen and (max-width: 900px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin for-desktop-down {
|
||||
@media (max-width: 1600px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin for-desktop-up {
|
||||
@media (min-width: 1800px) {
|
||||
@media only screen and (max-width: 1600px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ $theme: #464545fd;
|
||||
// sizes
|
||||
$small: 0.5rem;
|
||||
$smaller: 0.25rem;
|
||||
$smallest: 0.15rem;
|
||||
$medium: 0.75rem;
|
||||
$large: 1.5rem;
|
||||
$larger: 2rem;
|
||||
@@ -59,11 +60,10 @@ $separator: $gray4;
|
||||
$margright: 0;
|
||||
$padbottom: 4rem;
|
||||
$maxwidth: 1438px;
|
||||
$navheight: 5rem;
|
||||
$navheight: 4.75rem;
|
||||
$cardwidth: 10.75rem;
|
||||
$maxpadleft: 5rem;
|
||||
|
||||
|
||||
$alt_layout_pad: max(2rem, calc((100% - 1280px) / 2));
|
||||
|
||||
$maxpadright: calc(100% - $maxwidth);
|
||||
|
||||
@@ -1,15 +1,43 @@
|
||||
@import
|
||||
"./mixins.scss",
|
||||
"./variables",
|
||||
"./ProgressBar.scss",
|
||||
"./BottomBar/BottomBar.scss",
|
||||
"./Global"
|
||||
;
|
||||
|
||||
@import "./mixins.scss", "./variables", "./ProgressBar.scss", "./BottomBar/BottomBar.scss", "./Global";
|
||||
|
||||
/* San Francisco Compact Display font */
|
||||
@font-face {
|
||||
font-family: "SFCompactDisplay";
|
||||
src: url("../sf-compact.woff") format("woff");
|
||||
font-display: swap;
|
||||
font-family: "SF Compact Display";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url("../fonts/SFCompactDisplay-Regular.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "SF Compact Display";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("../fonts/SFCompactDisplay-Medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "SF Compact Display";
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-display: swap;
|
||||
src: url("../fonts/SFCompactDisplay-Semibold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "SF Compact Display";
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url("../fonts/SFCompactDisplay-Bold.woff2") format("woff2");
|
||||
}
|
||||
|
||||
/* San Francisco Mono font */
|
||||
@font-face {
|
||||
font-family: "SF Mono";
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url("../fonts/sf-mono-medium.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="album_disc.is_album_disc_number"
|
||||
class="album_disc_header no-select"
|
||||
>
|
||||
<div 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>
|
||||
</div>
|
||||
@@ -22,11 +19,17 @@ defineProps<{
|
||||
grid-template-columns: 1fr max-content;
|
||||
align-items: center;
|
||||
padding-left: 1rem;
|
||||
margin-top: $small;
|
||||
height: $song-item-height;
|
||||
|
||||
.disc_number {
|
||||
font-size: $medium;
|
||||
font-weight: 500;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
@include largePhones {
|
||||
padding-left: 0.5rem !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from "vue";
|
||||
import useAlbumStore from "@/stores/pages/album";
|
||||
import useArtistStore from "@/stores/pages/artist";
|
||||
import { computed, onMounted } from "vue";
|
||||
|
||||
import { getShift } from "@/utils/colortools/shift";
|
||||
|
||||
@@ -74,6 +74,7 @@ onMounted(hookAction);
|
||||
gap: 1rem;
|
||||
padding-right: $medium;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.genre-pill {
|
||||
@@ -82,6 +83,7 @@ onMounted(hookAction);
|
||||
text-align: center;
|
||||
padding: $small 1rem;
|
||||
font-weight: 700;
|
||||
transition: background-color 0.2s ease-out, color 0.2s ease-out;
|
||||
|
||||
&:first-child {
|
||||
background-color: white;
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
import { computed } from "vue";
|
||||
|
||||
import { Album } from "@/interfaces";
|
||||
import { formatSeconds } from "@/utils";
|
||||
import { isSmallPhone } from "@/stores/content-width";
|
||||
import { formatSeconds } from "@/utils";
|
||||
|
||||
import ArtistName from "@/components/shared/ArtistName.vue";
|
||||
|
||||
@@ -33,18 +33,14 @@ 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.toLocaleString()} Track${
|
||||
props.album.count > 1 ? "s" : ""
|
||||
}`
|
||||
: ""
|
||||
!is_single ? `• ${props.album.count.toLocaleString()} Track${props.album.count > 1 ? "s" : ""}` : ""
|
||||
} • ${formatSeconds(props.album.duration, true)}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.album-stats {
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 14px;
|
||||
padding-left: $smaller;
|
||||
|
||||
@@ -13,10 +13,7 @@
|
||||
background: colors.bg ? colors.bg : '',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="big-img no-scroll"
|
||||
:class="`${isHeaderSmall ? 'imgSmall' : ''} shadow-lg rounded-sm`"
|
||||
>
|
||||
<div class="big-img no-scroll" :class="`${isHeaderSmall ? 'imgSmall' : ''} shadow-lg rounded-sm`">
|
||||
<img :src="imguri.thumb.large + album.image" class="rounded-sm" />
|
||||
</div>
|
||||
<Info />
|
||||
@@ -24,8 +21,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref } from "vue";
|
||||
|
||||
import { paths } from "@/config";
|
||||
import { isHeaderSmall } from "@/stores/content-width";
|
||||
@@ -111,7 +108,7 @@ useVisibility(albumheaderthing, handleVisibilityState);
|
||||
}
|
||||
}
|
||||
|
||||
@include smallPhone {
|
||||
@include largePhones {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 2rem 1rem;
|
||||
height: 25rem;
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
:class="{ isSmallPhone }"
|
||||
style="height: 100%; width: 100%"
|
||||
:style="{
|
||||
boxShadow: !useCircularImage
|
||||
? colors.bg.length
|
||||
? `0 .5rem 2rem ${colors.bg}`
|
||||
: undefined
|
||||
: undefined,
|
||||
boxShadow: !useCircularImage ? (colors.bg.length ? `0 .5rem 2rem ${colors.bg}` : undefined) : undefined,
|
||||
}"
|
||||
></div>
|
||||
<div
|
||||
@@ -27,11 +23,7 @@
|
||||
height: containerHeight,
|
||||
}"
|
||||
>
|
||||
<img
|
||||
id="artist-avatar"
|
||||
:src="paths.images.artist.large + artist.image"
|
||||
@load="store.setBgColor"
|
||||
/>
|
||||
<img id="artist-avatar" :src="paths.images.artist.large + artist.image" @load="store.setBgColor" />
|
||||
</div>
|
||||
<div
|
||||
v-if="!useCircularImage"
|
||||
@@ -39,10 +31,7 @@
|
||||
:style="{
|
||||
backgroundImage: colors.bg
|
||||
? `linear-gradient(${gradientDirection}, transparent ${
|
||||
isSmall
|
||||
? 60
|
||||
: gradientTransparentWidth -
|
||||
(width < 700 ? 40 : width < 900 ? 20 : 10)
|
||||
isSmall ? 60 : gradientTransparentWidth - (width < 700 ? 40 : width < 900 ? 20 : 10)
|
||||
}%,
|
||||
${colors.bg} ${gradientWidth}%,
|
||||
${colors.bg} 100%)`
|
||||
@@ -53,19 +42,18 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
import { useElementSize } from "@vueuse/core";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { Ref, computed, onMounted, ref } from "vue";
|
||||
import { onBeforeRouteUpdate } from "vue-router";
|
||||
import { useElementSize } from "@vueuse/core";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
|
||||
import { paths } from "@/config";
|
||||
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";
|
||||
import useArtistStore from "@/stores/pages/artist";
|
||||
import Info from "./HeaderComponents/Info.vue";
|
||||
|
||||
const image_width_px = 450;
|
||||
const store = useArtistStore();
|
||||
@@ -87,18 +75,12 @@ onBeforeRouteUpdate(() => updateTitle());
|
||||
const artistheader: Ref<HTMLElement | null> = ref(null);
|
||||
const { width } = useElementSize(artistheader);
|
||||
|
||||
const gradientTransparentWidth = computed(() =>
|
||||
Math.floor((image_width_px / (width.value || 1)) * 100)
|
||||
);
|
||||
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
|
||||
);
|
||||
const isSmallPhone = computed(() => width.value <= 660);
|
||||
const useCircularImage = computed(() => !isSmallPhone.value && settings.useCircularArtistImg);
|
||||
|
||||
const gradientDirection = computed(() =>
|
||||
isSmallPhone.value ? "210deg" : "to left"
|
||||
);
|
||||
const gradientDirection = computed(() => (isSmallPhone.value ? "210deg" : "to left"));
|
||||
|
||||
const gradientWidth = computed(() => {
|
||||
return isSmallPhone.value ? "80" : Math.min(gradientTransparentWidth.value, 50);
|
||||
@@ -155,22 +137,12 @@ const containerHeight = computed(() => {
|
||||
|
||||
.gradient {
|
||||
position: absolute;
|
||||
background-image: linear-gradient(
|
||||
to left,
|
||||
transparent 10%,
|
||||
$gray 50%,
|
||||
$gray 100%
|
||||
);
|
||||
background-image: linear-gradient(to left, transparent 10%, $gray 50%, $gray 100%);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
&.isSmallPhone {
|
||||
background-image: linear-gradient(
|
||||
210deg,
|
||||
transparent 20%,
|
||||
$gray 80%,
|
||||
$gray 100%
|
||||
);
|
||||
background-image: linear-gradient(210deg, transparent 20%, $gray 80%, $gray 100%);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,33 +2,21 @@
|
||||
<div
|
||||
class="artist-info"
|
||||
:style="{
|
||||
color: !useCircularImage
|
||||
? artist.colors[0]
|
||||
? getTextColor(artist.colors[0])
|
||||
: undefined
|
||||
: undefined,
|
||||
color: !useCircularImage ? (artist.colors[0] ? getTextColor(artist.colors[0]) : undefined) : undefined,
|
||||
}"
|
||||
>
|
||||
<section class="text">
|
||||
<div class="card-title">Artist</div>
|
||||
<div
|
||||
class="artist-name"
|
||||
:class="`${useCircularImage ? 'ellip' : 'ellip2'}`"
|
||||
:title="artist.name"
|
||||
>
|
||||
<div class="artist-name" :class="`${useCircularImage ? 'ellip' : 'ellip2'}`" :title="artist.name">
|
||||
{{ artist.name }}
|
||||
</div>
|
||||
<div class="stats">
|
||||
<span v-if="artist.trackcount">
|
||||
{{ artist.trackcount.toLocaleString() }} Track{{
|
||||
`${artist.trackcount == 1 ? "" : "s"}`
|
||||
}}
|
||||
{{ artist.trackcount.toLocaleString() }} Track{{ `${artist.trackcount == 1 ? "" : "s"}` }}
|
||||
</span>
|
||||
{{ artist.albumcount && artist.trackcount.toLocaleString() ? "•" : "" }}
|
||||
<span v-if="artist.albumcount">
|
||||
{{ artist.albumcount.toLocaleString() }} Album{{
|
||||
`${artist.albumcount == 1 ? "" : "s"}`
|
||||
}}
|
||||
{{ artist.albumcount.toLocaleString() }} Album{{ `${artist.albumcount == 1 ? "" : "s"}` }}
|
||||
</span>
|
||||
<span v-if="artist.duration">
|
||||
{{ ` • ${formatSeconds(artist.duration, true)}` }}
|
||||
@@ -42,9 +30,9 @@
|
||||
<script setup lang="ts">
|
||||
import { getTextColor } from "@/utils/colortools/shift";
|
||||
|
||||
import { Artist } from "@/interfaces";
|
||||
import formatSeconds from "@/utils/useFormatSeconds";
|
||||
import Buttons from "./Buttons.vue";
|
||||
import { Artist } from "@/interfaces";
|
||||
|
||||
defineProps<{
|
||||
artist: Artist;
|
||||
@@ -77,8 +65,9 @@ defineProps<{
|
||||
|
||||
.artist-name {
|
||||
font-size: 3.5rem;
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
word-wrap: break-all;
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
|
||||
@@ -53,6 +53,11 @@ defineProps<{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-left: 1rem !important; // applies to favorite page
|
||||
padding-right: $small;
|
||||
|
||||
@include largePhones {
|
||||
padding-left: $small !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,25 +2,26 @@
|
||||
<div
|
||||
class="b-bar"
|
||||
:style="{
|
||||
padding: `${settings.is_default_layout ? '0 1rem 0 1rem' : ''}`,
|
||||
paddingLeft: `${settings.is_default_layout ? '1rem' : ''}`,
|
||||
paddingRight: `${settings.is_default_layout ? '1rem' : ''}`,
|
||||
}"
|
||||
>
|
||||
<LeftGroup @handleFav="handleFav" />
|
||||
<div class="center">
|
||||
<div v-if="!isMobile" class="with-time">
|
||||
<div class="time time-current">
|
||||
<span>
|
||||
<div class="numbers">
|
||||
{{ formatSeconds(queue.duration.current || 0) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="buttons rounded-sm border">
|
||||
<HotKeys />
|
||||
</div>
|
||||
<div class="time time-full">
|
||||
<span>
|
||||
<div class="numbers">
|
||||
{{ formatSeconds(queue.duration.full) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Progress />
|
||||
@@ -69,12 +70,30 @@ function handleFav() {
|
||||
z-index: 1;
|
||||
|
||||
@include allPhones {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: max-content 1.5rem max-content;
|
||||
padding: 0 1rem $medium 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: unset;
|
||||
gap: $small;
|
||||
padding: $medium 1rem;
|
||||
|
||||
.center > input {
|
||||
height: 2px !important;
|
||||
/* Hiding the dot/thumb/handle for readonly input */
|
||||
/* Webkit browsers, Firefox, IE etc */
|
||||
&:hover > .center > #progress::-webkit-slider-thumb {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover > .center > #progress::-moz-range-thumb {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover > .center > #progress::-ms-thumb {
|
||||
display: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +101,7 @@ function handleFav() {
|
||||
background: transparent;
|
||||
border-radius: $small;
|
||||
width: 3rem;
|
||||
transition: background-color 0.2s ease-out, border-color 0.2s ease-out;
|
||||
|
||||
&:hover {
|
||||
border: solid 1px $gray3 !important;
|
||||
@@ -91,6 +111,29 @@ function handleFav() {
|
||||
@include allPhones {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
@include largePhones {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
|
||||
&:nth-child(2) {
|
||||
width: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include smallestPhones {
|
||||
&:first-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
margin-left: $smaller;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -131,33 +174,37 @@ function handleFav() {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
gap: $small;
|
||||
margin-bottom: -$small;
|
||||
margin-bottom: -$smallest;
|
||||
|
||||
width: 30rem;
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
@media only screen and (max-width: 1080px) {
|
||||
width: 20rem !important;
|
||||
}
|
||||
|
||||
@include allPhones {
|
||||
width: 100% !important;
|
||||
margin-bottom: $small;
|
||||
}
|
||||
margin: 4px -16px;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
|
||||
.time {
|
||||
font-size: $medium;
|
||||
height: fit-content;
|
||||
width: 3rem;
|
||||
|
||||
span {
|
||||
background-color: $gray3;
|
||||
border-radius: $smaller;
|
||||
padding: 0 $smaller;
|
||||
> #progress {
|
||||
height: 1px !important;
|
||||
width: 100vw !important;
|
||||
margin: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.time-full {
|
||||
text-align: end;
|
||||
.time {
|
||||
font-weight: 500;
|
||||
font-size: $medium;
|
||||
|
||||
.numbers {
|
||||
background-color: $gray3;
|
||||
border-radius: $smaller;
|
||||
padding: 1px $smaller;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,9 +36,7 @@
|
||||
</div>
|
||||
<ArtistName
|
||||
:artists="queue.currenttrack?.artists || []"
|
||||
:albumartists="
|
||||
queue.currenttrack?.albumartists || 'Welcome to Swing Music'
|
||||
"
|
||||
:albumartists="queue.currenttrack?.albumartists || 'Welcome to Swing Music'"
|
||||
class="artist"
|
||||
/>
|
||||
</div>
|
||||
@@ -52,17 +50,17 @@ import { paths } from "@/config";
|
||||
import { Routes } from "@/router";
|
||||
import { getShift } from "@/utils/colortools/shift";
|
||||
|
||||
import useQStore from "@/stores/queue";
|
||||
import useColorStore from "@/stores/colors";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
import { isLargerMobile, isMobile } from "@/stores/content-width";
|
||||
import useQStore from "@/stores/queue";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
|
||||
import Actions from "./Right.vue";
|
||||
import ExpandSvg from "@/assets/icons/expand.svg";
|
||||
import ArtistName from "@/components/shared/ArtistName.vue";
|
||||
import HotKeys from "../LeftSidebar/NP/HotKeys.vue";
|
||||
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";
|
||||
import Actions from "./Right.vue";
|
||||
|
||||
const queue = useQStore();
|
||||
const settings = useSettingsStore();
|
||||
@@ -77,9 +75,12 @@ defineEmits<{
|
||||
.left-group {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
gap: $small;
|
||||
gap: $medium;
|
||||
align-items: center;
|
||||
font-size: small;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
margin-right: $medium;
|
||||
|
||||
.np-image {
|
||||
position: relative;
|
||||
@@ -101,16 +102,28 @@ defineEmits<{
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
transition: all 0.2s;
|
||||
|
||||
svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: small;
|
||||
&:hover {
|
||||
.expandicon {
|
||||
transform: translateY(-$medium);
|
||||
height: 130%;
|
||||
}
|
||||
}
|
||||
|
||||
@include largePhones {
|
||||
flex-shrink: 0;
|
||||
margin-right: $medium;
|
||||
}
|
||||
|
||||
@include smallerPhones {
|
||||
margin-right: $small;
|
||||
}
|
||||
}
|
||||
|
||||
.heart-button {
|
||||
@@ -121,32 +134,45 @@ defineEmits<{
|
||||
}
|
||||
|
||||
.track-info {
|
||||
.artistname {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.75;
|
||||
font-weight: bold;
|
||||
.title {
|
||||
color: $white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: $white;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.artistname {
|
||||
opacity: 0.75;
|
||||
|
||||
a {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
@include allPhones {
|
||||
width: calc(100% + 8px);
|
||||
}
|
||||
|
||||
@include largePhones {
|
||||
width: unset;
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@include allPhones {
|
||||
height: 4rem;
|
||||
grid-template-columns: max-content 1fr max-content;
|
||||
grid-template-columns: max-content 1fr max-content max-content;
|
||||
margin-right: unset;
|
||||
|
||||
.heart-button {
|
||||
height: max-content;
|
||||
border: none !important;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 550px) {
|
||||
grid-template-columns: max-content 1fr max-content max-content;
|
||||
@include largePhones {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
max-width: calc(100% - 8px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
<template>
|
||||
<div class="right-group">
|
||||
<LyricsButton v-if="settings.use_lyrics_plugin || lyrics.exists" />
|
||||
<LyricsButton />
|
||||
<Volume />
|
||||
<button
|
||||
class="repeat"
|
||||
:class="{ 'repeat-disabled': settings.no_repeat }"
|
||||
:title="
|
||||
settings.repeat_all
|
||||
? 'Repeat all'
|
||||
: settings.no_repeat
|
||||
? 'No repeat'
|
||||
: 'Repeat one'
|
||||
"
|
||||
:title="settings.repeat_all ? 'Repeat all' : settings.no_repeat ? 'No repeat' : 'Repeat one'"
|
||||
@click="settings.toggleRepeatMode"
|
||||
>
|
||||
<RepeatOneSvg v-if="settings.repeat_one" />
|
||||
@@ -32,17 +26,15 @@
|
||||
<script setup lang="ts">
|
||||
import useQueue from "@/stores/queue";
|
||||
import useSettings from "@/stores/settings";
|
||||
import useLyrics from "@/stores/lyrics";
|
||||
|
||||
import Volume from "./Volume.vue";
|
||||
import RepeatOneSvg from "@/assets/icons/repeat-one.svg";
|
||||
import RepeatAllSvg from "@/assets/icons/repeat.svg";
|
||||
import ShuffleSvg from "@/assets/icons/shuffle.svg";
|
||||
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 ShuffleSvg from "@/assets/icons/shuffle.svg";
|
||||
import Volume from "./Volume.vue";
|
||||
|
||||
const queue = useQueue();
|
||||
const lyrics = useLyrics();
|
||||
const settings = useSettings();
|
||||
|
||||
defineProps<{
|
||||
@@ -64,6 +56,7 @@ defineEmits<{
|
||||
|
||||
@include allPhones {
|
||||
width: max-content;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
button {
|
||||
|
||||
@@ -15,16 +15,20 @@
|
||||
step="0.01"
|
||||
:value="settings.volume"
|
||||
@input="changeVolume"
|
||||
:style="{
|
||||
backgroundSize: `${(settings.volume / 1) * 100}% 100%`,
|
||||
}"
|
||||
/>
|
||||
<div className="volume_indicator">{{ ((settings.volume / 1) * 100).toFixed(0) }}</div>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
import VolumeMuteSvg from "@/assets/icons/volume-mute.svg";
|
||||
import VolumeLowSvg from "@/assets/icons/volume-low.svg";
|
||||
import VolumeMidSvg from "@/assets/icons/volume-mid.svg";
|
||||
import VolumeMuteSvg from "@/assets/icons/volume-mute.svg";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
|
||||
const settings = useSettingsStore();
|
||||
|
||||
@@ -50,6 +54,12 @@ const handleMouseWheel = (event: WheelEvent) => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.b-bar .right-group button.speaker {
|
||||
border-top: 1px solid transparent !important;
|
||||
border-top-left-radius: 0 !important;
|
||||
border-top-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.speaker {
|
||||
position: relative;
|
||||
|
||||
@@ -65,20 +75,33 @@ const handleMouseWheel = (event: WheelEvent) => {
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background-color: $gray4;
|
||||
position: absolute;
|
||||
bottom: 2.95rem;
|
||||
height: 2.5rem;
|
||||
cursor: default;
|
||||
bottom: 56px;
|
||||
left: -1px;
|
||||
height: 48px;
|
||||
padding: 0 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background-color: $gray;
|
||||
border-top: 1px solid $gray3;
|
||||
border-bottom: 1px solid $gray3;
|
||||
border-right: 1px solid $gray3;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
transform: rotate(270deg) translateX(-50%) perspective(1px);
|
||||
transform-origin: left top;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: visibility 0.2s ease-in;
|
||||
transition-delay: 0.25s;
|
||||
cursor: default;
|
||||
transition: opacity 0.2s ease-out, visibility 0.2s ease-out;
|
||||
|
||||
input {
|
||||
width: max-content;
|
||||
max-width: 87px;
|
||||
margin: 0;
|
||||
touch-action: pan-x;
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
height: 1rem;
|
||||
@@ -96,9 +119,16 @@ const handleMouseWheel = (event: WheelEvent) => {
|
||||
|
||||
&:hover {
|
||||
.dialog {
|
||||
transition-delay: 0.25s;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.volume_indicator {
|
||||
font-weight: 600;
|
||||
width: 24px;
|
||||
height: 18px;
|
||||
transform: rotate(90deg) translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
class="context-menu rounded shadow-lg no-select"
|
||||
:style="{
|
||||
visibility: context.visible ? 'visible' : 'hidden',
|
||||
opacity: context.visible ? '1' : '0',
|
||||
}"
|
||||
>
|
||||
<ContextItem
|
||||
@@ -20,8 +21,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { onClickOutside } from "@vueuse/core";
|
||||
import { ref } from "vue";
|
||||
|
||||
import useContextStore from "@/stores/context";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
@@ -65,15 +66,17 @@ context.$subscribe((mutation, state) => {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 12.5rem;
|
||||
z-index: 10000 !important;
|
||||
transform: scale(0);
|
||||
width: 13rem;
|
||||
z-index: 1000 !important;
|
||||
height: min-content;
|
||||
|
||||
padding: $small;
|
||||
background: $context;
|
||||
transform-origin: top left;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.25s ease-out, visibility 0.25s ease-out;
|
||||
|
||||
.separator {
|
||||
height: 1px;
|
||||
@@ -85,6 +88,7 @@ context.$subscribe((mutation, state) => {
|
||||
|
||||
.critical {
|
||||
color: $red;
|
||||
transition: background-color 0.2s ease-out, color 0.2s ease-out;
|
||||
|
||||
&:hover {
|
||||
background-color: $red;
|
||||
|
||||
@@ -2,37 +2,25 @@
|
||||
<div
|
||||
ref="parentRef"
|
||||
class="context-item"
|
||||
@mouseenter="
|
||||
option.children &&
|
||||
!isSmall &&
|
||||
childrenShowMode === contextChildrenShowMode.hover &&
|
||||
showChildren()
|
||||
"
|
||||
@mouseleave="
|
||||
option.children &&
|
||||
!isSmall &&
|
||||
childrenShowMode === contextChildrenShowMode.hover &&
|
||||
hideChildren()
|
||||
"
|
||||
@mouseenter="option.children && !isSmall && childrenShowMode === contextChildrenShowMode.hover && showChildren()"
|
||||
@mouseleave="option.children && !isSmall && childrenShowMode === contextChildrenShowMode.hover && hideChildren()"
|
||||
@click="runAction"
|
||||
>
|
||||
<div class="icon image" v-html="option.icon"></div>
|
||||
<div class="label ellip">{{ option.label }}</div>
|
||||
<div v-if="option.children" class="more" v-html="ExpandIcon"></div>
|
||||
<div
|
||||
v-if="option.children"
|
||||
ref="childRef"
|
||||
class="children rounded shadow-sm"
|
||||
>
|
||||
<div
|
||||
v-for="child in option.children"
|
||||
:key="child.label"
|
||||
class="context-item"
|
||||
:class="[{ critical: child.critical }, child.type]"
|
||||
@click="child.action && runChildAction(child.action)"
|
||||
>
|
||||
<div class="label ellip">
|
||||
{{ child.label }}
|
||||
<div v-if="option.children" ref="childRef" class="children rounded shadow-sm">
|
||||
<div className="wrapper">
|
||||
<div
|
||||
v-for="child in option.children"
|
||||
:key="child.label"
|
||||
class="context-item"
|
||||
:class="[{ critical: child.critical }, child.type]"
|
||||
@click="child.action && runChildAction(child.action)"
|
||||
>
|
||||
<div class="label ellip">
|
||||
{{ child.label }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,7 +28,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { createPopper, Instance } from "@popperjs/core";
|
||||
import { createPopper, Instance, Modifier, Placement, Rect } from "@popperjs/core";
|
||||
import { ref } from "vue";
|
||||
|
||||
import { contextChildrenShowMode } from "@/enums";
|
||||
@@ -70,27 +58,50 @@ function showChildren() {
|
||||
return;
|
||||
}
|
||||
|
||||
popperInstance = createPopper(
|
||||
parentRef.value as HTMLElement,
|
||||
childRef.value as HTMLElement,
|
||||
{
|
||||
placement: "right-start",
|
||||
modifiers: [
|
||||
{
|
||||
name: "flip",
|
||||
options: {
|
||||
fallbackPlacements: ["left-start", "auto"],
|
||||
},
|
||||
const offsetModifier: Modifier<
|
||||
"offset",
|
||||
{ offset: [number, number] | ((args: { placement: Placement; reference: Rect; popper: Rect }) => [number, number]) }
|
||||
> = {
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: ({ placement }) => {
|
||||
// Correct type for placement automatically inferred
|
||||
if (placement.includes("right") || placement.includes("left")) {
|
||||
return [-7, 0];
|
||||
}
|
||||
return [0, 0];
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
popperInstance = createPopper(parentRef.value as HTMLElement, childRef.value as HTMLElement, {
|
||||
placement: "right-start",
|
||||
modifiers: [
|
||||
{
|
||||
name: "preventOverflow",
|
||||
options: {
|
||||
altAxis: true,
|
||||
boundariesElement: "viewport",
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
},
|
||||
{
|
||||
name: "flip",
|
||||
options: {
|
||||
fallbackPlacements: ["left-start", "auto"],
|
||||
boundariesElement: "viewport",
|
||||
},
|
||||
},
|
||||
offsetModifier,
|
||||
],
|
||||
});
|
||||
childRef.value ? (childRef.value.style.visibility = "visible") : null;
|
||||
childRef.value ? (childRef.value.style.opacity = "1") : null;
|
||||
childrenShown.value = true;
|
||||
}
|
||||
|
||||
function hideChildren() {
|
||||
childRef.value ? (childRef.value.style.visibility = "hidden") : null;
|
||||
childRef.value ? (childRef.value.style.opacity = "0") : null;
|
||||
popperInstance?.destroy();
|
||||
childrenShown.value = false;
|
||||
}
|
||||
@@ -129,12 +140,14 @@ function runChildAction(action: () => void) {
|
||||
padding: 0.4rem;
|
||||
position: relative;
|
||||
border-radius: $small;
|
||||
transition: background-color 0.2s ease-out;
|
||||
|
||||
.more {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
right: 2px;
|
||||
bottom: 5px;
|
||||
transform: scale(0.65);
|
||||
}
|
||||
|
||||
@@ -142,19 +155,38 @@ function runChildAction(action: () => void) {
|
||||
position: absolute;
|
||||
width: 12rem;
|
||||
z-index: 10;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
transform: scale(0);
|
||||
background-color: $context;
|
||||
transform: scale(0);
|
||||
padding: $small;
|
||||
padding: $small $smaller;
|
||||
border: solid 1px $gray3;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.25s ease-out, visibility 0.25s ease-out;
|
||||
|
||||
max-height: calc(100vh / 2);
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:hover ::-webkit-scrollbar-thumb {
|
||||
background-color: $gray2;
|
||||
}
|
||||
|
||||
&:hover ::-webkit-scrollbar-thumb:hover {
|
||||
background-color: $gray1;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 0 $smaller;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: calc(100vh / 2 - 2rem);
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.context-item {
|
||||
line-height: 1.2;
|
||||
padding: $small 1rem;
|
||||
padding: 0.4rem;
|
||||
padding: 0.4rem 0.6rem;
|
||||
}
|
||||
|
||||
.separator {
|
||||
|
||||
@@ -4,27 +4,19 @@
|
||||
:class="fav.type"
|
||||
:to="{
|
||||
name: fav.type === 'album' ? Routes.album : Routes.artist,
|
||||
params:
|
||||
fav.type === 'album'
|
||||
? { albumhash: fav.item.albumhash }
|
||||
: { hash: fav.item.artisthash },
|
||||
params: fav.type === 'album' ? { albumhash: fav.item.albumhash } : { hash: fav.item.artisthash },
|
||||
}"
|
||||
>
|
||||
<div class="imagegroup">
|
||||
<img
|
||||
:src="
|
||||
fav.type === 'album'
|
||||
? paths.images.thumb.large + fav.item.image
|
||||
: paths.images.artist.large + fav.item.image
|
||||
fav.type === 'album' ? paths.images.thumb.large + fav.item.image : paths.images.artist.large + fav.item.image
|
||||
"
|
||||
alt=""
|
||||
class="rounded-sm"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="name ellip"
|
||||
:title="fav.type === 'artist' ? fav.item.name : fav.item.title"
|
||||
>
|
||||
<div class="name ellip" :title="fav.type === 'artist' ? fav.item.name : fav.item.title">
|
||||
{{ fav.type === "artist" ? fav.item.name : fav.item.title }}
|
||||
</div>
|
||||
<div class="label ellip" :class="{ on_artist: fav.type === 'artist' }">
|
||||
@@ -73,7 +65,7 @@ defineProps<{
|
||||
.label {
|
||||
font-size: 0.8rem;
|
||||
color: $gray1;
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
border-radius: 1rem;
|
||||
text-transform: capitalize;
|
||||
margin-top: -$smaller;
|
||||
|
||||
@@ -39,22 +39,29 @@ onUpdated(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.breadcrumb-nav {
|
||||
display: flex;
|
||||
gap: $smaller;
|
||||
|
||||
.designatedOS .breadcrumb-nav {
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.breadcrumb-nav {
|
||||
display: flex;
|
||||
gap: $smaller;
|
||||
|
||||
.path {
|
||||
white-space: nowrap;
|
||||
margin: auto 0;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.text {
|
||||
padding: $smaller;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
padding: $smaller $small;
|
||||
border-radius: $smaller;
|
||||
transition: background-color 0.2s ease-out;
|
||||
}
|
||||
|
||||
&::before {
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
<div
|
||||
v-auto-animate
|
||||
class="f-item"
|
||||
:style="{
|
||||
backgroundColor: is_checked ? '#234ece' : '',
|
||||
:class="{
|
||||
selected: is_checked,
|
||||
context_menu_showing: context_menu_showing,
|
||||
}"
|
||||
:class="{ context_menu_showing }"
|
||||
@click="(e) => (folder_page ? null : handleClick(e))"
|
||||
@mouseover="mouse_over = true"
|
||||
@mouseleave="mouse_over = false"
|
||||
@@ -16,8 +16,8 @@
|
||||
<FolderSvg v-else />
|
||||
<div class="info">
|
||||
<div class="f-item-text ellip">{{ folder.name }}</div>
|
||||
<div v-if="folder.count" class="f-count">
|
||||
{{ folder.count.toLocaleString() + ` File${folder.count == 1 ? "" : "s"}` }}
|
||||
<div class="f-count" v-if="folder.trackcount">
|
||||
{{ folder.trackcount.toLocaleString() + ` File${folder.trackcount == 1 ? "" : "s"}` }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!folder_page" class="check">
|
||||
@@ -39,8 +39,8 @@ import SymLinkSvg from "@/assets/icons/symlink.svg";
|
||||
|
||||
import CheckFilledSvg from "@/assets/icons/check.filled.svg";
|
||||
import CheckSvg from "@/assets/icons/square.svg";
|
||||
import { showFolderContextMenu } from "@/helpers/contextMenuHandler";
|
||||
import { ContextSrc } from "@/enums";
|
||||
import { showFolderContextMenu } from "@/helpers/contextMenuHandler";
|
||||
|
||||
const props = defineProps<{
|
||||
folder: Folder;
|
||||
@@ -70,12 +70,7 @@ function handleClick(e: MouseEvent) {
|
||||
}
|
||||
|
||||
function showContextMenu(e: MouseEvent) {
|
||||
showFolderContextMenu(
|
||||
e,
|
||||
context_menu_showing,
|
||||
ContextSrc.FolderCard,
|
||||
props.folder.path
|
||||
);
|
||||
showFolderContextMenu(e, context_menu_showing, ContextSrc.FolderCard, props.folder.path);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -90,6 +85,7 @@ function showContextMenu(e: MouseEvent) {
|
||||
position: relative;
|
||||
padding: 0 0 0 1rem;
|
||||
gap: $small;
|
||||
transition: background-color 0.2s ease-out;
|
||||
|
||||
&.context_menu_showing {
|
||||
background-color: $gray4;
|
||||
@@ -97,6 +93,7 @@ function showContextMenu(e: MouseEvent) {
|
||||
|
||||
svg {
|
||||
color: $gray1;
|
||||
height: 1.75rem;
|
||||
}
|
||||
|
||||
.f-count {
|
||||
@@ -118,11 +115,8 @@ function showContextMenu(e: MouseEvent) {
|
||||
transform: scale(0.75);
|
||||
}
|
||||
|
||||
@include largePhones {
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.f-item-text {
|
||||
font-weight: 600;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,27 +2,20 @@
|
||||
<div
|
||||
class="f-container rounded-sm"
|
||||
:class="{
|
||||
'list-mode': isIphoneSE ? true : settings.folder_list_mode,
|
||||
'list-mode': isSmallestPhone ? true : settings.folder_list_mode,
|
||||
}"
|
||||
>
|
||||
<div id="f-items" class="rounded">
|
||||
<FolderItem
|
||||
v-for="folder in folders"
|
||||
:key="folder.path"
|
||||
:folder="folder"
|
||||
:folder_page="true"
|
||||
/>
|
||||
<FolderItem v-for="folder in folders" :key="folder.path" :folder="folder" :folder_page="true" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Folder } from "@/interfaces";
|
||||
import FolderItem from "./FolderItem.vue";
|
||||
import { isSmallestPhone } from "@/stores/content-width";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
import { isIphoneSE } from "@/stores/content-width";
|
||||
|
||||
import { ref } from "vue";
|
||||
import FolderItem from "./FolderItem.vue";
|
||||
|
||||
defineProps<{
|
||||
folders: Folder[];
|
||||
@@ -51,11 +44,13 @@ const settings = useSettingsStore();
|
||||
gap: 0;
|
||||
|
||||
.f-item {
|
||||
line-height: 1.2;
|
||||
transition: none;
|
||||
height: 3.25rem;
|
||||
border-radius: $small;
|
||||
background-color: transparent;
|
||||
padding-left: $small;
|
||||
transition: background-color 0.2s ease-out;
|
||||
|
||||
.options {
|
||||
display: block;
|
||||
@@ -66,7 +61,7 @@ const settings = useSettingsStore();
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: $small;
|
||||
padding-right: $medium;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -62,6 +62,7 @@ const browselist = [
|
||||
.btitle {
|
||||
font-size: 1.15rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
|
||||
.browselist {
|
||||
@@ -72,9 +73,11 @@ const browselist = [
|
||||
}
|
||||
|
||||
.browseitem {
|
||||
font-weight: 500;
|
||||
padding: 1.5rem 0;
|
||||
background-color: $gray;
|
||||
color: $white;
|
||||
transition: background-color 0.2s ease-out;
|
||||
}
|
||||
|
||||
.browseitem:hover {
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<template>
|
||||
<div class="home-count">
|
||||
<div
|
||||
class="count-item"
|
||||
:title="count.count.toLocaleString()"
|
||||
v-for="count in parseCount()"
|
||||
:key="count.text"
|
||||
>
|
||||
<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>
|
||||
@@ -57,7 +52,7 @@ function parseCount() {
|
||||
|
||||
.text {
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
margin-left: $smaller;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="q.currenttrack?.bitrate"
|
||||
class="bitrate"
|
||||
title="file type • bitrate"
|
||||
>
|
||||
{{ q.currenttrack.filepath?.split('.').pop() }} • {{ q.currenttrack.bitrate }}
|
||||
<div v-if="q.currenttrack?.bitrate" class="bitrate" title="file type • bitrate">
|
||||
{{ q.currenttrack.filepath?.split(".").pop() }} • {{ q.currenttrack.bitrate }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -18,6 +14,7 @@ const q = useQueueStore();
|
||||
.bitrate {
|
||||
position: absolute;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
width: max-content;
|
||||
padding: 0.2rem 0.35rem;
|
||||
bottom: $medium;
|
||||
|
||||
@@ -18,10 +18,7 @@
|
||||
import { usePlayer } from "@/stores/player";
|
||||
import useQStore from "@/stores/queue";
|
||||
|
||||
import {
|
||||
default as NextSvg,
|
||||
default as PrevSvg,
|
||||
} from "@/assets/icons/next.svg";
|
||||
import { 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";
|
||||
@@ -54,6 +51,12 @@ const { buffering } = usePlayer();
|
||||
svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
&:active {
|
||||
svg {
|
||||
transform: rotate(180deg) scale(0.75);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button:nth-child(2) {
|
||||
@@ -64,10 +67,20 @@ const { buffering } = usePlayer();
|
||||
grid-template-columns: 1fr max-content 1fr;
|
||||
position: relative;
|
||||
margin-right: -$small;
|
||||
gap: 0;
|
||||
|
||||
button:first-child {
|
||||
margin-left: $small;
|
||||
}
|
||||
}
|
||||
|
||||
@include largePhones {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
|
||||
button:first-child {
|
||||
margin-left: $smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
min="0"
|
||||
:max="time.full"
|
||||
step="0.1"
|
||||
:style="{
|
||||
backgroundSize: `${(time.current / (time.full || 0)) * 100}% 100%`,
|
||||
}"
|
||||
:style="{ backgroundSize: `${(time.current / (time.full || 1)) * 100}% 100%` }"
|
||||
@change="seek"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -41,6 +41,8 @@ import { menus } from "./navitems";
|
||||
align-items: center;
|
||||
padding: $small 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: background-color 0.2s ease-out;
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
@@ -78,7 +80,18 @@ import { menus } from "./navitems";
|
||||
}
|
||||
}
|
||||
|
||||
@include allPhones {
|
||||
.circular.nav-item:last-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.circular.nav-item:nth-child(3) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 1.5rem;
|
||||
margin: 0 $small 0 $small;
|
||||
border-radius: $small;
|
||||
transform: scale(0.9);
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
<script setup lang="ts">
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
|
||||
import Navigation from "@/components/LeftSidebar/NavButtons.vue";
|
||||
import Logo from "@/components/Logo.vue";
|
||||
import SongCard from "./NP/SongCard.vue";
|
||||
import Navigation from "@/components/LeftSidebar/NavButtons.vue";
|
||||
|
||||
const settings = useSettingsStore();
|
||||
</script>
|
||||
@@ -31,10 +31,22 @@ const settings = useSettingsStore();
|
||||
|
||||
.scrollable {
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: 1rem 0;
|
||||
|
||||
@include hideScrollbars;
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .scrollable::-webkit-scrollbar-thumb {
|
||||
background-color: $gray2;
|
||||
}
|
||||
|
||||
&:hover .scrollable::-webkit-scrollbar-thumb:hover {
|
||||
background-color: $gray1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<router-link class="swing-logo rounded-md" :to="{ name: 'Home' }">
|
||||
<LogoSvg /> <span>Swing Music</span>
|
||||
</router-link>
|
||||
<router-link class="swing-logo rounded-md" :to="{ name: 'Home' }"> <LogoSvg /> <span>Swing Music</span> </router-link>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -10,12 +8,14 @@ import LogoSvg from "@/assets/icons/logos/logo-fill.light.svg";
|
||||
|
||||
<style lang="scss">
|
||||
.swing-logo {
|
||||
background-image: linear-gradient(37deg, rgb(29, 28, 28), transparent);
|
||||
padding-left: 1rem;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $medium;
|
||||
padding-left: 1rem;
|
||||
border: solid 1px $gray5;
|
||||
background-image: linear-gradient(37deg, rgb(29, 28, 28), transparent);
|
||||
transition: background-color 0.2s ease-out;
|
||||
|
||||
svg {
|
||||
transform: scale(1.25);
|
||||
|
||||
@@ -1,41 +1,47 @@
|
||||
<template>
|
||||
<div v-if="notifStore.notifs" class="toasts">
|
||||
<div
|
||||
v-for="notif in notifStore.notifs"
|
||||
:key="notif.text"
|
||||
class="new-notif rounded-sm"
|
||||
:class="notif.type"
|
||||
v-if="notifStore.notifs"
|
||||
class="toasts"
|
||||
>
|
||||
<component :is="getSvg(notif.type)" class="notif-icon" />
|
||||
<div class="notif-text">{{ notif.text }}</div>
|
||||
<div
|
||||
v-for="notif in notifStore.notifs"
|
||||
:key="notif.text"
|
||||
class="new-notif rounded-sm"
|
||||
:class="notif.type"
|
||||
>
|
||||
<component
|
||||
:is="getSvg(notif.type)"
|
||||
class="notif-icon"
|
||||
/>
|
||||
<div class="notif-text">{{ notif.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useNotifStore, NotifType } from "../stores/notification";
|
||||
import { NotifType, useToast } from '../stores/notification'
|
||||
|
||||
import HeartSvg from "../assets/icons/heart.svg";
|
||||
import InfoSvg from "../assets/icons/toast/info.svg";
|
||||
import SuccessSvg from "../assets/icons/toast/ok.svg";
|
||||
import ErrorSvg from "../assets/icons/toast/error.svg";
|
||||
import WorkingSvg from "../assets/icons/toast/working.svg";
|
||||
import HeartSvg from '../assets/icons/heart.svg'
|
||||
import ErrorSvg from '../assets/icons/toast/error.svg'
|
||||
import InfoSvg from '../assets/icons/toast/info.svg'
|
||||
import SuccessSvg from '../assets/icons/toast/ok.svg'
|
||||
import WorkingSvg from '../assets/icons/toast/working.svg'
|
||||
|
||||
const notifStore = useNotifStore();
|
||||
const notifStore = useToast()
|
||||
|
||||
function getSvg(notif: NotifType) {
|
||||
switch (notif) {
|
||||
case NotifType.Error:
|
||||
return ErrorSvg;
|
||||
case NotifType.Info:
|
||||
return InfoSvg;
|
||||
case NotifType.Success:
|
||||
return SuccessSvg;
|
||||
case NotifType.Working:
|
||||
return WorkingSvg;
|
||||
case NotifType.Favorite:
|
||||
return HeartSvg;
|
||||
}
|
||||
switch (notif) {
|
||||
case NotifType.Error:
|
||||
return ErrorSvg
|
||||
case NotifType.Info:
|
||||
return InfoSvg
|
||||
case NotifType.Success:
|
||||
return SuccessSvg
|
||||
case NotifType.Working:
|
||||
return WorkingSvg
|
||||
case NotifType.Favorite:
|
||||
return HeartSvg
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
@@ -43,45 +49,53 @@ function getSvg(notif: NotifType) {
|
||||
position: fixed;
|
||||
bottom: 6rem;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
transform: translate(-50%);
|
||||
z-index: 100;
|
||||
z-index: 1003;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column-reverse;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.new-notif {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
width: 18rem;
|
||||
height: 4rem;
|
||||
min-height: 4rem;
|
||||
background-color: $gray;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
box-shadow: 0px 0px 2rem rgba(0, 0, 0, 0.466);
|
||||
font-size: 0.85rem;
|
||||
padding: 1rem $small;
|
||||
|
||||
grid-template-columns: 2rem 3fr;
|
||||
gap: $smaller;
|
||||
grid-template-columns: 2rem 3fr;
|
||||
gap: $smaller;
|
||||
|
||||
.notif-text {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include smallestPhones {
|
||||
max-width: calc(100% - 2rem);
|
||||
}
|
||||
}
|
||||
|
||||
.new-notif.error {
|
||||
$bg: rgb(197, 72, 72);
|
||||
background-color: $bg;
|
||||
$bg: rgb(197, 72, 72);
|
||||
background-color: $bg;
|
||||
}
|
||||
|
||||
.new-notif.info,
|
||||
.new-notif.favorite,.new-notif.success {
|
||||
$bg: rgb(255, 255, 255);
|
||||
background-color: $bg;
|
||||
color: $black;
|
||||
.new-notif.favorite,
|
||||
.new-notif.success {
|
||||
$bg: rgb(255, 255, 255);
|
||||
background-color: $bg;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.new-notif.working {
|
||||
$bg: $gray4;
|
||||
background-color: $bg;
|
||||
$bg: $gray4;
|
||||
background-color: $bg;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,11 +12,7 @@
|
||||
title="Go to Album"
|
||||
class="np-image"
|
||||
>
|
||||
<img
|
||||
v-motion-fade
|
||||
class="rounded"
|
||||
:src="paths.images.thumb.original + queue.currenttrack?.image"
|
||||
/>
|
||||
<img v-motion-fade class="rounded" :src="paths.images.thumb.large + queue.currenttrack?.image" />
|
||||
</RouterLink>
|
||||
<NowPlayingInfo @handle-fav="handleFav" />
|
||||
<Progress v-if="isSmallPhone" />
|
||||
@@ -30,7 +26,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h3 v-if="queue.next">Up Next</h3>
|
||||
<h3 class="nowplaying_title" v-if="queue.next">Up Next</h3>
|
||||
<SongItem
|
||||
v-if="queue.next"
|
||||
:track="queue.next"
|
||||
@@ -38,24 +34,24 @@
|
||||
:source="dropSources.folder"
|
||||
@play-this="queue.playNext"
|
||||
/>
|
||||
<h3>Queue</h3>
|
||||
<h3 class="nowplaying_title">Queue</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { paths } from "@/config";
|
||||
import { dropSources, favType } from "@/enums";
|
||||
import favoriteHandler from "@/helpers/favoriteHandler";
|
||||
import { Routes } from "@/router";
|
||||
import { isSmallPhone } from "@/stores/content-width";
|
||||
import useQueueStore from "@/stores/queue";
|
||||
import { formatSeconds } from "@/utils";
|
||||
import { dropSources, favType } from "@/enums";
|
||||
import { isSmallPhone } from "@/stores/content-width";
|
||||
import favoriteHandler from "@/helpers/favoriteHandler";
|
||||
|
||||
import PlayingFrom from "./PlayingFrom.vue";
|
||||
import Progress from "@/components/LeftSidebar/NP/Progress.vue";
|
||||
import Buttons from "../BottomBar/Right.vue";
|
||||
import SongItem from "../shared/SongItem.vue";
|
||||
import NowPlayingInfo from "./NowPlayingInfo.vue";
|
||||
import Progress from "@/components/LeftSidebar/NP/Progress.vue";
|
||||
import PlayingFrom from "./PlayingFrom.vue";
|
||||
|
||||
const queue = useQueueStore();
|
||||
|
||||
@@ -72,9 +68,23 @@ function handleFav() {
|
||||
|
||||
<style lang="scss">
|
||||
.now-playing-header {
|
||||
padding-bottom: 1rem;
|
||||
padding-bottom: $smaller;
|
||||
position: relative;
|
||||
|
||||
.nowplaying_title {
|
||||
padding-left: 1rem;
|
||||
margin: 1.25rem 0;
|
||||
|
||||
&:last-child {
|
||||
padding-top: $large;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
@include largePhones {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.below-progress {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -82,12 +92,48 @@ function handleFav() {
|
||||
margin-top: 1rem;
|
||||
|
||||
.time {
|
||||
font-size: 12px;
|
||||
font-size: $medium;
|
||||
font-weight: 500;
|
||||
background-color: $gray3;
|
||||
padding: 0 $smaller;
|
||||
padding: 1px $smaller;
|
||||
min-width: 2.5rem;
|
||||
text-align: center;
|
||||
border-radius: $smaller;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@include largePhones {
|
||||
.right-group button.speaker {
|
||||
border-top: 1px solid transparent !important;
|
||||
border-top-left-radius: 0 !important;
|
||||
border-top-right-radius: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@include smallestPhones {
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
align-items: unset;
|
||||
gap: $small;
|
||||
|
||||
.time:first-child {
|
||||
align-self: baseline;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.time:last-child {
|
||||
align-self: end;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.right-group {
|
||||
width: 100% !important;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,21 +8,12 @@
|
||||
:albumartists="queue.currenttrack?.albumartists || ''"
|
||||
/>
|
||||
<span v-else class="artist author">
|
||||
<a href="https://github.com/mungai-njoroge" target="_blank"
|
||||
>built by @mungai-njoroge ↗</a
|
||||
>
|
||||
<a href="https://github.com/mungai-njoroge" target="_blank">built by @mungai-njoroge ↗</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<HeartSvg
|
||||
:state="queue.currenttrack?.is_favorite"
|
||||
@handle-fav="$emit('handleFav', queue.currenttrackhash)"
|
||||
/>
|
||||
<OptionSvg
|
||||
class="optionsvg"
|
||||
:class="{ context_menu_showing }"
|
||||
@click="showMenu"
|
||||
/>
|
||||
<HeartSvg :state="queue.currenttrack?.is_favorite" @handle-fav="$emit('handleFav', queue.currenttrackhash)" />
|
||||
<OptionSvg class="optionsvg" :class="{ context_menu_showing }" @click="showMenu" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -34,9 +25,9 @@ import { useRoute } from "vue-router";
|
||||
import ArtistName from "../shared/ArtistName.vue";
|
||||
import HeartSvg from "../shared/HeartSvg.vue";
|
||||
|
||||
import useQueueStore from "@/stores/queue";
|
||||
import OptionSvg from "@/assets/icons/more.svg";
|
||||
import { showTrackContextMenu } from "@/helpers/contextMenuHandler";
|
||||
import useQueueStore from "@/stores/queue";
|
||||
|
||||
const route = useRoute();
|
||||
const context_menu_showing = ref(false);
|
||||
@@ -50,13 +41,7 @@ defineEmits<{
|
||||
function showMenu(e: MouseEvent) {
|
||||
if (!queue.currenttrack) return;
|
||||
|
||||
showTrackContextMenu(
|
||||
e,
|
||||
queue.currenttrack,
|
||||
context_menu_showing,
|
||||
route,
|
||||
false
|
||||
);
|
||||
showTrackContextMenu(e, queue.currenttrack, context_menu_showing, route, false);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -66,6 +51,7 @@ function showMenu(e: MouseEvent) {
|
||||
grid-template-columns: 1fr max-content;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
font-weight: 500;
|
||||
|
||||
.artist {
|
||||
font-size: 0.8rem;
|
||||
@@ -80,6 +66,7 @@ function showMenu(e: MouseEvent) {
|
||||
.optionsvg {
|
||||
transform: scale(1.5) rotate(90deg);
|
||||
border-radius: $small;
|
||||
transition: background-color 0.2s ease-out;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray3;
|
||||
@@ -94,6 +81,7 @@ function showMenu(e: MouseEvent) {
|
||||
|
||||
.heart-button {
|
||||
background-color: $gray;
|
||||
transition: background-color 0.2s ease-out;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray4;
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
<template>
|
||||
<div class="now-playing-top">
|
||||
<router-link
|
||||
class="now-playling-from-link"
|
||||
:to="(data.location as RouteLocationRaw)"
|
||||
title="Go to Play Source"
|
||||
>
|
||||
<router-link class="now-playling-from-link" :to="(data.location as RouteLocationRaw)" title="Go to Play Source">
|
||||
<div class="from">
|
||||
<img
|
||||
v-if="
|
||||
tracklist.from.type === FromOptions.album ||
|
||||
tracklist.from.type === FromOptions.artist
|
||||
"
|
||||
v-if="tracklist.from.type === FromOptions.album || tracklist.from.type === FromOptions.artist"
|
||||
:src="data.image + '.webp'"
|
||||
:alt="`Now Playing ${tracklist.from.type} image`"
|
||||
:class="`${
|
||||
tracklist.from.type === FromOptions.artist
|
||||
? 'circular'
|
||||
: 'rounded-sm'
|
||||
}`"
|
||||
:class="`${tracklist.from.type === FromOptions.artist ? 'circular' : 'rounded-sm'}`"
|
||||
/>
|
||||
<div v-else class="from-icon border rounded-sm">
|
||||
<component :is="data.icon"></component>
|
||||
@@ -97,6 +86,7 @@ function showContextMenu(e: MouseEvent) {
|
||||
padding: $smaller;
|
||||
aspect-ratio: 1;
|
||||
width: 2.5rem;
|
||||
margin-right: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -108,6 +98,11 @@ function showContextMenu(e: MouseEvent) {
|
||||
text-transform: capitalize;
|
||||
font-size: 0.8rem;
|
||||
color: $gray1;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.type + div {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,15 +4,20 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style lang="scss">
|
||||
.p-after-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 64px;
|
||||
padding: 0 1rem;
|
||||
margin-top: $small;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: $gray1;
|
||||
|
||||
@include largePhones {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,37 +28,32 @@
|
||||
class="album-header-ambient rounded-lg"
|
||||
style="height: 100%; width: 100%"
|
||||
:style="{
|
||||
boxShadow: colors.bg
|
||||
? `0 .5rem 2rem ${colors.bg}`
|
||||
: '0 .5rem 2rem black',
|
||||
boxShadow: colors.bg ? `0 .5rem 2rem ${colors.bg}` : '0 .5rem 2rem black',
|
||||
}"
|
||||
></div>
|
||||
<div v-if="info.has_image && useSqrImg" class="sqr_img">
|
||||
<img :src="(playlist.info.image as string)" class="rounded-sm" />
|
||||
</div>
|
||||
<BannerImages
|
||||
v-if="playlist.info.count && !info.has_image && useSqrImg"
|
||||
class="sqr_img rounded-sm"
|
||||
/>
|
||||
<BannerImages v-if="playlist.info.count && !info.has_image && useSqrImg" class="sqr_img rounded-sm" />
|
||||
<Info :text-color="textColor" :btn_color="colors.btn" />
|
||||
<LastUpdated />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed } from "vue";
|
||||
|
||||
import usePStore from "@/stores/pages/playlist";
|
||||
import { getTextColor } from "@/utils/colortools/shift";
|
||||
import { pinUnpinPlaylist } from "@/requests/playlists";
|
||||
import { heightLarge, isSmallPhone } from "@/stores/content-width";
|
||||
import usePStore from "@/stores/pages/playlist";
|
||||
import { getTextColor } from "@/utils/colortools/shift";
|
||||
|
||||
import Info from "./Header/Info.vue";
|
||||
import PinSvg from "@/assets/icons/pin.svg";
|
||||
import LastUpdated from "./Header/LastUpdated.vue";
|
||||
import BannerImages from "./Header/BannerImages.vue";
|
||||
import PinFillSvg from "@/assets/icons/pin.fill.svg";
|
||||
import PinSvg from "@/assets/icons/pin.svg";
|
||||
import BannerImages from "./Header/BannerImages.vue";
|
||||
import Info from "./Header/Info.vue";
|
||||
import LastUpdated from "./Header/LastUpdated.vue";
|
||||
|
||||
const playlist = usePStore();
|
||||
|
||||
@@ -66,10 +61,7 @@ const { info, colors } = storeToRefs(playlist);
|
||||
|
||||
const bg = computed(() => {
|
||||
if (playlist.info.has_image) {
|
||||
if (
|
||||
isSmallPhone.value ||
|
||||
(!playlist.info.settings.square_img && !isSmallPhone.value)
|
||||
) {
|
||||
if (isSmallPhone.value || (!playlist.info.settings.square_img && !isSmallPhone.value)) {
|
||||
return `url(${info.value.image})`;
|
||||
}
|
||||
}
|
||||
@@ -77,9 +69,7 @@ const bg = computed(() => {
|
||||
return colors.value.bg ? colors.value.bg : "";
|
||||
});
|
||||
|
||||
const useSqrImg = computed(
|
||||
() => !playlist.info.has_image || !bg.value.startsWith("url")
|
||||
);
|
||||
const useSqrImg = computed(() => !playlist.info.has_image || !bg.value.startsWith("url"));
|
||||
|
||||
const textColor = computed(() => {
|
||||
if (colors.value.bg !== "") {
|
||||
@@ -130,7 +120,7 @@ function pinPlaylist(pid: number) {
|
||||
font-size: 3.75rem !important;
|
||||
}
|
||||
|
||||
@include smallPhone {
|
||||
@include largePhones {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
@@ -176,7 +166,7 @@ function pinPlaylist(pid: number) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@include smallPhone {
|
||||
@include largePhones {
|
||||
.title {
|
||||
font-size: 2.5rem !important;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import usePStore from "@/stores/pages/playlist";
|
||||
import { paths } from "@/config";
|
||||
import usePStore from "@/stores/pages/playlist";
|
||||
|
||||
const playlist = usePStore();
|
||||
</script>
|
||||
@@ -32,7 +32,7 @@ const playlist = usePStore();
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
@include smallPhone {
|
||||
@include largePhones {
|
||||
right: -4rem;
|
||||
|
||||
img {
|
||||
|
||||
@@ -9,28 +9,13 @@
|
||||
<PlayBtnRect :source="playSources.playlist" :bg_color="btn_color" />
|
||||
</div>
|
||||
<div class="duration">
|
||||
{{
|
||||
playlist.info.count.toLocaleString() +
|
||||
` ${playlist.info.count == 1 ? "Track" : "Tracks"}`
|
||||
}}
|
||||
{{ playlist.info.count.toLocaleString() + ` ${playlist.info.count == 1 ? "Track" : "Tracks"}` }}
|
||||
•
|
||||
{{ formatSeconds(playlist.info.duration, true) }}
|
||||
</div>
|
||||
<div ref="test_elem"></div>
|
||||
<div
|
||||
class="title"
|
||||
:class="`${
|
||||
playlist.info.settings.square_img && isSmall ? 'ellip' : 'ellip2'
|
||||
}`"
|
||||
>
|
||||
<span
|
||||
v-for="t in balanceText(
|
||||
playlist.info.name,
|
||||
test_elem?.offsetWidth || 0,
|
||||
'4rem'
|
||||
)"
|
||||
:key="t"
|
||||
>
|
||||
<div class="title" :class="`${playlist.info.settings.square_img && isSmall ? 'ellip' : 'ellip2'}`">
|
||||
<span v-for="t in balanceText(playlist.info.name, test_elem?.offsetWidth || 0, '4rem')" :key="t">
|
||||
{{ t }}
|
||||
<br />
|
||||
</span>
|
||||
@@ -40,11 +25,11 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { playSources } from "@/enums";
|
||||
import { formatSeconds } from "@/utils";
|
||||
import { isSmall } from "@/stores/content-width";
|
||||
import { formatSeconds } from "@/utils";
|
||||
|
||||
import usePStore from "@/stores/pages/playlist";
|
||||
import PlayBtnRect from "@/components/shared/PlayBtnRect.vue";
|
||||
import usePStore from "@/stores/pages/playlist";
|
||||
import { balanceText } from "@/utils/balanceText";
|
||||
import { Ref, ref } from "vue";
|
||||
|
||||
@@ -64,7 +49,7 @@ const test_elem: Ref<HTMLElement | null> = ref(null);
|
||||
height: 100%;
|
||||
display: grid;
|
||||
z-index: 10;
|
||||
padding-left: 1.25rem;
|
||||
padding: 0 1.25rem;
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
|
||||
|
||||
@@ -1,61 +1,69 @@
|
||||
<template>
|
||||
<div class="last-updated">
|
||||
<span v-if="!isHeaderSmall" class="status"
|
||||
>Last updated {{ playlist.info.last_updated }}  |  </span
|
||||
>
|
||||
<div
|
||||
v-if="Number.isInteger(playlist.info.id)"
|
||||
class="edit"
|
||||
@click="editPlaylist"
|
||||
>
|
||||
Edit  
|
||||
<div class="last-updated">
|
||||
<span
|
||||
v-if="!isHeaderSmall"
|
||||
class="status"
|
||||
>Last updated {{ playlist.info.last_updated }}</span
|
||||
>
|
||||
<div
|
||||
v-if="Number.isInteger(playlist.info.id)"
|
||||
class="edit"
|
||||
|
||||
>
|
||||
  |   <span @click="editPlaylist">Edit</span>  
|
||||
{{ Number.isInteger(playlist.info.id) ? ' | ' : '' }}
|
||||
<DeleteSvg
|
||||
class="edit"
|
||||
@click="deletePlaylist"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{ Number.isInteger(playlist.info.id) ? " | " : "" }}
|
||||
<DeleteSvg class="edit" @click="deletePlaylist" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import DeleteSvg from "@/assets/icons/delete.svg";
|
||||
import DeleteSvg from '@/assets/icons/delete.svg'
|
||||
|
||||
import { isHeaderSmall } from "@/stores/content-width";
|
||||
import { isHeaderSmall } from '@/stores/content-width'
|
||||
|
||||
import useModalStore from "@/stores/modal";
|
||||
import usePStore from "@/stores/pages/playlist";
|
||||
import useModalStore from '@/stores/modal'
|
||||
import usePStore from '@/stores/pages/playlist'
|
||||
|
||||
const playlist = usePStore();
|
||||
const modal = useModalStore();
|
||||
const playlist = usePStore()
|
||||
const modal = useModalStore()
|
||||
|
||||
function editPlaylist() {
|
||||
modal.showEditPlaylistModal();
|
||||
modal.showEditPlaylistModal()
|
||||
}
|
||||
|
||||
function deletePlaylist() {
|
||||
modal.showDeletePlaylistModal(playlist.info.id);
|
||||
modal.showDeletePlaylistModal(playlist.info.id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.last-updated {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
padding: $smaller $small;
|
||||
font-size: 0.9rem;
|
||||
border-radius: $smaller;
|
||||
z-index: 12;
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
padding: $smaller $small;
|
||||
font-size: 0.9rem;
|
||||
border-radius: $smaller;
|
||||
z-index: 12;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.edit {
|
||||
cursor: pointer;
|
||||
color: $brown;
|
||||
}
|
||||
.edit {
|
||||
cursor: pointer;
|
||||
color: $brown;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
svg {
|
||||
transform: scale(0.75);
|
||||
margin-bottom: -0.2rem;
|
||||
color: $red !important;
|
||||
}
|
||||
svg {
|
||||
transform: scale(0.75);
|
||||
margin-bottom: -0.2rem;
|
||||
color: $red !important;
|
||||
height: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,24 +1,9 @@
|
||||
<template>
|
||||
<router-link
|
||||
:to="{ name: 'PlaylistView', params: { pid: playlist.id } }"
|
||||
class="p-card rounded no-scroll"
|
||||
>
|
||||
<div
|
||||
v-if="!playlist.has_image && playlist.images.length"
|
||||
class="image-grid rounded-sm no-scroll"
|
||||
>
|
||||
<img
|
||||
v-for="(img, index) in playlist.images"
|
||||
:key="index"
|
||||
:src="paths.images.thumb.large + img"
|
||||
/>
|
||||
<router-link :to="{ name: 'PlaylistView', params: { pid: playlist.id } }" class="p-card rounded no-scroll">
|
||||
<div v-if="!playlist.has_image && playlist.images.length" class="image-grid rounded-sm no-scroll">
|
||||
<img v-for="(img, index) in playlist.images" :key="index" :src="paths.images.thumb.large + img" />
|
||||
</div>
|
||||
<img
|
||||
v-else
|
||||
:src="imguri + playlist.thumb"
|
||||
class="rounded-sm"
|
||||
:class="{ border: !playlist.thumb }"
|
||||
/>
|
||||
<img v-else :src="imguri + playlist.thumb" class="rounded-sm" :class="{ border: !playlist.thumb }" />
|
||||
<div class="overlay rounded">
|
||||
<div v-if="playlist.help_text" class="rhelp playlist">
|
||||
<span class="help">{{ playlist.help_text }}</span>
|
||||
@@ -26,10 +11,7 @@
|
||||
</div>
|
||||
<div class="p-name ellip">{{ playlist.name }}</div>
|
||||
<div class="p-count">
|
||||
<b>{{
|
||||
playlist.count.toLocaleString() +
|
||||
` Track${playlist.count === 1 ? "" : "s"}`
|
||||
}}</b>
|
||||
<b>{{ playlist.count.toLocaleString() + ` Track${playlist.count === 1 ? "" : "s"}` }}</b>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
@@ -54,6 +36,7 @@ defineProps<{
|
||||
gap: $small;
|
||||
user-select: none;
|
||||
height: max-content;
|
||||
transition: background-color 0.2s ease-out;
|
||||
|
||||
.image-grid {
|
||||
display: grid;
|
||||
@@ -61,7 +44,6 @@ defineProps<{
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition: all 0.25s ease;
|
||||
background-color: $gray4 !important;
|
||||
background-blend-mode: screen;
|
||||
}
|
||||
@@ -79,9 +61,13 @@ defineProps<{
|
||||
justify-content: flex-end;
|
||||
transition: all 0.25s ease;
|
||||
|
||||
.p-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.p-count {
|
||||
opacity: 0.75;
|
||||
font-size: 0.75rem;
|
||||
color: #ffffffbf;
|
||||
margin-top: $smaller;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Playlist } from "@/interfaces";
|
||||
import PlaylistCard from "@/components/PlaylistsList/PlaylistCard.vue";
|
||||
import { Playlist } from "@/interfaces";
|
||||
|
||||
defineProps<{
|
||||
playlists: Playlist[];
|
||||
@@ -21,7 +21,7 @@ defineProps<{
|
||||
.playlistcardgroup {
|
||||
margin-bottom: 4rem;
|
||||
h3 {
|
||||
margin-left: $small;
|
||||
margin-left: $medium;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
<script setup lang="ts">
|
||||
import useTabStore from "@/stores/tabs";
|
||||
|
||||
import DashBoard from "./Home/Main.vue";
|
||||
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();
|
||||
@@ -64,4 +64,12 @@ const tabs = useTabStore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.designatedOS .r-sidebar > .r-content > .r-queue > .queue-virtual-scroller > .scroller::-webkit-scrollbar-track {
|
||||
background-color: $gray;
|
||||
}
|
||||
|
||||
.designatedOS .r-sidebar > .r-content > .r-queue > .queue-virtual-scroller > .scroller::-webkit-scrollbar-thumb {
|
||||
border: 4px solid $gray;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
<template>
|
||||
<div class="queue-actions">
|
||||
<div class="left">
|
||||
<button
|
||||
v-if="!onNowPlaying"
|
||||
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"
|
||||
>
|
||||
<button class="menu" :class="{ 'btn-active': context_showing }" @click="showContextMenu">
|
||||
<OptionsSvg />
|
||||
</button>
|
||||
</div>
|
||||
@@ -25,9 +16,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import useQueue from "@/stores/queue";
|
||||
import useTracklist from "@/stores/queue/tracklist";
|
||||
import { ref } from "vue";
|
||||
|
||||
import { showQueueContextMenu } from "@/helpers/contextMenuHandler";
|
||||
|
||||
@@ -67,7 +58,7 @@ defineProps<{
|
||||
}
|
||||
|
||||
// hide on screens less than 600px
|
||||
@media screen and (max-width: 600px) {
|
||||
@media only screen and (max-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<template>
|
||||
<div class="right-search">
|
||||
<TabsWrapper
|
||||
:tabs="tabs"
|
||||
:current-tab="currentTab"
|
||||
:tab-content="true"
|
||||
@switchTab="switchTab"
|
||||
>
|
||||
<TabsWrapper :tabs="tabs" :current-tab="currentTab" :tab-content="true" @switchTab="switchTab">
|
||||
<Tab :name="currentTab" />
|
||||
</TabsWrapper>
|
||||
</div>
|
||||
@@ -41,6 +36,10 @@ function switchTab(tab: string) {
|
||||
|
||||
.tabheaders {
|
||||
padding: 1rem;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.input {
|
||||
@@ -48,7 +47,5 @@ function switchTab(tab: string) {
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -56,8 +56,17 @@ defineEmits<{
|
||||
|
||||
#tab-content {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.designatedOS #tab-content::-webkit-scrollbar-track {
|
||||
background-color: $gray;
|
||||
}
|
||||
|
||||
.designatedOS #tab-content::-webkit-scrollbar-thumb {
|
||||
border: 4px solid $gray;
|
||||
}
|
||||
|
||||
#right-tabs.tabContent {
|
||||
|
||||
@@ -2,19 +2,12 @@
|
||||
<RouterLink
|
||||
:to="{
|
||||
name: res_type === 'artist' ? Routes.artist : Routes.album,
|
||||
params:
|
||||
res_type === 'artist'
|
||||
? { hash: item.artisthash || ' ' }
|
||||
: { albumhash: item.albumhash || ' ' },
|
||||
params: res_type === 'artist' ? { hash: item.artisthash || ' ' } : { albumhash: item.albumhash || ' ' },
|
||||
}"
|
||||
class="top-result-item rounded"
|
||||
>
|
||||
<img
|
||||
:src="
|
||||
res_type === 'artist'
|
||||
? paths.images.artist.large + item.image
|
||||
: paths.images.thumb.large + item.image
|
||||
"
|
||||
:src="res_type === 'artist' ? paths.images.artist.medium + item.image : paths.images.thumb.medium + item.image"
|
||||
alt=""
|
||||
class="rounded-sm"
|
||||
:class="{ circular: res_type === 'artist' }"
|
||||
@@ -36,10 +29,7 @@
|
||||
{{ item.trackcount === 1 ? "track" : "tracks" }}
|
||||
</div>
|
||||
<div v-if="res_type === 'track'" class="artists flex">
|
||||
<ArtistName
|
||||
:artists="item.artists"
|
||||
:albumartists="item.albumartists"
|
||||
/>
|
||||
<ArtistName :artists="item.artists" :albumartists="item.albumartists" />
|
||||
•
|
||||
{{ formatSeconds(item.duration, true) }}
|
||||
</div>
|
||||
@@ -57,11 +47,7 @@
|
||||
</button>
|
||||
<PlayBtn
|
||||
:source="
|
||||
res_type == 'album'
|
||||
? playSources.album
|
||||
: res_type == 'artist'
|
||||
? playSources.artist
|
||||
: playSources.track
|
||||
res_type == 'album' ? playSources.album : res_type == 'artist' ? playSources.artist : playSources.track
|
||||
"
|
||||
:album-hash="item.albumhash"
|
||||
:album-name="item.title"
|
||||
@@ -74,22 +60,22 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { Routes } from "@/router";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { computed, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import useSearchStore from "@/stores/search";
|
||||
import { showTrackContextMenu as showContext } from "@/helpers/contextMenuHandler";
|
||||
import useSearchStore from "@/stores/search";
|
||||
|
||||
import { paths } from "@/config";
|
||||
import { formatSeconds } from "@/utils";
|
||||
import { Album, Artist, Track } from "@/interfaces";
|
||||
import ArtistName from "@/components/shared/ArtistName.vue";
|
||||
import Moresvg from "@/assets/icons/more.svg";
|
||||
import ArtistName from "@/components/shared/ArtistName.vue";
|
||||
import { paths } from "@/config";
|
||||
import { Album, Artist, Track } from "@/interfaces";
|
||||
import { formatSeconds } from "@/utils";
|
||||
|
||||
import { playSources } from "@/enums";
|
||||
import PlayBtn from "@/components/shared/PlayBtn.vue";
|
||||
import { playSources } from "@/enums";
|
||||
|
||||
const search = useSearchStore();
|
||||
const route = useRoute();
|
||||
@@ -139,7 +125,7 @@ function showMenu(e: MouseEvent) {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
transition: opacity 0.2s ease-in-out, background-color 0.2s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +156,7 @@ function showMenu(e: MouseEvent) {
|
||||
|
||||
.type {
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
background-color: $darkblue;
|
||||
width: max-content;
|
||||
@@ -188,6 +175,7 @@ function showMenu(e: MouseEvent) {
|
||||
|
||||
.artists {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
import { Track } from "@/interfaces";
|
||||
|
||||
import useQueueStore from "@/stores/queue";
|
||||
import useSearchStore from "@/stores/search";
|
||||
import useTracklist from "@/stores/queue/tracklist";
|
||||
import useSearchStore from "@/stores/search";
|
||||
|
||||
import TrackItem from "@/components/shared/TrackItem.vue";
|
||||
|
||||
@@ -37,7 +37,7 @@ function handlePlay(track: Track) {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.track-item {
|
||||
padding: $small 1.1rem;
|
||||
padding: $small;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<div
|
||||
class="gsearch-input"
|
||||
@click="
|
||||
!settings.use_sidebar && $route.name !== Routes.search &&
|
||||
!settings.use_sidebar &&
|
||||
$route.name !== Routes.search &&
|
||||
$router.push({
|
||||
name: Routes.search,
|
||||
params: { page: 'top' },
|
||||
@@ -13,9 +14,7 @@
|
||||
<div id="ginner" ref="inputRef" tabindex="0">
|
||||
<button
|
||||
v-auto-animate
|
||||
:title="
|
||||
tabs.current === tabs.tabs.search ? 'back to queue' : 'go to search'
|
||||
"
|
||||
:title="tabs.current === tabs.tabs.search ? 'back to queue' : 'go to search'"
|
||||
:class="{ no_bg: on_nav }"
|
||||
@click.prevent="handleButton"
|
||||
>
|
||||
@@ -32,16 +31,16 @@
|
||||
@blur.prevent="removeFocusedClass"
|
||||
@focus.prevent="addFocusedClass"
|
||||
/>
|
||||
<div class="clear_input noSelect" :class="{ active: search.query.length > 0 }" @click="clearInput">X</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
import useTabStore from "@/stores/tabs";
|
||||
import useSearch from "@/stores/search";
|
||||
import useSettings from "@/stores/settings";
|
||||
import useTabStore from "@/stores/tabs";
|
||||
import { ref } from "vue";
|
||||
|
||||
import BackSvg from "@/assets/icons/arrow.svg";
|
||||
import SearchSvg from "@/assets/icons/search.svg";
|
||||
@@ -56,16 +55,27 @@ const search = useSearch();
|
||||
const settings = useSettings();
|
||||
|
||||
// HANDLE FOCUS
|
||||
const inputRef = ref<HTMLElement>();
|
||||
const inputRef = ref<HTMLInputElement | null>(null);
|
||||
|
||||
// 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");
|
||||
if (inputRef.value) {
|
||||
inputRef.value.classList.add("search-focused");
|
||||
}
|
||||
}
|
||||
|
||||
function removeFocusedClass() {
|
||||
inputRef.value?.classList.remove("search-focused");
|
||||
if (inputRef.value) {
|
||||
inputRef.value.classList.remove("search-focused");
|
||||
}
|
||||
}
|
||||
|
||||
function clearInput() {
|
||||
search.query = "";
|
||||
if (inputRef.value) {
|
||||
inputRef.value.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// @end
|
||||
@@ -81,7 +91,23 @@ function handleButton() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.clear_search {
|
||||
/* Style applied when clear_search class is active */
|
||||
visibility: visible;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.right > .gsearch-input > #ginner > input {
|
||||
width: 140px;
|
||||
|
||||
@include allPhones {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.gsearch-input {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr max-content;
|
||||
@@ -93,20 +119,28 @@ function handleButton() {
|
||||
// gap: $small;
|
||||
border-radius: 3rem;
|
||||
background-color: $gray5;
|
||||
outline: solid 2px transparent;
|
||||
transition: outline-color 0.2s ease-out;
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: none;
|
||||
width: 3rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
padding: 0;
|
||||
margin-left: 4px;
|
||||
border-radius: 3rem;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
|
||||
&:hover {
|
||||
transition: all 0.2s ease;
|
||||
background-color: $gray2;
|
||||
}
|
||||
|
||||
@include allPhones {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
button.no_bg {
|
||||
@@ -119,16 +153,58 @@ function handleButton() {
|
||||
line-height: 2.25rem;
|
||||
color: inherit;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
width: 7rem;
|
||||
@include allPhones {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
padding-right: $small;
|
||||
}
|
||||
}
|
||||
|
||||
.clear_input {
|
||||
cursor: pointer;
|
||||
padding: 10px 1rem;
|
||||
border-top-right-radius: 3rem;
|
||||
border-bottom-right-radius: 3rem;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s ease-out, visibility 0.3s ease-out;
|
||||
|
||||
@include allPhones {
|
||||
border-radius: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.clear_input.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.clear_input.active:active {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
@include allPhones {
|
||||
border-radius: unset;
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@include allPhones {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.search-focused {
|
||||
outline: solid 2px #fff !important;
|
||||
outline: solid 2px #fff;
|
||||
|
||||
@include allPhones {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,92 +1,89 @@
|
||||
<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">
|
||||
<div class="aboutswingmusic">
|
||||
<div class="version">Swing Music v{{ settings.version }}</div>
|
||||
Swing Music is a labor of love developed by
|
||||
<a
|
||||
href="mailto:geoffreymungai45@gmail.com?subject=Job Offer&body=Hi Mungai,
|
||||
"
|
||||
target="_blank"
|
||||
><button>Write Email</button></a
|
||||
href="https://github.com/cwilvx"
|
||||
target="_blank"
|
||||
>@<u>cwilvx</u></a
|
||||
>
|
||||
</div>
|
||||
on GitHub. If you like this software, a star on GitHub would be nice.
|
||||
Hope you enjoy using it as much as I enjoy building it. 😁🤗
|
||||
<br /><br />
|
||||
<div class="links">
|
||||
<h2>Links</h2>
|
||||
<div class="flex">
|
||||
<a
|
||||
href="https://swingmusic.vercel.app/guide/introduction.html"
|
||||
target="_blank"
|
||||
><button>Docs</button></a
|
||||
>
|
||||
<a
|
||||
href="https://github.com/cwilvx/swingmusic"
|
||||
target="_blank"
|
||||
><button>Star on Github</button></a
|
||||
>
|
||||
<a
|
||||
href="https://github.com/cwilvx/swingmusic/issues/new/choose"
|
||||
target="_blank"
|
||||
>
|
||||
<button>Report issue</button>
|
||||
</a>
|
||||
<a
|
||||
href="https://github.com/cwilvx/swingmusic/blob/master/.github/contributing.md"
|
||||
target="_blank"
|
||||
><button>Contribute</button></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="contact">
|
||||
<h2>Get in touch</h2>
|
||||
If you like my work, and would like to say hi, I'd like to hear from
|
||||
you.
|
||||
<br /><br />
|
||||
<div class="flex">
|
||||
<a
|
||||
href="mailto:geoffreymungai45@gmail.com?subject=Hiii 👋😁&body=Hi Mungai,
|
||||
"
|
||||
target="_blank"
|
||||
><button>Send email</button></a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
<script setup lang="ts">
|
||||
import useSettings from '@/stores/settings'
|
||||
|
||||
const settings = useSettings()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.aboutswingmusic {
|
||||
padding: $small;
|
||||
margin-top: 2rem;
|
||||
padding: $small;
|
||||
|
||||
.flex {
|
||||
gap: 1rem;
|
||||
}
|
||||
.version {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: $small;
|
||||
border-bottom: solid 1px $separator;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.hireme {
|
||||
background-color: #ffffff;
|
||||
background-image: linear-gradient(
|
||||
37deg,
|
||||
#bfeaf0 0%,
|
||||
#ffffff00 50%,
|
||||
#a7dcff 100%
|
||||
);
|
||||
padding: 1rem;
|
||||
color: $black;
|
||||
.links .flex {
|
||||
margin-top: $small;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: $blue;
|
||||
.contact button {
|
||||
background-color: $blue;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: $small;
|
||||
margin-top: 0;
|
||||
margin-bottom: $smaller;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,78 +1,92 @@
|
||||
<template>
|
||||
<div class="list-items">
|
||||
<div v-for="i in items" :key="i.title" class="option-list-item">
|
||||
<div class="with-icon">
|
||||
<component :is="icon_" />
|
||||
<div class="text ellip">
|
||||
{{ i.title }}
|
||||
<div class="list-items">
|
||||
<div
|
||||
v-for="i in items"
|
||||
:key="i.title"
|
||||
class="option-list-item"
|
||||
>
|
||||
<div class="with-icon">
|
||||
<component :is="icon_" />
|
||||
<div class="text ellip">
|
||||
{{ i.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="icon"
|
||||
@click="i.action"
|
||||
>
|
||||
<DeleteSvg />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!items.length"
|
||||
class="option-list-item"
|
||||
style="opacity: 0.5"
|
||||
>
|
||||
Root directories not configured. Use the "Configure" button above to
|
||||
configure
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon" @click="i.action">
|
||||
<DeleteSvg />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!items.length" class="option-list-item" style="opacity: 0.5">
|
||||
Root directories not configured. Use the "Configure" button above to
|
||||
configure
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import FolderSvg from "@/assets/icons/folder.svg";
|
||||
import DeleteSvg from "@/assets/icons/delete.svg";
|
||||
import DeleteSvg from '@/assets/icons/delete.svg'
|
||||
import FolderSvg from '@/assets/icons/folder.svg'
|
||||
|
||||
const props = defineProps<{
|
||||
items: {
|
||||
title: string;
|
||||
action: () => void;
|
||||
}[];
|
||||
icon: "folder";
|
||||
}>();
|
||||
items: {
|
||||
title: string
|
||||
action: () => void
|
||||
}[]
|
||||
icon: 'folder'
|
||||
}>()
|
||||
|
||||
function getIcon() {
|
||||
switch (props.icon) {
|
||||
case "folder":
|
||||
return FolderSvg;
|
||||
switch (props.icon) {
|
||||
case 'folder':
|
||||
return FolderSvg
|
||||
|
||||
default:
|
||||
return FolderSvg;
|
||||
}
|
||||
default:
|
||||
return FolderSvg
|
||||
}
|
||||
}
|
||||
|
||||
const icon_ = getIcon();
|
||||
const icon_ = getIcon()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.setting-item.is-list {
|
||||
$color: $gray5;
|
||||
.list-items {
|
||||
border: solid 1px $gray5;
|
||||
border-radius: $small;
|
||||
margin-top: 1rem;
|
||||
overflow: hidden;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.list-items {
|
||||
background-color: $color;
|
||||
border-radius: $small;
|
||||
margin-top: 1rem;
|
||||
overflow: hidden;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
.option-list-item {
|
||||
padding: $small 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
|
||||
.option-list-item {
|
||||
padding: $small 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
svg {
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.with-icon {
|
||||
display: flex;
|
||||
gap: $small;
|
||||
align-items: center;
|
||||
font-family: "SF Mono", monospace;
|
||||
font-weight: 500;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
.icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -31,6 +31,8 @@ function submit(action: "plus" | "minus") {
|
||||
|
||||
.number {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
button {
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
<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>
|
||||
@@ -36,13 +36,16 @@ const optionsWithActive = computed(() => {
|
||||
.setting-select {
|
||||
display: flex;
|
||||
background-color: $gray3;
|
||||
margin-left: 8px;
|
||||
|
||||
.option {
|
||||
font-weight: 500;
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
min-width: 4rem;
|
||||
text-align: center;
|
||||
transition: background-color 0.2s ease-out;
|
||||
}
|
||||
|
||||
.option.active {
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
<template>
|
||||
<div class="artist-separators-input">
|
||||
<form @submit.prevent="submitInput">
|
||||
<input
|
||||
ref="separatorinput"
|
||||
type="search"
|
||||
class="rounded-sm"
|
||||
@input="updateInput"
|
||||
/>
|
||||
<input ref="separatorinput" type="search" class="rounded-sm" @input="updateInput" />
|
||||
<div class="preview">
|
||||
<span
|
||||
v-for="p in preview_items"
|
||||
:key="p"
|
||||
class="circular"
|
||||
:class="!default_input_list.includes(p) ? 'new' : ''"
|
||||
<span v-for="p in preview_items" :key="p" class="circular" :class="!default_input_list.includes(p) ? 'new' : ''"
|
||||
><b>
|
||||
{{ p }}
|
||||
</b></span
|
||||
@@ -68,9 +59,7 @@ function submitInput() {
|
||||
}
|
||||
|
||||
const preview_items = computed(() => splitInput(input.value));
|
||||
const default_input = computed(() =>
|
||||
props.default ? props.default.join(", ") : ""
|
||||
);
|
||||
const default_input = computed(() => (props.default ? props.default.join(", ") : ""));
|
||||
|
||||
onMounted(() => {
|
||||
const text = props.default.join(", ");
|
||||
@@ -95,6 +84,7 @@ onMounted(() => {
|
||||
background-color: $gray5;
|
||||
color: white;
|
||||
font-size: 1rem;
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.preview {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div v-if="group.show_if ? group.show_if() : true" class="settingsgroup">
|
||||
<div v-if="group.title || group.desc" class="info">
|
||||
<div v-if="group && (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
|
||||
}}<span v-if="group.experimental" class="badge experimental circular">
|
||||
@@ -8,12 +8,10 @@
|
||||
</span>
|
||||
</h4>
|
||||
<div v-if="group.desc" class="desc">{{ group.desc }}</div>
|
||||
</div>
|
||||
<div class="setting rounded pad-lg">
|
||||
</div> -->
|
||||
<div class="setting pad-lg">
|
||||
<div
|
||||
v-for="(setting, index) in group.settings.filter((s) =>
|
||||
s.show_if ? s.show_if() : true
|
||||
)"
|
||||
v-for="(setting, index) in group.settings.filter((s) => (s.show_if ? s.show_if() : true))"
|
||||
:key="index"
|
||||
class="setting-item"
|
||||
:class="{
|
||||
@@ -21,31 +19,18 @@
|
||||
'is-list': setting.type === SettingType.root_dirs,
|
||||
}"
|
||||
>
|
||||
<div
|
||||
class="text"
|
||||
@click="
|
||||
setting.defaultAction ? setting.defaultAction() : setting.action()
|
||||
"
|
||||
>
|
||||
<div class="text" @click="setting.defaultAction ? setting.defaultAction() : setting.action()">
|
||||
<div class="title">
|
||||
<span class="ellip">
|
||||
{{ setting.title }}
|
||||
<span
|
||||
v-if="setting.experimental"
|
||||
class="badge experimental circular"
|
||||
>
|
||||
<span v-if="setting.experimental" class="badge experimental circular">
|
||||
{{ setting.experimental ? "experimental" : "" }}
|
||||
</span>
|
||||
<span v-if="setting.new" class="badge new circular">
|
||||
{{ setting.new ? "new" : "" }}
|
||||
</span>
|
||||
</span>
|
||||
<button
|
||||
v-if="setting.type == SettingType.root_dirs"
|
||||
@click="setting.action"
|
||||
>
|
||||
<ReloadSvg /> rescan
|
||||
</button>
|
||||
<button v-if="setting.type == SettingType.root_dirs" @click="setting.action"><ReloadSvg /> rescan</button>
|
||||
</div>
|
||||
<div v-if="setting.desc" class="desc">
|
||||
{{ setting.desc }}
|
||||
@@ -63,10 +48,7 @@
|
||||
:source="setting.state !== null ? setting.state : () => ''"
|
||||
:setter-fn="setting.action"
|
||||
/>
|
||||
<button
|
||||
v-if="setting.type === SettingType.button"
|
||||
@click="setting.action"
|
||||
>
|
||||
<button v-if="setting.type === SettingType.button" @click="setting.action">
|
||||
{{ setting.button_text && setting.button_text() }}
|
||||
</button>
|
||||
<LockedNumberInput
|
||||
@@ -80,7 +62,7 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<QuickActions v-if="setting.type == SettingType.quick_actions" />
|
||||
<!-- Custom components -->
|
||||
<List
|
||||
v-if="setting.type === SettingType.root_dirs"
|
||||
icon="folder"
|
||||
@@ -91,22 +73,28 @@
|
||||
:submit="setting.action"
|
||||
:default="setting.state ? setting.state() : []"
|
||||
/>
|
||||
<Profile v-if="setting.type === SettingType.profile"/>
|
||||
<Accounts v-if="setting.type === SettingType.accounts"/>
|
||||
<About v-if="setting.type === SettingType.about"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SettingType } from "@/settings/enums";
|
||||
import { SettingGroup } from "@/interfaces/settings";
|
||||
import { SettingType } from "@/settings/enums";
|
||||
|
||||
import List from "./Components/List.vue";
|
||||
import Switch from "./Components/Switch.vue";
|
||||
import Select from "./Components/Select.vue";
|
||||
import ReloadSvg from "@/assets/icons/reload.svg";
|
||||
import QuickActions from "./Components/QuickSettings.vue";
|
||||
import SeparatorsInput from "./Components/SeparatorsInput.vue";
|
||||
import List from "./Components/List.vue";
|
||||
import LockedNumberInput from "./Components/LockedNumberInput.vue";
|
||||
import Select from "./Components/Select.vue";
|
||||
import SeparatorsInput from "./Components/SeparatorsInput.vue";
|
||||
import Switch from "./Components/Switch.vue";
|
||||
|
||||
import About from "./About.vue";
|
||||
import Profile from "../modals/settings/Profile.vue";
|
||||
import Accounts from "../modals/settings/custom/Accounts.vue";
|
||||
|
||||
defineProps<{
|
||||
group: SettingGroup;
|
||||
@@ -116,30 +104,10 @@ defineProps<{
|
||||
<style lang="scss">
|
||||
.settingsgroup {
|
||||
display: grid;
|
||||
// grid-template-columns: 20rem 1fr;
|
||||
gap: $small;
|
||||
margin-top: 2rem;
|
||||
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;
|
||||
}
|
||||
@@ -156,10 +124,11 @@ defineProps<{
|
||||
.desc {
|
||||
opacity: 0.5;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.setting {
|
||||
background-color: $gray;
|
||||
// background-color: $gray;
|
||||
|
||||
.inactive {
|
||||
opacity: 0.5;
|
||||
@@ -175,7 +144,7 @@ defineProps<{
|
||||
.setting-item {
|
||||
user-select: none;
|
||||
border-bottom: solid 1px $gray5;
|
||||
padding: $medium 0;
|
||||
padding: 1.25rem 0;
|
||||
|
||||
.options {
|
||||
margin: auto 0;
|
||||
@@ -186,11 +155,16 @@ defineProps<{
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: self-start;
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
font-weight: 500;
|
||||
margin: auto 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: $small;
|
||||
width: 100%;
|
||||
|
||||
button > svg {
|
||||
@@ -212,5 +186,18 @@ defineProps<{
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
@include smallerPhones {
|
||||
.info ~ .setting > .setting-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: $small;
|
||||
|
||||
.options > .setting-select {
|
||||
margin-left: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<div class="welcome-to-swing">
|
||||
<h3 class="t-center">
|
||||
Welcome to
|
||||
<span class="app-name">Swing Music</span>
|
||||
</h3>
|
||||
<p>
|
||||
Swing Music is a web based music player that is designed to provide a home
|
||||
🏡 for all your local music files 💿️.
|
||||
</p>
|
||||
<hr />
|
||||
<p class="t-center">Let there be music!</p>
|
||||
<hr />
|
||||
<br />
|
||||
|
||||
<div class="bottom-banner">
|
||||
<div class="creator t-center">
|
||||
Designed and developed by
|
||||
<span class="name"
|
||||
><a target="_blank" href="https://github.com/cwilvx"
|
||||
>@cwilvx</a
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
<style lang="scss">
|
||||
.welcome-to-swing {
|
||||
p {
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-bottom: 1px $gray3 solid;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 1.5rem;
|
||||
color: $pink;
|
||||
}
|
||||
|
||||
.bottom-banner {
|
||||
font-size: small;
|
||||
margin-top: 1rem;
|
||||
opacity: 0.74;
|
||||
|
||||
a {
|
||||
color: $pink;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,119 +1,150 @@
|
||||
<template>
|
||||
<div v-if="modal.visible" class="modal">
|
||||
<div class="bg" @click="modal.hideModal"></div>
|
||||
<!-- 👇 login modal should not be dismissable -->
|
||||
<div
|
||||
v-motion-slide-top
|
||||
class="m-content rounded"
|
||||
:style="{
|
||||
maxWidth:
|
||||
modal.component == modal.options.setRootDirs ? '56rem' : '30rem',
|
||||
}"
|
||||
v-if="modal.visible || modal.component == ModalOptions.login"
|
||||
class="modal"
|
||||
>
|
||||
<div class="heading">{{ modal.title }}</div>
|
||||
<NewPlaylist
|
||||
v-if="modal.component == modal.options.newPlaylist"
|
||||
v-bind="modal.props"
|
||||
@hideModal="hideModal"
|
||||
@setTitle="setTitle"
|
||||
/>
|
||||
<UpdatePlaylist
|
||||
v-if="modal.component == modal.options.updatePlaylist"
|
||||
v-bind="modal.props"
|
||||
@hideModal="hideModal"
|
||||
@setTitle="setTitle"
|
||||
/>
|
||||
<WelcomeModal v-if="modal.component == modal.options.welcome" />
|
||||
<div v-if="modal.component == modal.options.deletePlaylist">
|
||||
<ConfirmModal
|
||||
:text="'Are you sure you want to permanently delete this playlist?'"
|
||||
:cancel-action="modal.hideModal"
|
||||
:confirm-action="deletePlaylist"
|
||||
/>
|
||||
</div>
|
||||
<SetRootDirs
|
||||
v-if="modal.component == modal.options.setRootDirs"
|
||||
@hideModal="hideModal"
|
||||
/>
|
||||
<RootDirsPrompt
|
||||
v-if="modal.component == modal.options.rootDirsPrompt"
|
||||
@hideModal="hideModal"
|
||||
/>
|
||||
<div
|
||||
class="bg"
|
||||
@click="modal.hideModal"
|
||||
></div>
|
||||
<div
|
||||
v-motion-slide-top
|
||||
class="m-content rounded"
|
||||
:class="{
|
||||
settings: modal.component == modal.options.settings,
|
||||
authlogin: modal.component == modal.options.login,
|
||||
}"
|
||||
:style="{
|
||||
maxWidth:
|
||||
modal.component == modal.options.setRootDirs
|
||||
? '56rem'
|
||||
: '30rem',
|
||||
}"
|
||||
>
|
||||
<!-- TODO: MOVE MAX WIDTH TO CLASS -->
|
||||
<div class="heading">{{ modal.title }}</div>
|
||||
<AuthLogin v-if="modal.component == modal.options.login" />
|
||||
<NewPlaylist
|
||||
v-if="modal.component == modal.options.newPlaylist"
|
||||
v-bind="modal.props"
|
||||
@hideModal="hideModal"
|
||||
@setTitle="setTitle"
|
||||
/>
|
||||
<UpdatePlaylist
|
||||
v-if="modal.component == modal.options.updatePlaylist"
|
||||
v-bind="modal.props"
|
||||
@hideModal="hideModal"
|
||||
@setTitle="setTitle"
|
||||
/>
|
||||
<div v-if="modal.component == modal.options.deletePlaylist">
|
||||
<ConfirmModal
|
||||
:text="'Are you sure you want to permanently delete this playlist?'"
|
||||
:cancel-action="modal.hideModal"
|
||||
:confirm-action="deletePlaylist"
|
||||
/>
|
||||
</div>
|
||||
<SetRootDirs
|
||||
v-if="modal.component == modal.options.setRootDirs"
|
||||
@hideModal="hideModal"
|
||||
/>
|
||||
<RootDirsPrompt
|
||||
v-if="modal.component == modal.options.rootDirsPrompt"
|
||||
@hideModal="hideModal"
|
||||
/>
|
||||
<Settings
|
||||
@set-title="setTitle"
|
||||
v-if="modal.component == modal.options.settings"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { deletePlaylist as delPlaylist } from "@/requests/playlists";
|
||||
import { useRouter } from "vue-router";
|
||||
import useModalStore from "@/stores/modal";
|
||||
import { deletePlaylist as delPlaylist } from '@/requests/playlists'
|
||||
import useModalStore, { ModalOptions } from '@/stores/modal'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import WelcomeModal from "./WelcomeModal.vue";
|
||||
import ConfirmModal from "./modals/ConfirmModal.vue";
|
||||
import NewPlaylist from "./modals/NewPlaylist.vue";
|
||||
import RootDirsPrompt from "./modals/RootDirsPrompt.vue";
|
||||
import SetRootDirs from "./modals/SetRootDirs.vue";
|
||||
import UpdatePlaylist from "./modals/updatePlaylist.vue";
|
||||
import AuthLogin from './modals/AuthLogin.vue'
|
||||
import ConfirmModal from './modals/ConfirmModal.vue'
|
||||
import NewPlaylist from './modals/NewPlaylist.vue'
|
||||
import RootDirsPrompt from './modals/RootDirsPrompt.vue'
|
||||
import SetRootDirs from './modals/SetRootDirs.vue'
|
||||
import UpdatePlaylist from './modals/updatePlaylist.vue'
|
||||
import Settings from './modals/Settings.vue'
|
||||
|
||||
const modal = useModalStore();
|
||||
const router = useRouter();
|
||||
const modal = useModalStore()
|
||||
const router = useRouter()
|
||||
|
||||
function setTitle(title: string) {
|
||||
modal.setTitle(title);
|
||||
modal.setTitle(title)
|
||||
}
|
||||
|
||||
function hideModal() {
|
||||
modal.hideModal();
|
||||
modal.hideModal()
|
||||
}
|
||||
|
||||
function deletePlaylist() {
|
||||
delPlaylist(modal.props.pid)
|
||||
.then(() => modal.hideModal())
|
||||
.then(() => router.back());
|
||||
delPlaylist(modal.props.pid)
|
||||
.then(() => modal.hideModal())
|
||||
.then(() => router.back())
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal {
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
input[type="search"] {
|
||||
margin: $small 0;
|
||||
border: none;
|
||||
background-color: $gray5;
|
||||
input[type='search'] {
|
||||
margin: $small 0;
|
||||
border: none;
|
||||
background-color: $gray5;
|
||||
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
height: 2.75rem !important;
|
||||
}
|
||||
|
||||
.bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(12, 12, 12, 0.767);
|
||||
}
|
||||
|
||||
.m-content {
|
||||
width: calc(100% - 4rem);
|
||||
max-height: 40rem;
|
||||
padding: 2rem 1.25rem;
|
||||
position: relative;
|
||||
background-color: $black;
|
||||
|
||||
@include smallPhone {
|
||||
width: calc(100% - 2rem);
|
||||
padding: 2rem 1rem;
|
||||
color: #fff;
|
||||
width: 100%;
|
||||
padding: $small $medium;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
height: 2.75rem !important;
|
||||
}
|
||||
|
||||
.bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(22, 22, 22, 0.753);
|
||||
// backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.m-content {
|
||||
width: calc(100% - 4rem);
|
||||
max-height: 40rem;
|
||||
padding: 2rem 1.25rem;
|
||||
position: relative;
|
||||
background-color: $black;
|
||||
|
||||
@include largePhones {
|
||||
width: calc(100% - 2rem);
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.m-content.settings {
|
||||
max-width: 50rem !important;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
// min-height: 39rem;
|
||||
}
|
||||
|
||||
.m-content.authlogin {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
310
src/components/modals/AuthLogin.vue
Normal file
@@ -0,0 +1,310 @@
|
||||
<template>
|
||||
<div
|
||||
class="loginmodal"
|
||||
v-auto-animate
|
||||
>
|
||||
<div
|
||||
class="head"
|
||||
:class="{ selected }"
|
||||
>
|
||||
<button
|
||||
class="back rounded-sm"
|
||||
title="Back to selection"
|
||||
@click="resetSelected"
|
||||
:style="{
|
||||
visibility: shownUsers.length > 1 ? 'visible' : 'hidden',
|
||||
}"
|
||||
>
|
||||
<span>back</span> <ArrowSvg />
|
||||
</button>
|
||||
<Logo />
|
||||
<button
|
||||
class="back back2 rounded-sm"
|
||||
title="Back to selection"
|
||||
>
|
||||
<span>back</span> <ArrowSvg />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="alcontent">
|
||||
<div class="helptext" v-if="!selected">
|
||||
<div class="h2">Welcome back</div>
|
||||
</div>
|
||||
<div
|
||||
class="selected-user"
|
||||
v-if="selected"
|
||||
>
|
||||
<User
|
||||
:user="
|
||||
selected.username === ''
|
||||
? { id: 0, username: username, firstname: '' }
|
||||
: selected
|
||||
"
|
||||
:selected="true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="userlist"
|
||||
v-auto-animate
|
||||
v-else
|
||||
>
|
||||
<User
|
||||
v-for="user in shownUsers"
|
||||
@click="setUser(user)"
|
||||
:user="user"
|
||||
:key="user.id"
|
||||
/>
|
||||
</div>
|
||||
<form
|
||||
class="passinput"
|
||||
v-if="selected"
|
||||
v-auto-animate
|
||||
@submit.prevent="loginUser"
|
||||
>
|
||||
<!-- Only show username input if there's no user list -->
|
||||
<Input
|
||||
placeholder="Enter username"
|
||||
v-if="selected.username === ''"
|
||||
input-id="loginuserinput"
|
||||
@input="(input: string) => username = input"
|
||||
/>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Enter password"
|
||||
input-id="loginpassinput"
|
||||
@input="(input: string) => password = input"
|
||||
/>
|
||||
<!-- v-if="username.length && password.length" -->
|
||||
<button
|
||||
class="submit"
|
||||
:class="{ long: selected.username !== ''}"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div
|
||||
v-if="guestAllowed"
|
||||
class="guestlink"
|
||||
@click="() => guestLogin()"
|
||||
>
|
||||
<span>Or continue as guest </span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Ref, computed, nextTick, onMounted, ref } from 'vue'
|
||||
|
||||
import useAuth from '@/stores/auth'
|
||||
import { UserSimplified } from '@/interfaces'
|
||||
import { getAllUsers } from '@/requests/auth'
|
||||
|
||||
import Logo from '../Logo.vue'
|
||||
import User from '../shared/LoginUserCard.vue'
|
||||
import ArrowSvg from '../../assets/icons/expand.svg'
|
||||
import Input from '../shared/Input.vue'
|
||||
|
||||
const auth = useAuth()
|
||||
|
||||
const username = ref('')
|
||||
const password = ref('')
|
||||
|
||||
const users: Ref<UserSimplified[]> = ref([])
|
||||
const shownUsers = computed(() => users.value.filter((user) => user.username !== 'guest'))
|
||||
const selected = ref<UserSimplified | null>(null)
|
||||
|
||||
const guestAllowed = computed(() =>
|
||||
users.value.some((user) => user.username === 'guest')
|
||||
)
|
||||
|
||||
async function setUser(user: UserSimplified) {
|
||||
selected.value = user
|
||||
username.value = user.username
|
||||
|
||||
// if user has no username, focus on username input
|
||||
await nextTick()
|
||||
if (user.username === '') {
|
||||
document.getElementById('loginuserinput')?.focus()
|
||||
} else {
|
||||
document.getElementById('loginpassinput')?.focus()
|
||||
}
|
||||
|
||||
// await nextTick()
|
||||
// loginpass.value?.focus()
|
||||
}
|
||||
|
||||
function resetSelected() {
|
||||
selected.value = null
|
||||
}
|
||||
|
||||
async function loginUser() {
|
||||
if (!password.value) {
|
||||
return
|
||||
}
|
||||
|
||||
await auth.login(username.value, password.value)
|
||||
}
|
||||
|
||||
async function guestLogin(
|
||||
username: string = 'guest',
|
||||
password: string = 'guest'
|
||||
) {
|
||||
await auth.login(username, password)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
let res = await getAllUsers()
|
||||
|
||||
// if there are no users, or only the guest user, set the user to empty user
|
||||
if (
|
||||
res.users.length === 0 ||
|
||||
(res.users.length == 1 && res.users[0].username === 'guest')
|
||||
) {
|
||||
setUser({ id: 0, username: '', firstname: '' })
|
||||
}
|
||||
|
||||
// if there's only one user, and it's not the guest user, select them
|
||||
if (res.users.filter((user) => user.username !== 'guest').length === 1) {
|
||||
setTimeout(() => {
|
||||
setUser(res.users[0])
|
||||
}, 250)
|
||||
}
|
||||
|
||||
// remove guest user
|
||||
// res.users = res.users
|
||||
|
||||
// finally, set the users
|
||||
users.value = res.users
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.loginmodal {
|
||||
height: 35rem;
|
||||
display: grid;
|
||||
grid-template-rows: max-content 1fr max-content;
|
||||
|
||||
.alcontent {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.guestlink {
|
||||
padding: 1rem;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
color: $gray2;
|
||||
display: flex;
|
||||
text-decoration: underline;
|
||||
|
||||
& > * {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.helptext {
|
||||
padding: 0 $small;
|
||||
text-align: center;
|
||||
margin: 1.5rem 0;
|
||||
color: $white;
|
||||
|
||||
.h2 {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.head {
|
||||
text-align: center;
|
||||
border-bottom: solid 1px $gray5;
|
||||
padding: 1rem;
|
||||
user-select: none;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.back {
|
||||
background: none;
|
||||
transform: rotate(180deg);
|
||||
opacity: 0;
|
||||
|
||||
span {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.back2 {
|
||||
// NOTE: This element is used to center the Swing Music logo
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.head.selected .back {
|
||||
opacity: 1;
|
||||
transition: all 0.25s;
|
||||
}
|
||||
|
||||
.swing-logo {
|
||||
width: max-content;
|
||||
padding: $small 2rem;
|
||||
background: none;
|
||||
border: none;
|
||||
pointer-events: none;
|
||||
|
||||
svg {
|
||||
transform: scale(0.85);
|
||||
}
|
||||
}
|
||||
|
||||
.selected-user {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.userlist {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
form {
|
||||
width: 60%;
|
||||
margin: 0 auto;
|
||||
margin-top: 1rem;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
padding: 1rem;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
outline: none;
|
||||
background-color: $gray5;
|
||||
color: $gray1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.submit {
|
||||
width: 7rem;
|
||||
border-radius: 4rem;
|
||||
margin: 0 auto;
|
||||
height: 3rem;
|
||||
background-color: $darkblue;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.submit.long {
|
||||
width: 100%;
|
||||
border-radius: $small;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -19,15 +19,16 @@
|
||||
import { onMounted } from "vue";
|
||||
|
||||
import {
|
||||
createNewPlaylist,
|
||||
saveAlbumAsPlaylist,
|
||||
saveArtistAsPlaylist,
|
||||
saveFolderAsPlaylist,
|
||||
saveTrackAsPlaylist,
|
||||
} from "@/requests/playlists";
|
||||
import { createNewPlaylist, saveFolderAsPlaylist } from "@/requests/playlists";
|
||||
|
||||
import useTracklist from "@/stores/queue/tracklist";
|
||||
import usePlaylistStore from "@/stores/pages/playlists";
|
||||
import { NotifType, Notification } from "@/stores/notification";
|
||||
import usePlaylistStore from "@/stores/pages/playlists";
|
||||
import useTracklist from "@/stores/queue/tracklist";
|
||||
|
||||
const props = defineProps<{
|
||||
trackhash?: string;
|
||||
@@ -41,9 +42,7 @@ const props = defineProps<{
|
||||
const store = usePlaylistStore();
|
||||
|
||||
onMounted(() => {
|
||||
const input_elem = document.getElementById(
|
||||
"modal-playlist-name-input"
|
||||
) as HTMLInputElement;
|
||||
const input_elem = document.getElementById("modal-playlist-name-input") as HTMLInputElement;
|
||||
input_elem.focus();
|
||||
input_elem.value = props.playlist_name || "";
|
||||
});
|
||||
@@ -160,6 +159,7 @@ function create(e: Event) {
|
||||
|
||||
label {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: $gray1;
|
||||
}
|
||||
|
||||
@@ -173,7 +173,6 @@ function create(e: Event) {
|
||||
width: 8rem;
|
||||
padding: 1.25rem;
|
||||
// font-weight: normal;
|
||||
transition: all 0.25s ease-out;
|
||||
background-color: $white;
|
||||
color: $black;
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
<div class="root-dirs-prompt">
|
||||
<h3 class="t-center">Where do you want to look for music?</h3>
|
||||
<div class="options-group">
|
||||
<div
|
||||
v-for="option in options"
|
||||
:key="option.id"
|
||||
v-motion-slide-bottom
|
||||
class="option"
|
||||
@click="option.action()"
|
||||
>
|
||||
<div v-for="option in options" :key="option.id" v-motion-slide-bottom class="option" @click="option.action()">
|
||||
<b>{{ option.title }}</b>
|
||||
<div class="info">{{ option.info }}</div>
|
||||
</div>
|
||||
@@ -19,9 +13,9 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
import { addRootDirs, getRootDirs } from "@/requests/settings/rootdirs";
|
||||
import useModalStore from "@/stores/modal";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
import { addRootDirs, getRootDirs } from "@/requests/settings/rootdirs";
|
||||
|
||||
const settings = useSettingsStore();
|
||||
|
||||
@@ -64,8 +58,6 @@ onMounted(() => {
|
||||
|
||||
<style lang="scss">
|
||||
.root-dirs-prompt {
|
||||
height: 14rem;
|
||||
|
||||
.option {
|
||||
padding: 1.25rem;
|
||||
border-radius: $small;
|
||||
@@ -73,6 +65,7 @@ onMounted(() => {
|
||||
background-color: #4e4b4b3f;
|
||||
margin-top: 1.25rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease-out;
|
||||
|
||||
&:hover {
|
||||
background-color: $darkblue;
|
||||
@@ -81,6 +74,7 @@ onMounted(() => {
|
||||
.info {
|
||||
margin-top: $smaller;
|
||||
font-size: small;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
<br /><br />
|
||||
<div style="position: relative">
|
||||
<div id="bread-nav" class="bread-nav rounded-sm">
|
||||
<span @click="fetchDirs('$root')">$root</span
|
||||
> <BreadCrumbNav :sub-paths="subPaths" @navigate="fetchDirs" />
|
||||
<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">
|
||||
📂 No folders here. Use the "Select here" button to select this
|
||||
<span class="folder_icon">📂</span> No folders here. Click the "Add this folder" button below to select this
|
||||
location.
|
||||
</h4>
|
||||
<div class="scrollable">
|
||||
@@ -16,21 +18,15 @@
|
||||
v-for="dir in dirs"
|
||||
:key="dir.name"
|
||||
:folder="dir"
|
||||
:is_checked="
|
||||
selected.filter((p) => p == dir.path).length > 0 ? true : false
|
||||
"
|
||||
: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">
|
||||
Add this folder
|
||||
</button>
|
||||
<button class="btn-active finish" @click="submitFolders">
|
||||
Add all checked ({{ getNewDirs().length }})
|
||||
</button>
|
||||
<button class="btn-active select-here" @click="selectHere">Add this folder</button>
|
||||
<button class="btn-active finish" @click="submitFolders">Add all checked ({{ getNewDirs().length }})</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,11 +35,7 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, Ref, ref } from "vue";
|
||||
|
||||
import {
|
||||
addRootDirs,
|
||||
getFolders,
|
||||
getRootDirs,
|
||||
} from "@/requests/settings/rootdirs";
|
||||
import { addRootDirs, getFolders, getRootDirs } from "@/requests/settings/rootdirs";
|
||||
|
||||
import { Folder, subPath } from "@/interfaces";
|
||||
import useSettingsStore from "@/stores/settings";
|
||||
@@ -132,27 +124,34 @@ onMounted(() => {
|
||||
<style lang="scss">
|
||||
.bread-nav {
|
||||
background-color: $gray4;
|
||||
padding: $small;
|
||||
padding: $smaller;
|
||||
padding-right: 0;
|
||||
width: max-content;
|
||||
margin-bottom: 1rem;
|
||||
position: absolute;
|
||||
top: -3.25rem;
|
||||
max-width: calc(100% - 2rem);
|
||||
overflow-x: scroll;
|
||||
scrollbar-width: none;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.bread-nav {
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-text {
|
||||
position: absolute;
|
||||
font-size: small;
|
||||
@@ -169,6 +168,11 @@ onMounted(() => {
|
||||
grid-template-rows: 1fr max-content;
|
||||
gap: 1.25rem;
|
||||
|
||||
.folder_icon {
|
||||
vertical-align: 2px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
@@ -182,13 +186,15 @@ onMounted(() => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
overflow-y: scroll;
|
||||
overflow-y: auto;
|
||||
scrollbar-gutter: stable;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: $medium;
|
||||
margin-right: 1rem;
|
||||
margin-bottom: -$medium;
|
||||
@@ -199,17 +205,31 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
button {
|
||||
font-weight: normal;
|
||||
font-weight: 500;
|
||||
padding: 0 1rem;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
}
|
||||
|
||||
.f-item {
|
||||
background-color: $gray5;
|
||||
transition: background-color 0.2s ease-out;
|
||||
|
||||
&:hover {
|
||||
background-color: $gray3;
|
||||
}
|
||||
|
||||
> svg {
|
||||
transition: color 0.2s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
.f-item.selected {
|
||||
background-color: #234ece;
|
||||
}
|
||||
|
||||
.f-item.selected > svg {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
114
src/components/modals/Settings.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<div class="settingsmodal">
|
||||
<Sidebar
|
||||
:current-group="(currentGroup as SettingGroup)"
|
||||
@set-tab="(tab) => (currentTab = tab)"
|
||||
/>
|
||||
<div class="content">
|
||||
<div
|
||||
class="head"
|
||||
v-auto-animate
|
||||
>
|
||||
<div class="h2">
|
||||
{{ currentGroup?.title }}
|
||||
<span
|
||||
v-if="currentGroup?.experimental"
|
||||
class="badge experimental circular"
|
||||
>
|
||||
{{ currentGroup?.experimental ? 'experimental' : '' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Content :settings="(currentGroup as SettingGroup)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import settingGroups from '@/settings'
|
||||
|
||||
import Content from './settings/Content.vue'
|
||||
import Sidebar from './settings/Sidebar.vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { SettingGroup } from '@/interfaces/settings'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'setTitle', title: string): void
|
||||
}>()
|
||||
|
||||
const currentTab = ref<string>('')
|
||||
const currentGroup = computed(() => {
|
||||
for (const group of settingGroups) {
|
||||
for (const settings of group.groups) {
|
||||
if (settings.title === currentTab.value) {
|
||||
return settings
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const group of settingGroups) {
|
||||
for (const settings of group.groups) {
|
||||
if (settings.title === 'Appearance') {
|
||||
return settings
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$modalheight: 35rem;
|
||||
|
||||
.settingsmodal {
|
||||
display: grid;
|
||||
grid-template-columns: 15rem 1fr;
|
||||
height: $modalheight;
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
grid-template-rows: 4rem 1fr;
|
||||
height: $modalheight;
|
||||
|
||||
.head {
|
||||
border-bottom: solid 1px $gray4;
|
||||
padding: 0 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.h2 {
|
||||
margin: 0;
|
||||
font-size: 1.15rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.desc {
|
||||
opacity: 0.5;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Role badges used in Profile and Accounts tabs
|
||||
.roles {
|
||||
display: flex;
|
||||
gap: $small;
|
||||
|
||||
.role {
|
||||
// margin: $smaller $small 0 0;
|
||||
padding: 3px $smaller;
|
||||
border-radius: $smaller;
|
||||
border: solid 1px $brown;
|
||||
color: $brown;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $small;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||