Compare commits

...

93 Commits

Author SHA1 Message Date
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
mungai-njoroge
feb35afa9a fix: clicking on search input taking you to the
+ add an unused library numbers component
+ add fav artists to browse section
2024-02-16 21:32:57 +03:00
mungai-njoroge
6b925a0434 add expand icon to bottom bar thumbnail
+ remove options button from nav in now playing page
2024-02-12 21:48:21 +03:00
mungai-njoroge
4211ccc685 add no sidebar layout
+ add search input on the top nav
+ scroll to top on page navigation
+ move folder list view to settings page
+ remove close button from modals
+ change notification background
+ add fav tracks to home>browse
+ redesign modal inputs and buttons
+ show time on recent items cards on hover (homepage)
+ make nav buttons circular
+ move homepage browse group to bottom of page
+ reduce context menu paddings
+ fix: artist header artist image gradient
+ fix: card size calculation with a contentresizer element
+ fix: next track not starting at 0 duration when progressbar is focused
+ fix: track not pausing when timer is set off when silence thing is on
+ refactor: break down nav bar into multiple components
+ enhancement: make queue context menu more verbose
+ code enhancements
2024-02-12 00:44:05 +03:00
mungai-njoroge
9c982283fb move lyrics to separate route 2024-01-18 23:53:50 +03:00
mungai-njoroge
888958db54 fix: volume not carrying over new track with no crossfade
+ fix: move next timer not being reset on cue track
2024-01-18 15:44:43 +03:00
mungai-njoroge
cb3e9ec2cd fix: pwa window color on latest chrome 2024-01-18 14:11:33 +03:00
mungai-njoroge
f7e604f11b Merge branch 'master' of github.com:cwilvx/swingmusic-client 2024-01-12 19:40:47 +03:00
mungai-njoroge
1b36b96239 fix track logging 2024-01-12 19:40:39 +03:00
Mungai Njoroge
ee3ef6c1e6 Merge pull request #22 from tpatchg/patch-1
Update QuickSettings.vue for spelling
2024-01-12 08:17:46 +03:00
tpatchg
83bd063bea Update QuickSettings.vue
Spelling Fix
2024-01-10 23:06:45 -06:00
mungai-njoroge
12531edec9 Merge branch 'master' of github.com:cwilvx/swingmusic-client 2024-01-09 15:02:30 +03:00
Mungai Njoroge
7edd501d0f Merge pull request #21 from swing-opensource/gapless
v1.4.6: Silence removal, Crossfade and others
2024-01-09 15:01:52 +03:00
mungai-njoroge
c7036b3a5d fix: enable typing to search on mobile view 2024-01-09 14:29:48 +03:00
mungai-njoroge
3075517af9 add silence skip to quick settings 2024-01-08 17:23:46 +03:00
mungai-njoroge
e8f0bc6b8b add quick actions section in settings
+ set CTRL + B to toggle sidebar
2024-01-07 17:20:50 +03:00
mungai-njoroge
ded3a48e0b extract crossfade into separate file 2024-01-05 01:01:41 +03:00
mungai-njoroge
a04a3c4fe4 fix: queue save as playlist 2024-01-04 20:07:45 +03:00
mungai-njoroge
4aaf70ba68 add context menu option to remove track from queue 2024-01-04 10:53:54 +03:00
mungai-njoroge
fa176cb027 remove queue is too short for shuffle warning 2024-01-04 10:45:55 +03:00
mungai-njoroge
c4ce344487 Add shuffle to bottom bar 2024-01-04 10:43:30 +03:00
mungai-njoroge
f2c7cccba1 increase playlist list grid width 2024-01-04 10:30:34 +03:00
mungai-njoroge
901406a337 respect use_crossfade in preloaded track move forward 2024-01-04 01:38:16 +03:00
mungai-njoroge
4b48b13561 add switch to explictly turn on crossfade
~ makes it possible to provide a default crossfade duration
+ hide the experimental flag on chromium browsers
2024-01-02 00:36:46 +03:00
mungai-njoroge
10b29f6349 rewrite tracker to use keys
+ fix tracker throttle function not working
2024-01-01 22:34:57 +03:00
mungai-njoroge
b24d833d12 add search on social to track context menu 2024-01-01 21:02:51 +03:00
mungai-njoroge
9004c02898 fix track play next
+ fix no repeat
2024-01-01 20:40:04 +03:00
mungai-njoroge
409edba74c add audio settings group
+ add components for crossfade duration
2024-01-01 20:18:08 +03:00
mungai-njoroge
2f1c07f786 disable crossfade if duration < 1000 2023-12-27 21:12:54 +03:00
mungai-njoroge
278e73745a rewrite add tracks to queue methods to use the insertAt method
+ clear timeout on seek
2023-12-27 20:23:56 +03:00
mungai-njoroge
3652432e0b + fix crossfade
+ move silence fetch to worker
+ disablecrossfade on manually triggered track plays
+ move remove track from queue to tracklist
+ fix tracker not working by reassigning event listeners
2023-12-27 19:38:19 +03:00
mungai-njoroge
67ad2ef206 Fix file count display in FolderItem.vue and add
new routes in Browse.vue
2023-12-23 18:53:34 +03:00
mungai-njoroge
f2e157a746 gapless initial try 2023-12-22 11:20:51 +03:00
mungai-njoroge
606515ffd5 hide recent items on homepage if they don't exist 2023-12-19 09:27:44 +03:00
mungai-njoroge
6ff67e5f94 resize song items grid size 2023-12-14 10:03:26 +03:00
mungai-njoroge
5f037fc647 add fetch callbacks to sidebar search 2023-12-14 09:52:43 +03:00
mungai-njoroge
d1c62f701e fix broken layout on favorite artists & albums page 2023-12-14 09:51:32 +03:00
mungai-njoroge
b2bef03373 remove see all from top search results 2023-12-14 09:23:14 +03:00
mungai-njoroge
49fe308da0 fix no bottom album rows on album page
+ add store resetter to artist page
2023-12-12 19:54:20 +03:00
mungai-njoroge
bce2772dcb show favorite tracks index in reverse order 2023-12-11 17:40:50 +03:00
mungai-njoroge
dd0b9d6d61 add store resetter methods
+ hide edit button on recently added playlist
2023-12-11 13:29:27 +03:00
mungai-njoroge
1e90298f72 fix padding left on tab headers on search page 2023-12-11 08:31:25 +03:00
mungai-njoroge
7f4385f8cb reduce cardScroller padding
+ spice up hire me
2023-12-11 08:27:21 +03:00
mungai-njoroge
48f2a73291 update settings text 2023-12-10 18:33:34 +03:00
mungai-njoroge
8cf33089a2 default to no sidebar 2023-12-10 18:15:03 +03:00
mungai-njoroge
c16be33d40 fix items glitch on search tracks page 2023-12-10 18:01:33 +03:00
mungai-njoroge
a9691a2a25 rewrite search grid pages with card row virtual list
+ rewrite sidebar tabs with infinite scroll and card row virtual lists
2023-12-10 16:22:43 +03:00
mungai-njoroge
e88e6dcc2d add favorites on recently played
+ create track logger timestamp on track play start
2023-12-10 13:28:52 +03:00
mungai-njoroge
00ffbdbc42 fetch app version from server 2023-12-09 22:16:31 +03:00
mungai-njoroge
97f348daf2 fix play from track card on favorites page 2023-12-09 17:52:18 +03:00
mungai-njoroge
d0f47b4504 move folder to top of browse list 2023-12-09 09:16:53 +03:00
mungai-njoroge
2db6bfebcf move browse to top of homepage
+ move it to separate component
2023-12-08 18:49:53 +03:00
mungai-njoroge
286003cf27 add album and artist list pages
+ fix number localization
+ a lot other things
2023-12-08 09:18:13 +03:00
mungai-njoroge
d27c61c7ce add google, yt, wikipedia and lastfm to album & artist search
+ persist tracker store
+ add generic header to favorites page
+ move lyrics components to Nowplaying page folder
+ update version
2023-12-06 11:05:05 +03:00
mungai-njoroge
c56ee65a73 fix padding right and move card scroller 2023-12-05 15:48:00 +03:00
mungai-njoroge
d3c0c7c596 rewrite appgrid to fix scrollbar paddng-issues 2023-12-04 20:50:34 +03:00
mungai-njoroge
e7fec30b7c fix card size issues on search page 2023-12-04 14:16:44 +03:00
mungai-njoroge
da36f8d7dd Redesign discography with generic header and tabs
+ extract settings tabs into generic tabs
2023-12-04 13:48:52 +03:00
mungai-njoroge
63de7a6613 Remove unused vue files after refactor 2023-12-03 23:31:16 +03:00
mungai-njoroge
b14c814c55 add see all link to card scroller 2023-12-03 20:25:54 +03:00
mungai-njoroge
7f8293e691 migrate horizontal scrollers to use a single component
+ rewrite album view with the dynamic scroller
2023-12-03 20:15:58 +03:00
mungai-njoroge
59a27d4489 log track on end event
+ add recently played to homepage
+ redesign playlist card to match others
+ update pinia persistented state package
2023-12-03 18:27:20 +03:00
mungai-njoroge
4e59e73ec5 maybe: fix scrolling on horizontal card lists 2023-12-03 12:58:19 +03:00
mungai-njoroge
df1c909fce add greetings and misc 2023-12-02 12:43:06 +03:00
mungai-njoroge
3aa0aebfc6 add recent items section in the homepage 2023-12-02 01:58:37 +03:00
mungai-njoroge
afdbb0dbb5 set up track logging via web worker 2023-12-01 10:55:47 +03:00
mungai-njoroge
9fc37034a6 lint code 2023-11-26 15:37:53 +03:00
mungai-njoroge
53fc0c6656 fix play from album 2023-11-26 15:33:22 +03:00
mungai-njoroge
cfe57b788b fix track add to queue 2023-11-25 14:30:26 +03:00
mungai-njoroge
3b55cc1c2c add about page in settings 2023-11-25 12:03:29 +03:00
mungai-njoroge
47c41be79a convert player into setup store 2023-11-25 02:56:31 +03:00
mungai-njoroge
cfc9c2632b break store into 2 and refactor 2023-11-24 00:10:41 +03:00
mungai-njoroge
c0cb2791d0 fix #20 2023-11-14 20:54:23 +03:00
mungai-njoroge
6520b686a3 release v1.4.0 2023-11-14 14:34:59 +03:00
mungai-njoroge
4041c8f588 add context option to search albums and artists on spotify, etc. 2023-11-12 23:21:11 +03:00
mungai-njoroge
da63f481c6 replace now playing with up next on now playing page
+ fix context menu flag watcher bug
2023-11-08 11:06:29 +03:00
mungai-njoroge
a5bcaadafb fix broken settings on ngrok 2023-11-07 23:54:51 +03:00
mungai-njoroge
19142b284a hook plugins settings to settings store
+ misc
2023-11-07 17:14:19 +03:00
mungai-njoroge
511fa58d66 fix disappearing artist separator input on settings page 2023-11-07 10:12:56 +03:00
mungai-njoroge
2fbac120b2 rewrite settings page to use vue router 2023-11-07 09:26:41 +03:00
mungai-njoroge
dea36af5cc clean lyrics plugin
+ update now playing page links to replace current route
+ remove versions from lyrics plugin
+ misc, etc.
2023-11-07 01:39:17 +03:00
mungai-njoroge
81d28461f6 Add settings for lyrics plugin 2023-11-03 17:16:45 +03:00
mungai-njoroge
92302e87e5 move plugin to store and extract dropdown 2023-11-03 16:13:12 +03:00
mungai-njoroge
1fd30b4ac3 add ui for lyrics plugin 2023-11-03 10:40:28 +03:00
mungai-njoroge
f751bbac97 use tabs in now playing page 2023-11-02 12:16:32 +03:00
mungai-njoroge
feb99103b8 break lyrics componnent into 2
+ add lyrics to now playing page
+ remove lyrics from sidebar: it make the sidebar look cluttered
2023-11-01 23:48:31 +03:00
mungai-njoroge
0851c76e65 show lyrics indicatior 2023-10-31 22:36:50 +03:00
mungai-njoroge
bf1f3bf00a refactors and error handling 2023-10-30 17:44:52 +03:00
mungai-njoroge
c0dd04bc94 add lyrics to sidebar 2023-10-30 12:44:33 +03:00
Mungai Njoroge
c2aba79db7 Merge pull request #19 from swing-opensource/handle-album-versions
v1.3.0
2023-10-11 15:00:13 -07:00
227 changed files with 6307 additions and 3167 deletions

View File

@@ -21,9 +21,7 @@
"motion": "^10.15.5",
"node-vibrant": "3.1.6",
"pinia": "^2.0.17",
"pinia-plugin-persistedstate": "^2.1.1",
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"pinia-plugin-persistedstate": "^3.2.0",
"v-wave": "^1.5.0",
"vue": "^v3.2.45",
"vue-debounce": "^3.0.2",
@@ -40,6 +38,8 @@
"eslint": "^8.46.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-vue": "^9.17.0",
"sass": "^1.56.1",
"sass-loader": "^13.2.0",
"typescript": "^5.0.4",
"vite": "^3.0.4",
"vite-plugin-compression": "^0.5.1",

View File

@@ -0,0 +1,15 @@
onmessage = (e) => {
const { trackhash, duration, source, timestamp } = e.data;
const is_dev = location.port === "5173";
const base_url = is_dev ? "http://localhost:1980" : location.origin;
const url = base_url + "/logger/track/log";
fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ trackhash, duration, source, timestamp }),
});
};

18
public/workers/silence.js Normal file
View File

@@ -0,0 +1,18 @@
onmessage = async (e) => {
const { ending_file, starting_file } = e.data;
const is_dev = location.port === "5173";
const base_url = is_dev ? "http://localhost:1980" : location.origin;
const url = base_url + "/file/silence";
const res = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ ending_file, starting_file }),
});
const data = await res.json();
postMessage(data);
};

View File

@@ -6,17 +6,27 @@
<section
id="app-grid"
:class="{
noSidebar: !settings.use_sidebar || !xl,
NoSideBorders: !xxl,
useSidebar: settings.use_sidebar && xl,
NoSideBorders: settings.is_alt_layout || !xxl,
extendWidth: settings.extend_width && settings.can_extend_width,
is_alt_layout: settings.is_alt_layout,
}"
:style="{
maxWidth: `${
settings.is_default_layout
? content_height > 1080
? '2220px'
: '1760px'
: ''
}`,
}"
:style="{ maxWidth: `${content_height > 1080 ? '2220px' : '1720px'}` }"
>
<LeftSidebar v-if="!isMobile" />
<LeftSidebar v-if="settings.is_default_layout && !isMobile" />
<NavBar />
<div id="acontent" v-element-size="updateContentElemSize">
<div id="contentresizer" ref="appcontent"></div>
<BalancerProvider>
<router-view />
<RouterView />
</BalancerProvider>
</div>
<RightSideBar v-if="settings.use_sidebar && xl" />
@@ -27,10 +37,10 @@
<script setup lang="ts">
// @libraries
import { onMounted } from "vue";
import { useRouter } from "vue-router";
import { onStartTyping } from "@vueuse/core";
import { vElementSize } from "@vueuse/components";
import { onStartTyping } from "@vueuse/core";
import { onMounted, Ref, ref } from "vue";
import { useRouter } from "vue-router";
import { BalancerProvider } from "vue-wrap-balancer";
// @stores
@@ -38,11 +48,13 @@ import {
content_height,
content_width,
isMobile,
updateCardWidth,
} from "@/stores/content-width";
import useModalStore from "@/stores/modal";
import useQStore from "@/stores/queue";
import useSettingsStore from "@/stores/settings";
import useQueueStore from "@/stores/queue";
import useLyrics from "@/stores/lyrics";
import useModal from "@/stores/modal";
import useQueue from "@/stores/queue";
import useSettings from "@/stores/settings";
import useTracker from "@/stores/tracker";
// @utils
import handleShortcuts from "@/helpers/useKeyboard";
@@ -56,33 +68,42 @@ import Notification from "@/components/Notification.vue";
// @app-grid-components
import BottomBar from "@/components/BottomBar/BottomBar.vue";
import LeftSidebar from "@/components/LeftSidebar/index.vue";
import NavBar from "@/components/nav/NavBar.vue";
import RightSideBar from "@/components/RightSideBar/Main.vue";
import LeftSidebar from "@/components/LeftSidebar/index.vue";
import { getAllSettings } from "@/requests/settings";
import { getRootDirs } from "@/requests/settings/rootdirs";
import { baseApiUrl } from "@/config";
// import BubbleManager from "./components/bubbles/BinManager.vue";
const appcontent: Ref<HTMLLegendElement | null> = ref(null);
const queue = useQueue();
const modal = useModal();
const lyrics = useLyrics();
const router = useRouter();
const queue = useQueueStore();
const modal = useModalStore();
const settings = useSettingsStore();
const settings = useSettings();
useTracker();
handleShortcuts(useQStore, useModalStore);
handleShortcuts(useQueue, useModal);
router.afterEach(() => {
(document.getElementById("acontent") as HTMLElement).scrollTo(0, 0);
});
onStartTyping(() => {
if (isMobile.value) return;
const elem = document.getElementById("globalsearch") as HTMLInputElement;
elem.focus();
elem.value = "";
});
function getContentSize() {
const elem = document.getElementById("acontent") as HTMLElement;
return {
width: elem.offsetWidth,
height: elem.offsetHeight,
};
}
function updateContentElemSize({
width,
height,
@@ -90,8 +111,12 @@ function updateContentElemSize({
width: number;
height: number;
}) {
content_width.value = width;
// 1572 is the maxwidth of the #acontent. see app-grid.scss > $maxwidth
const elem_width = appcontent.value?.offsetWidth || 0;
content_width.value = elem_width;
content_height.value = height;
updateCardWidth();
}
function handleWelcomeModal() {
@@ -118,16 +143,26 @@ function handleRootDirsPrompt() {
}
onMounted(() => {
queue.startBufferingStatusWatcher();
const { width, height } = getContentSize();
updateContentElemSize({ width, height });
handleWelcomeModal();
settings.initializeVolume();
if (baseApiUrl.value === null) {
modal.showSetIPModal();
return;
}
handleRootDirsPrompt();
getAllSettings()
.then(({ settings: data }) => {
settings.mapDbSettings(data);
})
.then(() => {
if (queue.currenttrack && !settings.use_lyrics_plugin) {
lyrics.checkExists(
queue.currenttrack.filepath,
queue.currenttrack.trackhash
);
}
});
});
</script>

View File

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

Before

Width:  |  Height:  |  Size: 931 B

After

Width:  |  Height:  |  Size: 936 B

View File

@@ -0,0 +1,3 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.10515 24.5753H21.6977C23.0148 24.5753 23.8007 23.9513 23.8007 22.9154C23.8007 19.9004 19.9776 15.7538 13.8955 15.7538C7.82523 15.7538 4 19.9004 4 22.9154C4 23.9513 4.78797 24.5753 6.10515 24.5753ZM13.9052 13.6731C16.4594 13.6731 18.6327 11.4019 18.6327 8.46591C18.6327 5.58403 16.453 3.39099 13.9052 3.39099C11.3573 3.39099 9.17547 5.62458 9.17969 8.48724C9.1818 11.4019 11.3434 13.6731 13.9052 13.6731Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 542 B

View File

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

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,4 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.39687 27.1131C9.04913 27.1131 9.53405 26.7937 10.3459 26.0807L14.3572 22.5294H21.4504C24.9784 22.5294 26.9547 20.503 26.9547 17.0251V7.93679C26.9547 4.45891 24.9784 2.4325 21.4504 2.4325H6.50218C2.97625 2.4325 1 4.4493 1 7.93679V17.0251C1 20.5126 3.02476 22.5294 6.42836 22.5294H6.9107V25.4181C6.9107 26.4533 7.45422 27.1131 8.39687 27.1131ZM8.96593 24.532V21.1863C8.96593 20.4986 8.67132 20.2335 8.01319 20.2335H6.56757C4.35225 20.2335 3.29592 19.1066 3.29592 16.9523V8.00007C3.29592 5.84569 4.35225 4.72842 6.56757 4.72842H21.3871C23.5928 4.72842 24.6588 5.84569 24.6588 8.00007V16.9523C24.6588 19.1066 23.5928 20.2335 21.3871 20.2335H14.2321C13.521 20.2335 13.1823 20.3484 12.6864 20.8497L8.96593 24.532Z" fill="currentColor"/>
<path d="M8.2771 11.2555C8.2771 12.6189 9.13022 13.6523 10.4502 13.6523C10.9762 13.6523 11.4768 13.547 11.7878 13.1582H11.9174C11.5281 14.0908 10.6426 14.7372 9.78389 14.9491C9.37561 15.0578 9.23194 15.2495 9.23194 15.5329C9.23194 15.8427 9.4928 16.0804 9.83147 16.0804C11.0784 16.0804 13.4024 14.5982 13.4024 11.7334C13.4024 10.0933 12.3606 8.83142 10.776 8.83142C9.34444 8.83142 8.2771 9.8315 8.2771 11.2555ZM14.6327 11.2555C14.6327 12.6189 15.4837 13.6523 16.8058 13.6523C17.3317 13.6523 17.8302 13.547 18.1412 13.1582H18.273C17.8837 14.0908 16.9982 14.7372 16.1394 14.9491C15.7387 15.0578 15.5875 15.2495 15.5875 15.5329C15.5875 15.8427 15.8462 16.0804 16.1849 16.0804C17.4318 16.0804 19.7559 14.5982 19.7559 11.7334C19.7559 10.0933 18.7141 8.83142 17.1316 8.83142C15.6979 8.83142 14.6327 9.8315 14.6327 11.2555Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,4 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.39687 27.1131C9.04913 27.1131 9.53405 26.7937 10.3459 26.0807L14.3572 22.5294H21.4504C24.9784 22.5294 26.9547 20.503 26.9547 17.0251V7.93679C26.9547 4.45891 24.9784 2.4325 21.4504 2.4325H6.50218C2.97625 2.4325 1 4.4493 1 7.93679V17.0251C1 20.5126 3.02476 22.5294 6.42836 22.5294H6.9107V25.4181C6.9107 26.4533 7.45422 27.1131 8.39687 27.1131ZM8.96593 24.532V21.1863C8.96593 20.4986 8.67132 20.2335 8.01319 20.2335H6.56757C4.35225 20.2335 3.29592 19.1066 3.29592 16.9523V8.00007C3.29592 5.84569 4.35225 4.72842 6.56757 4.72842H21.3871C23.5928 4.72842 24.6588 5.84569 24.6588 8.00007V16.9523C24.6588 19.1066 23.5928 20.2335 21.3871 20.2335H14.2321C13.521 20.2335 13.1823 20.3484 12.6864 20.8497L8.96593 24.532Z" fill="currentColor"/>
<path d="M8.2771 11.2555C8.2771 12.6189 9.13022 13.6523 10.4502 13.6523C10.9762 13.6523 11.4768 13.547 11.7878 13.1582H11.9174C11.5281 14.0908 10.6426 14.7372 9.78389 14.9491C9.37561 15.0578 9.23194 15.2495 9.23194 15.5329C9.23194 15.8427 9.4928 16.0804 9.83147 16.0804C11.0784 16.0804 13.4024 14.5982 13.4024 11.7334C13.4024 10.0933 12.3606 8.83142 10.776 8.83142C9.34444 8.83142 8.2771 9.8315 8.2771 11.2555ZM14.6327 11.2555C14.6327 12.6189 15.4837 13.6523 16.8058 13.6523C17.3317 13.6523 17.8302 13.547 18.1412 13.1582H18.273C17.8837 14.0908 16.9982 14.7372 16.1394 14.9491C15.7387 15.0578 15.5875 15.2495 15.5875 15.5329C15.5875 15.8427 15.8462 16.0804 16.1849 16.0804C17.4318 16.0804 19.7559 14.5982 19.7559 11.7334C19.7559 10.0933 18.7141 8.83142 17.1316 8.83142C15.6979 8.83142 14.6327 9.8315 14.6327 11.2555Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

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

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

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

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,4 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.6328 35L8.625 10.3203H8.41406C8.74219 14.5234 8.90625 17.7891 8.90625 20.1172V35H0.703125V0.734375H13.0312L20.1797 25.0625H20.3672L27.375 0.734375H39.7266V35H31.2188V19.9766C31.2188 19.1953 31.2266 18.3281 31.2422 17.375C31.2734 16.4219 31.3828 14.0859 31.5703 10.3672H31.3594L24.4453 35H15.6328ZM76.2422 0.734375V21.3594C76.2422 25.8438 74.9688 29.3203 72.4219 31.7891C69.8906 34.2422 66.2344 35.4688 61.4531 35.4688C56.7812 35.4688 53.1875 34.2734 50.6719 31.8828C48.1719 29.4922 46.9219 26.0547 46.9219 21.5703V0.734375H56.2266V20.8438C56.2266 23.2656 56.6797 25.0234 57.5859 26.1172C58.4922 27.2109 59.8281 27.7578 61.5938 27.7578C63.4844 27.7578 64.8516 27.2188 65.6953 26.1406C66.5547 25.0469 66.9844 23.2656 66.9844 20.7969V0.734375H76.2422ZM105.844 24.5938C105.844 26.7188 105.305 28.6094 104.227 30.2656C103.148 31.9062 101.594 33.1875 99.5625 34.1094C97.5312 35.0156 95.1484 35.4688 92.4141 35.4688C90.1328 35.4688 88.2188 35.3125 86.6719 35C85.125 34.6719 83.5156 34.1094 81.8438 33.3125V25.0625C83.6094 25.9688 85.4453 26.6797 87.3516 27.1953C89.2578 27.6953 91.0078 27.9453 92.6016 27.9453C93.9766 27.9453 94.9844 27.7109 95.625 27.2422C96.2656 26.7578 96.5859 26.1406 96.5859 25.3906C96.5859 24.9219 96.4531 24.5156 96.1875 24.1719C95.9375 23.8125 95.5234 23.4531 94.9453 23.0938C94.3828 22.7344 92.8672 22 90.3984 20.8906C88.1641 19.875 86.4844 18.8906 85.3594 17.9375C84.25 16.9844 83.4219 15.8906 82.875 14.6562C82.3438 13.4219 82.0781 11.9609 82.0781 10.2734C82.0781 7.11719 83.2266 4.65625 85.5234 2.89062C87.8203 1.125 90.9766 0.242188 94.9922 0.242188C98.5391 0.242188 102.156 1.0625 105.844 2.70312L103.008 9.85156C99.8047 8.38281 97.0391 7.64844 94.7109 7.64844C93.5078 7.64844 92.6328 7.85938 92.0859 8.28125C91.5391 8.70312 91.2656 9.22656 91.2656 9.85156C91.2656 10.5234 91.6094 11.125 92.2969 11.6562C93 12.1875 94.8906 13.1562 97.9688 14.5625C100.922 15.8906 102.969 17.3203 104.109 18.8516C105.266 20.3672 105.844 22.2812 105.844 24.5938ZM111.141 35V0.734375H120.445V35H111.141ZM143.133 7.83594C140.93 7.83594 139.211 8.74219 137.977 10.5547C136.742 12.3516 136.125 14.8359 136.125 18.0078C136.125 24.6016 138.633 27.8984 143.648 27.8984C145.164 27.8984 146.633 27.6875 148.055 27.2656C149.477 26.8438 150.906 26.3359 152.344 25.7422V33.5703C149.484 34.8359 146.25 35.4688 142.641 35.4688C137.469 35.4688 133.5 33.9688 130.734 30.9688C127.984 27.9688 126.609 23.6328 126.609 17.9609C126.609 14.4141 127.273 11.2969 128.602 8.60938C129.945 5.92188 131.867 3.85938 134.367 2.42188C136.883 0.96875 139.836 0.242188 143.227 0.242188C146.93 0.242188 150.469 1.04688 153.844 2.65625L151.008 9.94531C149.742 9.35156 148.477 8.85156 147.211 8.44531C145.945 8.03906 144.586 7.83594 143.133 7.83594Z" fill="white"/>
<path d="M160.289 14.4453C159.008 12.5859 158.367 9.9375 158.367 6.5V3.10156H167.602L185.039 22.2031C186.023 23.2969 186.68 24.2188 187.008 24.9688C187.336 25.7188 187.578 26.4062 187.734 27.0312C188.078 28.2812 188.25 29.8203 188.25 31.6484V35H179.273L160.359 14.4453H160.289ZM178.102 13.1094C178.102 8.8125 178.766 6.01562 180.094 4.71875C180.797 4.03125 181.711 3.59375 182.836 3.40625C183.977 3.20312 185.359 3.10156 186.984 3.10156H188.25V6.80469C188.25 10.1328 187.375 12.3594 185.625 13.4844C184.375 14.2969 182.273 14.7031 179.32 14.7031H178.102V13.1094ZM158.367 31.3438C158.367 28.0156 159.242 25.7812 160.992 24.6406C162.242 23.8438 164.344 23.4453 167.297 23.4453H168.516V25.0391C168.516 29.3828 167.852 32.1719 166.523 33.4062C165.617 34.25 164.297 34.75 162.562 34.9062C161.688 34.9688 160.711 35 159.633 35H158.367V31.3438Z" fill="#4AD168"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -5,7 +5,6 @@
display: grid;
grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
padding: 0 1rem;
padding-bottom: 4rem;
overflow: auto;
max-height: 100%;
gap: 2rem 1rem;

View File

@@ -2,92 +2,26 @@ $g-border: solid 1px $gray5;
#app-grid {
display: grid;
grid-template-columns: min-content 1fr 29rem;
grid-template-rows: max-content 1fr 5rem;
// grid-template-columns: min-content 1fr 29rem;
grid-template-columns: min-content 1fr;
grid-template-rows: $navheight 1fr $navheight;
grid-template-areas:
"l-sidebar nav r-sidebar"
"l-sidebar content r-sidebar"
"bottombar bottombar bottombar";
"l-sidebar nav"
"l-sidebar content"
"bottombar bottombar";
height: 100%;
border: $g-border;
border-top: none;
border-bottom: none;
margin: 0 auto;
@include for-desktop-down {
grid-template-columns: min-content 1fr 24rem;
}
}
#acontent {
width: 100%;
grid-area: content;
padding-right: calc($medium);
overflow: hidden;
}
.vue-recycle-scroller__item-wrapper {
overflow: visible !important;
}
.vue-recycle-scroller {
padding-left: 1.25rem;
scrollbar-gutter: stable;
}
.r-sidebar {
grid-area: r-sidebar;
border-left: $g-border;
}
.topnav {
grid-area: nav;
margin: 1rem 0;
}
.b-bar {
grid-area: bottombar;
border-top: $g-border;
}
.content-page {
margin-left: 1.25rem;
margin-right: -$medium;
padding-right: $medium;
scrollbar-gutter: stable;
}
// ====== MODIFIERS =======
#app-grid.extendWidth {
padding-right: 0;
border-left: none;
border-right: none;
max-width: 100% !important;
}
#app-grid.noSidebar {
grid-template-columns: min-content 1fr;
grid-template-areas:
"l-sidebar nav"
"l-sidebar content"
"bottombar bottombar";
#acontent {
margin-right: 0 !important;
padding-right: $medium !important;
}
.topnav {
//reduce width to match #acontent
width: calc(100% - 1rem);
padding-right: 0;
#contentresizer {
margin: 0 $padright 0 $padleft;
}
@include allPhones {
grid-template-columns: 1fr;
// grid-template-columns: 1fr;
grid-template-rows: max-content 1fr 9.5rem;
grid-template-areas:
"nav"
@@ -96,25 +30,177 @@ $g-border: solid 1px $gray5;
}
}
#app-grid.NoSideBorders {
border-right: none;
#acontent {
width: 100%;
grid-area: content;
overflow: hidden;
margin-right: $margright;
}
.topnav {
grid-area: nav;
height: $navheight;
padding: 1rem $padleft;
padding-right: $padright;
}
.b-bar {
grid-area: bottombar;
border-top: $g-border;
}
.content-page {
scrollbar-gutter: stable;
padding-left: $padleft;
padding-right: $padright;
padding-bottom: $padbottom;
}
.vue-recycle-scroller__item-wrapper {
overflow: visible !important;
}
.vue-recycle-scroller {
scrollbar-gutter: stable;
padding-left: $padleft;
}
.r-sidebar {
grid-area: r-sidebar;
border-left: $g-border;
.vue-recycle-scroller {
padding-left: 0;
}
}
// ====== MODIFIERS =======
#app-grid.is_alt_layout {
grid-template-columns: 1fr;
grid-template-rows: max-content 1fr 5rem;
grid-template-areas:
"nav"
"content"
"bottombar";
.vue-recycle-scroller,
.content-page,
.topnav,
#songlist-scroller {
padding-left: $alt_layout_pad;
padding-right: $alt_layout_pad;
}
.b-bar,
.search-page-top-results {
padding: 0 $alt_layout_pad;
}
#contentresizer {
margin: 0 $alt_layout_pad;
}
.topnav {
background-color: $gray;
}
.vue-recycle-scroller,
.content-page {
padding-top: 2rem;
}
.search-page-top-results {
padding-bottom: $padbottom;
}
.search-view .buttons-area {
padding-left: $alt_layout_pad;
}
.lyricsview {
padding-bottom: 2rem;
}
#lyricscontent {
padding-top: 0;
padding-left: 2rem;
padding-right: 2rem;
}
.nolyrics {
margin-top: 0;
}
@media (min-width: 1980px) {
// NOTE: Styles for 1680px and below
$alt_layout_pad: max(2rem, calc((100% - 1680px) / 2));
.vue-recycle-scroller,
.content-page,
.topnav,
#songlist-scroller {
padding-left: $alt_layout_pad;
padding-right: $alt_layout_pad;
}
#contentresizer {
margin: 0 $alt_layout_pad;
}
.search-view .buttons-area {
padding-left: $alt_layout_pad;
}
.b-bar,
.search-page-top-results {
padding: 0 $alt_layout_pad;
}
}
}
#app-grid.extendWidth {
padding-right: 0;
border-left: none;
border-right: none;
max-width: 100% !important;
}
#app-grid.useSidebar {
grid-template-columns: min-content 1fr 28rem;
grid-template-areas:
"l-sidebar nav r-sidebar"
"l-sidebar content r-sidebar"
"bottombar bottombar bottombar";
@include for-desktop-down {
grid-template-columns: min-content 1fr 24rem;
}
#acontent {
// margin-right: 0 !important;
// padding-right: $medium !important;
}
}
#app-grid.NoSideBorders {
border-right: none !important;
border-left: none !important;
}
.v-scroll-page {
width: calc(100% + $medium) !important;
.scroller {
padding-right: $padright;
height: 100%;
width: 100%;
padding-right: 1.25rem;
padding-bottom: $content-padding-bottom;
padding-bottom: $padbottom;
}
}
.isSmall {
.songlist-item {
grid-template-columns: 2fr 5.5rem;
grid-template-columns: 2fr 5.5rem !important;
}
.song-artists,

View File

@@ -28,7 +28,7 @@ input[type="search"]::-webkit-search-cancel-button {
}
.heading {
font-size: 2rem;
font-size: 1.5rem;
font-weight: bold;
}
@@ -140,7 +140,6 @@ button {
opacity: 0.5;
}
// NO THIS, NO THAT (OVERRIDES)
.no-border {
border: none;
}
@@ -170,3 +169,75 @@ button {
position: absolute;
left: -20rem;
}
.spinner {
border: solid 3px rgb(0, 0, 0);
border-top: solid 3px transparent;
border-left: solid 3px transparent;
border-radius: 50%;
width: 1.25rem;
height: 1.25rem;
animation: spin 0.45s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.card-list-scroll-x {
overflow: hidden;
h3 {
display: grid;
grid-template-columns: 1fr max-content;
align-items: baseline;
padding: 0 $medium;
margin-bottom: $medium;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax($cardwidth, 1fr));
overflow-x: auto;
scroll-snap-type: x mandatory;
flex-direction: row;
padding-bottom: 2rem;
@include hideScrollbars;
}
.album-card {
&:hover {
background-color: $gray;
}
}
}
.rhelp {
text-transform: uppercase;
font-size: 11px;
color: $purple;
font-weight: bold;
margin: $smaller 0;
&.album {
color: $orange;
}
&.track {
color: $pink;
}
&.folder {
color: $teal;
}
&.playlist {
color: $green;
}
}

View File

@@ -37,7 +37,7 @@
}
@mixin tablet-portrait {
@media (max-width: 810) {
@media (max-width: 810px) {
@content;
}
}

View File

@@ -26,14 +26,14 @@ $gray2: rgb(99, 99, 102);
$gray3: rgb(72, 72, 74);
$gray4: rgb(58, 58, 60);
$gray5: rgb(44, 44, 46);
$body: rgba(0, 0, 0, 0.95);
$body: #111111;
$red: #ff453a;
$blue: #0a84ff;
$darkblue: #055ee2;
$green: rgb(20, 160, 55);
$green: rgb(94, 247, 132);
$yellow: rgb(255, 214, 10);
$orange: rgb(255, 159, 10);
$orange: #ff9f0a;
$pink: rgb(255, 55, 95);
$purple: #bf5af2;
$brown: rgb(172, 142, 104);
@@ -55,3 +55,27 @@ $side-nav-svg: $red;
$overshoot: cubic-bezier(0.68, -0.55, 0.265, 1.55);
$separator: $gray4;
$margright: 0;
$padbottom: 4rem;
$maxwidth: 1438px;
$navheight: 5rem;
$cardwidth: 10.75rem;
$maxpadleft: 5rem;
$alt_layout_pad: max(2rem, calc((100% - 1280px) / 2));
$maxpadright: calc(100% - $maxwidth);
$padleft: clamp(2rem, $maxpadright, $maxpadleft);
// 👇 fixed width with content floating to the left.
// $padright: clamp(
// 1rem,
// max($maxpadright, 5rem),
// calc($maxpadright + $maxpadleft)
// );
$padright: $padleft;
$margright: calc(0rem - $padright);

View File

@@ -3,8 +3,7 @@
"./variables",
"./ProgressBar.scss",
"./BottomBar/BottomBar.scss",
"./Global",
"./moz.scss"
"./Global"
;

View File

@@ -1,40 +0,0 @@
// Styles that only apply to our dear Firefox
@-moz-document url-prefix() {
#acontent {
margin-right: calc(-1rem + 1px);
padding-right: 1rem;
}
// applies to playlist list page
.content-page {
margin-right: calc(0rem - ($medium + 4px));
}
// virtual scroller pages: folder, playlist, album
.header-list-layout {
margin-right: calc(0rem - ($medium + 4px)) !important;
.scrollable {
padding-right: calc(1rem - 3px) !important;
}
}
.v-scroll-page {
width: calc(100% + 1rem) !important;
.scroller {
height: 100%;
width: 100%;
// padding-right: 1.25rem !important;
}
}
#app-grid.noSidebar > #acontent {
padding-right: 1rem !important;
}
.search-view {
margin-right: -1rem !important;
}
}

View File

@@ -1,7 +1,7 @@
<template>
<div
class="album_disc_header no-select"
v-if="album_disc.is_album_disc_number"
class="album_disc_header no-select"
>
<div class="disc_number">Disc {{ album_disc.album_page_disc_number }}</div>
<div></div>

View File

@@ -1,78 +0,0 @@
<template>
<div class="card-list-scroll-x">
<h3>
<span>{{ title }}</span>
<SeeAll
v-if="route && maxAbumCards - 1 <= albums.length"
:route="route"
@click="
!favorites ? useArtistDiscographyStore().setPage(albumType) : null
"
/>
</h3>
<div ref="artistItemsWrappers" class="cards">
<AlbumCard
v-for="a in albums"
:key="a.albumhash"
:album="a"
:show_date="show_date"
:artist_page="artist_page"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Album } from "@/interfaces";
import { discographyAlbumTypes } from "@/enums";
import { maxAbumCards } from "@/stores/content-width";
import useArtistDiscographyStore from "@/stores/pages/artistDiscog";
import AlbumCard from "../shared/AlbumCard.vue";
import SeeAll from "../shared/SeeAll.vue";
defineProps<{
title: string;
albums: Album[];
albumType?: discographyAlbumTypes;
favorites?: boolean;
route?: string;
show_date?: boolean;
artist_page?: boolean;
}>();
const artistItemsWrappers = ref<HTMLElement | null>(null);
</script>
<style lang="scss">
.card-list-scroll-x {
overflow: hidden;
h3 {
display: grid;
grid-template-columns: 1fr max-content;
align-items: baseline;
padding: 0 $medium;
margin-bottom: $medium;
}
.cards {
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
scroll-snap-type: x mandatory;
flex-direction: row;
padding-bottom: 2rem;
@include hideScrollbars;
}
.album-card {
&:hover {
background-color: $gray;
}
}
}
</style>

View File

@@ -4,8 +4,8 @@
<HeartSvg
:state="album.is_favorite"
@handleFav="handleFav"
:color="colors.bg ? colors.bg : ''"
@handleFav="handleFav"
/>
<button
class="options"

View File

@@ -16,12 +16,13 @@
</template>
<script setup lang="ts">
import { computed } from "vue";
import { Album } from "@/interfaces";
import { formatSeconds } from "@/utils";
import { isSmallPhone } from "@/stores/content-width";
import ArtistName from "@/components/shared/ArtistName.vue";
import { computed } from "vue";
const props = defineProps<{
album: Album;
@@ -33,7 +34,9 @@ const statsText = computed(() => {
// hide track count if it's a single, also add an s to track if it's plural
return `${props.album.date} ${
!is_single
? `${props.album.count} Track${props.album.count > 1 ? "s" : ""}`
? `${props.album.count.toLocaleString()} Track${
props.album.count > 1 ? "s" : ""
}`
: ""
}${formatSeconds(props.album.duration, true)}`;
});

View File

@@ -24,8 +24,8 @@
</template>
<script setup lang="ts">
import { storeToRefs } from "pinia";
import { ref } from "vue";
import { storeToRefs } from "pinia";
import { paths } from "@/config";
import { isHeaderSmall } from "@/stores/content-width";

View File

@@ -1,21 +1,35 @@
<template>
<div style="height: 1px"></div>
<div style="height: 1px">
<button v-if="show_text" @click="fetch_callback">Load More</button>
</div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
import { nextTick, onMounted } from "vue";
import { onBeforeRouteUpdate } from "vue-router";
import { updateCardWidth } from "@/stores/content-width";
const props = defineProps<{
fetch_callback: () => void;
reset_callback: () => void;
show_text?: boolean;
fetch_callback: () => Promise<void>;
reset_callback?: () => Promise<void>;
outside_route?: boolean;
}>();
const update = async () => {
await nextTick();
updateCardWidth();
};
onMounted(async () => {
props.fetch_callback();
props.fetch_callback().then(update);
});
onBeforeRouteUpdate(() => {
props.reset_callback();
});
!props.outside_route &&
onBeforeRouteUpdate(() => {
if (!props.reset_callback) return;
props.reset_callback().then(update);
});
</script>

View File

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

View File

@@ -14,18 +14,19 @@
<div
class="artist-name"
:class="`${useCircularImage ? 'ellip' : 'ellip2'}`"
:title="artist.name"
>
{{ artist.name }}
</div>
<div class="stats">
<span v-if="artist.trackcount">
{{ artist.trackcount }} Track{{
{{ artist.trackcount.toLocaleString() }} Track{{
`${artist.trackcount == 1 ? "" : "s"}`
}}
</span>
{{ artist.albumcount && artist.trackcount ? "" : "" }}
{{ artist.albumcount && artist.trackcount.toLocaleString() ? "" : "" }}
<span v-if="artist.albumcount">
{{ artist.albumcount }} Album{{
{{ artist.albumcount.toLocaleString() }} Album{{
`${artist.albumcount == 1 ? "" : "s"}`
}}
</span>
@@ -77,7 +78,7 @@ defineProps<{
.artist-name {
font-size: 3.5rem;
font-weight: bold;
word-wrap: break-word;
word-wrap: break-all;
}
.stats {

View File

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

View File

@@ -1,126 +0,0 @@
<template>
<div class="artists-view rounded">
<div class="al-header">
<div class="heading">ALL ARTISTS</div>
<div class="search">
<input type="search" placeholder="Search artists" />
</div>
</div>
<div class="all-albums">
<div class="item rounded" v-for="artist in artists" :key="artist">
<div class="album-art image rounded"></div>
<div class="name t-center ellip">{{ artist.name }}</div>
</div>
</div>
</div>
</template>
<script>
export default {
setup() {
const artists = [
{
name: "Juice Wrld",
},
{
name: "Eminem",
},
{
name: "Sting",
},
{
name: "Juice Wrld",
},
{
name: "Eminem",
},
{
name: "Sting",
},
{
name: "Juice Wrld",
},
];
return {
artists,
};
},
};
</script>
<style lang="scss">
.artists-view {
height: calc(100% - 14.5rem);
padding: $small;
margin-top: $small;
overflow: hidden;
.all-albums {
height: calc(100% - 4rem);
border-top: 1px solid $separator;
padding: $small 0 0 0;
overflow-y: auto;
.item {
position: relative;
width: 10.9rem;
height: 12rem;
padding: $small 0.95rem $small 0.95rem;
margin: $smaller;
transition: all 0.2s ease-in-out;
cursor: default;
float: left;
&:hover {
background-color: rgb(74, 84, 87);
// border: solid
}
.album-art {
height: 9em;
width: 9em;
border-radius: 50%;
background-image: url(../../assets/images/null.webp);
}
.name {
margin-top: $small;
}
}
}
.al-header {
display: flex;
align-items: center;
position: relative;
height: 4em;
padding: 0 $small 0 $small;
.search {
position: absolute;
right: 0;
padding-right: $small;
input {
width: 30rem;
border: none;
border-radius: 2rem;
padding-left: 1rem;
background-color: #4645456c;
color: rgba(255, 255, 255, 0.521);
font-size: 1rem;
line-height: 3rem;
outline: none;
}
input::-webkit-search-cancel-button {
position: relative;
right: 20px;
cursor: default;
width: 50px;
height: 50px;
}
}
}
}
</style>

View File

@@ -1,160 +0,0 @@
<template>
<div class="top-artists">
<div class="heading">TOP ARTISTS</div>
<div class="items">
<div class="item rounded" v-for="artist in artists" :key="artist">
<div class="image"></div>
<div class="info">
<div class="name ellip">{{ artist.name }}</div>
<div class="artist ellip">{{ artist.album_count }} Albums</div>
<div class="separator"></div>
<div class="top">
<div class="play-icon"></div>
<div class="text ellip">{{ artist.top_track }}</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script >
export default {
setup() {
const artists = [
{
name: "Sting",
album_count: "12",
top_track: "Alien in Newyork",
},
{
name: "Juice Wrld",
album_count: "4",
top_track: "Girl Of My Dreams",
},
{
name: "Lil Peep",
album_count: "6",
top_track: "Haunt U",
},
];
return {
artists,
};
},
};
</script>
<style lang="scss">
.top-artists {
height: 14rem;
border-radius: $small;
padding: $small;
.heading {
margin: $small 0 1.5em $small;
}
.items {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: $small;
.item {
height: 10rem;
width: 100%;
max-width: 25rem;
background-color: rgb(7, 6, 6);
display: grid;
align-items: center;
grid-template-columns: 7.5rem 1fr;
padding: $small;
cursor: default;
transition: all 0.2s ease-in-out;
&:hover {
transform: translateY(-.5em);
}
.image {
height: 7rem;
width: 7rem;
// background-image: url("../../assets/images/null.webp");
border-radius: 50%;
}
.info .name {
font-size: 1.5rem;
font-weight: bold;
}
.info .artist {
font-size: small;
color: rgba(255, 255, 255, 0.699);
}
.info .top {
height: 2.5rem;
background-color: rgb(51, 129, 20);
border-radius: $small;
margin-left: auto;
display: grid;
grid-template-columns: 2.5rem 1fr;
align-items: center;
transition: all 0.2s ease-in-out;
user-select: none;
.play-icon {
margin: 0 0 0 $small;
height: 1.2rem;
width: 1.2rem;
background-image: url(../../assets/icons/play.svg);
background-size: 96%;
background-repeat: no-repeat;
background-position: center;
transition: all 0.2s ease-in-out;
}
.text {
color: white;
}
&:hover {
background-color: rgb(0, 134, 89);
transition: all 0.2s ease-in-out;
.play-icon {
transform: scale(1.2);
transition: all 0.2s ease-in-out;
}
}
&:active {
transform: scale(0.95);
transition: all 0.1s ease-in-out;
}
}
&:first-child {
background-color: rgb(177, 116, 2);
.image {
background-image: url("../../assets/images/null.webp");
}
}
&:nth-child(2){
background-color: rgb(0, 74, 117);
.image {
background-image: url("../../assets/images/null.webp");
}
}
&:nth-child(3){
background-color: rgb(161, 106, 106);
}
}
}
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -147,7 +147,7 @@ function runChildAction(action: () => void) {
transform: scale(0);
background-color: $context;
transform: scale(0);
padding: $medium;
padding: $small;
border: solid 1px $gray3;
max-height: calc(100vh / 2);
@@ -178,7 +178,8 @@ function runChildAction(action: () => void) {
}
}
&:nth-child(2) .icon > svg {
// add to queue icon
&:nth-child(3) .icon > svg {
transform: scale(0.9);
}

View File

@@ -1,26 +0,0 @@
<template>
<div class="card-list-scroll-x">
<h3>Recent</h3>
<div ref="recentitemswrappers" class="cards">
<Recentsitemcard
v-for="fav in favs.slice(0, maxAbumCards)"
:key="JSON.stringify(fav)"
:fav="fav"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import Recentsitemcard from "@/components/Favorites/RecentsItemCard.vue";
import { RecentFavResult } from "@/interfaces";
import { maxAbumCards } from "@/stores/content-width";
defineProps<{
favs: RecentFavResult[];
}>();
const recentitemswrappers = ref<HTMLElement | null>(null);
</script>

View File

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

View File

@@ -1,9 +1,9 @@
<template>
<div class="breadcrumb-nav">
<div
class="path"
v-for="path in subPaths"
:key="path.path"
class="path"
:class="{ inthisfolder: path.active }"
@click.prevent="$emit('navigate', path.path)"
>
@@ -14,9 +14,14 @@
</template>
<script setup lang="ts">
import { onUpdated } from "vue";
import { subPath } from "@/interfaces";
import { focusElemByClass } from "@/utils";
import { onUpdated } from "vue";
import useSettings from "@/stores/settings";
const settings = useSettings();
defineProps<{
subPaths: subPath[];
@@ -27,7 +32,9 @@ defineEmits<{
}>();
onUpdated(() => {
focusElemByClass("inthisfolder");
if (settings.is_default_layout) {
focusElemByClass("inthisfolder");
}
});
</script>

View File

@@ -1,26 +1,26 @@
<template>
<router-link :to="{ name: Routes.folder, params: { path: folder.path } }">
<div
v-auto-animate
class="f-item"
@click="(e) => (folder_page ? null : handleClick(e))"
@mouseover="mouse_over = true"
@mouseleave="mouse_over = false"
:style="{
backgroundColor: is_checked ? '#234ece' : '',
}"
:class="{ context_menu_showing }"
@click="(e) => (folder_page ? null : handleClick(e))"
@mouseover="mouse_over = true"
@mouseleave="mouse_over = false"
@contextmenu.prevent="(e) => (!folder_page ? null : showContextMenu(e))"
v-auto-animate
>
<SymLinkSvg v-if="folder.is_sym" />
<FolderSvg v-else />
<div class="info">
<div class="f-item-text ellip">{{ folder.name }}</div>
<div class="f-count" v-if="folder.count">
{{ folder.count + ` File${folder.count == 1 ? "" : "s"}` }}
<div v-if="folder.count" class="f-count">
{{ folder.count.toLocaleString() + ` File${folder.count == 1 ? "" : "s"}` }}
</div>
</div>
<div class="check" v-if="!folder_page">
<div v-if="!folder_page" class="check">
<CheckSvg v-if="!is_checked && mouse_over" />
<CheckFilledSvg v-if="is_checked" />
</div>

View File

@@ -1,3 +0,0 @@
<template>
<h1>This is your Homepage</h1>
</template>

View File

@@ -0,0 +1,84 @@
<template>
<div class="homebrowse">
<div class="btitle"><b>Browse Library</b></div>
<div class="browselist">
<RouterLink
v-for="i in browselist"
:key="i.title"
class="browseitem rounded-sm t-center"
:to="{ name: i.route, params: i.params }"
:style="{ width: `${album_card_with - 24}px` }"
>
{{ i.title }}
</RouterLink>
</div>
</div>
</template>
<script setup lang="ts">
import { Routes } from "@/router";
import { album_card_with } from "@/stores/content-width";
const browselist = [
{
title: "Folders",
route: Routes.folder,
params: {
path: "$home",
},
},
{
title: "Albums",
route: Routes.AlbumList,
},
{
title: "Artists",
route: Routes.ArtistList,
},
{
title: "Playlists",
route: Routes.playlists,
},
{
title: "Favorites",
route: Routes.favorites,
},
{
title: "Favorite Tracks",
route: Routes.favoriteTracks,
},
{
title: "Favorite Artists",
route: Routes.favoriteArtists,
},
];
</script>
<style lang="scss">
.homebrowse {
padding: 1.5rem 0;
padding-left: $small;
.btitle {
font-size: 1.15rem;
margin-bottom: 1rem;
}
.browselist {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
margin-top: $small;
}
.browseitem {
padding: 1.5rem 0;
background-color: $gray;
color: $white;
}
.browseitem:hover {
background-color: $gray5;
}
}
</style>

View File

@@ -0,0 +1,66 @@
<template>
<div class="home-count">
<div
class="count-item"
:title="count.count.toLocaleString()"
v-for="count in parseCount()"
:key="count.text"
>
<div>{{ count.formatted }}</div>
<div class="text">{{ count.text }}</div>
</div>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
counts: { [key: string]: number }[];
}>();
function formatNumber(num: number) {
if (num >= 1e6) {
return [(num / 1e6).toFixed(1), "m"];
} else if (num >= 1e3) {
return [(num / 1e3).toFixed(1), "k"];
} else {
return [num.toString(), ""];
}
}
function parseCount() {
return props.counts.map((count) => {
const num = count[Object.keys(count)[0]];
const formatted = formatNumber(count[Object.keys(count)[0]]);
return {
count: num,
formatted: formatted[0] + formatted[1],
text: Object.keys(count)[0],
};
});
}
</script>
<style lang="scss">
.home-count {
height: 100%;
display: flex;
gap: 1rem;
.count-item {
font-size: 3rem;
font-weight: bolder;
display: flex;
flex-direction: column;
border-right: solid 1px $gray5;
padding-right: 1rem;
.text {
font-size: 1rem;
font-weight: normal;
text-transform: uppercase;
margin-left: $smaller;
}
}
}
</style>

View File

@@ -17,10 +17,10 @@
}"
></div>
<div
v-else
id="home-button"
class="nav-button"
:class="{ active: $route.name === menu.route_name }"
id="home-button"
v-else
>
<div class="in">
<component :is="menu.icon"></component>

View File

@@ -1,7 +1,7 @@
<template>
<div
class="bitrate"
v-if="q.currenttrack?.bitrate"
class="bitrate"
title="file type • bitrate"
>
{{ q.currenttrack.filepath?.split('.').pop() }} {{ q.currenttrack.bitrate }}

View File

@@ -1,31 +1,33 @@
<template>
<div class="hotkeys no-scroll">
<button @click.prevent="q.playPrev">
<button @click.prevent="queue.playPrev">
<PrevSvg />
</button>
<button @click.prevent="q.playPause">
<Spinner v-if="q.buffering && q.playing" />
<PauseSvg v-else-if="q.playing" />
<PlaySvg v-else/>
<button @click.prevent="queue.playPause">
<Spinner v-if="buffering && queue.playing" />
<PauseSvg v-else-if="queue.playing" />
<PlaySvg v-else />
</button>
<button @click.prevent="q.playNext">
<button @click.prevent="queue.playNext">
<NextSvg />
</button>
</div>
</template>
<script setup lang="ts">
import { usePlayer } from "@/stores/player";
import useQStore from "@/stores/queue";
import {
default as NextSvg,
default as PrevSvg,
default as NextSvg,
default as PrevSvg,
} from "@/assets/icons/next.svg";
import PauseSvg from "@/assets/icons/pause.svg";
import PlaySvg from "@/assets/icons/play.svg";
import Spinner from "@/components/shared/Spinner.vue";
const q = useQStore();
const queue = useQStore();
const { buffering } = usePlayer();
</script>
<style lang="scss">
@@ -68,4 +70,4 @@ const q = useQStore();
}
}
}
</style>
</style>

View File

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

View File

@@ -13,6 +13,9 @@
<router-link
:to="{
name: Routes.nowPlaying,
params: {
tab: 'home',
},
}"
>
<img

View File

@@ -1,9 +1,7 @@
<template>
<div class="side-nav-container">
<router-link
v-for="(menu, index) in menus.filter((i) =>
i.unlock_key ? i.unlock_key() : true
)"
v-for="(menu, index) in menus"
:key="index"
v-wave
:to="{
@@ -18,7 +16,7 @@
}"
>
<div v-if="!menu.separator">
<component :is="menu.icon" :class="menu.class" />
<component :is="menu.icon" />
<span>{{ menu.name }}</span>
</div>
</router-link>
@@ -86,7 +84,7 @@ import { menus } from "./navitems";
transform: scale(0.9);
opacity: 0.75;
}
svg.radiosvg {
transform: scale(0.7);
}

View File

@@ -6,14 +6,36 @@ import HeartSvg from "@/assets/icons/heart.svg";
import PlaylistSvg from "@/assets/icons/playlist-1.svg";
import SearchSvg from "@/assets/icons/search.svg";
import SettingsSvg from "@/assets/icons/settings.svg";
import HomeSvg from "@/assets/icons/home.svg";
const folder = {
name: "folders",
route_name: Routes.folder,
params: { path: "$home" },
icon: FolderSvg,
};
const favorites = {
name: "favorites",
route_name: Routes.favorites,
icon: HeartSvg,
};
const playlists = {
name: "playlists",
route_name: Routes.playlists,
icon: PlaylistSvg,
};
const home = {
name: "home",
route_name: Routes.Home,
icon: HomeSvg,
};
export const menus = [
{
name: "folders",
route_name: Routes.folder,
params: { path: "$home" },
icon: FolderSvg,
},
home,
folder,
{
name: "search",
route_name: Routes.search,
@@ -24,22 +46,17 @@ export const menus = [
{
separator: true,
},
{
name: "favorites",
route_name: Routes.favorites,
icon: HeartSvg,
},
{
name: "playlists",
route_name: Routes.playlists,
icon: PlaylistSvg,
},
favorites,
playlists,
{
separator: true,
},
{
name: "settings",
route_name: Routes.settings,
params: { tab: "general" },
icon: SettingsSvg,
},
];
export const topnavitems = [home, folder, favorites, playlists];

View File

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

View File

@@ -19,7 +19,7 @@
/>
</RouterLink>
<NowPlayingInfo @handle-fav="handleFav" />
<Progress v-if="isSmallPhone"/>
<Progress v-if="isSmallPhone" />
<div v-if="isSmallPhone" class="below-progress">
<div class="time">
{{ formatSeconds(queue.duration.current) }}
@@ -30,18 +30,15 @@
</div>
</div>
</div>
<h3 v-if="queue.currenttrack">Now Playing</h3>
<h3 v-if="queue.next">Up Next</h3>
<SongItem
v-if="queue.currenttrack"
:track="queue.currenttrack"
:index="queue.currentindex + 1"
v-if="queue.next"
:track="queue.next"
:index="queue.nextindex + 1"
:source="dropSources.folder"
:style="{
backgroundColor: colors.theme1,
color: getTextColor(colors.theme1),
}"
@play-this="() => {}"
@play-this="queue.playNext"
/>
<h3>Queue</h3>
</div>
</template>
@@ -49,21 +46,18 @@
import { paths } from "@/config";
import { Routes } from "@/router";
import useQueueStore from "@/stores/queue";
import Progress from "@/components/LeftSidebar/NP/Progress.vue";
import { formatSeconds } from "@/utils";
import { dropSources, favType } from "@/enums";
import { isSmallPhone } from "@/stores/content-width";
import favoriteHandler from "@/helpers/favoriteHandler";
import useColorStore from "@/stores/colors";
import { getTextColor } from "@/utils/colortools/shift";
import SongItem from "../shared/SongItem.vue";
import NowPlayingInfo from "./NowPlayingInfo.vue";
import PlayingFrom from "./PlayingFrom.vue";
import Buttons from "../BottomBar/Right.vue";
import { isSmallPhone } from "@/stores/content-width";
import { formatSeconds } from "@/utils";
import SongItem from "../shared/SongItem.vue";
import NowPlayingInfo from "./NowPlayingInfo.vue";
import Progress from "@/components/LeftSidebar/NP/Progress.vue";
const queue = useQueueStore();
const colors = useColorStore();
function handleFav() {
favoriteHandler(

View File

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

View File

@@ -1,15 +0,0 @@
<template>
<div class="now-playing-tabs">
<div class="tab-items">
<h3>Queue</h3>
</div>
</div>
</template>
<style lang="scss">
.now-playing-tabs {
.tab-items {
border: solid transparent;
}
}
</style>

View File

@@ -1,30 +0,0 @@
<template>
<div class="card-list-scroll-x">
<h3>{{ title }} <SeeAll v-if="route" :route="route" /></h3>
<div ref="artistItemswrappers" class="cards">
<ArtistCard
v-for="artist in artists.slice(0, maxAbumCards)"
:key="artist.image"
:artist="artist"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { Artist } from "@/interfaces";
import { maxAbumCards } from "@/stores/content-width";
import ArtistCard from "@/components/shared/ArtistCard.vue";
import SeeAll from "../shared/SeeAll.vue";
defineProps<{
artists: Artist[];
title: string;
route?: string;
}>();
const artistItemswrappers = ref<HTMLElement | null>(null);
</script>

View File

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

View File

@@ -10,7 +10,7 @@
</div>
<div class="duration">
{{
playlist.info.count +
playlist.info.count.toLocaleString() +
` ${playlist.info.count == 1 ? "Track" : "Tracks"}`
}}

View File

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

View File

@@ -20,9 +20,16 @@
:class="{ border: !playlist.thumb }"
/>
<div class="overlay rounded">
<div v-if="playlist.help_text" class="rhelp playlist">
<span class="help">{{ playlist.help_text }}</span>
<span class="time">{{ playlist.time }}</span>
</div>
<div class="p-name ellip">{{ playlist.name }}</div>
<div class="p-count">
{{ playlist.count + ` ${playlist.count === 1 ? "Track" : "Tracks"}` }}
<b>{{
playlist.count.toLocaleString() +
` Track${playlist.count === 1 ? "" : "s"}`
}}</b>
</div>
</div>
</router-link>
@@ -33,8 +40,7 @@ import { paths } from "../../config";
import { Playlist } from "../../interfaces";
const imguri = paths.images.playlist;
const props = defineProps<{
defineProps<{
playlist: Playlist;
}>();
</script>
@@ -44,9 +50,10 @@ const props = defineProps<{
background-color: #2c2c2e45;
display: grid;
grid-template-rows: 1fr max-content;
padding: 1rem;
padding: $medium;
gap: $small;
user-select: none;
height: max-content;
.image-grid {
display: grid;
@@ -55,7 +62,7 @@ const props = defineProps<{
&:hover {
transition: all 0.25s ease;
background-color: $gray3;
background-color: $gray4 !important;
background-blend-mode: screen;
}
@@ -65,6 +72,7 @@ const props = defineProps<{
object-fit: cover;
transition: all 0.5s ease;
}
.overlay {
display: flex;
flex-direction: column;
@@ -74,25 +82,7 @@ const props = defineProps<{
.p-count {
opacity: 0.75;
font-size: 0.75rem;
}
}
&:hover {
.p-name {
text-decoration: underline;
}
}
.bottom {
margin-top: $smaller;
.name {
font-weight: 900;
}
.count {
font-size: $medium;
opacity: 0.5;
margin-top: $smaller;
}
}
}

View File

@@ -1,18 +1,18 @@
<template>
<div class="r-home">
<UpNext :track="queue.tracklist[queue.next]" :playNext="queue.playNext" />
<UpNext :track="queue.tracklist[queue.next]" :play-next="queue.playNext" />
<!-- <Recommendations /> -->
</div>
</template>
<style lang="scss">
.r-home {
height: calc(100% - 1rem);
}
</style>
<script setup lang="ts">
import useQStore from "../../../stores/queue";
import UpNext from "../Queue/upNext.vue";
const queue = useQStore();
</script>
<style lang="scss">
.r-home {
height: calc(100% - 1rem);
}
</style>

View File

@@ -2,7 +2,7 @@
<div class="r-tracks rounded bg-primary">
<div class="heading">Similar tracks</div>
<div class="tracks">
<div class="song-item" v-for="song in songs" :key="song.artist">
<div v-for="song in songs" :key="song.artist" class="song-item">
<img src="" class="rounded" loading="lazy"/>
<div class="tags">
<div class="title">{{ song.title }}</div>

View File

@@ -1,14 +1,14 @@
<template>
<div class="r-sidebar">
<SearchInput />
<div v-auto-animate class="r-content no-scroll" >
<div class="r-dash" v-if="tabs.current === tabs.tabs.home">
<div v-auto-animate class="r-content no-scroll">
<div v-if="tabs.current === tabs.tabs.home" class="r-dash">
<DashBoard />
</div>
<div class="r-search" v-if="tabs.current === tabs.tabs.search">
<div v-if="tabs.current === tabs.tabs.search" class="r-search">
<Search />
</div>
<div class="r-queue" v-if="tabs.current === tabs.tabs.queue">
<div v-if="tabs.current === tabs.tabs.queue" class="r-queue">
<Queue />
</div>
</div>
@@ -16,10 +16,11 @@
</template>
<script setup lang="ts">
import useTabStore from "../../stores/tabs";
import DashBoard from "./Home/Main.vue";
import useTabStore from "@/stores/tabs";
import Queue from "./Queue.vue";
import Search from "./Search/Main.vue";
import DashBoard from "./Home/Main.vue";
import SearchInput from "./SearchInput.vue";
const tabs = useTabStore();

View File

@@ -6,7 +6,7 @@
@mouseout="mouseover = false"
>
<NoItems
:flag="!queue.tracklist.length"
:flag="!store.tracklist.length"
:title="'No songs in queue'"
:description="'When you start playing songs, they will appear here.'"
:icon="QueueSvg"
@@ -36,18 +36,23 @@
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import useQStore from "@/stores/queue";
import useInterface from "@/stores/interface";
import useTracklist from "@/stores/queue/tracklist";
import TrackItem from "@/components/shared/TrackItem.vue";
import QueueActions from "./Queue/QueueActions.vue";
import NoItems from "../shared/NoItems.vue";
import QueueActions from "./Queue/QueueActions.vue";
import TrackItem from "@/components/shared/TrackItem.vue";
import QueueSvg from "@/assets/icons/queue.svg";
const itemHeight = 64;
const queue = useQStore();
const store = useTracklist();
const mouseover = ref(false);
const { focusCurrentInSidebar, setScrollFunction } = useInterface();
const scrollerItems = computed(() => {
return queue.tracklist.map((track, index) => ({
return store.tracklist.map((track, index) => ({
track,
id: index,
}));
@@ -70,12 +75,12 @@ function scrollToCurrent() {
}
onMounted(() => {
queue.setScrollFunction(scrollToCurrent, mouseover);
queue.focusCurrentInSidebar();
setScrollFunction(scrollToCurrent, mouseover);
focusCurrentInSidebar();
});
onBeforeUnmount(() => {
queue.setScrollFunction(() => {}, null);
setScrollFunction(() => {}, null);
});
</script>
@@ -83,9 +88,5 @@ onBeforeUnmount(() => {
.queue-virtual-scroller {
height: 100%;
overflow: hidden;
.vue-recycle-scroller {
padding: 0 !important;
}
}
</style>

View File

@@ -1,13 +1,20 @@
<template>
<div class="queue-actions">
<div class="left">
<button v-wave class="shuffle-queue action" @click="queue.shuffleQueue">
<button
v-if="!onNowPlaying"
v-wave
class="shuffle-queue action"
@click="queue.shuffleQueue"
>
<ShuffleSvg />
<span>Shuffle</span>
</button>
<h2 v-else style="margin: 0">Now Playing</h2>
</div>
<div class="right">
<button
class="menu"
:class="{ 'btn-active': context_showing }"
@click="showContextMenu"
>
@@ -19,20 +26,28 @@
<script setup lang="ts">
import { ref } from "vue";
import useQueueStore from "@/stores/queue";
import useQueue from "@/stores/queue";
import useTracklist from "@/stores/queue/tracklist";
import { showQueueContextMenu } from "@/helpers/contextMenuHandler";
import OptionsSvg from "@/assets/icons/more.svg";
import ShuffleSvg from "@/assets/icons/shuffle.svg";
import { showQueueContextMenu } from "@/helpers/contextMenuHandler";
const queue = useQueueStore();
const queue = useQueue();
const { tracklist } = useTracklist();
const context_showing = ref(false);
function showContextMenu(e: MouseEvent) {
if (!queue.tracklist.length) return;
if (!tracklist.length) return;
showQueueContextMenu(e, context_showing);
}
defineProps<{
onNowPlaying?: boolean;
}>();
</script>
<style lang="scss">
@@ -43,8 +58,23 @@ function showContextMenu(e: MouseEvent) {
margin: 1rem;
margin-bottom: 0;
.lyricsversion {
display: flex;
gap: 1rem;
.save {
background-color: transparent;
}
// hide on screens less than 600px
@media screen and (max-width: 600px) {
display: none;
}
}
.left {
display: flex;
align-items: center;
gap: $small;
}
@@ -56,11 +86,16 @@ function showContextMenu(e: MouseEvent) {
}
}
.right > button {
padding: 0 $smaller;
.right {
display: flex;
gap: $medium;
svg {
transform: scale(1.2) rotate(90deg);
.menu {
padding: 0 $smaller;
svg {
transform: scale(1.2) rotate(90deg);
}
}
}
}

View File

@@ -1,65 +0,0 @@
<template>
<div v-auto-animate class="artists-results">
<div>
<div
v-if="album_grid == true && search.albums.value.length"
class="search-results-grid"
>
<AlbumCard
v-for="a in search.albums.value"
:key="a.albumid"
:album="a"
/>
</div>
<div
v-else-if="!album_grid && search.artists.value.length"
class="search-results-grid"
>
<ArtistCard
v-for="artist in search.artists.value"
:key="artist.image"
:artist="artist"
:alt="true"
/>
</div>
<div v-else class="t-center">
<h5>No {{ album_grid ? "albums" : "artists" }}</h5>
</div>
</div>
<LoadMore
v-if="search.albums.value.length || search.artists.value.length"
:loader="album_grid ? search.loadAlbums : search.loadArtists"
:can_load_more="album_grid ? search.albums.more : search.artists.more"
/>
</div>
</template>
<script setup lang="ts">
import useSearchStore from "../../../stores/search";
import AlbumCard from "@/components/shared/AlbumCard.vue";
import ArtistCard from "../../shared/ArtistCard.vue";
import LoadMore from "./LoadMore.vue";
const search = useSearchStore();
defineProps<{
album_grid?: boolean;
}>();
</script>
<style lang="scss">
.artists-results {
height: 100%;
display: grid;
margin: 0 1rem;
grid-template-rows: 1fr max-content;
}
.search-results-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr));
min-height: 10rem;
padding-bottom: $small;
}
</style>

View File

@@ -1,41 +0,0 @@
<template>
<div class="morexx">
<button
@click.prevent="loader()"
:class="{
load_disabled: !can_load_more,
}"
>
<div class="text">Load More</div>
</button>
</div>
</template>
<script setup lang="ts">
defineProps<{
loader: () => void;
can_load_more: boolean;
}>();
</script>
<style lang="scss">
.morexx {
display: grid;
place-items: center;
margin-top: $small;
button {
padding: 0 1rem !important;
width: calc(100% + 2rem);
border-radius: 0;
height: 3rem;
background: $darkestblue;
border: none;
&:hover {
background: $darkblue;
border-color: $darkblue;
}
}
}
</style>

View File

@@ -2,9 +2,9 @@
<div class="right-search">
<TabsWrapper
:tabs="tabs"
:current-tab="currentTab"
:tab-content="true"
@switchTab="switchTab"
:currentTab="currentTab"
:tabContent="true"
>
<Tab :name="currentTab" />
</TabsWrapper>
@@ -48,5 +48,7 @@ function switchTab(tab: string) {
align-items: center;
position: relative;
}
}
</style>

View File

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

View File

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

View File

@@ -1,10 +1,7 @@
<template>
<div class="right-search-top-albums-or-artists">
<AlbumCard
v-for="album in search.top_results.albums.slice(
0,
!onSearchPage ? 3 : search.top_results.albums.length
)"
v-for="album in search.top_results.albums.slice(0, 3)"
:key="album.albumhash"
:album="album"
/>
@@ -16,10 +13,6 @@ import useSearchstore from "@/stores/search";
import AlbumCard from "@/components/shared/AlbumCard.vue";
const search = useSearchstore();
defineProps<{
onSearchPage?: boolean;
}>();
</script>
<style lang="scss">

View File

@@ -1,10 +1,7 @@
<template>
<div class="right-search-top-albums-or-artists">
<ArtistCard
v-for="artist in search.top_results.artists.slice(
0,
!onSearchPage ? 3 : search.top_results.artists.length
)"
v-for="artist in search.top_results.artists.slice(0, 3)"
:key="artist.artisthash"
:artist="artist"
/>
@@ -12,12 +9,8 @@
</template>
<script setup lang="ts">
import ArtistCard from "@/components/shared/ArtistCard.vue";
import useSearchStore from "@/stores/search";
import ArtistCard from "@/components/shared/ArtistCard.vue";
const search = useSearchStore();
defineProps<{
onSearchPage?: boolean;
}>();
</script>
</script>

View File

@@ -17,15 +17,17 @@ import { Track } from "@/interfaces";
import useQueueStore from "@/stores/queue";
import useSearchStore from "@/stores/search";
import useTracklist from "@/stores/queue/tracklist";
import TrackItem from "@/components/shared/TrackItem.vue";
const search = useSearchStore();
const queue = useQueueStore();
const tracklist = useTracklist();
function handlePlay(track: Track) {
queue.clearQueue();
queue.playFromSearch(search.query, [track]);
tracklist.setFromSearch(search.query, [track]);
queue.play(0);
}
</script>

View File

@@ -1,44 +1,75 @@
<template>
<div id="tracks-results">
<div v-if="search.tracks.value.length">
<TrackItem
v-for="(track, index) in search.tracks.value"
:key="track.id"
:isCurrent="queue.currenttrackhash === track.trackhash"
:isHighlighted="false"
:isCurrentPlaying="
queue.currenttrackhash === track.trackhash && queue.playing
"
:track="track"
@playThis="updateQueue(index)"
:index="index + 1"
/>
<div v-if="!search.tracks.value.length" class="t-center">
<h5>No tracks</h5>
</div>
<div v-else class="t-center"><h5>No tracks</h5></div>
<LoadMore
:loader="search.loadTracks"
:can_load_more="search.tracks.more"
v-if="search.tracks.value.length"
/>
<RecycleScroller
v-slot="{ item, index }"
class="scroller"
style="height: 100%"
:items="scrollerItems"
:item-size="64"
key-field="id"
>
<component
:is="item.component"
v-bind="item.props"
@playThis="updateQueue(index)"
/>
</RecycleScroller>
</div>
</template>
<script setup lang="ts">
import { onMounted } from "vue";
import { computed, onMounted } from "vue";
import useQueue from "@/stores/queue";
import useSearch from "@/stores/search";
import useTracklist from "@/stores/queue/tracklist";
import TrackItem from "@/components/shared/TrackItem.vue";
import useQStore from "@/stores/queue";
import useSearchStore from "@/stores/search";
import LoadMore from "./LoadMore.vue";
import AlbumsFetcher from "@/components/ArtistView/AlbumsFetcher.vue";
const queue = useQStore();
const search = useSearchStore();
const queue = useQueue();
const search = useSearch();
const tracklist = useTracklist();
function updateQueue(index: number) {
queue.playFromSearch(search.query, search.tracks.value);
tracklist.setFromSearch(search.query, search.tracks.value);
queue.play(index);
}
const scrollerItems = computed(() => {
const items: any[] = search.tracks.value.map((track, index) => {
return {
track,
id: index,
component: TrackItem,
props: {
track,
index: index + 1,
isCurrent: queue.currenttrackhash === track.trackhash,
isCurrentPlaying:
queue.currenttrackhash === track.trackhash && queue.playing,
isHighlighted: false,
},
};
});
items.push({
id: Math.random(),
component: AlbumsFetcher,
props: {
fetch_callback: search.loadTracks,
showtext: search.tracks.more,
outside_route: true,
},
});
return items;
});
onMounted(() => {
search.switchTab("tracks");
});

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,40 @@
<template>
<div class="lockernumberinput">
<button class="minus" @click="submit('minus')">-</button>
<div class="number">{{ value }}{{ unit }}</div>
<button class="plus" @click="submit('plus')">+</button>
</div>
</template>
<script setup lang="ts">
const props = defineProps<{
value: number;
min: number;
max: number;
step: number;
unit: string;
onChange: (value: number) => void;
}>();
function submit(action: "plus" | "minus") {
const newValue = props.value + (action === "plus" ? props.step : -props.step);
if (newValue < props.min || newValue > props.max) return;
props.onChange(newValue);
}
</script>
<style lang="scss">
.lockernumberinput {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
.number {
text-align: center;
}
button {
width: 2.25rem;
}
}
</style>

View File

@@ -0,0 +1,80 @@
<template>
<div class="settings-quickactions grid">
<button
v-for="a in actions"
:key="a.label"
class="qaction rounded-sm"
@click="a.action"
>
<component :is="a.icon" v-if="a.icon" />
<div class="label">{{ a.label }}</div>
<Switch v-if="a.state" :state="a.state()" />
</button>
</div>
</template>
<script setup lang="ts">
import useSettings from "@/stores/settings";
import { triggerScan } from "@/requests/settings/rootdirs";
import Switch from "./Switch.vue";
import ReloadSvg from "@/assets/icons/reload.svg";
const settings = useSettings();
const actions = [
{
label: "Rescan library",
action: triggerScan,
icon: ReloadSvg,
},
{
label: "Sidebar",
action: settings.toggleDisableSidebar,
state: () => settings.use_sidebar,
},
{
label: "Silence skip",
action: settings.toggleUseSilenceSkip,
state: () => settings.use_silence_skip,
},
{
label: "Crossfade",
action: settings.toggleCrossfade,
state: () => settings.use_crossfade,
},
];
</script>
<style lang="scss">
.settings-quickactions.grid {
gap: 1.25rem;
padding-top: $medium;
grid-template-columns: 1fr 1fr;
.qaction {
background-color: $gray5;
padding: 1.5rem $small;
font-size: 14px;
border: solid 1px $gray4;
// justify-content: space-between;
gap: $smaller;
&:hover {
background-color: $gray4;
}
}
.switch {
transform: scale(0.8);
}
.qaction svg {
transform: scale(0.75);
}
.qaction:nth-child(2) svg {
transform: rotate(90deg);
}
}
</style>

View File

@@ -1,9 +1,9 @@
<template>
<div class="setting-select rounded-sm no-scroll">
<div
class="option"
v-for="option in optionsWithActive"
:key="option.title"
class="option"
:class="{ active: option.active }"
@click="setterFn(option.value)"
>

View File

@@ -24,13 +24,13 @@
</template>
<script setup lang="ts">
import { Ref, computed, ref, watch } from "vue";
import { Ref, computed, onMounted, ref } from "vue";
const separatorinput: Ref<HTMLInputElement | null> = ref(null);
const input = ref("");
const props = defineProps<{
default: () => string[];
default: string[];
submit: (input: string) => void;
}>();
@@ -69,14 +69,16 @@ function submitInput() {
const preview_items = computed(() => splitInput(input.value));
const default_input = computed(() =>
props.default() ? props.default().join(", ") : ""
props.default ? props.default.join(", ") : ""
);
watch(props.default, (newval, _) => {
const text = newval.join(", ");
if (separatorinput.value) separatorinput.value.value = text;
onMounted(() => {
const text = props.default.join(", ");
input.value = text;
if (separatorinput.value) {
separatorinput.value.value = text;
}
});
</script>

View File

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

View File

@@ -6,44 +6,62 @@
Customize your settings and preferences
</template>
</GenericHeader>
<GenericTabs
:items="
settingGroups.map((g) => ({
title: g.title,
params: {
tab: g.title.toLowerCase(),
},
}))
"
:active="(item) => item.title === currentTab?.title"
:route="Routes.settings"
/>
<Group
v-for="(group, index) in settingGroups[current].groups"
v-for="(group, index) in currentTab?.groups"
:key="index"
:group="group"
/>
<About v-if="currentTab?.title === 'About'" />
<div class="version t-center">
<LogoSvg /> <span>Swing Music - v{{ VERSION }}</span>
<b>Swing Music - v{{ settings.version }}</b>
</div>
</div>
</template>
<script setup lang="ts">
import { VERSION } from "@/config";
import { computed } from "vue";
import { Routes } from "@/router";
import { useRoute } from "vue-router";
import settingGroups from "@/settings";
import useSettings from "@/stores/settings";
import Group from "./Group.vue";
import About from "./About.vue";
import GenericTabs from "@/components/shared/GenericTabs.vue";
import GenericHeader from "@/components/shared/GenericHeader.vue";
import LogoSvg from "@/assets/icons/logos/logo-light.svg";
defineProps<{
current: number;
}>();
const route = useRoute();
const settings = useSettings();
const currentTab = computed(() => {
const tab = route.params.tab;
return settingGroups.find((group) => group.title.toLowerCase() === tab);
});
</script>
<style lang="scss">
.settingscontent {
width: 35rem;
width: 31rem;
max-width: 100%;
padding-bottom: 2rem;
.version {
margin: 2rem auto;
color: $gray1;
height: 3rem;
width: max-content;
display: flex;
align-items: center;
gap: 1rem;
opacity: 0.5;
font-size: 14px;
}
}
</style>

View File

@@ -1,7 +1,12 @@
<template>
<div v-if="group.show_if ? group.show_if() : true" class="settingsgroup">
<div v-if="group.title || group.desc" class="info">
<h4 v-if="group.title">{{ group.title }}</h4>
<h4 v-if="group.title">
{{ group.title
}}<span v-if="group.experimental" class="badge experimental circular">
{{ group.experimental ? "experimental" : "" }}
</span>
</h4>
<div v-if="group.desc" class="desc">{{ group.desc }}</div>
</div>
<div class="setting rounded pad-lg">
@@ -25,6 +30,15 @@
<div class="title">
<span class="ellip">
{{ setting.title }}
<span
v-if="setting.experimental"
class="badge experimental circular"
>
{{ setting.experimental ? "experimental" : "" }}
</span>
<span v-if="setting.new" class="badge new circular">
{{ setting.new ? "new" : "" }}
</span>
</span>
<button
v-if="setting.type == SettingType.root_dirs"
@@ -55,8 +69,18 @@
>
{{ setting.button_text && setting.button_text() }}
</button>
<LockedNumberInput
v-if="setting.type == SettingType.locked_number_input"
:value="setting.state !== null ? setting.state() : 0"
:min="0"
:max="10"
:step="1"
:unit="'s'"
:on-change="setting.action"
/>
</div>
<QuickActions v-if="setting.type == SettingType.quick_actions" />
<List
v-if="setting.type === SettingType.root_dirs"
icon="folder"
@@ -65,7 +89,7 @@
<SeparatorsInput
v-if="setting.type === SettingType.separators_input && setting.action"
:submit="setting.action"
:default="setting.state ? setting.state : () => []"
:default="setting.state ? setting.state() : []"
/>
</div>
</div>
@@ -76,11 +100,13 @@
import { SettingType } from "@/settings/enums";
import { SettingGroup } from "@/interfaces/settings";
import List from "./Components/List.vue";
import Switch from "./Components/Switch.vue";
import Select from "./Components/Select.vue";
import List from "./Components/List.vue";
import SeparatorsInput from "./Components/SeparatorsInput.vue";
import ReloadSvg from "@/assets/icons/reload.svg";
import QuickActions from "./Components/QuickSettings.vue";
import SeparatorsInput from "./Components/SeparatorsInput.vue";
import LockedNumberInput from "./Components/LockedNumberInput.vue";
defineProps<{
group: SettingGroup;
@@ -96,6 +122,24 @@ defineProps<{
border-bottom: solid 1px $gray;
padding-bottom: 2rem;
.badge {
margin-left: $small;
opacity: 0.75;
padding: 0 $smaller;
border-radius: $smaller;
font-size: 12px !important;
}
.experimental {
border: solid 1px $yellow;
color: $yellow;
}
.badge.new {
background-color: $blue;
opacity: 1;
}
&:first-child {
margin-top: 0;
}
@@ -116,8 +160,6 @@ defineProps<{
.setting {
background-color: $gray;
// display: grid;
// gap: 1rem;
.inactive {
opacity: 0.5;

View File

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

View File

@@ -1,18 +1,15 @@
<template>
<div class="modal" v-if="modal.visible">
<div v-if="modal.visible" class="modal">
<div class="bg" @click="modal.hideModal"></div>
<div
v-motion-slide-top
class="m-content rounded"
:style="{
maxWidth:
modal.component == modal.options.setRootDirs ? '56rem' : '30rem',
}"
v-motion-slide-top
>
<div class="heading">{{ modal.title }}</div>
<div class="close circular" @click="modal.hideModal">
<PlusSvg />
</div>
<NewPlaylist
v-if="modal.component == modal.options.newPlaylist"
v-bind="modal.props"
@@ -20,8 +17,8 @@
@setTitle="setTitle"
/>
<UpdatePlaylist
v-bind="modal.props"
v-if="modal.component == modal.options.updatePlaylist"
v-bind="modal.props"
@hideModal="hideModal"
@setTitle="setTitle"
/>
@@ -29,14 +26,10 @@
<div v-if="modal.component == modal.options.deletePlaylist">
<ConfirmModal
:text="'Are you sure you want to permanently delete this playlist?'"
:cancelAction="modal.hideModal"
:confirmAction="deletePlaylist"
:cancel-action="modal.hideModal"
:confirm-action="deletePlaylist"
/>
</div>
<SetIP
v-if="modal.component == modal.options.SetIP"
@setTitle="setTitle"
/>
<SetRootDirs
v-if="modal.component == modal.options.setRootDirs"
@hideModal="hideModal"
@@ -54,12 +47,10 @@ import { deletePlaylist as delPlaylist } from "@/requests/playlists";
import { useRouter } from "vue-router";
import useModalStore from "@/stores/modal";
import PlusSvg from "@/assets/icons/plus.svg";
import WelcomeModal from "./WelcomeModal.vue";
import ConfirmModal from "./modals/ConfirmModal.vue";
import NewPlaylist from "./modals/NewPlaylist.vue";
import RootDirsPrompt from "./modals/RootDirsPrompt.vue";
import SetIP from "./modals/SetIP.vue";
import SetRootDirs from "./modals/SetRootDirs.vue";
import UpdatePlaylist from "./modals/updatePlaylist.vue";
@@ -92,13 +83,15 @@ function deletePlaylist() {
input[type="search"] {
margin: $small 0;
border: 2px solid $gray3;
background-color: transparent;
border: none;
background-color: $gray5;
color: #fff;
width: 100%;
padding: 0.5rem;
font-size: 1rem;
font-size: 14px;
outline: none;
height: 2.75rem !important;
}
.bg {
@@ -121,28 +114,6 @@ function deletePlaylist() {
width: calc(100% - 2rem);
padding: 2rem 1rem;
}
.close {
position: absolute;
top: 1.5rem;
right: 1.25rem;
transform: rotate(45deg);
svg {
background-color: $gray3;
border-radius: 1rem;
}
cursor: pointer;
opacity: 0.75;
&:hover {
svg {
background-color: $red;
}
}
}
}
}
</style>

View File

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

View File

@@ -1,48 +0,0 @@
<template>
<div class="set-ip-modal">
<label for="">Change the </label>
<input
id="modal-input"
type="text"
class="rounded-sm"
v-model="ip_address"
/>
<button
class="circular btn-active"
@click.prevent="setBaseApiUrl(ip_address)"
>
Set address
</button>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { setBaseApiUrl, baseApiUrl } from "@/config";
const emit = defineEmits<{
(e: "setTitle", title: string): void;
}>();
const title = "Set backend address";
const ip_address = ref("http://localhost:1970");
onMounted(() => {
emit("setTitle", title);
document.getElementById("modal-input")?.focus();
});
</script>
<style lang="scss">
.set-ip-modal {
#modal-input {
margin-top: 1rem;
}
button {
margin: 0 auto;
margin-top: $small;
padding: 0 1rem;
}
}
</style>

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,73 @@
<template>
<div class="topnavlinks">
<RouterLink
v-for="link in topnavitems"
:key="link.name"
class="link"
:to="{
name: link.route_name,
params: link?.params,
}"
:class="{
active: $route.name && $route.name === link.route_name,
}"
>
{{ link.name || "|" }}
</RouterLink>
</div>
</template>
<script setup lang="ts">
import { topnavitems } from "../LeftSidebar/navitems";
</script>
<style lang="scss">
.topnavlinks {
display: flex;
gap: $smaller;
align-items: center;
width: max-content;
user-select: none;
.link {
text-transform: capitalize;
padding: 0.25rem $medium;
cursor: pointer;
border-radius: 2rem;
display: flex;
align-items: center;
svg {
transform: scale(0.75);
}
&:hover {
color: $primary;
background-color: #fff;
}
}
.active {
background-color: #fff;
color: $primary;
}
.link.home {
padding: 0;
padding-right: $small;
display: flex;
align-items: center;
justify-content: center;
gap: $smaller;
}
.separator_ {
padding: 0;
pointer-events: none;
background-color: transparent;
opacity: 0.2;
}
}
</style>

View File

@@ -0,0 +1,33 @@
<template>
<div class="info">
<SettingsTitle v-if="$route.name == Routes.settings" :text="'Settings'" />
<SearchTitle v-if="$route.name == Routes.search" />
<PlaylistsTitle v-if="$route.name == Routes.playlists" />
<SimpleNav
v-if="$route.name == Routes.artistTracks"
:text="$route.query.artist as string || 'Artist Tracks'"
/>
<SimpleNav
v-if="$route.name === Routes.favoriteAlbums"
:text="'Favorite Albums'"
/>
<SimpleNav
v-if="$route.name === Routes.favoriteArtists"
:text="'Favorite Artists'"
/>
<SimpleNav
v-if="$route.name === Routes.favoriteTracks"
:text="'Favorite Tracks'"
/>
<SimpleNav v-if="$route.name === Routes.nowPlaying" :text="'Now Playing'" />
</div>
</template>
<script setup lang="ts">
import { Routes } from "@/router";
import SimpleNav from "./Titles/SimpleNav.vue";
import SearchTitle from "./Titles/SearchTitle.vue";
import SettingsTitle from "./Titles/SettingsTitle.vue";
import PlaylistsTitle from "./Titles/PlaylistsTitle.vue";
</script>

View File

@@ -1,118 +0,0 @@
<template>
<div class="artist-discography-nav">
<h1 class="ellip">{{ store.artistname }}</h1>
<div class="buttons">
<div class="select rounded-sm" v-auto-animate="{ duration: 10 }">
<button class="selected" @click.prevent="showDropDown = !showDropDown">
<span class="ellip">{{ store.page }}</span>
<ArrowSvg />
</button>
<div
ref="dropOptionsRef"
class="options rounded-sm shadow-lg"
v-if="showDropDown"
>
<div
class="option"
v-for="a in albums"
@click.prevent="switchView(a)"
>
{{ a }}
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onClickOutside } from "@vueuse/core";
import ArrowSvg from "@/assets/icons/expand.svg";
import { discographyAlbumTypes as albums } from "@/enums";
import { Ref, ref } from "vue";
import useArtistDiscogStore from "@/stores/pages/artistDiscog";
const store = useArtistDiscogStore();
const showDropDown = ref(false);
const dropOptionsRef: Ref<HTMLElement | undefined> = ref();
function hideDropDown() {
showDropDown.value = false;
}
function switchView(album: albums) {
store.setAlbums(album);
hideDropDown();
}
onClickOutside(dropOptionsRef, (e) => {
// @ts-ignore
e.stopImmediatePropagation();
hideDropDown();
});
</script>
<style lang="scss">
.artist-discography-nav {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
h1 {
margin: 0;
}
.buttons {
display: flex;
}
.selected {
display: grid;
grid-template-columns: 1fr 2rem;
gap: $smaller;
width: 100%;
padding-right: 0;
svg {
transform: rotate(90deg) scale(0.9);
}
}
.select {
position: relative;
width: 8rem;
display: flex;
align-items: center;
font-size: calc($medium + 2px);
z-index: 10;
.options {
background-color: $gray;
position: absolute;
top: 120%;
padding: $small $smaller;
display: grid;
}
.option {
padding: $small;
border-bottom: 1px solid $gray4;
width: 7.5rem;
&:hover {
border-radius: $smaller;
border-bottom: 1px solid transparent;
background-color: $darkestblue;
}
&:last-child {
border-bottom: none;
}
}
}
}
</style>

View File

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

View File

@@ -1,23 +0,0 @@
<template>
<div class="nav-queue-title">
<QueueActions />
</div>
</template>
<script setup lang="ts">
import QueueActions from "@/components/RightSideBar/Queue/QueueActions.vue";
</script>
<style lang="scss">
.nav-queue-title {
display: grid;
grid-template-columns: 1fr max-content;
align-items: center;
.queue-actions {
margin: 0;
width: 100%;
}
}
</style>

View File

@@ -1,28 +1,26 @@
<template>
<div class="nav-search-input">
<SearchInput :on_nav="true" />
<div v-if="!isMobile" class="buttons-area">
<Tabs
:tabs="tabs"
:current-tab="($route.params.page as string)"
@switchTab="(tab: string) => {
<Tabs
v-if="!(content_width < 800)"
:tabs="tabs"
:current-tab="($route.params.page as string)"
@switchTab="(tab: string) => {
$router.replace({ name: Routes.search, params: { page: tab }, query: {
q: search.query,
} });
search.switchTab(tab);
}"
/>
</div>
/>
</div>
</template>
<script setup lang="ts">
import { Routes } from "@/router";
import Tabs from "@/components/RightSideBar/Search/TabsWrapper.vue";
import SearchInput from "@/components/RightSideBar/SearchInput.vue";
import useSearchStore from "@/stores/search";
import { isMobile } from "@/stores/content-width";
import { content_width } from "@/stores/content-width";
import Tabs from "@/components/RightSideBar/Search/TabsWrapper.vue";
const search = useSearchStore();
const tabs = ["top", "tracks", "albums", "artists"];
@@ -30,35 +28,8 @@ const tabs = ["top", "tracks", "albums", "artists"];
<style lang="scss">
.nav-search-input {
align-items: center;
display: grid;
grid-template-columns: minmax(14rem, 20rem) max-content;
justify-content: space-between;
gap: 2rem;
@include allPhones {
grid-template-columns: 1fr;
gap: 0;
}
.buttons-area {
position: relative;
height: 100%;
width: 18rem;
}
#right-tabs {
width: max-content;
height: max-content;
.tabheaders {
height: 38px;
}
}
.tabheaders {
height: 2.25rem;
margin: 0;
position: relative;
}
}
</style>

View File

@@ -22,6 +22,10 @@
/>
</div>
<div>
<div v-if="album.help_text" class="rhelp album">
<span class="help">{{ album.help_text }}</span>
<span class="time">{{ album.time }}</span>
</div>
<h4 v-tooltip class="title ellip">
{{ album.title }}
</h4>
@@ -38,30 +42,6 @@
{{ `${artists[0].name}` }}
</RouterLink>
</div>
<!-- when showing other versions -->
<!-- <div
v-if="
album.versions.length === 0 &&
hide_artists &&
$route.name === Routes.album
"
class="artist ellip"
>
{{ album.date }}
{{
album.albumartists.length > 1
? ` • ${album.albumartists[1].name}`
: ""
}}
</div> -->
<!-- <div v-else>
<div class="artist ellip">
{{ album.date }}
</div>
</div> -->
<!-- end -->
<div v-if="album.versions.length" class="versions">
<MasterFlag
v-for="v in getVersions(
@@ -121,8 +101,6 @@ const artists = computed(() => {
<style lang="scss">
.album-card {
flex: 0 0 10.1rem;
display: grid;
gap: $small;
padding: $medium;

View File

@@ -8,17 +8,40 @@
}"
class="artist-card"
>
<img class="artist-image circular" :src="imguri + artist.image" />
<div class="image circular">
<img class="artist-image circular" :src="imguri + artist.image" />
<div
class="overlay circular"
:style="{
background: `linear-gradient(to top, ${artist.colors[0]} 20%, transparent)`,
}"
></div>
<PlayBtn
:artisthash="artist.artisthash"
:artistname="artist.name"
:source="playSources.artist"
/>
</div>
<div v-if="artist.help_text" class="rhelp t-center">
<span class="help">{{ artist.help_text }}</span>
<span class="time">{{ artist.time }}</span>
</div>
<div class="artist-name t-center">
{{ artist.name }}
</div>
<div v-if="artist.help_text && artist.trackcount" class="racount t-center">
{{ artist.trackcount }} Track{{ artist.trackcount == 1 ? "" : "s" }}
</div>
</RouterLink>
</template>
<script setup lang="ts">
import { Artist } from "@/interfaces";
import { Routes } from "@/router";
import { paths } from "../../config";
import { paths } from "@/config";
import PlayBtn from "./PlayBtn.vue";
import { playSources } from "@/enums";
const imguri = paths.images.artist.large;
@@ -29,32 +52,67 @@ defineProps<{
<style lang="scss">
.artist-card {
flex: 0 0 10.1rem;
overflow: hidden;
position: relative;
border-radius: $medium;
display: grid;
gap: $small;
justify-content: center;
padding: 1.2rem 1rem !important;
font-size: 0.9rem;
font-weight: bolder;
height: max-content;
.image {
position: relative;
.overlay {
position: absolute;
width: 100%;
height: calc(100% - $small + 1px);
top: 0;
opacity: 0;
}
}
$btnwidth: 4rem;
.play-btn {
opacity: 0;
position: absolute;
width: 4rem;
bottom: 0;
left: calc(50% - ($btnwidth / 2));
transition: all 0.25s;
}
&:hover {
background-color: $gray4;
.play-btn {
opacity: 1;
transform: translateY(-1.25rem);
}
.overlay {
opacity: 1;
}
}
.artist-image {
width: 100%;
transition: all 0.5s ease-in-out;
object-fit: cover;
margin-bottom: $smaller;
}
.artist-name {
margin-bottom: $smaller;
word-break: break-word;
}
.racount {
font-size: 12px;
color: #ffffffbf;
}
}
</style>

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