Compare commits

..

44 Commits

Author SHA1 Message Date
cwilvx
ab0c566c92 remove welcome dialog 2024-05-11 21:53:07 +03:00
cwilvx
e0654b182a update image paths 2024-05-11 21:24:18 +03:00
cwilvx
06de7a26e1 Merge branch 'master' into introducing-auth 2024-05-11 21:09:40 +03:00
Mungai Njoroge
176b38ffa5 merge pr #30 from @Simonh2o
Responsive UI rework, some additional features and bugfixes
2024-05-09 13:11:42 -04:00
Simonh2o
12e7db7310 changed playlist btn & toast height 2024-05-05 23:41:09 +02:00
mungai-njoroge
32ac77c426 fix a few broken svgs
probably broke even more
2024-05-05 23:56:18 +03:00
Simonh2o
bce03af941 didn't show 2 cards on mobile, and missing 1 card on some random resolutions, fix 2024-05-04 21:58:03 +02:00
Simonh2o
f6921fedc9 Fixed cards overflowing or showing too few. New playing animation for tracks, misc scrollbar view changes. 2024-05-04 21:46:30 +02:00
mungai-njoroge
3477397072 implement deleting users 2024-05-01 23:41:59 +03:00
mungai-njoroge
0bb7bf56d2 fix: 401 on web workers
+ rewrite the about component
+ fix: broken svg on folder item
+ show welcome back text
+ hide guest on accounts user list
+ remove settings link from nav profile icon
+ fix: broken svg on breadcrumb
+ fix: delete playlist icon showing on recently added playlist
+ fix: showing about in settings dialog
+ fix: broken svg on root dirs list
+ add is_admin getter to auth store
+ fix: playing from showing full path on now playing page
+ remove unused search folders component
2024-05-01 23:21:16 +03:00
Simonh2o
d2b0e28751 Improved responsive layout, added clear search btn, new mobile sidenav, various bugfixes 2024-05-01 21:53:05 +02:00
mungai-njoroge
2d0146cfc3 handle username input on auth dialog 2024-04-29 16:27:55 +03:00
Stan
cfcfd4d7d7 Merge pull request #1 from Simonh2o/dev
Dev
2024-04-29 01:03:27 +02:00
Stannnnn
92c6e76ad5 Removed conditional ref based on CSS grid to simplify responsiveness 2024-04-29 00:58:53 +02:00
Simonh2o
5a4efb41bc width alt layout bug 2024-04-28 19:16:43 +02:00
mungai-njoroge
211bab805a fix: updating local user on self profile update 2024-04-27 21:13:17 +03:00
mungai-njoroge
b7583ac889 fuck i'm sooo tired!
- hide settings depending on admin status
-  a looot!
2024-04-27 20:51:26 +03:00
mungai-njoroge
a63717961b implement adding new users 2024-04-26 21:31:57 +03:00
mungai-njoroge
9a7596a6ba more account settings stuff 2024-04-26 19:45:56 +03:00
mungai-njoroge
62c65873f5 add accounts settings component 2024-04-25 23:53:00 +03:00
mungai-njoroge
821b0029dd finalize Profile update 2024-04-25 19:59:27 +03:00
Mungai Njoroge
336a9755b8 Merge PR #29 from @Simonh2o
Firefox breadcrumb overflow scroll fix, some responsive improvements …
2024-04-24 00:46:36 +03:00
Simonh2o
f9a522240e Firefox breadcrumb overflow scroll fix, some responsive improvements (hopefully) and other UI alignment fixes 2024-04-23 23:38:37 +02:00
Mungai Njoroge
94af90e481 Merge PR #28 from Simonh2o
Reverted back to SF Compact Display with proper fonts, other changes …
2024-04-23 22:21:20 +03:00
mungai-njoroge
85dfb5700e move auth requests to auth store
+ disable dismissing the login dialog
+ cleanup
2024-04-21 12:13:01 +03:00
mungai-njoroge
45010fe4d1 auth draft v1 2024-04-21 11:23:10 +03:00
Simonh2o
3948deb165 Reverted back to SF Compact Display with proper fonts, other changes related to fonts and UI 2024-04-20 20:23:17 +02:00
Mungai Njoroge
e5f73d8495 Merge pr #27 from Simonh2o
UI & Font revamp, minor tweaks to take use of the new fonts
2024-04-20 08:12:04 +03:00
Simonh2o
18afef151c Revamped and improved more of the changes from previous commit, small changes 2024-04-19 23:47:56 +02:00
Simonh2o
80dbd98beb Swapped out old version of font with brand new SF Pro Display, added new Mono font. Lots of UI changes to work with new font. Refined root-dirs window 2024-04-19 06:01:37 +02:00
Mungai Njoroge
a12d1a86e5 Merge PR #26 from Simonh2o
Fixed spacing/transitions for search/results, reduced top spacing
2024-04-18 00:12:25 +03:00
Simonh2o
449e7b4bb7 Fixed spacing/transitions for search/results, reduced top spacing 2024-04-17 18:22:41 +02:00
Mungai Njoroge
5ee0b81ed9 Merge PR #25 from Simonh2o
Improved overall UI, fixing old/new animations, new volume slider and scrollbar
2024-04-17 13:01:41 +03:00
Simonh2o
3496fde28c Added slight animations to just about every page, context menus, buttons etc, fixed sorting buttons growing onclick, improved UI spacing for songs and play area. Introduced a new volume slider and scrollbar. 2024-04-16 19:55:54 +02:00
mungai-njoroge
58b2a02989 fix adding root dirs 2024-04-15 07:28:42 +03:00
mungai-njoroge
eb69d78a38 use large thumbnails on now playing 2024-04-13 20:28:45 +03:00
mungai-njoroge
b7a9a35c4f remove vue-progressive-image css 2024-04-13 19:47:23 +03:00
mungai-njoroge
8de2a701ea remove vue-progressive-image 2024-04-07 19:27:53 +03:00
mungai-njoroge
681ccaba8c fix folder card count 2024-04-07 19:21:12 +03:00
mungai-njoroge
6d3f64ffc7 add card content loader 2024-04-07 18:35:05 +03:00
mungai-njoroge
aa780977f1 update routes 2024-03-25 01:35:33 +03:00
mungai-njoroge
4cb77334fb updates from server refactors so far 2024-03-10 19:57:57 +03:00
mungai-njoroge
293e95c5b0 disable wide artist header by default 2024-02-25 23:53:28 +03:00
Mungai Njoroge
302095ef80 v1.4.8 - No Sidebar Layout 2024-02-16 22:05:05 +03:00
194 changed files with 6617 additions and 4017 deletions

2
.gitignore vendored
View File

@@ -24,7 +24,7 @@ pnpm-debug.log*
*.sw? *.sw?
# TODO # TODO
TODO.md # TODO.md
__pycache__ __pycache__
dev-dist/* dev-dist/*

2
TODO.md Normal file
View File

@@ -0,0 +1,2 @@
- Remove welcome dialog
- Update folder page breadcrumb when response has skipped empty folders

View File

@@ -3,15 +3,15 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <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> <title>Swing Music</title>
<base href="./"> <base href="./" />
</head> </head>
<body> <body>
<noscript> <noscript>
<strong <strong
>We're sorry but this app doesn't work properly without JavaScript >We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to
enabled. Please enable it to continue.</strong continue.</strong
> >
</noscript> </noscript>
<div id="app"></div> <div id="app"></div>

View File

@@ -24,6 +24,7 @@
"pinia-plugin-persistedstate": "^3.2.0", "pinia-plugin-persistedstate": "^3.2.0",
"v-wave": "^1.5.0", "v-wave": "^1.5.0",
"vue": "^v3.2.45", "vue": "^v3.2.45",
"vue-boring-avatars": "^1.4.0",
"vue-debounce": "^3.0.2", "vue-debounce": "^3.0.2",
"vue-router": "^4.1.3", "vue-router": "^4.1.3",
"vue-template-compiler": "^2.0.0", "vue-template-compiler": "^2.0.0",

View File

@@ -11,5 +11,6 @@ onmessage = (e) => {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ trackhash, duration, source, timestamp }), body: JSON.stringify({ trackhash, duration, source, timestamp }),
credentials: "include"
}); });
}; };

View File

@@ -10,7 +10,8 @@ onmessage = async (e) => {
headers: { headers: {
"Content-Type": "application/json", "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(); const data = await res.json();

View File

@@ -1,177 +1,199 @@
<template> <template>
<ContextMenu /> <ContextMenu />
<Modal /> <Modal />
<Notification /> <Notification />
<div id="drag-img" class="ellip2" style=""></div> <div
<section id="drag-img"
id="app-grid" class="ellip2"
:class="{ style=""
useSidebar: settings.use_sidebar && xl, ></div>
NoSideBorders: settings.is_alt_layout || !xxl, <section
extendWidth: settings.extend_width && settings.can_extend_width, id="app-grid"
is_alt_layout: settings.is_alt_layout, :class="{
}" useSidebar: settings.use_sidebar && xl,
:style="{ NoSideBorders: settings.is_alt_layout || !xxl,
maxWidth: `${ extendWidth: settings.extend_width && settings.can_extend_width,
settings.is_default_layout is_alt_layout: settings.is_alt_layout,
? content_height > 1080 }"
? '2220px' :style="{
: '1760px' 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> <LeftSidebar v-if="settings.is_default_layout && !isMobile" />
<RouterView /> <NavBar />
</BalancerProvider> <div
</div> id="acontent"
<RightSideBar v-if="settings.use_sidebar && xl" /> v-element-size="updateContentElemSize"
<BottomBar /> >
<!-- <BubbleManager /> --> <div
</section> id="contentresizer"
ref="appcontent"
></div>
<BalancerProvider>
<RouterView />
</BalancerProvider>
</div>
<RightSideBar v-if="settings.use_sidebar && xl" />
<BottomBar />
<!-- <BubbleManager /> -->
</section>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// @libraries // @libraries
import { vElementSize } from "@vueuse/components"; import { vElementSize } from '@vueuse/components'
import { onStartTyping } from "@vueuse/core"; import { onStartTyping } from '@vueuse/core'
import { onMounted, Ref, ref } from "vue"; import { onMounted, Ref, ref } from 'vue'
import { useRouter } from "vue-router"; import { useRouter } from 'vue-router'
import { BalancerProvider } from "vue-wrap-balancer"; import { BalancerProvider } from 'vue-wrap-balancer'
// @stores // @stores
import { import {
content_height, content_height,
content_width, content_width,
isMobile, isMobile,
resizer_height,
resizer_width,
updateCardWidth, updateCardWidth,
} from "@/stores/content-width"; } from '@/stores/content-width'
import useLyrics from "@/stores/lyrics"; import useLyrics from '@/stores/lyrics'
import useModal from "@/stores/modal"; import useModal from '@/stores/modal'
import useQueue from "@/stores/queue"; import useQueue from '@/stores/queue'
import useSettings from "@/stores/settings"; import useSettings from '@/stores/settings'
import useTracker from "@/stores/tracker"; import useTracker from '@/stores/tracker'
import useAuth from '@/stores/auth'
// @utils // @utils
import handleShortcuts from "@/helpers/useKeyboard"; import handleShortcuts from '@/helpers/useKeyboard'
import { readLocalStorage, writeLocalStorage } from "@/utils"; import { readLocalStorage, writeLocalStorage } from '@/utils'
import { xl, xxl } from "./composables/useBreakpoints"; import { xl, xxl } from './composables/useBreakpoints'
// @small-components // @small-components
import ContextMenu from "@/components/ContextMenu.vue"; import ContextMenu from '@/components/ContextMenu.vue'
import Modal from "@/components/modal.vue"; import Modal from '@/components/modal.vue'
import Notification from "@/components/Notification.vue"; import Notification from '@/components/Notification.vue'
// @app-grid-components // @app-grid-components
import BottomBar from "@/components/BottomBar/BottomBar.vue"; import BottomBar from '@/components/BottomBar/BottomBar.vue'
import LeftSidebar from "@/components/LeftSidebar/index.vue"; import LeftSidebar from '@/components/LeftSidebar/index.vue'
import NavBar from "@/components/nav/NavBar.vue"; import NavBar from '@/components/nav/NavBar.vue'
import RightSideBar from "@/components/RightSideBar/Main.vue"; import RightSideBar from '@/components/RightSideBar/Main.vue'
import { getAllSettings } from "@/requests/settings";
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"; // import BubbleManager from "./components/bubbles/BinManager.vue";
const appcontent: Ref<HTMLLegendElement | null> = ref(null); const appcontent: Ref<HTMLLegendElement | null> = ref(null)
const queue = useQueue(); const auth = useAuth()
const modal = useModal(); const resizercontent: Ref<HTMLLegendElement | null> = ref(null);
const lyrics = useLyrics(); const queue = useQueue()
const router = useRouter(); const modal = useModal()
const settings = useSettings(); const lyrics = useLyrics()
useTracker(); const router = useRouter()
const settings = useSettings()
useTracker()
handleShortcuts(useQueue, useModal); handleShortcuts(useQueue, useModal)
router.afterEach(() => { router.afterEach(() => {
(document.getElementById("acontent") as HTMLElement).scrollTo(0, 0); ;(document.getElementById('acontent') as HTMLElement).scrollTo(0, 0)
}); })
onStartTyping(() => { onStartTyping(() => {
const elem = document.getElementById("globalsearch") as HTMLInputElement; const elem = document.getElementById('globalsearch') as HTMLInputElement
elem.focus(); elem.focus()
elem.value = ""; elem.value = ''
}); })
function getContentSize() { function getContentSize() {
const elem = document.getElementById("acontent") as HTMLElement; const elem = document.getElementById('acontent') as HTMLElement
return { return {
width: elem.offsetWidth, width: elem.offsetWidth,
height: elem.offsetHeight, height: elem.offsetHeight,
}; }
} }
function updateContentElemSize({ function updateContentElemSize({ width, height }: { width: number; height: number }) {
width,
height,
}: {
width: number;
height: number;
}) {
// 1572 is the maxwidth of the #acontent. see app-grid.scss > $maxwidth // 1572 is the maxwidth of the #acontent. see app-grid.scss > $maxwidth
const elem_width = appcontent.value?.offsetWidth || 0; const elem_width = appcontent.value?.offsetWidth || 0;
content_width.value = elem_width; content_width.value = elem_width;
content_height.value = height; content_height.value = height;
const elem_resizer_width = resizercontent.value?.offsetWidth || 0;
resizer_width.value = elem_resizer_width;
resizer_height.value = height;
updateCardWidth(); updateCardWidth();
} }
function handleWelcomeModal() { function handleWelcomeModal() {
let welcomeShowCount = readLocalStorage("shown-welcome-message"); let welcomeShowCount = readLocalStorage('shown-welcome-message')
if (!welcomeShowCount) { if (!welcomeShowCount) {
welcomeShowCount = 0; welcomeShowCount = 0
} }
if (welcomeShowCount < 2) { if (welcomeShowCount < 2) {
modal.showWelcomeModal(); modal.showWelcomeModal()
writeLocalStorage("shown-welcome-message", welcomeShowCount + 1); writeLocalStorage('shown-welcome-message', welcomeShowCount + 1)
} }
} }
function handleRootDirsPrompt() { function handleRootDirsPrompt() {
getRootDirs().then((dirs) => { getRootDirs().then((dirs) => {
if (dirs.length === 0) { if (dirs.length === 0) {
modal.showRootDirsPromptModal(); modal.showRootDirsPromptModal()
} else { } else {
settings.setRootDirs(dirs); settings.setRootDirs(dirs)
} }
}); })
} }
onMounted(() => { onMounted(async () => {
const { width, height } = getContentSize(); const { width, height } = getContentSize()
updateContentElemSize({ width, height }); updateContentElemSize({ width, height })
handleWelcomeModal(); const res = await getLoggedInUser()
settings.initializeVolume();
handleRootDirsPrompt(); if (res.status == 200) {
auth.setUser(res.data)
} else {
return
}
getAllSettings() handleWelcomeModal()
.then(({ settings: data }) => { settings.initializeVolume()
settings.mapDbSettings(data);
}) handleRootDirsPrompt()
.then(() => {
if (queue.currenttrack && !settings.use_lyrics_plugin) { getAllSettings()
lyrics.checkExists( .then(({ settings: data }) => {
queue.currenttrack.filepath, settings.mapDbSettings(data)
queue.currenttrack.trackhash })
); .then(() => {
} if (queue.currenttrack && !settings.use_lyrics_plugin) {
}); lyrics.checkExists(
}); queue.currenttrack.filepath,
queue.currenttrack.trackhash
)
}
})
})
</script> </script>
<style lang="scss"> <style lang="scss">
@import "./assets/scss/mixins.scss"; @import './assets/scss/mixins.scss';
.designatedOS .r-sidebar {
.r-sidebar { &::-webkit-scrollbar {
&::-webkit-scrollbar { display: none;
display: none; }
}
} }
</style> </style>

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

Binary file not shown.

View File

@@ -1,3 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"> <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="#fff"/> <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> </svg>

Before

Width:  |  Height:  |  Size: 737 B

After

Width:  |  Height:  |  Size: 722 B

View File

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

Before

Width:  |  Height:  |  Size: 936 B

After

Width:  |  Height:  |  Size: 913 B

View File

@@ -1,3 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"> <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"/> <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> </svg>

Before

Width:  |  Height:  |  Size: 542 B

After

Width:  |  Height:  |  Size: 519 B

View File

@@ -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 <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" /> 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> </svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

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

View 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

View File

@@ -1,3 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"> <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="#F2F2F2"/> <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> </svg>

Before

Width:  |  Height:  |  Size: 538 B

After

Width:  |  Height:  |  Size: 520 B

View File

@@ -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"/> <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> </svg>

Before

Width:  |  Height:  |  Size: 898 B

After

Width:  |  Height:  |  Size: 875 B

View File

@@ -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" /> <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> </svg>

Before

Width:  |  Height:  |  Size: 1016 B

After

Width:  |  Height:  |  Size: 993 B

View File

@@ -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="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"/> <path d="M13.5938 9.84375C13.5938 10.2167 13.7419 10.5744 14.0056 10.8381C14.2694 11.1018 14.627 11.25 15 11.25C15.373 11.25 15.7306 11.1018 15.9944 10.8381C16.2581 10.5744 16.4062 10.2167 16.4062 9.84375C16.4062 9.47079 16.2581 9.1131 15.9944 8.84938C15.7306 8.58566 15.373 8.4375 15 8.4375C14.627 8.4375 14.2694 8.58566 14.0056 8.84938C13.7419 9.1131 13.5938 9.47079 13.5938 9.84375V9.84375ZM15.7031 13.125H14.2969C14.168 13.125 14.0625 13.2305 14.0625 13.3594V21.3281C14.0625 21.457 14.168 21.5625 14.2969 21.5625H15.7031C15.832 21.5625 15.9375 21.457 15.9375 21.3281V13.3594C15.9375 13.2305 15.832 13.125 15.7031 13.125Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -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.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"/> <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> </svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

3
src/assets/icons/mic.svg Normal file
View 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

View 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

View File

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

View File

@@ -1,3 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"> <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"/> <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> </svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,3 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"> <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="#F2F2F2"/> <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> </svg>

Before

Width:  |  Height:  |  Size: 574 B

After

Width:  |  Height:  |  Size: 556 B

View File

@@ -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="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="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"/> <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

View File

@@ -6,7 +6,7 @@
height: 100%; height: 100%;
padding-right: $small; padding-right: $small;
@include largePhones { @include allPhones {
grid-template-columns: 1fr 9.2rem; grid-template-columns: 1fr 9.2rem;
} }
@@ -42,11 +42,5 @@
align-items: center; align-items: center;
gap: $small; gap: $small;
} }
.volume-group {
@include tablet-portrait {
display: none;
}
}
} }
} }

View File

@@ -6,6 +6,7 @@
grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
padding: 0 1rem; padding: 0 1rem;
overflow: auto; overflow: auto;
-webkit-overflow-scrolling: touch;
max-height: 100%; max-height: 100%;
gap: 2rem 1rem; gap: 2rem 1rem;
} }

View File

@@ -4,7 +4,7 @@ $g-border: solid 1px $gray5;
display: grid; display: grid;
// grid-template-columns: min-content 1fr 29rem; // grid-template-columns: min-content 1fr 29rem;
grid-template-columns: min-content 1fr; grid-template-columns: min-content 1fr;
grid-template-rows: $navheight 1fr $navheight; grid-template-rows: $navheight 1fr 5rem;
grid-template-areas: grid-template-areas:
"l-sidebar nav" "l-sidebar nav"
"l-sidebar content" "l-sidebar content"
@@ -14,6 +14,7 @@ $g-border: solid 1px $gray5;
border-top: none; border-top: none;
border-bottom: none; border-bottom: none;
margin: 0 auto; margin: 0 auto;
position: relative;
#contentresizer { #contentresizer {
margin: 0 $padright 0 $padleft; margin: 0 $padright 0 $padleft;
@@ -21,12 +22,11 @@ $g-border: solid 1px $gray5;
@include allPhones { @include allPhones {
grid-template-columns: 1fr; grid-template-columns: 1fr;
// grid-template-columns: 1fr;
grid-template-rows: max-content 1fr 9.5rem;
grid-template-areas: grid-template-areas:
"nav" "nav"
"content" "content"
"bottombar"; "bottombar";
grid-template-rows: auto 1fr auto;
} }
} }
@@ -42,6 +42,16 @@ $g-border: solid 1px $gray5;
height: $navheight; height: $navheight;
padding: 1rem $padleft; padding: 1rem $padleft;
padding-right: $padright; 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 { .b-bar {
@@ -54,6 +64,12 @@ $g-border: solid 1px $gray5;
padding-left: $padleft; padding-left: $padleft;
padding-right: $padright; padding-right: $padright;
padding-bottom: $padbottom; padding-bottom: $padbottom;
-webkit-overflow-scrolling: touch;
@include allPhones {
padding-left: 1rem;
padding-right: 1rem;
}
} }
.vue-recycle-scroller__item-wrapper { .vue-recycle-scroller__item-wrapper {
@@ -84,6 +100,15 @@ $g-border: solid 1px $gray5;
"content" "content"
"bottombar"; "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, .vue-recycle-scroller,
.content-page, .content-page,
.topnav, .topnav,
@@ -128,11 +153,7 @@ $g-border: solid 1px $gray5;
padding-right: 2rem; padding-right: 2rem;
} }
.nolyrics { @media only screen and (min-width: 1980px) {
margin-top: 0;
}
@media (min-width: 1980px) {
// NOTE: Styles for 1680px and below // NOTE: Styles for 1680px and below
$alt_layout_pad: max(2rem, calc((100% - 1680px) / 2)); $alt_layout_pad: max(2rem, calc((100% - 1680px) / 2));
@@ -195,12 +216,27 @@ $g-border: solid 1px $gray5;
width: 100%; width: 100%;
padding-bottom: $content-padding-bottom; padding-bottom: $content-padding-bottom;
padding-bottom: $padbottom; padding-bottom: $padbottom;
-webkit-overflow-scrolling: touch;
@include allPhones {
padding-left: 1rem;
padding-right: 1rem;
}
} }
} }
.song-title > .isSmallArtists {
display: none;
}
.isSmall { .isSmall {
.songlist-item { .songlist-item {
grid-template-columns: 2fr 5.5rem !important; grid-template-columns: 2fr 5.5rem !important;
@include mediumPhones {
grid-template-columns: 2fr 2.5rem !important;
gap: $small !important;
}
} }
.song-artists, .song-artists,
@@ -209,7 +245,9 @@ $g-border: solid 1px $gray5;
} }
.isSmallArtists { .isSmallArtists {
display: unset !important; display: flex !important;
align-items: center;
gap: 4px;
font-size: small; font-size: small;
color: $white; color: $white;
opacity: 0.67; opacity: 0.67;

View File

@@ -29,7 +29,7 @@ input[type="search"]::-webkit-search-cancel-button {
.heading { .heading {
font-size: 1.5rem; font-size: 1.5rem;
font-weight: bold; font-weight: 700;
} }
a { a {
@@ -72,18 +72,22 @@ a {
// BUTTONS // BUTTONS
button { 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-size: 0.9rem !important;
font-weight: 700;
line-height: 1.2;
color: inherit; color: inherit;
border-radius: $small; border-radius: $small;
border: none;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 2.25rem; height: 2.25rem;
padding: 0 $small; padding: 0 $small;
transition: background-color 0.2s ease-out, color 0.2s ease-out, border 0.2s ease-out;
background-color: $gray4; background-color: $gray4;
font-weight: 700;
cursor: pointer; cursor: pointer;
svg { svg {
@@ -222,7 +226,7 @@ button {
text-transform: uppercase; text-transform: uppercase;
font-size: 11px; font-size: 11px;
color: $purple; color: $purple;
font-weight: bold; font-weight: 700;
margin: $smaller 0; margin: $smaller 0;
&.album { &.album {
@@ -240,4 +244,23 @@ button {
&.playlist { &.playlist {
color: $green; 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;
} }

View File

@@ -1,6 +1,5 @@
@import "./app-grid.scss", "./controls.scss", "./inputs.scss", @import "./app-grid.scss", "./controls.scss", "./inputs.scss", "./scrollbars.scss", "./state.scss", "./variants.scss",
"./scrollbars.scss", "./state.scss", "./variants.scss", "./basic.scss", "./basic.scss", "./search-tabheaders.scss", "./album-grid.scss";
"./search-tabheaders.scss", "./album-grid.scss";
* { * {
box-sizing: border-box; box-sizing: border-box;
@@ -13,6 +12,8 @@
html { html {
cursor: default !important; cursor: default !important;
overflow: hidden; overflow: hidden;
color: $white;
background-color: $body;
& > * { & > * {
overflow: visible !important; overflow: visible !important;
@@ -28,20 +29,60 @@ html.loading * {
} }
body { body {
background-color: $body; font-family: "SF Compact Display", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif,
color: $white; "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-family: "SFCompactDisplay", -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
font-size: 1rem; font-size: 1rem;
font-weight: 400;
color: $white;
image-rendering: -webkit-optimize-contrast; 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; height: 100vh;
width: 100vw; width: 100vw;
overflow: hidden; overflow: hidden;
margin: 0; margin: 0;
background-color: $body;
color-scheme: dark;
#app { #app {
width: 100%; width: 100%;
height: 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;
}
}

View File

@@ -5,5 +5,7 @@ input[type="number"] {
} }
input[type="search"] { 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; height: 2.25rem !important;
} }

View File

@@ -1,23 +1,58 @@
::-webkit-scrollbar { /* Total width */
width: 3px; .designatedOS ::-webkit-scrollbar {
background-color: $body;
width: 14px;
} }
/* Track */ /* Background of the scrollbar except button or resizer */
.designatedOS ::-webkit-scrollbar-track {
::-webkit-scrollbar-track { background-color: $body;
background: transparent;
border-radius: 1rem;
} }
/* Handle */ /* Scrollbar itself */
.designatedOS ::-webkit-scrollbar-thumb {
::-webkit-scrollbar-thumb { background-color: $gray2;
background: $gray1; border-radius: 16px;
border-radius: 1rem; border: 4px solid $body;
} }
/* Handle on hover */ .designatedOS ::-webkit-scrollbar-thumb:hover {
background-color: $gray1;
::-webkit-scrollbar-thumb:hover { }
background: $blue;
/* 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;
} }

View File

@@ -9,8 +9,10 @@
background-color: $gray5; background-color: $gray5;
border: none; border: none;
transition: all 0.3s ease; transition: background-color 0.3s ease, color 0.3s ease;
padding: 0 $medium; padding: 0 $medium;
line-height: 1.2;
font-weight: 700;
} }
.activetab { .activetab {

View File

@@ -1,16 +1,61 @@
.now-playing-track-indicator { .now-playing-track-indicator {
background-image: url(../../assets/icons/playing.gif); opacity: 0;
height: 2rem; visibility: hidden;
display: flex;
align-items: center;
justify-content: center;
width: 2rem; width: 2rem;
height: 2rem;
border-radius: 50%; border-radius: 50%;
background-color: $white; 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 { .now-playing-track-indicator.last_played {
background-image: url(../../assets/icons/playing.webp); #wave {
transition: all 0.3s ease-in-out; @for $i from 1 through 9 {
#Line_#{$i} {
animation-play-state: paused;
}
}
}
} }
.hidden { .hidden {

View File

@@ -5,51 +5,47 @@
} }
// media query mixins // media query mixins
@mixin smallPhone { @mixin smallestPhones {
@media (max-width: 550px) { @media only screen and (max-width: 320px) {
@content; @content;
} }
} }
@mixin iphoneSE { @mixin smallerPhones {
@media (max-width: 386px) { @media only screen and (max-width: 360px) {
@content; @content;
} }
} }
@mixin allPhones { @mixin smallPhones {
@media (max-width: 900px) { @media only screen and (max-width: 420px) {
@content; @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 { @mixin largePhones {
// 550px <= width < 900px @media only screen and (max-width: 660px) {
@media (min-width: 550px) {
@content; @content;
} }
} }
@mixin tablet-landscape { /* This query is "synced" with content-width.ts and index.ts values */
@media (max-width: 1080px) { @mixin allPhones {
@content; @media only screen and (max-width: 900px) {
}
}
@mixin tablet-portrait {
@media (max-width: 810px) {
@content; @content;
} }
} }
@mixin for-desktop-down { @mixin for-desktop-down {
@media (max-width: 1600px) { @media only screen and (max-width: 1600px) {
@content;
}
}
@mixin for-desktop-up {
@media (min-width: 1800px) {
@content; @content;
} }
} }

View File

@@ -8,6 +8,7 @@ $theme: #464545fd;
// sizes // sizes
$small: 0.5rem; $small: 0.5rem;
$smaller: 0.25rem; $smaller: 0.25rem;
$smallest: 0.15rem;
$medium: 0.75rem; $medium: 0.75rem;
$large: 1.5rem; $large: 1.5rem;
$larger: 2rem; $larger: 2rem;
@@ -59,11 +60,10 @@ $separator: $gray4;
$margright: 0; $margright: 0;
$padbottom: 4rem; $padbottom: 4rem;
$maxwidth: 1438px; $maxwidth: 1438px;
$navheight: 5rem; $navheight: 4.75rem;
$cardwidth: 10.75rem; $cardwidth: 10.75rem;
$maxpadleft: 5rem; $maxpadleft: 5rem;
$alt_layout_pad: max(2rem, calc((100% - 1280px) / 2)); $alt_layout_pad: max(2rem, calc((100% - 1280px) / 2));
$maxpadright: calc(100% - $maxwidth); $maxpadright: calc(100% - $maxwidth);

View File

@@ -1,15 +1,43 @@
@import @import "./mixins.scss", "./variables", "./ProgressBar.scss", "./BottomBar/BottomBar.scss", "./Global";
"./mixins.scss",
"./variables",
"./ProgressBar.scss",
"./BottomBar/BottomBar.scss",
"./Global"
;
/* San Francisco Compact Display font */
@font-face { @font-face {
font-family: "SFCompactDisplay"; font-family: "SF Compact Display";
src: url("../sf-compact.woff") format("woff"); font-style: normal;
font-display: swap; 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");
}

View File

Binary file not shown.

View File

@@ -1,8 +1,5 @@
<template> <template>
<div <div v-if="album_disc.is_album_disc_number" class="album_disc_header no-select">
v-if="album_disc.is_album_disc_number"
class="album_disc_header no-select"
>
<div class="disc_number">Disc {{ album_disc.album_page_disc_number }}</div> <div class="disc_number">Disc {{ album_disc.album_page_disc_number }}</div>
<div></div> <div></div>
</div> </div>
@@ -22,11 +19,17 @@ defineProps<{
grid-template-columns: 1fr max-content; grid-template-columns: 1fr max-content;
align-items: center; align-items: center;
padding-left: 1rem; padding-left: 1rem;
margin-top: $small;
height: $song-item-height; height: $song-item-height;
.disc_number { .disc_number {
font-size: $medium; font-size: $medium;
font-weight: 500;
opacity: 0.75; opacity: 0.75;
} }
@include largePhones {
padding-left: 0.5rem !important;
}
} }
</style> </style>

View File

@@ -24,9 +24,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted } from "vue";
import useAlbumStore from "@/stores/pages/album"; import useAlbumStore from "@/stores/pages/album";
import useArtistStore from "@/stores/pages/artist"; import useArtistStore from "@/stores/pages/artist";
import { computed, onMounted } from "vue";
import { getShift } from "@/utils/colortools/shift"; import { getShift } from "@/utils/colortools/shift";
@@ -74,6 +74,7 @@ onMounted(hookAction);
gap: 1rem; gap: 1rem;
padding-right: $medium; padding-right: $medium;
overflow-x: hidden; overflow-x: hidden;
-webkit-overflow-scrolling: touch;
} }
.genre-pill { .genre-pill {
@@ -82,6 +83,7 @@ onMounted(hookAction);
text-align: center; text-align: center;
padding: $small 1rem; padding: $small 1rem;
font-weight: 700; font-weight: 700;
transition: background-color 0.2s ease-out, color 0.2s ease-out;
&:first-child { &:first-child {
background-color: white; background-color: white;

View File

@@ -19,8 +19,8 @@
import { computed } from "vue"; import { computed } from "vue";
import { Album } from "@/interfaces"; import { Album } from "@/interfaces";
import { formatSeconds } from "@/utils";
import { isSmallPhone } from "@/stores/content-width"; import { isSmallPhone } from "@/stores/content-width";
import { formatSeconds } from "@/utils";
import ArtistName from "@/components/shared/ArtistName.vue"; 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 // hide track count if it's a single, also add an s to track if it's plural
return `${props.album.date} ${ return `${props.album.date} ${
!is_single !is_single ? `${props.album.count.toLocaleString()} Track${props.album.count > 1 ? "s" : ""}` : ""
? `${props.album.count.toLocaleString()} Track${
props.album.count > 1 ? "s" : ""
}`
: ""
}${formatSeconds(props.album.duration, true)}`; }${formatSeconds(props.album.duration, true)}`;
}); });
</script> </script>
<style lang="scss"> <style lang="scss">
.album-stats { .album-stats {
font-weight: bold; font-weight: 700;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
font-size: 14px; font-size: 14px;
padding-left: $smaller; padding-left: $smaller;

View File

@@ -13,10 +13,7 @@
background: colors.bg ? colors.bg : '', background: colors.bg ? colors.bg : '',
}" }"
> >
<div <div class="big-img no-scroll" :class="`${isHeaderSmall ? 'imgSmall' : ''} shadow-lg rounded-sm`">
class="big-img no-scroll"
:class="`${isHeaderSmall ? 'imgSmall' : ''} shadow-lg rounded-sm`"
>
<img :src="imguri.thumb.large + album.image" class="rounded-sm" /> <img :src="imguri.thumb.large + album.image" class="rounded-sm" />
</div> </div>
<Info /> <Info />
@@ -24,8 +21,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { ref } from "vue";
import { paths } from "@/config"; import { paths } from "@/config";
import { isHeaderSmall } from "@/stores/content-width"; import { isHeaderSmall } from "@/stores/content-width";
@@ -111,7 +108,7 @@ useVisibility(albumheaderthing, handleVisibilityState);
} }
} }
@include smallPhone { @include largePhones {
grid-template-columns: 1fr; grid-template-columns: 1fr;
padding: 2rem 1rem; padding: 2rem 1rem;
height: 25rem; height: 25rem;

View File

@@ -5,11 +5,7 @@
:class="{ isSmallPhone }" :class="{ isSmallPhone }"
style="height: 100%; width: 100%" style="height: 100%; width: 100%"
:style="{ :style="{
boxShadow: !useCircularImage boxShadow: !useCircularImage ? (colors.bg.length ? `0 .5rem 2rem ${colors.bg}` : undefined) : undefined,
? colors.bg.length
? `0 .5rem 2rem ${colors.bg}`
: undefined
: undefined,
}" }"
></div> ></div>
<div <div
@@ -27,11 +23,7 @@
height: containerHeight, height: containerHeight,
}" }"
> >
<img <img id="artist-avatar" :src="paths.images.artist.large + artist.image" @load="store.setBgColor" />
id="artist-avatar"
:src="paths.images.artist.large + artist.image"
@load="store.setBgColor"
/>
</div> </div>
<div <div
v-if="!useCircularImage" v-if="!useCircularImage"
@@ -39,10 +31,7 @@
:style="{ :style="{
backgroundImage: colors.bg backgroundImage: colors.bg
? `linear-gradient(${gradientDirection}, transparent ${ ? `linear-gradient(${gradientDirection}, transparent ${
isSmall isSmall ? 60 : gradientTransparentWidth - (width < 700 ? 40 : width < 900 ? 20 : 10)
? 60
: gradientTransparentWidth -
(width < 700 ? 40 : width < 900 ? 20 : 10)
}%, }%,
${colors.bg} ${gradientWidth}%, ${colors.bg} ${gradientWidth}%,
${colors.bg} 100%)` ${colors.bg} 100%)`
@@ -53,19 +42,18 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import useSettingsStore from "@/stores/settings";
import { useElementSize } from "@vueuse/core";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { Ref, computed, onMounted, ref } from "vue"; import { Ref, computed, onMounted, ref } from "vue";
import { onBeforeRouteUpdate } from "vue-router"; import { onBeforeRouteUpdate } from "vue-router";
import { useElementSize } from "@vueuse/core";
import useSettingsStore from "@/stores/settings";
import { paths } from "@/config"; import { paths } from "@/config";
import updatePageTitle from "@/utils/updatePageTitle"; 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 { isSmall } from "@/stores/content-width";
import useArtistStore from "@/stores/pages/artist";
import Info from "./HeaderComponents/Info.vue";
const image_width_px = 450; const image_width_px = 450;
const store = useArtistStore(); const store = useArtistStore();
@@ -87,18 +75,12 @@ onBeforeRouteUpdate(() => updateTitle());
const artistheader: Ref<HTMLElement | null> = ref(null); const artistheader: Ref<HTMLElement | null> = ref(null);
const { width } = useElementSize(artistheader); const { width } = useElementSize(artistheader);
const gradientTransparentWidth = computed(() => const gradientTransparentWidth = computed(() => Math.floor((image_width_px / (width.value || 1)) * 100));
Math.floor((image_width_px / (width.value || 1)) * 100)
);
const isSmallPhone = computed(() => width.value <= 550); const isSmallPhone = computed(() => width.value <= 660);
const useCircularImage = computed( const useCircularImage = computed(() => !isSmallPhone.value && settings.useCircularArtistImg);
() => !isSmallPhone.value && settings.useCircularArtistImg
);
const gradientDirection = computed(() => const gradientDirection = computed(() => (isSmallPhone.value ? "210deg" : "to left"));
isSmallPhone.value ? "210deg" : "to left"
);
const gradientWidth = computed(() => { const gradientWidth = computed(() => {
return isSmallPhone.value ? "80" : Math.min(gradientTransparentWidth.value, 50); return isSmallPhone.value ? "80" : Math.min(gradientTransparentWidth.value, 50);
@@ -155,22 +137,12 @@ const containerHeight = computed(() => {
.gradient { .gradient {
position: absolute; position: absolute;
background-image: linear-gradient( background-image: linear-gradient(to left, transparent 10%, $gray 50%, $gray 100%);
to left,
transparent 10%,
$gray 50%,
$gray 100%
);
height: 100%; height: 100%;
width: 100%; width: 100%;
&.isSmallPhone { &.isSmallPhone {
background-image: linear-gradient( background-image: linear-gradient(210deg, transparent 20%, $gray 80%, $gray 100%);
210deg,
transparent 20%,
$gray 80%,
$gray 100%
);
} }
} }

View File

@@ -2,33 +2,21 @@
<div <div
class="artist-info" class="artist-info"
:style="{ :style="{
color: !useCircularImage color: !useCircularImage ? (artist.colors[0] ? getTextColor(artist.colors[0]) : undefined) : undefined,
? artist.colors[0]
? getTextColor(artist.colors[0])
: undefined
: undefined,
}" }"
> >
<section class="text"> <section class="text">
<div class="card-title">Artist</div> <div class="card-title">Artist</div>
<div <div class="artist-name" :class="`${useCircularImage ? 'ellip' : 'ellip2'}`" :title="artist.name">
class="artist-name"
:class="`${useCircularImage ? 'ellip' : 'ellip2'}`"
:title="artist.name"
>
{{ artist.name }} {{ artist.name }}
</div> </div>
<div class="stats"> <div class="stats">
<span v-if="artist.trackcount"> <span v-if="artist.trackcount">
{{ artist.trackcount.toLocaleString() }} Track{{ {{ artist.trackcount.toLocaleString() }} Track{{ `${artist.trackcount == 1 ? "" : "s"}` }}
`${artist.trackcount == 1 ? "" : "s"}`
}}
</span> </span>
{{ artist.albumcount && artist.trackcount.toLocaleString() ? "" : "" }} {{ artist.albumcount && artist.trackcount.toLocaleString() ? "" : "" }}
<span v-if="artist.albumcount"> <span v-if="artist.albumcount">
{{ artist.albumcount.toLocaleString() }} Album{{ {{ artist.albumcount.toLocaleString() }} Album{{ `${artist.albumcount == 1 ? "" : "s"}` }}
`${artist.albumcount == 1 ? "" : "s"}`
}}
</span> </span>
<span v-if="artist.duration"> <span v-if="artist.duration">
{{ ` ${formatSeconds(artist.duration, true)}` }} {{ ` ${formatSeconds(artist.duration, true)}` }}
@@ -42,9 +30,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { getTextColor } from "@/utils/colortools/shift"; import { getTextColor } from "@/utils/colortools/shift";
import { Artist } from "@/interfaces";
import formatSeconds from "@/utils/useFormatSeconds"; import formatSeconds from "@/utils/useFormatSeconds";
import Buttons from "./Buttons.vue"; import Buttons from "./Buttons.vue";
import { Artist } from "@/interfaces";
defineProps<{ defineProps<{
artist: Artist; artist: Artist;
@@ -77,8 +65,9 @@ defineProps<{
.artist-name { .artist-name {
font-size: 3.5rem; font-size: 3.5rem;
font-weight: bold; font-weight: 700;
word-wrap: break-all; word-wrap: break-all;
margin-left: -1px;
} }
.stats { .stats {

View File

@@ -53,6 +53,11 @@ defineProps<{
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
padding-left: 1rem !important; // applies to favorite page padding-left: 1rem !important; // applies to favorite page
padding-right: $small;
@include largePhones {
padding-left: $small !important;
}
} }
} }
</style> </style>

View File

@@ -2,25 +2,26 @@
<div <div
class="b-bar" class="b-bar"
:style="{ :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" /> <LeftGroup @handleFav="handleFav" />
<div class="center"> <div class="center">
<div v-if="!isMobile" class="with-time"> <div v-if="!isMobile" class="with-time">
<div class="time time-current"> <div class="time time-current">
<span> <div class="numbers">
{{ formatSeconds(queue.duration.current || 0) }} {{ formatSeconds(queue.duration.current || 0) }}
</span> </div>
</div> </div>
<div class="buttons rounded-sm border"> <div class="buttons rounded-sm border">
<HotKeys /> <HotKeys />
</div> </div>
<div class="time time-full"> <div class="time time-full">
<span> <div class="numbers">
{{ formatSeconds(queue.duration.full) }} {{ formatSeconds(queue.duration.full) }}
</span> </div>
</div> </div>
</div> </div>
<Progress /> <Progress />
@@ -69,12 +70,30 @@ function handleFav() {
z-index: 1; z-index: 1;
@include allPhones { @include allPhones {
grid-template-columns: 1fr; display: flex;
grid-template-rows: max-content 1.5rem max-content; flex-direction: column;
padding: 0 1rem $medium 1rem; align-items: unset;
gap: $small;
padding: $medium 1rem;
.center > input { /* Hiding the dot/thumb/handle for readonly input */
height: 2px !important; /* 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; background: transparent;
border-radius: $small; border-radius: $small;
width: 3rem; width: 3rem;
transition: background-color 0.2s ease-out, border-color 0.2s ease-out;
&:hover { &:hover {
border: solid 1px $gray3 !important; border: solid 1px $gray3 !important;
@@ -91,6 +111,29 @@ function handleFav() {
@include allPhones { @include allPhones {
height: 3rem; 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 { &:hover {
@@ -131,33 +174,37 @@ function handleFav() {
display: grid; display: grid;
align-items: center; align-items: center;
gap: $small; gap: $small;
margin-bottom: -$small; margin-bottom: -$smallest;
width: 30rem; width: 30rem;
@media (max-width: 1080px) { @media only screen and (max-width: 1080px) {
width: 20rem !important; width: 20rem !important;
} }
@include allPhones { @include allPhones {
width: 100% !important; width: 100% !important;
margin-bottom: $small; margin: 4px -16px;
} user-select: none;
pointer-events: none;
.time { > #progress {
font-size: $medium; height: 1px !important;
height: fit-content; width: 100vw !important;
width: 3rem; margin: unset;
span {
background-color: $gray3;
border-radius: $smaller;
padding: 0 $smaller;
} }
} }
.time-full { .time {
text-align: end; font-weight: 500;
font-size: $medium;
.numbers {
background-color: $gray3;
border-radius: $smaller;
padding: 1px $smaller;
font-variant-numeric: tabular-nums;
}
} }
} }

View File

@@ -36,9 +36,7 @@
</div> </div>
<ArtistName <ArtistName
:artists="queue.currenttrack?.artists || []" :artists="queue.currenttrack?.artists || []"
:albumartists=" :albumartists="queue.currenttrack?.albumartists || 'Welcome to Swing Music'"
queue.currenttrack?.albumartists || 'Welcome to Swing Music'
"
class="artist" class="artist"
/> />
</div> </div>
@@ -52,17 +50,17 @@ import { paths } from "@/config";
import { Routes } from "@/router"; import { Routes } from "@/router";
import { getShift } from "@/utils/colortools/shift"; import { getShift } from "@/utils/colortools/shift";
import useQStore from "@/stores/queue";
import useColorStore from "@/stores/colors"; import useColorStore from "@/stores/colors";
import useSettingsStore from "@/stores/settings";
import { isLargerMobile, isMobile } from "@/stores/content-width"; 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 HeartSvg from "../shared/HeartSvg.vue";
import MasterFlag from "../shared/MasterFlag.vue"; import MasterFlag from "../shared/MasterFlag.vue";
import HotKeys from "../LeftSidebar/NP/HotKeys.vue"; import Actions from "./Right.vue";
import ArtistName from "@/components/shared/ArtistName.vue";
import ExpandSvg from "@/assets/icons/expand.svg";
const queue = useQStore(); const queue = useQStore();
const settings = useSettingsStore(); const settings = useSettingsStore();
@@ -77,9 +75,12 @@ defineEmits<{
.left-group { .left-group {
display: grid; display: grid;
grid-template-columns: max-content 1fr; grid-template-columns: max-content 1fr;
gap: $small; gap: $medium;
align-items: center; align-items: center;
font-size: small; font-size: small;
font-weight: 700;
line-height: 1.2;
margin-right: $medium;
.np-image { .np-image {
position: relative; position: relative;
@@ -101,16 +102,28 @@ defineEmits<{
align-items: center; align-items: center;
justify-content: center; justify-content: center;
opacity: 0; opacity: 0;
transition: opacity 0.2s; transition: all 0.2s;
svg { svg {
transform: rotate(-90deg); transform: rotate(-90deg);
} }
} }
}
a { &:hover {
font-size: small; .expandicon {
transform: translateY(-$medium);
height: 130%;
}
}
@include largePhones {
flex-shrink: 0;
margin-right: $medium;
}
@include smallerPhones {
margin-right: $small;
}
} }
.heart-button { .heart-button {
@@ -121,32 +134,45 @@ defineEmits<{
} }
.track-info { .track-info {
.artistname { .title {
font-size: 0.9rem; color: $white;
opacity: 0.75; display: flex;
font-weight: bold; align-items: center;
margin-bottom: 2px; margin-bottom: 2px;
} }
.title { .artistname {
color: $white; opacity: 0.75;
font-weight: bold;
display: flex; a {
align-items: center; font-size: 0.8rem;
}
}
@include allPhones {
width: calc(100% + 8px);
}
@include largePhones {
width: unset;
flex-grow: 1;
} }
} }
@include allPhones { @include allPhones {
height: 4rem; grid-template-columns: max-content 1fr max-content max-content;
grid-template-columns: max-content 1fr max-content; margin-right: unset;
.heart-button { .heart-button {
height: max-content; height: max-content;
border: none !important; border: 1px solid transparent;
} }
} }
@media screen and (min-width: 550px) { @include largePhones {
grid-template-columns: max-content 1fr max-content max-content; display: flex;
gap: 0;
max-width: calc(100% - 8px);
} }
} }
</style> </style>

View File

@@ -1,17 +1,11 @@
<template> <template>
<div class="right-group"> <div class="right-group">
<LyricsButton v-if="settings.use_lyrics_plugin || lyrics.exists" /> <LyricsButton />
<Volume /> <Volume />
<button <button
class="repeat" class="repeat"
:class="{ 'repeat-disabled': settings.no_repeat }" :class="{ 'repeat-disabled': settings.no_repeat }"
:title=" :title="settings.repeat_all ? 'Repeat all' : settings.no_repeat ? 'No repeat' : 'Repeat one'"
settings.repeat_all
? 'Repeat all'
: settings.no_repeat
? 'No repeat'
: 'Repeat one'
"
@click="settings.toggleRepeatMode" @click="settings.toggleRepeatMode"
> >
<RepeatOneSvg v-if="settings.repeat_one" /> <RepeatOneSvg v-if="settings.repeat_one" />
@@ -32,17 +26,15 @@
<script setup lang="ts"> <script setup lang="ts">
import useQueue from "@/stores/queue"; import useQueue from "@/stores/queue";
import useSettings from "@/stores/settings"; import useSettings from "@/stores/settings";
import useLyrics from "@/stores/lyrics";
import Volume from "./Volume.vue"; import 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 HeartSvg from "../shared/HeartSvg.vue";
import LyricsButton from "../shared/LyricsButton.vue"; import LyricsButton from "../shared/LyricsButton.vue";
import RepeatAllSvg from "@/assets/icons/repeat.svg"; import Volume from "./Volume.vue";
import RepeatOneSvg from "@/assets/icons/repeat-one.svg";
import ShuffleSvg from "@/assets/icons/shuffle.svg";
const queue = useQueue(); const queue = useQueue();
const lyrics = useLyrics();
const settings = useSettings(); const settings = useSettings();
defineProps<{ defineProps<{
@@ -64,6 +56,7 @@ defineEmits<{
@include allPhones { @include allPhones {
width: max-content; width: max-content;
height: unset;
} }
button { button {

View File

@@ -15,16 +15,20 @@
step="0.01" step="0.01"
:value="settings.volume" :value="settings.volume"
@input="changeVolume" @input="changeVolume"
:style="{
backgroundSize: `${(settings.volume / 1) * 100}% 100%`,
}"
/> />
<div className="volume_indicator">{{ ((settings.volume / 1) * 100).toFixed(0) }}</div>
</div> </div>
</button> </button>
</template> </template>
<script setup lang="ts"> <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 VolumeLowSvg from "@/assets/icons/volume-low.svg";
import VolumeMidSvg from "@/assets/icons/volume-mid.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(); const settings = useSettingsStore();
@@ -50,6 +54,12 @@ const handleMouseWheel = (event: WheelEvent) => {
</script> </script>
<style lang="scss"> <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 { .speaker {
position: relative; position: relative;
@@ -65,20 +75,33 @@ const handleMouseWheel = (event: WheelEvent) => {
} }
.dialog { .dialog {
background-color: $gray4;
position: absolute; position: absolute;
bottom: 2.95rem; cursor: default;
height: 2.5rem; bottom: 56px;
left: -1px;
height: 48px;
padding: 0 6px;
display: flex; display: flex;
align-items: center; 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; visibility: hidden;
transition: visibility 0.2s ease-in; transition: opacity 0.2s ease-out, visibility 0.2s ease-out;
transition-delay: 0.25s;
cursor: default;
input { input {
width: max-content; width: max-content;
max-width: 87px;
margin: 0; margin: 0;
touch-action: pan-x;
&::-webkit-slider-thumb { &::-webkit-slider-thumb {
height: 1rem; height: 1rem;
@@ -96,9 +119,16 @@ const handleMouseWheel = (event: WheelEvent) => {
&:hover { &:hover {
.dialog { .dialog {
transition-delay: 0.25s; opacity: 1;
visibility: visible; visibility: visible;
} }
} }
.volume_indicator {
font-weight: 600;
width: 24px;
height: 18px;
transform: rotate(90deg) translate3d(0, 0, 0);
}
} }
</style> </style>

View File

@@ -5,6 +5,7 @@
class="context-menu rounded shadow-lg no-select" class="context-menu rounded shadow-lg no-select"
:style="{ :style="{
visibility: context.visible ? 'visible' : 'hidden', visibility: context.visible ? 'visible' : 'hidden',
opacity: context.visible ? '1' : '0',
}" }"
> >
<ContextItem <ContextItem
@@ -20,8 +21,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue";
import { onClickOutside } from "@vueuse/core"; import { onClickOutside } from "@vueuse/core";
import { ref } from "vue";
import useContextStore from "@/stores/context"; import useContextStore from "@/stores/context";
import useSettingsStore from "@/stores/settings"; import useSettingsStore from "@/stores/settings";
@@ -65,15 +66,17 @@ context.$subscribe((mutation, state) => {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
width: 12.5rem; width: 13rem;
z-index: 10000 !important; z-index: 1000 !important;
transform: scale(0);
height: min-content; height: min-content;
padding: $small; padding: $small;
background: $context; background: $context;
transform-origin: top left; transform-origin: top left;
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 500;
opacity: 0;
visibility: hidden;
transition: opacity 0.25s ease-out, visibility 0.25s ease-out;
.separator { .separator {
height: 1px; height: 1px;
@@ -85,6 +88,7 @@ context.$subscribe((mutation, state) => {
.critical { .critical {
color: $red; color: $red;
transition: background-color 0.2s ease-out, color 0.2s ease-out;
&:hover { &:hover {
background-color: $red; background-color: $red;

View File

@@ -2,37 +2,25 @@
<div <div
ref="parentRef" ref="parentRef"
class="context-item" class="context-item"
@mouseenter=" @mouseenter="option.children && !isSmall && childrenShowMode === contextChildrenShowMode.hover && showChildren()"
option.children && @mouseleave="option.children && !isSmall && childrenShowMode === contextChildrenShowMode.hover && hideChildren()"
!isSmall &&
childrenShowMode === contextChildrenShowMode.hover &&
showChildren()
"
@mouseleave="
option.children &&
!isSmall &&
childrenShowMode === contextChildrenShowMode.hover &&
hideChildren()
"
@click="runAction" @click="runAction"
> >
<div class="icon image" v-html="option.icon"></div> <div class="icon image" v-html="option.icon"></div>
<div class="label ellip">{{ option.label }}</div> <div class="label ellip">{{ option.label }}</div>
<div v-if="option.children" class="more" v-html="ExpandIcon"></div> <div v-if="option.children" class="more" v-html="ExpandIcon"></div>
<div <div v-if="option.children" ref="childRef" class="children rounded shadow-sm">
v-if="option.children" <div className="wrapper">
ref="childRef" <div
class="children rounded shadow-sm" v-for="child in option.children"
> :key="child.label"
<div class="context-item"
v-for="child in option.children" :class="[{ critical: child.critical }, child.type]"
:key="child.label" @click="child.action && runChildAction(child.action)"
class="context-item" >
:class="[{ critical: child.critical }, child.type]" <div class="label ellip">
@click="child.action && runChildAction(child.action)" {{ child.label }}
> </div>
<div class="label ellip">
{{ child.label }}
</div> </div>
</div> </div>
</div> </div>
@@ -40,7 +28,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { createPopper, Instance } from "@popperjs/core"; import { createPopper, Instance, Modifier, Placement, Rect } from "@popperjs/core";
import { ref } from "vue"; import { ref } from "vue";
import { contextChildrenShowMode } from "@/enums"; import { contextChildrenShowMode } from "@/enums";
@@ -70,27 +58,50 @@ function showChildren() {
return; return;
} }
popperInstance = createPopper( const offsetModifier: Modifier<
parentRef.value as HTMLElement, "offset",
childRef.value as HTMLElement, { offset: [number, number] | ((args: { placement: Placement; reference: Rect; popper: Rect }) => [number, number]) }
{ > = {
placement: "right-start", name: "offset",
modifiers: [ options: {
{ offset: ({ placement }) => {
name: "flip", // Correct type for placement automatically inferred
options: { if (placement.includes("right") || placement.includes("left")) {
fallbackPlacements: ["left-start", "auto"], 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.visibility = "visible") : null;
childRef.value ? (childRef.value.style.opacity = "1") : null;
childrenShown.value = true; childrenShown.value = true;
} }
function hideChildren() { function hideChildren() {
childRef.value ? (childRef.value.style.visibility = "hidden") : null; childRef.value ? (childRef.value.style.visibility = "hidden") : null;
childRef.value ? (childRef.value.style.opacity = "0") : null;
popperInstance?.destroy(); popperInstance?.destroy();
childrenShown.value = false; childrenShown.value = false;
} }
@@ -129,12 +140,14 @@ function runChildAction(action: () => void) {
padding: 0.4rem; padding: 0.4rem;
position: relative; position: relative;
border-radius: $small; border-radius: $small;
transition: background-color 0.2s ease-out;
.more { .more {
height: 1.5rem; height: 1.5rem;
width: 1.5rem; width: 1.5rem;
position: absolute; position: absolute;
right: 0; right: 2px;
bottom: 5px;
transform: scale(0.65); transform: scale(0.65);
} }
@@ -142,19 +155,38 @@ function runChildAction(action: () => void) {
position: absolute; position: absolute;
width: 12rem; width: 12rem;
z-index: 10; z-index: 10;
overflow-y: auto;
overflow-x: hidden;
transform: scale(0); transform: scale(0);
background-color: $context; background-color: $context;
transform: scale(0); padding: $small $smaller;
padding: $small;
border: solid 1px $gray3; 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 { .context-item {
line-height: 1.2;
padding: $small 1rem; padding: $small 1rem;
padding: 0.4rem; padding: 0.4rem 0.6rem;
} }
.separator { .separator {

View File

@@ -4,27 +4,19 @@
:class="fav.type" :class="fav.type"
:to="{ :to="{
name: fav.type === 'album' ? Routes.album : Routes.artist, name: fav.type === 'album' ? Routes.album : Routes.artist,
params: params: fav.type === 'album' ? { albumhash: fav.item.albumhash } : { hash: fav.item.artisthash },
fav.type === 'album'
? { albumhash: fav.item.albumhash }
: { hash: fav.item.artisthash },
}" }"
> >
<div class="imagegroup"> <div class="imagegroup">
<img <img
:src=" :src="
fav.type === 'album' fav.type === 'album' ? paths.images.thumb.large + fav.item.image : paths.images.artist.large + fav.item.image
? paths.images.thumb.large + fav.item.image
: paths.images.artist.large + fav.item.image
" "
alt="" alt=""
class="rounded-sm" class="rounded-sm"
/> />
</div> </div>
<div <div class="name ellip" :title="fav.type === 'artist' ? fav.item.name : fav.item.title">
class="name ellip"
:title="fav.type === 'artist' ? fav.item.name : fav.item.title"
>
{{ fav.type === "artist" ? fav.item.name : fav.item.title }} {{ fav.type === "artist" ? fav.item.name : fav.item.title }}
</div> </div>
<div class="label ellip" :class="{ on_artist: fav.type === 'artist' }"> <div class="label ellip" :class="{ on_artist: fav.type === 'artist' }">
@@ -73,7 +65,7 @@ defineProps<{
.label { .label {
font-size: 0.8rem; font-size: 0.8rem;
color: $gray1; color: $gray1;
font-weight: bold; font-weight: 700;
border-radius: 1rem; border-radius: 1rem;
text-transform: capitalize; text-transform: capitalize;
margin-top: -$smaller; margin-top: -$smaller;

View File

@@ -39,22 +39,29 @@ onUpdated(() => {
</script> </script>
<style lang="scss"> <style lang="scss">
.breadcrumb-nav { .designatedOS .breadcrumb-nav {
display: flex;
gap: $smaller;
&::-webkit-scrollbar { &::-webkit-scrollbar {
display: none; display: none;
} }
}
.breadcrumb-nav {
display: flex;
gap: $smaller;
.path { .path {
white-space: nowrap; white-space: nowrap;
margin: auto 0; margin: auto 0;
cursor: pointer; cursor: pointer;
display: flex;
align-items: center;
.text { .text {
padding: $smaller; font-size: 1rem;
font-weight: 500;
padding: $smaller $small;
border-radius: $smaller; border-radius: $smaller;
transition: background-color 0.2s ease-out;
} }
&::before { &::before {

View File

@@ -3,10 +3,10 @@
<div <div
v-auto-animate v-auto-animate
class="f-item" class="f-item"
:style="{ :class="{
backgroundColor: is_checked ? '#234ece' : '', selected: is_checked,
context_menu_showing: context_menu_showing,
}" }"
:class="{ context_menu_showing }"
@click="(e) => (folder_page ? null : handleClick(e))" @click="(e) => (folder_page ? null : handleClick(e))"
@mouseover="mouse_over = true" @mouseover="mouse_over = true"
@mouseleave="mouse_over = false" @mouseleave="mouse_over = false"
@@ -16,8 +16,8 @@
<FolderSvg v-else /> <FolderSvg v-else />
<div class="info"> <div class="info">
<div class="f-item-text ellip">{{ folder.name }}</div> <div class="f-item-text ellip">{{ folder.name }}</div>
<div v-if="folder.count" class="f-count"> <div class="f-count" v-if="folder.trackcount">
{{ folder.count.toLocaleString() + ` File${folder.count == 1 ? "" : "s"}` }} {{ folder.trackcount.toLocaleString() + ` File${folder.trackcount == 1 ? "" : "s"}` }}
</div> </div>
</div> </div>
<div v-if="!folder_page" class="check"> <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 CheckFilledSvg from "@/assets/icons/check.filled.svg";
import CheckSvg from "@/assets/icons/square.svg"; import CheckSvg from "@/assets/icons/square.svg";
import { showFolderContextMenu } from "@/helpers/contextMenuHandler";
import { ContextSrc } from "@/enums"; import { ContextSrc } from "@/enums";
import { showFolderContextMenu } from "@/helpers/contextMenuHandler";
const props = defineProps<{ const props = defineProps<{
folder: Folder; folder: Folder;
@@ -70,12 +70,7 @@ function handleClick(e: MouseEvent) {
} }
function showContextMenu(e: MouseEvent) { function showContextMenu(e: MouseEvent) {
showFolderContextMenu( showFolderContextMenu(e, context_menu_showing, ContextSrc.FolderCard, props.folder.path);
e,
context_menu_showing,
ContextSrc.FolderCard,
props.folder.path
);
} }
</script> </script>
@@ -90,6 +85,7 @@ function showContextMenu(e: MouseEvent) {
position: relative; position: relative;
padding: 0 0 0 1rem; padding: 0 0 0 1rem;
gap: $small; gap: $small;
transition: background-color 0.2s ease-out;
&.context_menu_showing { &.context_menu_showing {
background-color: $gray4; background-color: $gray4;
@@ -97,6 +93,7 @@ function showContextMenu(e: MouseEvent) {
svg { svg {
color: $gray1; color: $gray1;
height: 1.75rem;
} }
.f-count { .f-count {
@@ -118,11 +115,8 @@ function showContextMenu(e: MouseEvent) {
transform: scale(0.75); transform: scale(0.75);
} }
@include largePhones {
height: 4rem;
}
.f-item-text { .f-item-text {
font-weight: 600;
margin-right: 1rem; margin-right: 1rem;
} }

View File

@@ -2,27 +2,20 @@
<div <div
class="f-container rounded-sm" class="f-container rounded-sm"
:class="{ :class="{
'list-mode': isIphoneSE ? true : settings.folder_list_mode, 'list-mode': isSmallestPhone ? true : settings.folder_list_mode,
}" }"
> >
<div id="f-items" class="rounded"> <div id="f-items" class="rounded">
<FolderItem <FolderItem v-for="folder in folders" :key="folder.path" :folder="folder" :folder_page="true" />
v-for="folder in folders"
:key="folder.path"
:folder="folder"
:folder_page="true"
/>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Folder } from "@/interfaces"; import { Folder } from "@/interfaces";
import FolderItem from "./FolderItem.vue"; import { isSmallestPhone } from "@/stores/content-width";
import useSettingsStore from "@/stores/settings"; import useSettingsStore from "@/stores/settings";
import { isIphoneSE } from "@/stores/content-width"; import FolderItem from "./FolderItem.vue";
import { ref } from "vue";
defineProps<{ defineProps<{
folders: Folder[]; folders: Folder[];
@@ -51,11 +44,13 @@ const settings = useSettingsStore();
gap: 0; gap: 0;
.f-item { .f-item {
line-height: 1.2;
transition: none; transition: none;
height: 3.25rem; height: 3.25rem;
border-radius: $small; border-radius: $small;
background-color: transparent; background-color: transparent;
padding-left: $small; padding-left: $small;
transition: background-color 0.2s ease-out;
.options { .options {
display: block; display: block;
@@ -66,7 +61,7 @@ const settings = useSettingsStore();
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding-right: $small; padding-right: $medium;
} }
&:hover { &:hover {

View File

@@ -62,6 +62,7 @@ const browselist = [
.btitle { .btitle {
font-size: 1.15rem; font-size: 1.15rem;
margin-bottom: 1rem; margin-bottom: 1rem;
padding-left: 0.25rem;
} }
.browselist { .browselist {
@@ -72,9 +73,11 @@ const browselist = [
} }
.browseitem { .browseitem {
font-weight: 500;
padding: 1.5rem 0; padding: 1.5rem 0;
background-color: $gray; background-color: $gray;
color: $white; color: $white;
transition: background-color 0.2s ease-out;
} }
.browseitem:hover { .browseitem:hover {

View File

@@ -1,11 +1,6 @@
<template> <template>
<div class="home-count"> <div class="home-count">
<div <div class="count-item" :title="count.count.toLocaleString()" v-for="count in parseCount()" :key="count.text">
class="count-item"
:title="count.count.toLocaleString()"
v-for="count in parseCount()"
:key="count.text"
>
<div>{{ count.formatted }}</div> <div>{{ count.formatted }}</div>
<div class="text">{{ count.text }}</div> <div class="text">{{ count.text }}</div>
</div> </div>
@@ -57,7 +52,7 @@ function parseCount() {
.text { .text {
font-size: 1rem; font-size: 1rem;
font-weight: normal; font-weight: 400;
text-transform: uppercase; text-transform: uppercase;
margin-left: $smaller; margin-left: $smaller;
} }

View File

@@ -1,10 +1,6 @@
<template> <template>
<div <div v-if="q.currenttrack?.bitrate" class="bitrate" title="file type • bitrate">
v-if="q.currenttrack?.bitrate" {{ q.currenttrack.filepath?.split(".").pop() }} {{ q.currenttrack.bitrate }}
class="bitrate"
title="file type • bitrate"
>
{{ q.currenttrack.filepath?.split('.').pop() }} {{ q.currenttrack.bitrate }}
</div> </div>
</template> </template>
@@ -18,6 +14,7 @@ const q = useQueueStore();
.bitrate { .bitrate {
position: absolute; position: absolute;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500;
width: max-content; width: max-content;
padding: 0.2rem 0.35rem; padding: 0.2rem 0.35rem;
bottom: $medium; bottom: $medium;

View File

@@ -18,10 +18,7 @@
import { usePlayer } from "@/stores/player"; import { usePlayer } from "@/stores/player";
import useQStore from "@/stores/queue"; import useQStore from "@/stores/queue";
import { import { default as NextSvg, default as PrevSvg } from "@/assets/icons/next.svg";
default as NextSvg,
default as PrevSvg,
} from "@/assets/icons/next.svg";
import PauseSvg from "@/assets/icons/pause.svg"; import PauseSvg from "@/assets/icons/pause.svg";
import PlaySvg from "@/assets/icons/play.svg"; import PlaySvg from "@/assets/icons/play.svg";
import Spinner from "@/components/shared/Spinner.vue"; import Spinner from "@/components/shared/Spinner.vue";
@@ -54,6 +51,12 @@ const { buffering } = usePlayer();
svg { svg {
transform: rotate(180deg); transform: rotate(180deg);
} }
&:active {
svg {
transform: rotate(180deg) scale(0.75);
}
}
} }
button:nth-child(2) { button:nth-child(2) {
@@ -64,10 +67,20 @@ const { buffering } = usePlayer();
grid-template-columns: 1fr max-content 1fr; grid-template-columns: 1fr max-content 1fr;
position: relative; position: relative;
margin-right: -$small; margin-right: -$small;
gap: 0;
button:first-child { button:first-child {
margin-left: $small; margin-left: $small;
} }
} }
@include largePhones {
display: flex;
flex-shrink: 0;
button:first-child {
margin-left: $smaller;
}
}
} }
</style> </style>

View File

@@ -6,9 +6,7 @@
min="0" min="0"
:max="time.full" :max="time.full"
step="0.1" step="0.1"
:style="{ :style="{ backgroundSize: `${(time.current / (time.full || 1)) * 100}% 100%` }"
backgroundSize: `${(time.current / (time.full || 0)) * 100}% 100%`,
}"
@change="seek" @change="seek"
/> />
</template> </template>

View File

@@ -41,6 +41,8 @@ import { menus } from "./navitems";
align-items: center; align-items: center;
padding: $small 0; padding: $small 0;
font-size: 14px; font-size: 14px;
font-weight: 500;
transition: background-color 0.2s ease-out;
& > div { & > div {
display: flex; 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 { svg {
height: 1.5rem;
margin: 0 $small 0 $small; margin: 0 $small 0 $small;
border-radius: $small; border-radius: $small;
transform: scale(0.9); transform: scale(0.9);

View File

@@ -12,9 +12,9 @@
<script setup lang="ts"> <script setup lang="ts">
import useSettingsStore from "@/stores/settings"; import useSettingsStore from "@/stores/settings";
import Navigation from "@/components/LeftSidebar/NavButtons.vue";
import Logo from "@/components/Logo.vue"; import Logo from "@/components/Logo.vue";
import SongCard from "./NP/SongCard.vue"; import SongCard from "./NP/SongCard.vue";
import Navigation from "@/components/LeftSidebar/NavButtons.vue";
const settings = useSettingsStore(); const settings = useSettingsStore();
</script> </script>
@@ -31,10 +31,22 @@ const settings = useSettingsStore();
.scrollable { .scrollable {
height: 100%; height: 100%;
overflow-y: scroll; overflow: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
padding: 1rem 0; 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> </style>

View File

@@ -1,7 +1,5 @@
<template> <template>
<router-link class="swing-logo rounded-md" :to="{ name: 'Home' }"> <router-link class="swing-logo rounded-md" :to="{ name: 'Home' }"> <LogoSvg /> <span>Swing Music</span> </router-link>
<LogoSvg /> <span>Swing Music</span>
</router-link>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -10,12 +8,14 @@ import LogoSvg from "@/assets/icons/logos/logo-fill.light.svg";
<style lang="scss"> <style lang="scss">
.swing-logo { .swing-logo {
background-image: linear-gradient(37deg, rgb(29, 28, 28), transparent); font-weight: 600;
padding-left: 1rem;
display: flex; display: flex;
align-items: center; align-items: center;
gap: $medium; gap: $medium;
padding-left: 1rem;
border: solid 1px $gray5; border: solid 1px $gray5;
background-image: linear-gradient(37deg, rgb(29, 28, 28), transparent);
transition: background-color 0.2s ease-out;
svg { svg {
transform: scale(1.25); transform: scale(1.25);

View File

@@ -1,41 +1,47 @@
<template> <template>
<div v-if="notifStore.notifs" class="toasts">
<div <div
v-for="notif in notifStore.notifs" v-if="notifStore.notifs"
:key="notif.text" class="toasts"
class="new-notif rounded-sm"
:class="notif.type"
> >
<component :is="getSvg(notif.type)" class="notif-icon" /> <div
<div class="notif-text">{{ notif.text }}</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>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useNotifStore, NotifType } from "../stores/notification"; import { NotifType, useToast } from '../stores/notification'
import HeartSvg from "../assets/icons/heart.svg"; import HeartSvg from '../assets/icons/heart.svg'
import InfoSvg from "../assets/icons/toast/info.svg"; import ErrorSvg from '../assets/icons/toast/error.svg'
import SuccessSvg from "../assets/icons/toast/ok.svg"; import InfoSvg from '../assets/icons/toast/info.svg'
import ErrorSvg from "../assets/icons/toast/error.svg"; import SuccessSvg from '../assets/icons/toast/ok.svg'
import WorkingSvg from "../assets/icons/toast/working.svg"; import WorkingSvg from '../assets/icons/toast/working.svg'
const notifStore = useNotifStore(); const notifStore = useToast()
function getSvg(notif: NotifType) { function getSvg(notif: NotifType) {
switch (notif) { switch (notif) {
case NotifType.Error: case NotifType.Error:
return ErrorSvg; return ErrorSvg
case NotifType.Info: case NotifType.Info:
return InfoSvg; return InfoSvg
case NotifType.Success: case NotifType.Success:
return SuccessSvg; return SuccessSvg
case NotifType.Working: case NotifType.Working:
return WorkingSvg; return WorkingSvg
case NotifType.Favorite: case NotifType.Favorite:
return HeartSvg; return HeartSvg
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -43,45 +49,53 @@ function getSvg(notif: NotifType) {
position: fixed; position: fixed;
bottom: 6rem; bottom: 6rem;
left: 50%; left: 50%;
width: 100%;
transform: translate(-50%); transform: translate(-50%);
z-index: 100; z-index: 1003;
display: flex; display: flex;
align-items: center;
flex-direction: column-reverse; flex-direction: column-reverse;
gap: 1rem; gap: 1rem;
} }
.new-notif { .new-notif {
font-size: 0.85rem;
font-weight: 600;
width: 18rem; width: 18rem;
height: 4rem; min-height: 4rem;
background-color: $gray; background-color: $gray;
display: grid; display: grid;
place-items: center; place-items: center;
box-shadow: 0px 0px 2rem rgba(0, 0, 0, 0.466); box-shadow: 0px 0px 2rem rgba(0, 0, 0, 0.466);
font-size: 0.85rem;
padding: 1rem $small; padding: 1rem $small;
grid-template-columns: 2rem 3fr; grid-template-columns: 2rem 3fr;
gap: $smaller; gap: $smaller;
.notif-text { .notif-text {
width: 100%; width: 100%;
} }
@include smallestPhones {
max-width: calc(100% - 2rem);
}
} }
.new-notif.error { .new-notif.error {
$bg: rgb(197, 72, 72); $bg: rgb(197, 72, 72);
background-color: $bg; background-color: $bg;
} }
.new-notif.info, .new-notif.info,
.new-notif.favorite,.new-notif.success { .new-notif.favorite,
$bg: rgb(255, 255, 255); .new-notif.success {
background-color: $bg; $bg: rgb(255, 255, 255);
color: $black; background-color: $bg;
color: $black;
} }
.new-notif.working { .new-notif.working {
$bg: $gray4; $bg: $gray4;
background-color: $bg; background-color: $bg;
} }
</style> </style>

View File

@@ -12,11 +12,7 @@
title="Go to Album" title="Go to Album"
class="np-image" class="np-image"
> >
<img <img v-motion-fade class="rounded" :src="paths.images.thumb.large + queue.currenttrack?.image" />
v-motion-fade
class="rounded"
:src="paths.images.thumb.original + queue.currenttrack?.image"
/>
</RouterLink> </RouterLink>
<NowPlayingInfo @handle-fav="handleFav" /> <NowPlayingInfo @handle-fav="handleFav" />
<Progress v-if="isSmallPhone" /> <Progress v-if="isSmallPhone" />
@@ -30,7 +26,7 @@
</div> </div>
</div> </div>
</div> </div>
<h3 v-if="queue.next">Up Next</h3> <h3 class="nowplaying_title" v-if="queue.next">Up Next</h3>
<SongItem <SongItem
v-if="queue.next" v-if="queue.next"
:track="queue.next" :track="queue.next"
@@ -38,24 +34,24 @@
:source="dropSources.folder" :source="dropSources.folder"
@play-this="queue.playNext" @play-this="queue.playNext"
/> />
<h3>Queue</h3> <h3 class="nowplaying_title">Queue</h3>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { paths } from "@/config"; import { paths } from "@/config";
import { dropSources, favType } from "@/enums";
import favoriteHandler from "@/helpers/favoriteHandler";
import { Routes } from "@/router"; import { Routes } from "@/router";
import { isSmallPhone } from "@/stores/content-width";
import useQueueStore from "@/stores/queue"; import useQueueStore from "@/stores/queue";
import { formatSeconds } from "@/utils"; 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 Buttons from "../BottomBar/Right.vue";
import SongItem from "../shared/SongItem.vue"; import SongItem from "../shared/SongItem.vue";
import NowPlayingInfo from "./NowPlayingInfo.vue"; import NowPlayingInfo from "./NowPlayingInfo.vue";
import Progress from "@/components/LeftSidebar/NP/Progress.vue"; import PlayingFrom from "./PlayingFrom.vue";
const queue = useQueueStore(); const queue = useQueueStore();
@@ -72,9 +68,23 @@ function handleFav() {
<style lang="scss"> <style lang="scss">
.now-playing-header { .now-playing-header {
padding-bottom: 1rem; padding-bottom: $smaller;
position: relative; 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 { .below-progress {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@@ -82,12 +92,48 @@ function handleFav() {
margin-top: 1rem; margin-top: 1rem;
.time { .time {
font-size: 12px; font-size: $medium;
font-weight: 500;
background-color: $gray3; background-color: $gray3;
padding: 0 $smaller; padding: 1px $smaller;
min-width: 2.5rem; min-width: 2.5rem;
text-align: center; text-align: center;
border-radius: $smaller; 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;
}
} }
} }

View File

@@ -8,21 +8,12 @@
:albumartists="queue.currenttrack?.albumartists || ''" :albumartists="queue.currenttrack?.albumartists || ''"
/> />
<span v-else class="artist author"> <span v-else class="artist author">
<a href="https://github.com/mungai-njoroge" target="_blank" <a href="https://github.com/mungai-njoroge" target="_blank">built by @mungai-njoroge </a>
>built by @mungai-njoroge </a
>
</span> </span>
</div> </div>
<div class="actions"> <div class="actions">
<HeartSvg <HeartSvg :state="queue.currenttrack?.is_favorite" @handle-fav="$emit('handleFav', queue.currenttrackhash)" />
:state="queue.currenttrack?.is_favorite" <OptionSvg class="optionsvg" :class="{ context_menu_showing }" @click="showMenu" />
@handle-fav="$emit('handleFav', queue.currenttrackhash)"
/>
<OptionSvg
class="optionsvg"
:class="{ context_menu_showing }"
@click="showMenu"
/>
</div> </div>
</div> </div>
</template> </template>
@@ -34,9 +25,9 @@ import { useRoute } from "vue-router";
import ArtistName from "../shared/ArtistName.vue"; import ArtistName from "../shared/ArtistName.vue";
import HeartSvg from "../shared/HeartSvg.vue"; import HeartSvg from "../shared/HeartSvg.vue";
import useQueueStore from "@/stores/queue";
import OptionSvg from "@/assets/icons/more.svg"; import OptionSvg from "@/assets/icons/more.svg";
import { showTrackContextMenu } from "@/helpers/contextMenuHandler"; import { showTrackContextMenu } from "@/helpers/contextMenuHandler";
import useQueueStore from "@/stores/queue";
const route = useRoute(); const route = useRoute();
const context_menu_showing = ref(false); const context_menu_showing = ref(false);
@@ -50,13 +41,7 @@ defineEmits<{
function showMenu(e: MouseEvent) { function showMenu(e: MouseEvent) {
if (!queue.currenttrack) return; if (!queue.currenttrack) return;
showTrackContextMenu( showTrackContextMenu(e, queue.currenttrack, context_menu_showing, route, false);
e,
queue.currenttrack,
context_menu_showing,
route,
false
);
} }
</script> </script>
@@ -66,6 +51,7 @@ function showMenu(e: MouseEvent) {
grid-template-columns: 1fr max-content; grid-template-columns: 1fr max-content;
gap: 1rem; gap: 1rem;
margin-top: 1rem; margin-top: 1rem;
font-weight: 500;
.artist { .artist {
font-size: 0.8rem; font-size: 0.8rem;
@@ -80,6 +66,7 @@ function showMenu(e: MouseEvent) {
.optionsvg { .optionsvg {
transform: scale(1.5) rotate(90deg); transform: scale(1.5) rotate(90deg);
border-radius: $small; border-radius: $small;
transition: background-color 0.2s ease-out;
&:hover { &:hover {
background-color: $gray3; background-color: $gray3;
@@ -94,6 +81,7 @@ function showMenu(e: MouseEvent) {
.heart-button { .heart-button {
background-color: $gray; background-color: $gray;
transition: background-color 0.2s ease-out;
&:hover { &:hover {
background-color: $gray4; background-color: $gray4;

View File

@@ -1,23 +1,12 @@
<template> <template>
<div class="now-playing-top"> <div class="now-playing-top">
<router-link <router-link class="now-playling-from-link" :to="(data.location as RouteLocationRaw)" title="Go to Play Source">
class="now-playling-from-link"
:to="(data.location as RouteLocationRaw)"
title="Go to Play Source"
>
<div class="from"> <div class="from">
<img <img
v-if=" v-if="tracklist.from.type === FromOptions.album || tracklist.from.type === FromOptions.artist"
tracklist.from.type === FromOptions.album ||
tracklist.from.type === FromOptions.artist
"
:src="data.image + '.webp'" :src="data.image + '.webp'"
:alt="`Now Playing ${tracklist.from.type} image`" :alt="`Now Playing ${tracklist.from.type} image`"
:class="`${ :class="`${tracklist.from.type === FromOptions.artist ? 'circular' : 'rounded-sm'}`"
tracklist.from.type === FromOptions.artist
? 'circular'
: 'rounded-sm'
}`"
/> />
<div v-else class="from-icon border rounded-sm"> <div v-else class="from-icon border rounded-sm">
<component :is="data.icon"></component> <component :is="data.icon"></component>
@@ -97,6 +86,7 @@ function showContextMenu(e: MouseEvent) {
padding: $smaller; padding: $smaller;
aspect-ratio: 1; aspect-ratio: 1;
width: 2.5rem; width: 2.5rem;
margin-right: 2px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -108,6 +98,11 @@ function showContextMenu(e: MouseEvent) {
text-transform: capitalize; text-transform: capitalize;
font-size: 0.8rem; font-size: 0.8rem;
color: $gray1; color: $gray1;
font-weight: 500;
}
.type + div {
font-weight: 500;
} }
} }
</style> </style>

View File

@@ -4,15 +4,20 @@
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss">
.p-after-header { .p-after-header {
display: flex; display: flex;
align-items: center; align-items: center;
height: 64px; height: 64px;
padding: 0 1rem; padding: 0 1rem;
margin-top: $small;
font-size: 14px; font-size: 14px;
font-weight: 500;
color: $gray1; color: $gray1;
@include largePhones {
padding-left: 0.5rem;
}
} }
</style> </style>

View File

@@ -28,37 +28,32 @@
class="album-header-ambient rounded-lg" class="album-header-ambient rounded-lg"
style="height: 100%; width: 100%" style="height: 100%; width: 100%"
:style="{ :style="{
boxShadow: colors.bg boxShadow: colors.bg ? `0 .5rem 2rem ${colors.bg}` : '0 .5rem 2rem black',
? `0 .5rem 2rem ${colors.bg}`
: '0 .5rem 2rem black',
}" }"
></div> ></div>
<div v-if="info.has_image && useSqrImg" class="sqr_img"> <div v-if="info.has_image && useSqrImg" class="sqr_img">
<img :src="(playlist.info.image as string)" class="rounded-sm" /> <img :src="(playlist.info.image as string)" class="rounded-sm" />
</div> </div>
<BannerImages <BannerImages v-if="playlist.info.count && !info.has_image && useSqrImg" class="sqr_img rounded-sm" />
v-if="playlist.info.count && !info.has_image && useSqrImg"
class="sqr_img rounded-sm"
/>
<Info :text-color="textColor" :btn_color="colors.btn" /> <Info :text-color="textColor" :btn_color="colors.btn" />
<LastUpdated /> <LastUpdated />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed } from "vue";
import { storeToRefs } from "pinia"; 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 { pinUnpinPlaylist } from "@/requests/playlists";
import { heightLarge, isSmallPhone } from "@/stores/content-width"; 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 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(); const playlist = usePStore();
@@ -66,10 +61,7 @@ const { info, colors } = storeToRefs(playlist);
const bg = computed(() => { const bg = computed(() => {
if (playlist.info.has_image) { if (playlist.info.has_image) {
if ( if (isSmallPhone.value || (!playlist.info.settings.square_img && !isSmallPhone.value)) {
isSmallPhone.value ||
(!playlist.info.settings.square_img && !isSmallPhone.value)
) {
return `url(${info.value.image})`; return `url(${info.value.image})`;
} }
} }
@@ -77,9 +69,7 @@ const bg = computed(() => {
return colors.value.bg ? colors.value.bg : ""; return colors.value.bg ? colors.value.bg : "";
}); });
const useSqrImg = computed( const useSqrImg = computed(() => !playlist.info.has_image || !bg.value.startsWith("url"));
() => !playlist.info.has_image || !bg.value.startsWith("url")
);
const textColor = computed(() => { const textColor = computed(() => {
if (colors.value.bg !== "") { if (colors.value.bg !== "") {
@@ -130,7 +120,7 @@ function pinPlaylist(pid: number) {
font-size: 3.75rem !important; font-size: 3.75rem !important;
} }
@include smallPhone { @include largePhones {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-end; justify-content: flex-end;
@@ -176,7 +166,7 @@ function pinPlaylist(pid: number) {
opacity: 0.5; opacity: 0.5;
} }
@include smallPhone { @include largePhones {
.title { .title {
font-size: 2.5rem !important; font-size: 2.5rem !important;
} }

View File

@@ -15,8 +15,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import usePStore from "@/stores/pages/playlist";
import { paths } from "@/config"; import { paths } from "@/config";
import usePStore from "@/stores/pages/playlist";
const playlist = usePStore(); const playlist = usePStore();
</script> </script>
@@ -32,7 +32,7 @@ const playlist = usePStore();
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
} }
@include smallPhone { @include largePhones {
right: -4rem; right: -4rem;
img { img {

View File

@@ -9,28 +9,13 @@
<PlayBtnRect :source="playSources.playlist" :bg_color="btn_color" /> <PlayBtnRect :source="playSources.playlist" :bg_color="btn_color" />
</div> </div>
<div class="duration"> <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) }} {{ formatSeconds(playlist.info.duration, true) }}
</div> </div>
<div ref="test_elem"></div> <div ref="test_elem"></div>
<div <div class="title" :class="`${playlist.info.settings.square_img && isSmall ? 'ellip' : 'ellip2'}`">
class="title" <span v-for="t in balanceText(playlist.info.name, test_elem?.offsetWidth || 0, '4rem')" :key="t">
: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 }} {{ t }}
<br /> <br />
</span> </span>
@@ -40,11 +25,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { playSources } from "@/enums"; import { playSources } from "@/enums";
import { formatSeconds } from "@/utils";
import { isSmall } from "@/stores/content-width"; import { isSmall } from "@/stores/content-width";
import { formatSeconds } from "@/utils";
import usePStore from "@/stores/pages/playlist";
import PlayBtnRect from "@/components/shared/PlayBtnRect.vue"; import PlayBtnRect from "@/components/shared/PlayBtnRect.vue";
import usePStore from "@/stores/pages/playlist";
import { balanceText } from "@/utils/balanceText"; import { balanceText } from "@/utils/balanceText";
import { Ref, ref } from "vue"; import { Ref, ref } from "vue";
@@ -64,7 +49,7 @@ const test_elem: Ref<HTMLElement | null> = ref(null);
height: 100%; height: 100%;
display: grid; display: grid;
z-index: 10; z-index: 10;
padding-left: 1.25rem; padding: 0 1.25rem;
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;

View File

@@ -1,61 +1,69 @@
<template> <template>
<div class="last-updated"> <div class="last-updated">
<span v-if="!isHeaderSmall" class="status" <span
>Last updated {{ playlist.info.last_updated }} &#160;|&#160;&#160;</span v-if="!isHeaderSmall"
> class="status"
<div >Last updated {{ playlist.info.last_updated }}</span
v-if="Number.isInteger(playlist.info.id)" >
class="edit" <div
@click="editPlaylist" v-if="Number.isInteger(playlist.info.id)"
> class="edit"
Edit&#160;&#160;
>
&#160;&#160;|&#160;&#160; <span @click="editPlaylist">Edit</span>&#160;&#160;
{{ Number.isInteger(playlist.info.id) ? ' | ' : '' }}
<DeleteSvg
class="edit"
@click="deletePlaylist"
/>
</div>
</div> </div>
{{ Number.isInteger(playlist.info.id) ? " | " : "" }}
<DeleteSvg class="edit" @click="deletePlaylist" />
</div>
</template> </template>
<script setup lang="ts"> <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 useModalStore from '@/stores/modal'
import usePStore from "@/stores/pages/playlist"; import usePStore from '@/stores/pages/playlist'
const playlist = usePStore(); const playlist = usePStore()
const modal = useModalStore(); const modal = useModalStore()
function editPlaylist() { function editPlaylist() {
modal.showEditPlaylistModal(); modal.showEditPlaylistModal()
} }
function deletePlaylist() { function deletePlaylist() {
modal.showDeletePlaylistModal(playlist.info.id); modal.showDeletePlaylistModal(playlist.info.id)
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.last-updated { .last-updated {
position: absolute; position: absolute;
bottom: 1rem; bottom: 1rem;
right: 1rem; right: 1rem;
padding: $smaller $small; padding: $smaller $small;
font-size: 0.9rem; font-size: 0.9rem;
border-radius: $smaller; border-radius: $smaller;
z-index: 12; z-index: 12;
display: flex; display: flex;
align-items: center; align-items: center;
.edit { .edit {
cursor: pointer; cursor: pointer;
color: $brown; color: $brown;
} display: flex;
align-items: center;
}
svg { svg {
transform: scale(0.75); transform: scale(0.75);
margin-bottom: -0.2rem; margin-bottom: -0.2rem;
color: $red !important; color: $red !important;
} height: 1.5rem;
}
} }
</style> </style>

View File

@@ -1,24 +1,9 @@
<template> <template>
<router-link <router-link :to="{ name: 'PlaylistView', params: { pid: playlist.id } }" class="p-card rounded no-scroll">
:to="{ name: 'PlaylistView', params: { pid: playlist.id } }" <div v-if="!playlist.has_image && playlist.images.length" class="image-grid rounded-sm no-scroll">
class="p-card rounded no-scroll" <img v-for="(img, index) in playlist.images" :key="index" :src="paths.images.thumb.large + img" />
>
<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> </div>
<img <img v-else :src="imguri + playlist.thumb" class="rounded-sm" :class="{ border: !playlist.thumb }" />
v-else
:src="imguri + playlist.thumb"
class="rounded-sm"
:class="{ border: !playlist.thumb }"
/>
<div class="overlay rounded"> <div class="overlay rounded">
<div v-if="playlist.help_text" class="rhelp playlist"> <div v-if="playlist.help_text" class="rhelp playlist">
<span class="help">{{ playlist.help_text }}</span> <span class="help">{{ playlist.help_text }}</span>
@@ -26,10 +11,7 @@
</div> </div>
<div class="p-name ellip">{{ playlist.name }}</div> <div class="p-name ellip">{{ playlist.name }}</div>
<div class="p-count"> <div class="p-count">
<b>{{ <b>{{ playlist.count.toLocaleString() + ` Track${playlist.count === 1 ? "" : "s"}` }}</b>
playlist.count.toLocaleString() +
` Track${playlist.count === 1 ? "" : "s"}`
}}</b>
</div> </div>
</div> </div>
</router-link> </router-link>
@@ -54,6 +36,7 @@ defineProps<{
gap: $small; gap: $small;
user-select: none; user-select: none;
height: max-content; height: max-content;
transition: background-color 0.2s ease-out;
.image-grid { .image-grid {
display: grid; display: grid;
@@ -61,7 +44,6 @@ defineProps<{
} }
&:hover { &:hover {
transition: all 0.25s ease;
background-color: $gray4 !important; background-color: $gray4 !important;
background-blend-mode: screen; background-blend-mode: screen;
} }
@@ -79,9 +61,13 @@ defineProps<{
justify-content: flex-end; justify-content: flex-end;
transition: all 0.25s ease; transition: all 0.25s ease;
.p-name {
font-weight: 600;
}
.p-count { .p-count {
opacity: 0.75;
font-size: 0.75rem; font-size: 0.75rem;
color: #ffffffbf;
margin-top: $smaller; margin-top: $smaller;
} }
} }

View File

@@ -8,8 +8,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Playlist } from "@/interfaces";
import PlaylistCard from "@/components/PlaylistsList/PlaylistCard.vue"; import PlaylistCard from "@/components/PlaylistsList/PlaylistCard.vue";
import { Playlist } from "@/interfaces";
defineProps<{ defineProps<{
playlists: Playlist[]; playlists: Playlist[];
@@ -21,7 +21,7 @@ defineProps<{
.playlistcardgroup { .playlistcardgroup {
margin-bottom: 4rem; margin-bottom: 4rem;
h3 { h3 {
margin-left: $small; margin-left: $medium;
} }
} }
</style> </style>

View File

@@ -18,9 +18,9 @@
<script setup lang="ts"> <script setup lang="ts">
import useTabStore from "@/stores/tabs"; import useTabStore from "@/stores/tabs";
import DashBoard from "./Home/Main.vue";
import Queue from "./Queue.vue"; import Queue from "./Queue.vue";
import Search from "./Search/Main.vue"; import Search from "./Search/Main.vue";
import DashBoard from "./Home/Main.vue";
import SearchInput from "./SearchInput.vue"; import SearchInput from "./SearchInput.vue";
const tabs = useTabStore(); 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> </style>

View File

@@ -1,23 +1,14 @@
<template> <template>
<div class="queue-actions"> <div class="queue-actions">
<div class="left"> <div class="left">
<button <button v-if="!onNowPlaying" v-wave class="shuffle-queue action" @click="queue.shuffleQueue">
v-if="!onNowPlaying"
v-wave
class="shuffle-queue action"
@click="queue.shuffleQueue"
>
<ShuffleSvg /> <ShuffleSvg />
<span>Shuffle</span> <span>Shuffle</span>
</button> </button>
<h2 v-else style="margin: 0">Now Playing</h2> <h2 v-else style="margin: 0">Now Playing</h2>
</div> </div>
<div class="right"> <div class="right">
<button <button class="menu" :class="{ 'btn-active': context_showing }" @click="showContextMenu">
class="menu"
:class="{ 'btn-active': context_showing }"
@click="showContextMenu"
>
<OptionsSvg /> <OptionsSvg />
</button> </button>
</div> </div>
@@ -25,9 +16,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue";
import useQueue from "@/stores/queue"; import useQueue from "@/stores/queue";
import useTracklist from "@/stores/queue/tracklist"; import useTracklist from "@/stores/queue/tracklist";
import { ref } from "vue";
import { showQueueContextMenu } from "@/helpers/contextMenuHandler"; import { showQueueContextMenu } from "@/helpers/contextMenuHandler";
@@ -67,7 +58,7 @@ defineProps<{
} }
// hide on screens less than 600px // hide on screens less than 600px
@media screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
display: none; display: none;
} }
} }

View File

@@ -1,11 +1,6 @@
<template> <template>
<div class="right-search"> <div class="right-search">
<TabsWrapper <TabsWrapper :tabs="tabs" :current-tab="currentTab" :tab-content="true" @switchTab="switchTab">
:tabs="tabs"
:current-tab="currentTab"
:tab-content="true"
@switchTab="switchTab"
>
<Tab :name="currentTab" /> <Tab :name="currentTab" />
</TabsWrapper> </TabsWrapper>
</div> </div>
@@ -41,6 +36,10 @@ function switchTab(tab: string) {
.tabheaders { .tabheaders {
padding: 1rem; padding: 1rem;
max-width: 100%;
overflow: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
} }
.input { .input {
@@ -48,7 +47,5 @@ function switchTab(tab: string) {
align-items: center; align-items: center;
position: relative; position: relative;
} }
} }
</style> </style>

View File

@@ -56,8 +56,17 @@ defineEmits<{
#tab-content { #tab-content {
height: 100%; height: 100%;
overflow: scroll; overflow: auto;
overflow-x: hidden; 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 { #right-tabs.tabContent {

View File

@@ -2,19 +2,12 @@
<RouterLink <RouterLink
:to="{ :to="{
name: res_type === 'artist' ? Routes.artist : Routes.album, name: res_type === 'artist' ? Routes.artist : Routes.album,
params: params: res_type === 'artist' ? { hash: item.artisthash || ' ' } : { albumhash: item.albumhash || ' ' },
res_type === 'artist'
? { hash: item.artisthash || ' ' }
: { albumhash: item.albumhash || ' ' },
}" }"
class="top-result-item rounded" class="top-result-item rounded"
> >
<img <img
:src=" :src="res_type === 'artist' ? paths.images.artist.medium + item.image : paths.images.thumb.medium + item.image"
res_type === 'artist'
? paths.images.artist.large + item.image
: paths.images.thumb.large + item.image
"
alt="" alt=""
class="rounded-sm" class="rounded-sm"
:class="{ circular: res_type === 'artist' }" :class="{ circular: res_type === 'artist' }"
@@ -36,10 +29,7 @@
{{ item.trackcount === 1 ? "track" : "tracks" }} {{ item.trackcount === 1 ? "track" : "tracks" }}
</div> </div>
<div v-if="res_type === 'track'" class="artists flex"> <div v-if="res_type === 'track'" class="artists flex">
<ArtistName <ArtistName :artists="item.artists" :albumartists="item.albumartists" />
:artists="item.artists"
:albumartists="item.albumartists"
/>
&nbsp; &nbsp; &nbsp; &nbsp;
{{ formatSeconds(item.duration, true) }} {{ formatSeconds(item.duration, true) }}
</div> </div>
@@ -57,11 +47,7 @@
</button> </button>
<PlayBtn <PlayBtn
:source=" :source="
res_type == 'album' res_type == 'album' ? playSources.album : res_type == 'artist' ? playSources.artist : playSources.track
? playSources.album
: res_type == 'artist'
? playSources.artist
: playSources.track
" "
:album-hash="item.albumhash" :album-hash="item.albumhash"
:album-name="item.title" :album-name="item.title"
@@ -74,22 +60,22 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from "vue";
import { Routes } from "@/router"; import { Routes } from "@/router";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { computed, ref } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import useSearchStore from "@/stores/search";
import { showTrackContextMenu as showContext } from "@/helpers/contextMenuHandler"; 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 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 PlayBtn from "@/components/shared/PlayBtn.vue";
import { playSources } from "@/enums";
const search = useSearchStore(); const search = useSearchStore();
const route = useRoute(); const route = useRoute();
@@ -139,7 +125,7 @@ function showMenu(e: MouseEvent) {
width: 2.5rem; width: 2.5rem;
height: 2.5rem; height: 2.5rem;
opacity: 0; 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 { .type {
font-size: 0.8rem; font-size: 0.8rem;
font-weight: 500;
color: white; color: white;
background-color: $darkblue; background-color: $darkblue;
width: max-content; width: max-content;
@@ -188,6 +175,7 @@ function showMenu(e: MouseEvent) {
.artists { .artists {
font-size: 14px; font-size: 14px;
font-weight: 500;
opacity: 0.8; opacity: 0.8;
} }

View File

@@ -16,8 +16,8 @@
import { Track } from "@/interfaces"; import { Track } from "@/interfaces";
import useQueueStore from "@/stores/queue"; import useQueueStore from "@/stores/queue";
import useSearchStore from "@/stores/search";
import useTracklist from "@/stores/queue/tracklist"; import useTracklist from "@/stores/queue/tracklist";
import useSearchStore from "@/stores/search";
import TrackItem from "@/components/shared/TrackItem.vue"; import TrackItem from "@/components/shared/TrackItem.vue";
@@ -37,7 +37,7 @@ function handlePlay(track: Track) {
margin-bottom: 2rem; margin-bottom: 2rem;
.track-item { .track-item {
padding: $small 1.1rem; padding: $small;
} }
} }
</style> </style>

View File

@@ -2,7 +2,8 @@
<div <div
class="gsearch-input" class="gsearch-input"
@click=" @click="
!settings.use_sidebar && $route.name !== Routes.search && !settings.use_sidebar &&
$route.name !== Routes.search &&
$router.push({ $router.push({
name: Routes.search, name: Routes.search,
params: { page: 'top' }, params: { page: 'top' },
@@ -13,9 +14,7 @@
<div id="ginner" ref="inputRef" tabindex="0"> <div id="ginner" ref="inputRef" tabindex="0">
<button <button
v-auto-animate v-auto-animate
:title=" :title="tabs.current === tabs.tabs.search ? 'back to queue' : 'go to search'"
tabs.current === tabs.tabs.search ? 'back to queue' : 'go to search'
"
:class="{ no_bg: on_nav }" :class="{ no_bg: on_nav }"
@click.prevent="handleButton" @click.prevent="handleButton"
> >
@@ -32,16 +31,16 @@
@blur.prevent="removeFocusedClass" @blur.prevent="removeFocusedClass"
@focus.prevent="addFocusedClass" @focus.prevent="addFocusedClass"
/> />
<div class="clear_input noSelect" :class="{ active: search.query.length > 0 }" @click="clearInput">X</div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue";
import useTabStore from "@/stores/tabs";
import useSearch from "@/stores/search"; import useSearch from "@/stores/search";
import useSettings from "@/stores/settings"; import useSettings from "@/stores/settings";
import useTabStore from "@/stores/tabs";
import { ref } from "vue";
import BackSvg from "@/assets/icons/arrow.svg"; import BackSvg from "@/assets/icons/arrow.svg";
import SearchSvg from "@/assets/icons/search.svg"; import SearchSvg from "@/assets/icons/search.svg";
@@ -56,16 +55,27 @@ const search = useSearch();
const settings = useSettings(); const settings = useSettings();
// HANDLE FOCUS // HANDLE FOCUS
const inputRef = ref<HTMLElement>(); const inputRef = ref<HTMLInputElement | null>(null);
// NOTE: Functions are used because classes are added to the sorrounding element // NOTE: Functions are used because classes are added to the sorrounding element
// and not the input itself. // and not the input itself.
function addFocusedClass() { function addFocusedClass() {
inputRef.value?.classList.add("search-focused"); if (inputRef.value) {
inputRef.value.classList.add("search-focused");
}
} }
function removeFocusedClass() { 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 // @end
@@ -81,7 +91,23 @@ function handleButton() {
} }
</script> </script>
<style>
.clear_search {
/* Style applied when clear_search class is active */
visibility: visible;
cursor: pointer;
}
</style>
<style lang="scss"> <style lang="scss">
.right > .gsearch-input > #ginner > input {
width: 140px;
@include allPhones {
width: 100%;
}
}
.gsearch-input { .gsearch-input {
display: grid; display: grid;
grid-template-columns: 1fr max-content; grid-template-columns: 1fr max-content;
@@ -93,20 +119,28 @@ function handleButton() {
// gap: $small; // gap: $small;
border-radius: 3rem; border-radius: 3rem;
background-color: $gray5; background-color: $gray5;
outline: solid 2px transparent;
transition: outline-color 0.2s ease-out;
button { button {
background: transparent; background: transparent;
border: none; border: none;
width: 3rem; width: 2rem;
height: 2rem;
padding: 0; padding: 0;
margin-left: 4px;
border-radius: 3rem; border-radius: 3rem;
height: 100%;
cursor: pointer; cursor: pointer;
flex-shrink: 0;
&:hover { &:hover {
transition: all 0.2s ease; transition: all 0.2s ease;
background-color: $gray2; background-color: $gray2;
} }
@include allPhones {
display: none;
}
} }
button.no_bg { button.no_bg {
@@ -119,16 +153,58 @@ function handleButton() {
line-height: 2.25rem; line-height: 2.25rem;
color: inherit; color: inherit;
font-size: 14px; font-size: 14px;
font-weight: 500;
background-color: transparent; background-color: transparent;
outline: none; outline: none;
appearance: none;
text-overflow: ellipsis;
@media screen and (max-width: 500px) { @include allPhones {
width: 7rem; 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 { .search-focused {
outline: solid 2px #fff !important; outline: solid 2px #fff;
@include allPhones {
outline: none;
}
} }
</style> </style>

View File

@@ -1,92 +1,89 @@
<template> <template>
<div class="aboutswingmusic"> <div class="aboutswingmusic">
Swing Music is a labor of love developed by <div class="version">Swing Music v{{ settings.version }}</div>
<a href="https://github.com/cwilvx" target="_blank">@<u>cwilvx</u></a> on Swing Music is a labor of love developed by
GitHub. If you like this software, please consider donating to support
development and giving it a star on GitHub. <br /><br /><br />
<div class="flex">
<a href="https://swingmusic.vercel.app/support-us.html" target="_blank">
<button>Donate</button></a
><a href="https://github.com/cwilvx/swingmusic" target="_blank"
><button>Star on Github</button></a
>
<a href="https://github.com/cwilvx" target="_blank"
><button>Follow @cwilvx on Github</button></a
>
</div>
<br /><br />
If you encounter any bugs, please open an issue on GitHub. If you would like
to get involved in development, start with the
<a
href="https://github.com/cwilvx/swingmusic/blob/master/.github/contributing.md"
target="_blank"
><u>contribution guidelines</u></a
>.
<br /><br /><br />
<div class="flex">
<a
href="https://github.com/cwilvx/swingmusic/issues/new/choose"
target="_blank"
>
<button>Open an Issue</button>
</a>
<a
href="https://github.com/cwilvx/swingmusic/blob/master/.github/contributing.md"
target="_blank"
><button>Contribute</button></a
>
</div>
<br /><br />
Hope you enjoy using Swing Music as much as I enjoy building it.
<br /><br />
<div class="hireme rounded">
<h2>Hire me</h2>
If you like my work, and would like me to work for you or your company,
I'm open to offers. Feel free to reach out to me via email.
<br /><br />
<div class="flex">
<a <a
href="mailto:geoffreymungai45@gmail.com?subject=Job Offer&body=Hi Mungai, href="https://github.com/cwilvx"
" target="_blank"
target="_blank" >@<u>cwilvx</u></a
><button>Write Email</button></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>
</div>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
import useSettings from '@/stores/settings'
const settings = useSettings()
</script>
<style lang="scss"> <style lang="scss">
.aboutswingmusic { .aboutswingmusic {
padding: $small; padding: $small;
margin-top: 2rem;
.flex { .version {
gap: 1rem; font-size: 1.5rem;
} font-weight: bold;
margin-bottom: $small;
border-bottom: solid 1px $separator;
padding-bottom: 1rem;
}
.hireme { .links .flex {
background-color: #ffffff; margin-top: $small;
background-image: linear-gradient( gap: 1rem;
37deg, }
#bfeaf0 0%,
#ffffff00 50%,
#a7dcff 100%
);
padding: 1rem;
color: $black;
button { .contact button {
background-color: $blue; background-color: $blue;
} }
h2 { h2 {
margin-top: $small; margin-top: 0;
margin-bottom: $smaller;
} }
}
} }
</style> </style>

View File

@@ -1,78 +1,92 @@
<template> <template>
<div class="list-items"> <div class="list-items">
<div v-for="i in items" :key="i.title" class="option-list-item"> <div
<div class="with-icon"> v-for="i in items"
<component :is="icon_" /> :key="i.title"
<div class="text ellip"> class="option-list-item"
{{ i.title }} >
<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>
<div class="icon" @click="i.action">
<DeleteSvg />
</div>
</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> </template>
<script setup lang="ts"> <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<{ const props = defineProps<{
items: { items: {
title: string; title: string
action: () => void; action: () => void
}[]; }[]
icon: "folder"; icon: 'folder'
}>(); }>()
function getIcon() { function getIcon() {
switch (props.icon) { switch (props.icon) {
case "folder": case 'folder':
return FolderSvg; return FolderSvg
default: default:
return FolderSvg; return FolderSvg
} }
} }
const icon_ = getIcon(); const icon_ = getIcon()
</script> </script>
<style lang="scss"> <style lang="scss">
.setting-item.is-list { .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 { .option-list-item {
background-color: $color; padding: $small 1rem;
border-radius: $small; display: flex;
margin-top: 1rem; justify-content: space-between;
overflow: hidden; align-items: center;
padding: 1rem 0; gap: 1rem;
}
.option-list-item { svg {
padding: $small 1rem; width: 1.25rem;
display: flex; }
justify-content: space-between;
align-items: center;
gap: 1rem;
.with-icon { .with-icon {
display: flex; display: flex;
gap: $small; gap: $small;
align-items: center; align-items: center;
font-family: "SF Mono", monospace; font-family: "SF Mono", monospace;
font-weight: 500;
font-size: 0.9rem; font-size: 0.9rem;
} }
.icon { .icon {
cursor: pointer; cursor: pointer;
}
} }
}
} }
</style> </style>

View File

@@ -31,6 +31,8 @@ function submit(action: "plus" | "minus") {
.number { .number {
text-align: center; text-align: center;
font-weight: 500;
font-variant-numeric: tabular-nums;
} }
button { button {

View File

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

View File

@@ -36,13 +36,16 @@ const optionsWithActive = computed(() => {
.setting-select { .setting-select {
display: flex; display: flex;
background-color: $gray3; background-color: $gray3;
margin-left: 8px;
.option { .option {
font-weight: 500;
padding: 0.5rem; padding: 0.5rem;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
min-width: 4rem; min-width: 4rem;
text-align: center; text-align: center;
transition: background-color 0.2s ease-out;
} }
.option.active { .option.active {

View File

@@ -1,18 +1,9 @@
<template> <template>
<div class="artist-separators-input"> <div class="artist-separators-input">
<form @submit.prevent="submitInput"> <form @submit.prevent="submitInput">
<input <input ref="separatorinput" type="search" class="rounded-sm" @input="updateInput" />
ref="separatorinput"
type="search"
class="rounded-sm"
@input="updateInput"
/>
<div class="preview"> <div class="preview">
<span <span v-for="p in preview_items" :key="p" class="circular" :class="!default_input_list.includes(p) ? 'new' : ''"
v-for="p in preview_items"
:key="p"
class="circular"
:class="!default_input_list.includes(p) ? 'new' : ''"
><b> ><b>
{{ p }} {{ p }}
</b></span </b></span
@@ -68,9 +59,7 @@ function submitInput() {
} }
const preview_items = computed(() => splitInput(input.value)); const preview_items = computed(() => splitInput(input.value));
const default_input = computed(() => const default_input = computed(() => (props.default ? props.default.join(", ") : ""));
props.default ? props.default.join(", ") : ""
);
onMounted(() => { onMounted(() => {
const text = props.default.join(", "); const text = props.default.join(", ");
@@ -95,6 +84,7 @@ onMounted(() => {
background-color: $gray5; background-color: $gray5;
color: white; color: white;
font-size: 1rem; font-size: 1rem;
appearance: none;
} }
.preview { .preview {

View File

@@ -1,6 +1,6 @@
<template> <template>
<div v-if="group.show_if ? group.show_if() : true" class="settingsgroup"> <div v-if="group && (group.show_if ? group.show_if() : true)" class="settingsgroup">
<div v-if="group.title || group.desc" class="info"> <!-- <div v-if="group.title || group.desc" class="info">
<h4 v-if="group.title"> <h4 v-if="group.title">
{{ group.title {{ group.title
}}<span v-if="group.experimental" class="badge experimental circular"> }}<span v-if="group.experimental" class="badge experimental circular">
@@ -8,12 +8,10 @@
</span> </span>
</h4> </h4>
<div v-if="group.desc" class="desc">{{ group.desc }}</div> <div v-if="group.desc" class="desc">{{ group.desc }}</div>
</div> </div> -->
<div class="setting rounded pad-lg"> <div class="setting pad-lg">
<div <div
v-for="(setting, index) in group.settings.filter((s) => v-for="(setting, index) in group.settings.filter((s) => (s.show_if ? s.show_if() : true))"
s.show_if ? s.show_if() : true
)"
:key="index" :key="index"
class="setting-item" class="setting-item"
:class="{ :class="{
@@ -21,31 +19,18 @@
'is-list': setting.type === SettingType.root_dirs, 'is-list': setting.type === SettingType.root_dirs,
}" }"
> >
<div <div class="text" @click="setting.defaultAction ? setting.defaultAction() : setting.action()">
class="text"
@click="
setting.defaultAction ? setting.defaultAction() : setting.action()
"
>
<div class="title"> <div class="title">
<span class="ellip"> <span class="ellip">
{{ setting.title }} {{ setting.title }}
<span <span v-if="setting.experimental" class="badge experimental circular">
v-if="setting.experimental"
class="badge experimental circular"
>
{{ setting.experimental ? "experimental" : "" }} {{ setting.experimental ? "experimental" : "" }}
</span> </span>
<span v-if="setting.new" class="badge new circular"> <span v-if="setting.new" class="badge new circular">
{{ setting.new ? "new" : "" }} {{ setting.new ? "new" : "" }}
</span> </span>
</span> </span>
<button <button v-if="setting.type == SettingType.root_dirs" @click="setting.action"><ReloadSvg /> rescan</button>
v-if="setting.type == SettingType.root_dirs"
@click="setting.action"
>
<ReloadSvg /> rescan
</button>
</div> </div>
<div v-if="setting.desc" class="desc"> <div v-if="setting.desc" class="desc">
{{ setting.desc }} {{ setting.desc }}
@@ -63,10 +48,7 @@
:source="setting.state !== null ? setting.state : () => ''" :source="setting.state !== null ? setting.state : () => ''"
:setter-fn="setting.action" :setter-fn="setting.action"
/> />
<button <button v-if="setting.type === SettingType.button" @click="setting.action">
v-if="setting.type === SettingType.button"
@click="setting.action"
>
{{ setting.button_text && setting.button_text() }} {{ setting.button_text && setting.button_text() }}
</button> </button>
<LockedNumberInput <LockedNumberInput
@@ -80,7 +62,7 @@
/> />
</div> </div>
<QuickActions v-if="setting.type == SettingType.quick_actions" /> <!-- Custom components -->
<List <List
v-if="setting.type === SettingType.root_dirs" v-if="setting.type === SettingType.root_dirs"
icon="folder" icon="folder"
@@ -91,22 +73,28 @@
:submit="setting.action" :submit="setting.action"
:default="setting.state ? setting.state() : []" :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> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { SettingType } from "@/settings/enums";
import { SettingGroup } from "@/interfaces/settings"; 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 ReloadSvg from "@/assets/icons/reload.svg";
import QuickActions from "./Components/QuickSettings.vue"; import List from "./Components/List.vue";
import SeparatorsInput from "./Components/SeparatorsInput.vue";
import LockedNumberInput from "./Components/LockedNumberInput.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<{ defineProps<{
group: SettingGroup; group: SettingGroup;
@@ -116,30 +104,10 @@ defineProps<{
<style lang="scss"> <style lang="scss">
.settingsgroup { .settingsgroup {
display: grid; display: grid;
// grid-template-columns: 20rem 1fr;
gap: $small; gap: $small;
margin-top: 2rem; margin-top: 2rem;
border-bottom: solid 1px $gray;
padding-bottom: 2rem; 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 { &:first-child {
margin-top: 0; margin-top: 0;
} }
@@ -156,10 +124,11 @@ defineProps<{
.desc { .desc {
opacity: 0.5; opacity: 0.5;
font-size: 0.8rem; font-size: 0.8rem;
font-weight: 500;
} }
.setting { .setting {
background-color: $gray; // background-color: $gray;
.inactive { .inactive {
opacity: 0.5; opacity: 0.5;
@@ -175,7 +144,7 @@ defineProps<{
.setting-item { .setting-item {
user-select: none; user-select: none;
border-bottom: solid 1px $gray5; border-bottom: solid 1px $gray5;
padding: $medium 0; padding: 1.25rem 0;
.options { .options {
margin: auto 0; margin: auto 0;
@@ -186,11 +155,16 @@ defineProps<{
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: self-start; align-items: self-start;
width: 100%;
.title { .title {
font-weight: 500;
margin: auto 0; margin: auto 0;
display: flex; display: flex;
align-items: center;
justify-content: space-between; justify-content: space-between;
flex-wrap: wrap;
gap: $small;
width: 100%; width: 100%;
button > svg { button > svg {
@@ -212,5 +186,18 @@ defineProps<{
border-bottom: none; border-bottom: none;
padding-bottom: 0; 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> </style>

View File

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

View File

@@ -1,119 +1,150 @@
<template> <template>
<div v-if="modal.visible" class="modal"> <!-- 👇 login modal should not be dismissable -->
<div class="bg" @click="modal.hideModal"></div>
<div <div
v-motion-slide-top v-if="modal.visible || modal.component == ModalOptions.login"
class="m-content rounded" class="modal"
:style="{
maxWidth:
modal.component == modal.options.setRootDirs ? '56rem' : '30rem',
}"
> >
<div class="heading">{{ modal.title }}</div> <div
<NewPlaylist class="bg"
v-if="modal.component == modal.options.newPlaylist" @click="modal.hideModal"
v-bind="modal.props" ></div>
@hideModal="hideModal" <div
@setTitle="setTitle" v-motion-slide-top
/> class="m-content rounded"
<UpdatePlaylist :class="{
v-if="modal.component == modal.options.updatePlaylist" settings: modal.component == modal.options.settings,
v-bind="modal.props" authlogin: modal.component == modal.options.login,
@hideModal="hideModal" }"
@setTitle="setTitle" :style="{
/> maxWidth:
<WelcomeModal v-if="modal.component == modal.options.welcome" /> modal.component == modal.options.setRootDirs
<div v-if="modal.component == modal.options.deletePlaylist"> ? '56rem'
<ConfirmModal : '30rem',
:text="'Are you sure you want to permanently delete this playlist?'" }"
:cancel-action="modal.hideModal" >
:confirm-action="deletePlaylist" <!-- TODO: MOVE MAX WIDTH TO CLASS -->
/> <div class="heading">{{ modal.title }}</div>
</div> <AuthLogin v-if="modal.component == modal.options.login" />
<SetRootDirs <NewPlaylist
v-if="modal.component == modal.options.setRootDirs" v-if="modal.component == modal.options.newPlaylist"
@hideModal="hideModal" v-bind="modal.props"
/> @hideModal="hideModal"
<RootDirsPrompt @setTitle="setTitle"
v-if="modal.component == modal.options.rootDirsPrompt" />
@hideModal="hideModal" <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>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { deletePlaylist as delPlaylist } from "@/requests/playlists"; import { deletePlaylist as delPlaylist } from '@/requests/playlists'
import { useRouter } from "vue-router"; import useModalStore, { ModalOptions } from '@/stores/modal'
import useModalStore from "@/stores/modal"; import { useRouter } from 'vue-router'
import WelcomeModal from "./WelcomeModal.vue"; import AuthLogin from './modals/AuthLogin.vue'
import ConfirmModal from "./modals/ConfirmModal.vue"; import ConfirmModal from './modals/ConfirmModal.vue'
import NewPlaylist from "./modals/NewPlaylist.vue"; import NewPlaylist from './modals/NewPlaylist.vue'
import RootDirsPrompt from "./modals/RootDirsPrompt.vue"; import RootDirsPrompt from './modals/RootDirsPrompt.vue'
import SetRootDirs from "./modals/SetRootDirs.vue"; import SetRootDirs from './modals/SetRootDirs.vue'
import UpdatePlaylist from "./modals/updatePlaylist.vue"; import UpdatePlaylist from './modals/updatePlaylist.vue'
import Settings from './modals/Settings.vue'
const modal = useModalStore(); const modal = useModalStore()
const router = useRouter(); const router = useRouter()
function setTitle(title: string) { function setTitle(title: string) {
modal.setTitle(title); modal.setTitle(title)
} }
function hideModal() { function hideModal() {
modal.hideModal(); modal.hideModal()
} }
function deletePlaylist() { function deletePlaylist() {
delPlaylist(modal.props.pid) delPlaylist(modal.props.pid)
.then(() => modal.hideModal()) .then(() => modal.hideModal())
.then(() => router.back()); .then(() => router.back())
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.modal { .modal {
position: fixed; position: fixed;
z-index: 20; z-index: 20;
height: 100vh; height: 100vh;
width: 100vw; width: 100vw;
display: grid; display: grid;
place-items: center; place-items: center;
input[type="search"] { input[type='search'] {
margin: $small 0; margin: $small 0;
border: none; border: none;
background-color: $gray5; background-color: $gray5;
color: #fff; color: #fff;
width: 100%; width: 100%;
padding: 0.5rem; padding: $small $medium;
font-size: 14px; font-size: 14px;
outline: none; outline: none;
height: 2.75rem !important; height: 2.75rem !important;
} }
.bg { .bg {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: rgba(12, 12, 12, 0.767); background-color: rgba(22, 22, 22, 0.753);
} // backdrop-filter: blur(5px);
}
.m-content {
width: calc(100% - 4rem); .m-content {
max-height: 40rem; width: calc(100% - 4rem);
padding: 2rem 1.25rem; max-height: 40rem;
position: relative; padding: 2rem 1.25rem;
background-color: $black; position: relative;
background-color: $black;
@include smallPhone {
width: calc(100% - 2rem); @include largePhones {
padding: 2rem 1rem; 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> </style>

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

View File

@@ -19,15 +19,16 @@
import { onMounted } from "vue"; import { onMounted } from "vue";
import { import {
createNewPlaylist,
saveAlbumAsPlaylist, saveAlbumAsPlaylist,
saveArtistAsPlaylist, saveArtistAsPlaylist,
saveFolderAsPlaylist,
saveTrackAsPlaylist, saveTrackAsPlaylist,
} from "@/requests/playlists"; } 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 { NotifType, Notification } from "@/stores/notification";
import usePlaylistStore from "@/stores/pages/playlists";
import useTracklist from "@/stores/queue/tracklist";
const props = defineProps<{ const props = defineProps<{
trackhash?: string; trackhash?: string;
@@ -41,9 +42,7 @@ const props = defineProps<{
const store = usePlaylistStore(); const store = usePlaylistStore();
onMounted(() => { onMounted(() => {
const input_elem = document.getElementById( const input_elem = document.getElementById("modal-playlist-name-input") as HTMLInputElement;
"modal-playlist-name-input"
) as HTMLInputElement;
input_elem.focus(); input_elem.focus();
input_elem.value = props.playlist_name || ""; input_elem.value = props.playlist_name || "";
}); });
@@ -160,6 +159,7 @@ function create(e: Event) {
label { label {
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 500;
color: $gray1; color: $gray1;
} }
@@ -173,7 +173,6 @@ function create(e: Event) {
width: 8rem; width: 8rem;
padding: 1.25rem; padding: 1.25rem;
// font-weight: normal; // font-weight: normal;
transition: all 0.25s ease-out;
background-color: $white; background-color: $white;
color: $black; color: $black;

View File

@@ -2,13 +2,7 @@
<div class="root-dirs-prompt"> <div class="root-dirs-prompt">
<h3 class="t-center">Where do you want to look for music?</h3> <h3 class="t-center">Where do you want to look for music?</h3>
<div class="options-group"> <div class="options-group">
<div <div v-for="option in options" :key="option.id" v-motion-slide-bottom class="option" @click="option.action()">
v-for="option in options"
:key="option.id"
v-motion-slide-bottom
class="option"
@click="option.action()"
>
<b>{{ option.title }}</b> <b>{{ option.title }}</b>
<div class="info">{{ option.info }}</div> <div class="info">{{ option.info }}</div>
</div> </div>
@@ -19,9 +13,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { addRootDirs, getRootDirs } from "@/requests/settings/rootdirs";
import useModalStore from "@/stores/modal"; import useModalStore from "@/stores/modal";
import useSettingsStore from "@/stores/settings"; import useSettingsStore from "@/stores/settings";
import { addRootDirs, getRootDirs } from "@/requests/settings/rootdirs";
const settings = useSettingsStore(); const settings = useSettingsStore();
@@ -64,8 +58,6 @@ onMounted(() => {
<style lang="scss"> <style lang="scss">
.root-dirs-prompt { .root-dirs-prompt {
height: 14rem;
.option { .option {
padding: 1.25rem; padding: 1.25rem;
border-radius: $small; border-radius: $small;
@@ -73,6 +65,7 @@ onMounted(() => {
background-color: #4e4b4b3f; background-color: #4e4b4b3f;
margin-top: 1.25rem; margin-top: 1.25rem;
cursor: pointer; cursor: pointer;
transition: background-color 0.2s ease-out;
&:hover { &:hover {
background-color: $darkblue; background-color: $darkblue;
@@ -81,6 +74,7 @@ onMounted(() => {
.info { .info {
margin-top: $smaller; margin-top: $smaller;
font-size: small; font-size: small;
font-weight: 500;
} }
} }
} }

View File

@@ -2,12 +2,14 @@
<br /><br /> <br /><br />
<div style="position: relative"> <div style="position: relative">
<div id="bread-nav" class="bread-nav rounded-sm"> <div id="bread-nav" class="bread-nav rounded-sm">
&nbsp;&nbsp;<span @click="fetchDirs('$root')">$root</span &nbsp;&nbsp;<span @click="fetchDirs('$root')">$root</span>&nbsp;&nbsp;<BreadCrumbNav
>&nbsp;&nbsp;<BreadCrumbNav :sub-paths="subPaths" @navigate="fetchDirs" /> :sub-paths="subPaths"
@navigate="fetchDirs"
/>
</div> </div>
<div class="set-root-dirs-browser"> <div class="set-root-dirs-browser">
<h4 v-if="no_more_dirs"> <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. location.
</h4> </h4>
<div class="scrollable"> <div class="scrollable">
@@ -16,21 +18,15 @@
v-for="dir in dirs" v-for="dir in dirs"
:key="dir.name" :key="dir.name"
:folder="dir" :folder="dir"
:is_checked=" :is_checked="selected.filter((p) => p == dir.path).length > 0 ? true : false"
selected.filter((p) => p == dir.path).length > 0 ? true : false
"
@navigate="fetchDirs(dir.path)" @navigate="fetchDirs(dir.path)"
@check="handleCheck(dir.path)" @check="handleCheck(dir.path)"
/> />
</div> </div>
</div> </div>
<div class="buttons"> <div class="buttons">
<button class="btn-active select-here" @click="selectHere"> <button class="btn-active select-here" @click="selectHere">Add this folder</button>
Add this folder <button class="btn-active finish" @click="submitFolders">Add all checked ({{ getNewDirs().length }})</button>
</button>
<button class="btn-active finish" @click="submitFolders">
Add all checked ({{ getNewDirs().length }})
</button>
</div> </div>
</div> </div>
</div> </div>
@@ -39,11 +35,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, Ref, ref } from "vue"; import { onMounted, Ref, ref } from "vue";
import { import { addRootDirs, getFolders, getRootDirs } from "@/requests/settings/rootdirs";
addRootDirs,
getFolders,
getRootDirs,
} from "@/requests/settings/rootdirs";
import { Folder, subPath } from "@/interfaces"; import { Folder, subPath } from "@/interfaces";
import useSettingsStore from "@/stores/settings"; import useSettingsStore from "@/stores/settings";
@@ -132,27 +124,34 @@ onMounted(() => {
<style lang="scss"> <style lang="scss">
.bread-nav { .bread-nav {
background-color: $gray4; background-color: $gray4;
padding: $small; padding: $smaller;
padding-right: 0;
width: max-content; width: max-content;
margin-bottom: 1rem; margin-bottom: 1rem;
position: absolute; position: absolute;
top: -3.25rem; top: -3.25rem;
max-width: calc(100% - 2rem); max-width: calc(100% - 2rem);
overflow-x: scroll; max-width: 100%;
scrollbar-width: none; overflow: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
display: flex; display: flex;
align-items: center; align-items: center;
&::-webkit-scrollbar {
display: none;
}
span { span {
cursor: pointer; cursor: pointer;
} }
} }
.bread-nav {
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.bottom-text { .bottom-text {
position: absolute; position: absolute;
font-size: small; font-size: small;
@@ -169,6 +168,11 @@ onMounted(() => {
grid-template-rows: 1fr max-content; grid-template-rows: 1fr max-content;
gap: 1.25rem; gap: 1.25rem;
.folder_icon {
vertical-align: 2px;
margin-right: 4px;
}
.scrollable { .scrollable {
overflow-x: hidden; overflow-x: hidden;
height: 100%; height: 100%;
@@ -182,13 +186,15 @@ onMounted(() => {
overflow: hidden; overflow: hidden;
} }
overflow-y: scroll; overflow-y: auto;
scrollbar-gutter: stable; scrollbar-gutter: stable;
-webkit-overflow-scrolling: touch;
} }
.buttons { .buttons {
display: flex; display: flex;
justify-content: center; justify-content: center;
flex-wrap: wrap;
gap: $medium; gap: $medium;
margin-right: 1rem; margin-right: 1rem;
margin-bottom: -$medium; margin-bottom: -$medium;
@@ -199,17 +205,31 @@ onMounted(() => {
} }
button { button {
font-weight: normal; font-weight: 500;
padding: 0 1rem; padding: 0 1rem;
font-variant-numeric: tabular-nums;
} }
} }
.f-item { .f-item {
background-color: $gray5; background-color: $gray5;
transition: background-color 0.2s ease-out;
&:hover { &:hover {
background-color: $gray3; background-color: $gray3;
} }
> svg {
transition: color 0.2s ease-out;
}
}
.f-item.selected {
background-color: #234ece;
}
.f-item.selected > svg {
color: $white;
} }
} }
</style> </style>

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

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