mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +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