mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-10 04:30:25 +02:00
use searchparams, localstorage for list filters
This commit is contained in:
@@ -0,0 +1,65 @@
|
|||||||
|
import { useLocalStorage } from '@mantine/hooks';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { parseAsString, useQueryState } from 'nuqs';
|
||||||
|
|
||||||
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
|
import { FolderButton } from '/@/renderer/features/shared/components/folder-button';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
||||||
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
interface ListMusicFolderDropdownProps {
|
||||||
|
listKey: ItemListKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ListMusicFolderDropdown = ({ listKey }: ListMusicFolderDropdownProps) => {
|
||||||
|
const server = useCurrentServer();
|
||||||
|
const { data: musicFolders } = useQuery(
|
||||||
|
sharedQueries.musicFolders({ query: null, serverId: server.id }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [persisted, setPersisted] = useLocalStorage({
|
||||||
|
defaultValue: '',
|
||||||
|
key: getPersistenceKey(listKey),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [musicFolderId, setMusicFolderId] = useQueryState(
|
||||||
|
getPersistenceKey(listKey),
|
||||||
|
parseAsString.withDefault(persisted || ''),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSetMusicFolder = (e: string) => {
|
||||||
|
if (e === musicFolderId) {
|
||||||
|
setMusicFolderId('');
|
||||||
|
setPersisted('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMusicFolderId(e);
|
||||||
|
setPersisted(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu position="bottom-start">
|
||||||
|
<DropdownMenu.Target>
|
||||||
|
<FolderButton isActive={!!musicFolderId} />
|
||||||
|
</DropdownMenu.Target>
|
||||||
|
<DropdownMenu.Dropdown>
|
||||||
|
{musicFolders?.items.map((folder) => (
|
||||||
|
<DropdownMenu.Item
|
||||||
|
isSelected={musicFolderId === folder.id}
|
||||||
|
key={`musicFolder-${folder.id}`}
|
||||||
|
onClick={() => handleSetMusicFolder(folder.id)}
|
||||||
|
value={folder.id}
|
||||||
|
>
|
||||||
|
{folder.name}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
))}
|
||||||
|
</DropdownMenu.Dropdown>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPersistenceKey = (listKey: ItemListKey) => {
|
||||||
|
return `list-${listKey}-musicFolder`;
|
||||||
|
};
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { eventEmitter } from '/@/renderer/events/event-emitter';
|
||||||
|
import { RefreshButton } from '/@/renderer/features/shared/components/refresh-button';
|
||||||
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
interface ListRefreshButtonProps {
|
||||||
|
listKey: ItemListKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ListRefreshButton = ({ listKey }: ListRefreshButtonProps) => {
|
||||||
|
const handleRefresh = useCallback(() => {
|
||||||
|
eventEmitter.emit('ITEM_LIST_REFRESH', { key: listKey });
|
||||||
|
}, [listKey]);
|
||||||
|
|
||||||
|
return <RefreshButton onClick={handleRefresh} />;
|
||||||
|
};
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
import { useLocalStorage } from '@mantine/hooks';
|
||||||
|
import { parseAsString, useQueryState } from 'nuqs';
|
||||||
|
|
||||||
|
import i18n from '/@/i18n/i18n';
|
||||||
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
import { Button } from '/@/shared/components/button/button';
|
||||||
|
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
||||||
|
import { AlbumListSort, LibraryItem, ServerType, SortOrder } from '/@/shared/types/domain-types';
|
||||||
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
interface ListSortByDropdownProps {
|
||||||
|
defaultSortByValue: string;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
listKey: ItemListKey;
|
||||||
|
onChange?: (value: string) => void;
|
||||||
|
target?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ListSortByDropdown = ({
|
||||||
|
defaultSortByValue,
|
||||||
|
itemType,
|
||||||
|
listKey,
|
||||||
|
onChange,
|
||||||
|
target,
|
||||||
|
}: ListSortByDropdownProps) => {
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
|
const [persisted, setPersisted] = useLocalStorage({
|
||||||
|
defaultValue: defaultSortByValue,
|
||||||
|
key: getPersistenceKey(listKey),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [sortBy, setSortBy] = useQueryState(
|
||||||
|
FILTER_KEYS.SORT_BY,
|
||||||
|
parseAsString.withDefault(persisted || defaultSortByValue),
|
||||||
|
);
|
||||||
|
|
||||||
|
const sortByLabel =
|
||||||
|
(itemType && FILTERS[itemType][server.type].find((f) => f.value === sortBy)?.name) || '—';
|
||||||
|
|
||||||
|
const handleSortByChange = (sortBy: string) => {
|
||||||
|
setSortBy(sortBy);
|
||||||
|
setPersisted(sortBy);
|
||||||
|
onChange?.(sortBy);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu position="bottom-start">
|
||||||
|
<DropdownMenu.Target>
|
||||||
|
{target ? target : <Button variant="subtle">{sortByLabel}</Button>}
|
||||||
|
</DropdownMenu.Target>
|
||||||
|
<DropdownMenu.Dropdown>
|
||||||
|
{FILTERS[itemType][server.type].map((f) => (
|
||||||
|
<DropdownMenu.Item
|
||||||
|
isSelected={f.value === sortBy}
|
||||||
|
key={`filter-${f.name}`}
|
||||||
|
onClick={() => handleSortByChange(f.value)}
|
||||||
|
value={f.value}
|
||||||
|
>
|
||||||
|
{f.name}
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
))}
|
||||||
|
</DropdownMenu.Dropdown>
|
||||||
|
</DropdownMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ALBUM_LIST_FILTERS: Partial<
|
||||||
|
Record<ServerType, Array<{ defaultOrder: SortOrder; name: string; value: string }>>
|
||||||
|
> = {
|
||||||
|
[ServerType.JELLYFIN]: [
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.ALBUM_ARTIST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.communityRating', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.COMMUNITY_RATING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.criticRating', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.CRITIC_RATING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.name', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.NAME,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.playCount', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.PLAY_COUNT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.random', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.RANDOM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.RECENTLY_ADDED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.releaseDate', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.RELEASE_DATE,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[ServerType.NAVIDROME]: [
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.ALBUM_ARTIST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.artist', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.ARTIST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.duration', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.DURATION,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.mostPlayed', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.PLAY_COUNT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.name', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.NAME,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.random', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.RANDOM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.rating', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.RATING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.RECENTLY_ADDED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.recentlyPlayed', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.RECENTLY_PLAYED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.songCount', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.SONG_COUNT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.favorited', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.FAVORITED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.releaseYear', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.YEAR,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[ServerType.SUBSONIC]: [
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.ALBUM_ARTIST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.mostPlayed', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.PLAY_COUNT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.name', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.NAME,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.random', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.RANDOM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.RECENTLY_ADDED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.recentlyPlayed', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.RECENTLY_PLAYED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.favorited', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.FAVORITED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.releaseYear', { postProcess: 'titleCase' }),
|
||||||
|
value: AlbumListSort.YEAR,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const FILTERS: Partial<Record<LibraryItem, any>> = {
|
||||||
|
[LibraryItem.ALBUM]: ALBUM_LIST_FILTERS,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPersistenceKey = (listKey: ItemListKey) => {
|
||||||
|
return `item_list_${listKey}-${FILTER_KEYS.SORT_BY}`;
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { useLocalStorage } from '@mantine/hooks';
|
||||||
|
import { parseAsString, useQueryState } from 'nuqs';
|
||||||
|
|
||||||
|
import { OrderToggleButton } from '/@/renderer/features/shared/components/order-toggle-button';
|
||||||
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
|
import { SortOrder } from '/@/shared/types/domain-types';
|
||||||
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
|
interface ListSortOrderToggleButtonProps {
|
||||||
|
listKey: ItemListKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ListSortOrderToggleButton = ({ listKey }: ListSortOrderToggleButtonProps) => {
|
||||||
|
const [persisted, setPersisted] = useLocalStorage({
|
||||||
|
defaultValue: SortOrder.ASC,
|
||||||
|
key: getPersistenceKey(listKey),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [sortOrder, setSortOrder] = useQueryState(
|
||||||
|
FILTER_KEYS.SORT_ORDER,
|
||||||
|
parseAsString.withDefault(persisted || SortOrder.ASC),
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleToggleSortOrder = () => {
|
||||||
|
const newSortOrder = sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
|
||||||
|
setSortOrder(newSortOrder);
|
||||||
|
setPersisted(newSortOrder);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OrderToggleButton onToggle={handleToggleSortOrder} sortOrder={sortOrder as SortOrder} />
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPersistenceKey = (listKey: ItemListKey) => {
|
||||||
|
return `item_list_${listKey}-${FILTER_KEYS.SORT_ORDER}`;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user