feat(tracks): search
This commit is contained in:
@@ -38,7 +38,7 @@
|
|||||||
"@protobuf-ts/grpcweb-transport": "^2.9.4",
|
"@protobuf-ts/grpcweb-transport": "^2.9.4",
|
||||||
"@protobuf-ts/plugin": "^2.9.4",
|
"@protobuf-ts/plugin": "^2.9.4",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"fzf": "^0.5.2",
|
"fuse.js": "^7.0.0",
|
||||||
"mode-watcher": "^0.5.0"
|
"mode-watcher": "^0.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getCoverUrl } from '$lib/covers';
|
import { getCoverUrl } from '$lib/covers';
|
||||||
import type { Track } from '$lib/proto/library';
|
import type { Track } from '$lib/proto/library';
|
||||||
import AudioLines from 'virtual:icons/lucide/audio-lines';
|
// import AudioLines from 'virtual:icons/lucide/audio-lines';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
track: Track;
|
track: Track;
|
||||||
@@ -12,16 +12,13 @@
|
|||||||
let { track, currentlyPlaying = false, oncontextmenu }: Props = $props();
|
let { track, currentlyPlaying = false, oncontextmenu }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="flex flex-col gap-3" data-track-hash={track.hash}>
|
||||||
class="flex flex-col gap-3"
|
|
||||||
data-track-hash={track.hash}
|
|
||||||
oncontextmenu={(e) => oncontextmenu?.(e, track.hash)}
|
|
||||||
>
|
|
||||||
<div class="relative aspect-square w-full">
|
<div class="relative aspect-square w-full">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="relative aspect-square h-full w-full overflow-hidden rounded-lg"
|
class="relative aspect-square h-full w-full overflow-hidden rounded-lg"
|
||||||
formaction="/tracks/{track.hash}?/play"
|
formaction="/tracks/{track.hash}?/play"
|
||||||
|
oncontextmenu={(e) => oncontextmenu?.(e, track.hash)}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="aspect-square h-full w-full transform-gpu transition-all duration-150 hover:scale-105 hover:saturate-150"
|
class="aspect-square h-full w-full transform-gpu transition-all duration-150 hover:scale-105 hover:saturate-150"
|
||||||
@@ -31,9 +28,9 @@
|
|||||||
height="640"
|
height="640"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<div class="absolute bottom-6 left-2 size-4 animate-pulse" class:hidden={!currentlyPlaying}>
|
<!-- <div class="absolute bottom-6 left-2 size-4 animate-pulse" class:hidden={!currentlyPlaying}>
|
||||||
<AudioLines class="text-primary" />
|
<AudioLines class="text-primary" />
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="relative space-y-1 text-sm">
|
<div class="relative space-y-1 text-sm">
|
||||||
<h3 class="font-medium leading-none">{track.name}</h3>
|
<h3 class="font-medium leading-none">{track.name}</h3>
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
import { Fzf } from 'fzf';
|
import Fuse from 'fuse.js';
|
||||||
import type { Track } from './proto/library';
|
import type { Track } from './proto/library';
|
||||||
import { getContext, setContext } from 'svelte';
|
import { getContext, setContext } from 'svelte';
|
||||||
|
|
||||||
class SearchState {
|
class SearchState {
|
||||||
input = $state('');
|
input = $state('');
|
||||||
|
#fuse: Fuse<Track> | null = null;
|
||||||
|
|
||||||
filterTracks(tracks: Track[]) {
|
setTracks(tracks: Track[]) {
|
||||||
const fzf = new Fzf(tracks, {
|
this.#fuse = new Fuse(tracks, {
|
||||||
selector: (t) => `${t.name} ${t.artistName}`,
|
keys: ['name', 'artistName'],
|
||||||
sort: true
|
shouldSort: true,
|
||||||
|
includeScore: true,
|
||||||
|
threshold: 0.2,
|
||||||
|
findAllMatches: true
|
||||||
});
|
});
|
||||||
return fzf.find(this.input);
|
}
|
||||||
|
|
||||||
|
filterTracks() {
|
||||||
|
if (this.input.length < 1 || !this.#fuse) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = this.#fuse.search(this.input);
|
||||||
|
|
||||||
|
return results.map((r) => r.item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,22 @@
|
|||||||
import Music2 from 'virtual:icons/lucide/music-2';
|
import Music2 from 'virtual:icons/lucide/music-2';
|
||||||
import ListMusic from 'virtual:icons/lucide/list-music';
|
import ListMusic from 'virtual:icons/lucide/list-music';
|
||||||
import ListEnd from 'virtual:icons/lucide/list-end';
|
import ListEnd from 'virtual:icons/lucide/list-end';
|
||||||
|
import { getSearchState } from '$lib/search.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: PageServerData;
|
data: PageServerData;
|
||||||
}
|
}
|
||||||
|
|
||||||
const player = getPlayerState();
|
|
||||||
|
|
||||||
let { data }: Props = $props();
|
let { data }: Props = $props();
|
||||||
|
|
||||||
|
const player = getPlayerState();
|
||||||
|
const search = getSearchState();
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
console.log('set tracks');
|
||||||
|
search.setTracks(data.tracks);
|
||||||
|
});
|
||||||
|
|
||||||
let contextMenuTarget = $state<string | null>(null);
|
let contextMenuTarget = $state<string | null>(null);
|
||||||
|
|
||||||
const submitPlayTrack: SubmitFunction = async () => {
|
const submitPlayTrack: SubmitFunction = async () => {
|
||||||
@@ -53,6 +60,8 @@
|
|||||||
function contextMenuItemClicked(e: MouseEvent) {
|
function contextMenuItemClicked(e: MouseEvent) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let filteredTracks = $derived(search.filterTracks());
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
@@ -60,7 +69,7 @@
|
|||||||
class="grid grid-cols-2 gap-4 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-7 2xl:grid-cols-8"
|
class="grid grid-cols-2 gap-4 sm:grid-cols-4 md:grid-cols-5 lg:grid-cols-6 xl:grid-cols-7 2xl:grid-cols-8"
|
||||||
use:enhance={submitPlayTrack}
|
use:enhance={submitPlayTrack}
|
||||||
>
|
>
|
||||||
{#each data.tracks as track}
|
{#each filteredTracks ?? data.tracks as track}
|
||||||
<TrackListing
|
<TrackListing
|
||||||
{track}
|
{track}
|
||||||
currentlyPlaying={player.currentlyPlaying?.hash === track.hash}
|
currentlyPlaying={player.currentlyPlaying?.hash === track.hash}
|
||||||
|
|||||||
Reference in New Issue
Block a user