mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
add fuse search utils
This commit is contained in:
@@ -14,6 +14,7 @@ import { ItemListHandle } from '/@/renderer/components/item-list/types';
|
|||||||
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
||||||
import { UserFavoriteEventPayload, UserRatingEventPayload } from '/@/renderer/events/events';
|
import { UserFavoriteEventPayload, UserRatingEventPayload } from '/@/renderer/events/events';
|
||||||
import { useIsPlayerFetching, usePlayer } from '/@/renderer/features/player/context/player-context';
|
import { useIsPlayerFetching, usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||||
|
import { searchLibraryItems } from '/@/renderer/features/shared/utils';
|
||||||
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
||||||
import {
|
import {
|
||||||
subscribeCurrentTrack,
|
subscribeCurrentTrack,
|
||||||
@@ -26,7 +27,6 @@ import {
|
|||||||
usePlayerQueueType,
|
usePlayerQueueType,
|
||||||
usePlayerSong,
|
usePlayerSong,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import { searchSongs } from '/@/renderer/utils/search-songs';
|
|
||||||
import { Flex } from '/@/shared/components/flex/flex';
|
import { Flex } from '/@/shared/components/flex/flex';
|
||||||
import { LoadingOverlay } from '/@/shared/components/loading-overlay/loading-overlay';
|
import { LoadingOverlay } from '/@/shared/components/loading-overlay/loading-overlay';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
@@ -140,7 +140,7 @@ export const PlayQueue = forwardRef<ItemListHandle, QueueProps>(({ listKey, sear
|
|||||||
|
|
||||||
const filteredData: QueueSong[] = useMemo(() => {
|
const filteredData: QueueSong[] = useMemo(() => {
|
||||||
if (debouncedSearchTerm) {
|
if (debouncedSearchTerm) {
|
||||||
const searched = searchSongs(data, debouncedSearchTerm);
|
const searched = searchLibraryItems(data, debouncedSearchTerm, LibraryItem.SONG);
|
||||||
return searched;
|
return searched;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
|
import Fuse from 'fuse.js';
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
|
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
|
import {
|
||||||
|
Album,
|
||||||
|
AlbumArtist,
|
||||||
|
Artist,
|
||||||
|
Genre,
|
||||||
|
LibraryItem,
|
||||||
|
Playlist,
|
||||||
|
QueueSong,
|
||||||
|
Song,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
import { Play } from '/@/shared/types/types';
|
import { Play } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const PLAY_TYPES = [
|
export const PLAY_TYPES = [
|
||||||
@@ -69,3 +80,148 @@ export const FILTER_KEYS = {
|
|||||||
SHARED: SharedFilterKeys,
|
SHARED: SharedFilterKeys,
|
||||||
SONG: SongFilterKeys,
|
SONG: SongFilterKeys,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface CreateFuseOptions {
|
||||||
|
fieldNormWeight?: number;
|
||||||
|
ignoreLocation?: boolean;
|
||||||
|
threshold?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FuseSearchableItem = Album | AlbumArtist | Artist | Genre | Playlist | QueueSong | Song;
|
||||||
|
|
||||||
|
export const createFuseForLibraryItem = <T extends FuseSearchableItem>(
|
||||||
|
items: T[],
|
||||||
|
itemType: LibraryItem,
|
||||||
|
options: CreateFuseOptions = {},
|
||||||
|
): Fuse<T> => {
|
||||||
|
const { fieldNormWeight = 1, ignoreLocation = true, threshold = 0.3 } = options;
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
return new Fuse(items, {
|
||||||
|
fieldNormWeight,
|
||||||
|
ignoreLocation,
|
||||||
|
keys: [],
|
||||||
|
threshold,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const sampleItem = items[0];
|
||||||
|
|
||||||
|
const stringKeys = Object.keys(sampleItem).filter(
|
||||||
|
(key) =>
|
||||||
|
typeof sampleItem[key as keyof T] === 'string' &&
|
||||||
|
!key.startsWith('_') &&
|
||||||
|
key !== 'id' &&
|
||||||
|
key !== 'albumId' &&
|
||||||
|
key !== 'streamUrl' &&
|
||||||
|
key !== 'serverId' &&
|
||||||
|
key !== 'ownerId',
|
||||||
|
) as string[];
|
||||||
|
|
||||||
|
const nestedKeys: Array<{ getFn: (item: T) => string; name: string }> = [];
|
||||||
|
|
||||||
|
switch (itemType) {
|
||||||
|
case LibraryItem.ALBUM: {
|
||||||
|
nestedKeys.push(
|
||||||
|
{
|
||||||
|
getFn: (item) => {
|
||||||
|
const a = item as Album;
|
||||||
|
return a.artists?.map((artist) => artist.name).join(' ') || '';
|
||||||
|
},
|
||||||
|
name: 'artists',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getFn: (item) => {
|
||||||
|
const a = item as Album;
|
||||||
|
return a.albumArtists?.map((artist) => artist.name).join(' ') || '';
|
||||||
|
},
|
||||||
|
name: 'albumArtists',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getFn: (item) => {
|
||||||
|
const a = item as Album;
|
||||||
|
return a.genres?.map((genre) => genre.name).join(' ') || '';
|
||||||
|
},
|
||||||
|
name: 'genres',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case LibraryItem.ALBUM_ARTIST: {
|
||||||
|
nestedKeys.push({
|
||||||
|
getFn: (item) => {
|
||||||
|
const aa = item as AlbumArtist;
|
||||||
|
return aa.genres?.map((genre) => genre.name).join(' ') || '';
|
||||||
|
},
|
||||||
|
name: 'genres',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case LibraryItem.ARTIST:
|
||||||
|
case LibraryItem.GENRE:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LibraryItem.PLAYLIST: {
|
||||||
|
nestedKeys.push({
|
||||||
|
getFn: (item) => {
|
||||||
|
const p = item as Playlist;
|
||||||
|
return p.genres?.map((genre) => genre.name).join(' ') || '';
|
||||||
|
},
|
||||||
|
name: 'genres',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case LibraryItem.PLAYLIST_SONG:
|
||||||
|
case LibraryItem.QUEUE_SONG:
|
||||||
|
case LibraryItem.SONG: {
|
||||||
|
nestedKeys.push(
|
||||||
|
{
|
||||||
|
getFn: (item) => {
|
||||||
|
const s = item as QueueSong | Song;
|
||||||
|
return s.artists?.map((artist) => artist.name).join(' ') || '';
|
||||||
|
},
|
||||||
|
name: 'artists',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getFn: (item) => {
|
||||||
|
const s = item as QueueSong | Song;
|
||||||
|
return s.albumArtists?.map((artist) => artist.name).join(' ') || '';
|
||||||
|
},
|
||||||
|
name: 'albumArtists',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getFn: (item) => {
|
||||||
|
const s = item as QueueSong | Song;
|
||||||
|
return s.genres?.map((genre) => genre.name).join(' ') || '';
|
||||||
|
},
|
||||||
|
name: 'genres',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Fuse(items, {
|
||||||
|
fieldNormWeight,
|
||||||
|
ignoreLocation,
|
||||||
|
keys: [...stringKeys, ...nestedKeys],
|
||||||
|
threshold,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const searchLibraryItems = <T extends FuseSearchableItem>(
|
||||||
|
items: T[],
|
||||||
|
searchTerm: string,
|
||||||
|
itemType: LibraryItem,
|
||||||
|
options?: CreateFuseOptions,
|
||||||
|
): T[] => {
|
||||||
|
if (!searchTerm.trim()) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fuse = createFuseForLibraryItem(items, itemType, options);
|
||||||
|
return fuse.search(searchTerm).map((result) => result.item);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import Fuse from 'fuse.js';
|
|
||||||
|
|
||||||
import { QueueSong } from '/@/shared/types/domain-types';
|
|
||||||
|
|
||||||
export const searchSongs = (songs: QueueSong[], searchTerm: string) => {
|
|
||||||
const fuse = new Fuse(songs, {
|
|
||||||
fieldNormWeight: 1,
|
|
||||||
ignoreLocation: true,
|
|
||||||
keys: [
|
|
||||||
'name',
|
|
||||||
'album',
|
|
||||||
{
|
|
||||||
getFn: (song) => song.artists.map((artist) => artist.name),
|
|
||||||
name: 'artist',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
threshold: 0,
|
|
||||||
});
|
|
||||||
return fuse.search(searchTerm).map((item) => item.item);
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user