mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
update shared filter components
This commit is contained in:
@@ -127,7 +127,8 @@
|
|||||||
"year": "year",
|
"year": "year",
|
||||||
"yes": "yes",
|
"yes": "yes",
|
||||||
"explicit": "explicit",
|
"explicit": "explicit",
|
||||||
"clean": "clean"
|
"clean": "clean",
|
||||||
|
"tableColumns": "table columns"
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
"album_one": "album",
|
"album_one": "album",
|
||||||
@@ -826,13 +827,33 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"general": {
|
"general": {
|
||||||
"autoFitColumns": "auto fit columns",
|
"autoFitColumns": "auto fit columns",
|
||||||
|
"autosize": "autosize",
|
||||||
|
"moveUp": "move up",
|
||||||
|
"moveDown": "move down",
|
||||||
|
"pinToLeft": "pin to left",
|
||||||
|
"pinToRight": "pin to right",
|
||||||
|
"alignLeft": "align left",
|
||||||
|
"alignCenter": "align center",
|
||||||
|
"alignRight": "align right",
|
||||||
"followCurrentSong": "follow current song",
|
"followCurrentSong": "follow current song",
|
||||||
"displayType": "display type",
|
"displayType": "display type",
|
||||||
"gap": "$t(common.gap)",
|
"gap": "$t(common.gap)",
|
||||||
"itemGap": "item gap (px)",
|
"itemGap": "item gap (px)",
|
||||||
"itemSize": "item size (px)",
|
"itemSize": "item size (px)",
|
||||||
|
"itemsPerRow": "items per row",
|
||||||
"size": "$t(common.size)",
|
"size": "$t(common.size)",
|
||||||
"tableColumns": "table columns"
|
"size_default": "default",
|
||||||
|
"size_compact": "compact",
|
||||||
|
"size_large": "large",
|
||||||
|
"tableColumns": "table columns",
|
||||||
|
"pagination": "pagination",
|
||||||
|
"pagination_itemsPerPage": "items per page",
|
||||||
|
"pagination_infinite": "infinite",
|
||||||
|
"pagination_paginate": "paginated",
|
||||||
|
"alternateRowColors": "alternate row colors",
|
||||||
|
"horizontalBorders": "horizontal borders",
|
||||||
|
"rowHoverHighlight": "row hover highlight",
|
||||||
|
"verticalBorders": "vertical borders"
|
||||||
},
|
},
|
||||||
"label": {
|
"label": {
|
||||||
"actions": "$t(common.action_other)",
|
"actions": "$t(common.action_other)",
|
||||||
@@ -849,6 +870,8 @@
|
|||||||
"duration": "$t(common.duration)",
|
"duration": "$t(common.duration)",
|
||||||
"favorite": "$t(common.favorite)",
|
"favorite": "$t(common.favorite)",
|
||||||
"genre": "$t(entity.genre_one)",
|
"genre": "$t(entity.genre_one)",
|
||||||
|
"genreBadge": "$t(entity.genre_one) (badges)",
|
||||||
|
"image": "image",
|
||||||
"lastPlayed": "last played",
|
"lastPlayed": "last played",
|
||||||
"note": "$t(common.note)",
|
"note": "$t(common.note)",
|
||||||
"owner": "$t(common.owner)",
|
"owner": "$t(common.owner)",
|
||||||
@@ -865,10 +888,8 @@
|
|||||||
"year": "$t(common.year)"
|
"year": "$t(common.year)"
|
||||||
},
|
},
|
||||||
"view": {
|
"view": {
|
||||||
"card": "card",
|
|
||||||
"grid": "grid",
|
"grid": "grid",
|
||||||
"list": "list",
|
"list": "list",
|
||||||
"poster": "poster",
|
|
||||||
"table": "table"
|
"table": "table"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.filter-bar {
|
.filter-bar {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
padding: var(--theme-spacing-md) var(--theme-spacing-sm);
|
padding: var(--theme-spacing-sm);
|
||||||
box-shadow: 0 5px 15px rgb(0 0 0 / 65%);
|
border-bottom: 1px solid var(--theme-colors-border);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const FolderButton = ({ isActive, ...props }: FolderButtonProps) => {
|
|||||||
<ActionIcon
|
<ActionIcon
|
||||||
icon="folder"
|
icon="folder"
|
||||||
iconProps={{
|
iconProps={{
|
||||||
fill: isActive ? 'primary' : undefined,
|
color: isActive ? 'primary' : undefined,
|
||||||
size: 'lg',
|
size: 'lg',
|
||||||
...props.iconProps,
|
...props.iconProps,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { ListConfigTable } from '/@/renderer/features/shared/components/list-config-menu';
|
import { ListConfigTable } from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import {
|
import {
|
||||||
DataGridProps,
|
DataGridProps,
|
||||||
DataListProps,
|
ItemListSettings,
|
||||||
useSettingsStore,
|
useSettingsStore,
|
||||||
useSettingsStoreActions,
|
useSettingsStoreActions,
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
@@ -28,8 +28,8 @@ type GridConfigProps = {
|
|||||||
export const GridConfig = ({ extraOptions, listKey }: GridConfigProps) => {
|
export const GridConfig = ({ extraOptions, listKey }: GridConfigProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const list = useSettingsStore((state) => state.lists[listKey]) as DataListProps;
|
const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings;
|
||||||
const grid = useSettingsStore((state) => state.lists[listKey].grid) as DataGridProps;
|
const grid = list.grid as DataGridProps;
|
||||||
const { setList } = useSettingsStoreActions();
|
const { setList } = useSettingsStoreActions();
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
@@ -182,6 +182,7 @@ export const GridConfig = ({ extraOptions, listKey }: GridConfigProps) => {
|
|||||||
grid: { itemsPerRowEnabled: e.target.checked },
|
grid: { itemsPerRowEnabled: e.target.checked },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
pr="md"
|
||||||
size="xs"
|
size="xs"
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -192,9 +193,5 @@ export const GridConfig = ({ extraOptions, listKey }: GridConfigProps) => {
|
|||||||
];
|
];
|
||||||
}, [list, t, grid, extraOptions, setList, listKey]);
|
}, [list, t, grid, extraOptions, setList, listKey]);
|
||||||
|
|
||||||
return (
|
return <ListConfigTable options={options} />;
|
||||||
<>
|
|
||||||
<ListConfigTable options={options} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { JellyfinAlbumFilters } from '/@/renderer/features/albums/components/jellyfin-album-filters';
|
||||||
|
import { NavidromeAlbumFilters } from '/@/renderer/features/albums/components/navidrome-album-filters';
|
||||||
|
import { SubsonicAlbumFilters } from '/@/renderer/features/albums/components/subsonic-album-filters';
|
||||||
|
import { FilterButton } from '/@/renderer/features/shared/components/filter-button';
|
||||||
|
import { JellyfinSongFilters } from '/@/renderer/features/songs/components/jellyfin-song-filters';
|
||||||
|
import { NavidromeSongFilters } from '/@/renderer/features/songs/components/navidrome-song-filters';
|
||||||
|
import { SubsonicSongFilters } from '/@/renderer/features/songs/components/subsonic-song-filter';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
|
import { Modal } from '/@/shared/components/modal/modal';
|
||||||
|
import { useDisclosure } from '/@/shared/hooks/use-disclosure';
|
||||||
|
import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
interface ListFiltersProps {
|
||||||
|
isActive?: boolean;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ListFilters = ({ isActive, itemType }: ListFiltersProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
|
const serverType = server.type;
|
||||||
|
|
||||||
|
const FilterComponent = FILTERS[serverType][itemType];
|
||||||
|
|
||||||
|
const [isOpen, handlers] = useDisclosure(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<FilterButton isActive={isActive} onClick={handlers.toggle} />
|
||||||
|
<Modal handlers={handlers} opened={isOpen} title={t('common.filters')}>
|
||||||
|
<FilterComponent />
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const FILTERS = {
|
||||||
|
[ServerType.JELLYFIN]: {
|
||||||
|
[LibraryItem.ALBUM]: JellyfinAlbumFilters,
|
||||||
|
[LibraryItem.SONG]: JellyfinSongFilters,
|
||||||
|
},
|
||||||
|
[ServerType.NAVIDROME]: {
|
||||||
|
[LibraryItem.ALBUM]: NavidromeAlbumFilters,
|
||||||
|
[LibraryItem.SONG]: NavidromeSongFilters,
|
||||||
|
},
|
||||||
|
[ServerType.SUBSONIC]: {
|
||||||
|
[LibraryItem.ALBUM]: SubsonicAlbumFilters,
|
||||||
|
[LibraryItem.SONG]: SubsonicSongFilters,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { parseAsString, useQueryState } from 'nuqs';
|
|
||||||
|
|
||||||
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
import { sharedQueries } from '/@/renderer/features/shared/api/shared-api';
|
||||||
import { FolderButton } from '/@/renderer/features/shared/components/folder-button';
|
import { FolderButton } from '/@/renderer/features/shared/components/folder-button';
|
||||||
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
||||||
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
|
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
|
||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
import { useMusicFolderIdFilter } from '/@/renderer/features/shared/hooks/use-music-folder-id-filter';
|
||||||
|
|
||||||
interface ListMusicFolderDropdownProps {
|
interface ListMusicFolderDropdownProps {
|
||||||
listKey: ItemListKey;
|
listKey: ItemListKey;
|
||||||
@@ -20,13 +21,10 @@ export const ListMusicFolderDropdown = ({ listKey }: ListMusicFolderDropdownProp
|
|||||||
|
|
||||||
const [persisted, setPersisted] = useLocalStorage({
|
const [persisted, setPersisted] = useLocalStorage({
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
key: getPersistenceKey(listKey),
|
key: getPersistenceKey(server.id, listKey),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [musicFolderId, setMusicFolderId] = useQueryState(
|
const { musicFolderId, setMusicFolderId } = useMusicFolderIdFilter(persisted);
|
||||||
getPersistenceKey(listKey),
|
|
||||||
parseAsString.withDefault(persisted || ''),
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSetMusicFolder = (e: string) => {
|
const handleSetMusicFolder = (e: string) => {
|
||||||
if (e === musicFolderId) {
|
if (e === musicFolderId) {
|
||||||
@@ -60,6 +58,6 @@ export const ListMusicFolderDropdown = ({ listKey }: ListMusicFolderDropdownProp
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPersistenceKey = (listKey: ItemListKey) => {
|
const getPersistenceKey = (serverId: string, listKey: ItemListKey) => {
|
||||||
return `list-${listKey}-musicFolder`;
|
return `${serverId}-list-${listKey}-${FILTER_KEYS.SHARED.MUSIC_FOLDER_ID}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import { parseAsString, useQueryState } from 'nuqs';
|
|
||||||
|
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { Button } from '/@/shared/components/button/button';
|
import { Button } from '/@/shared/components/button/button';
|
||||||
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
import { DropdownMenu } from '/@/shared/components/dropdown-menu/dropdown-menu';
|
||||||
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
|
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
|
||||||
import { AlbumListSort, LibraryItem, ServerType, SortOrder } from '/@/shared/types/domain-types';
|
import {
|
||||||
|
AlbumListSort,
|
||||||
|
LibraryItem,
|
||||||
|
ServerType,
|
||||||
|
SongListSort,
|
||||||
|
SortOrder,
|
||||||
|
} from '/@/shared/types/domain-types';
|
||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
import { useSortByFilter } from '/@/renderer/features/shared/hooks/use-sort-by-filter';
|
||||||
|
|
||||||
interface ListSortByDropdownProps {
|
interface ListSortByDropdownProps {
|
||||||
defaultSortByValue: string;
|
defaultSortByValue: string;
|
||||||
@@ -28,13 +33,10 @@ export const ListSortByDropdown = ({
|
|||||||
|
|
||||||
const [persisted, setPersisted] = useLocalStorage({
|
const [persisted, setPersisted] = useLocalStorage({
|
||||||
defaultValue: defaultSortByValue,
|
defaultValue: defaultSortByValue,
|
||||||
key: getPersistenceKey(listKey),
|
key: getPersistenceKey(server.id, listKey),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [sortBy, setSortBy] = useQueryState(
|
const { sortBy, setSortBy } = useSortByFilter(persisted || defaultSortByValue);
|
||||||
FILTER_KEYS.SORT_BY,
|
|
||||||
parseAsString.withDefault(persisted || defaultSortByValue),
|
|
||||||
);
|
|
||||||
|
|
||||||
const sortByLabel =
|
const sortByLabel =
|
||||||
(itemType && FILTERS[itemType][server.type].find((f) => f.value === sortBy)?.name) || '—';
|
(itemType && FILTERS[itemType][server.type].find((f) => f.value === sortBy)?.name) || '—';
|
||||||
@@ -217,10 +219,157 @@ const ALBUM_LIST_FILTERS: Partial<
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const FILTERS: Partial<Record<LibraryItem, any>> = {
|
const SONG_LIST_FILTERS: Partial<
|
||||||
[LibraryItem.ALBUM]: ALBUM_LIST_FILTERS,
|
Record<ServerType, Array<{ defaultOrder: SortOrder; name: string; value: string }>>
|
||||||
|
> = {
|
||||||
|
[ServerType.JELLYFIN]: [
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.album', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.ALBUM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.ALBUM_ARTIST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.artist', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.ARTIST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.duration', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.DURATION,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.playCount', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.PLAY_COUNT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.name', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.NAME,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.random', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.RANDOM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.RECENTLY_ADDED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.recentlyPlayed', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.RECENTLY_PLAYED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.releaseDate', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.RELEASE_DATE,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[ServerType.NAVIDROME]: [
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.album', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.ALBUM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.albumArtist', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.ALBUM_ARTIST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.artist', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.ARTIST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.bpm', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.BPM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('common.channel', { count: 2, postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.CHANNELS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.comment', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.COMMENT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.duration', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.DURATION,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.isFavorited', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.FAVORITED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.genre', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.GENRE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.name', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.NAME,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.playCount', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.PLAY_COUNT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.random', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.RANDOM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.rating', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.RATING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.recentlyAdded', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.RECENTLY_ADDED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.recentlyPlayed', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.RECENTLY_PLAYED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.DESC,
|
||||||
|
name: i18n.t('filter.releaseYear', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.YEAR,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[ServerType.SUBSONIC]: [
|
||||||
|
{
|
||||||
|
defaultOrder: SortOrder.ASC,
|
||||||
|
name: i18n.t('filter.name', { postProcess: 'titleCase' }),
|
||||||
|
value: SongListSort.NAME,
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPersistenceKey = (listKey: ItemListKey) => {
|
const FILTERS: Partial<Record<LibraryItem, any>> = {
|
||||||
return `item_list_${listKey}-${FILTER_KEYS.SORT_BY}`;
|
[LibraryItem.ALBUM]: ALBUM_LIST_FILTERS,
|
||||||
|
[LibraryItem.SONG]: SONG_LIST_FILTERS,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPersistenceKey = (serverId: string, listKey: ItemListKey) => {
|
||||||
|
return `${serverId}-list-${listKey}-${FILTER_KEYS.SHARED.SORT_BY}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { parseAsString, useQueryState } from 'nuqs';
|
|
||||||
|
|
||||||
import { OrderToggleButton } from '/@/renderer/features/shared/components/order-toggle-button';
|
import { OrderToggleButton } from '/@/renderer/features/shared/components/order-toggle-button';
|
||||||
|
import { useSortOrderFilter } from '/@/renderer/features/shared/hooks/use-sort-order-filter';
|
||||||
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
|
import { useLocalStorage } from '/@/shared/hooks/use-local-storage';
|
||||||
import { SortOrder } from '/@/shared/types/domain-types';
|
import { SortOrder } from '/@/shared/types/domain-types';
|
||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
@@ -11,15 +11,14 @@ interface ListSortOrderToggleButtonProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ListSortOrderToggleButton = ({ listKey }: ListSortOrderToggleButtonProps) => {
|
export const ListSortOrderToggleButton = ({ listKey }: ListSortOrderToggleButtonProps) => {
|
||||||
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const [persisted, setPersisted] = useLocalStorage({
|
const [persisted, setPersisted] = useLocalStorage({
|
||||||
defaultValue: SortOrder.ASC,
|
defaultValue: SortOrder.ASC,
|
||||||
key: getPersistenceKey(listKey),
|
key: getPersistenceKey(server.id, listKey),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [sortOrder, setSortOrder] = useQueryState(
|
const { sortOrder, setSortOrder } = useSortOrderFilter(persisted || SortOrder.ASC);
|
||||||
FILTER_KEYS.SORT_ORDER,
|
|
||||||
parseAsString.withDefault(persisted || SortOrder.ASC),
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleToggleSortOrder = () => {
|
const handleToggleSortOrder = () => {
|
||||||
const newSortOrder = sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
|
const newSortOrder = sortOrder === SortOrder.ASC ? SortOrder.DESC : SortOrder.ASC;
|
||||||
@@ -32,6 +31,6 @@ export const ListSortOrderToggleButton = ({ listKey }: ListSortOrderToggleButton
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPersistenceKey = (listKey: ItemListKey) => {
|
const getPersistenceKey = (serverId: string, listKey: ItemListKey) => {
|
||||||
return `item_list_${listKey}-${FILTER_KEYS.SORT_ORDER}`;
|
return `${serverId}-list-${listKey}-${FILTER_KEYS.SHARED.SORT_ORDER}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
ListConfigBooleanControl,
|
ListConfigBooleanControl,
|
||||||
ListConfigTable,
|
ListConfigTable,
|
||||||
} from '/@/renderer/features/shared/components/list-config-menu';
|
} from '/@/renderer/features/shared/components/list-config-menu';
|
||||||
import { DataListProps, useSettingsStore, useSettingsStoreActions } from '/@/renderer/store';
|
import { ItemListSettings, useSettingsStore, useSettingsStoreActions } from '/@/renderer/store';
|
||||||
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
|
import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon';
|
||||||
import { Badge } from '/@/shared/components/badge/badge';
|
import { Badge } from '/@/shared/components/badge/badge';
|
||||||
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
import { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
||||||
@@ -40,7 +40,7 @@ interface TableConfigProps {
|
|||||||
export const TableConfig = ({ extraOptions, listKey, tableColumnsData }: TableConfigProps) => {
|
export const TableConfig = ({ extraOptions, listKey, tableColumnsData }: TableConfigProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const list = useSettingsStore((state) => state.lists[listKey]) as DataListProps;
|
const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings;
|
||||||
const { setList } = useSettingsStoreActions();
|
const { setList } = useSettingsStoreActions();
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { parseAsString, useQueryState } from 'nuqs';
|
||||||
|
|
||||||
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
|
|
||||||
|
export const useMusicFolderIdFilter = (defaultValue?: string) => {
|
||||||
|
const [musicFolderId, setMusicFolderId] = useQueryState(
|
||||||
|
FILTER_KEYS.SHARED.MUSIC_FOLDER_ID,
|
||||||
|
defaultValue ? parseAsString.withDefault(defaultValue) : parseAsString,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
[FILTER_KEYS.SHARED.MUSIC_FOLDER_ID]: musicFolderId ?? undefined,
|
||||||
|
setMusicFolderId,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { useDebouncedValue } from '@mantine/hooks';
|
||||||
|
import { parseAsString, useQueryState } from 'nuqs';
|
||||||
|
|
||||||
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
|
|
||||||
|
export const useSearchTermFilter = (defaultValue?: string) => {
|
||||||
|
const [searchTerm, setSearchTerm] = useQueryState(
|
||||||
|
FILTER_KEYS.SHARED.SEARCH_TERM,
|
||||||
|
defaultValue ? parseAsString.withDefault(defaultValue) : parseAsString,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300);
|
||||||
|
|
||||||
|
return {
|
||||||
|
[FILTER_KEYS.SHARED.SEARCH_TERM]: debouncedSearchTerm ?? undefined,
|
||||||
|
rawSearchTerm: searchTerm ?? undefined,
|
||||||
|
setSearchTerm,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { parseAsString, useQueryState } from 'nuqs';
|
||||||
|
|
||||||
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
|
|
||||||
|
export const useSortByFilter = <TSortBy>(defaultValue?: string) => {
|
||||||
|
const [sortBy, setSortBy] = useQueryState(
|
||||||
|
FILTER_KEYS.SHARED.SORT_BY,
|
||||||
|
defaultValue ? parseAsString.withDefault(defaultValue) : parseAsString,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
[FILTER_KEYS.SHARED.SORT_BY]: (sortBy as TSortBy) ?? undefined,
|
||||||
|
setSortBy,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { parseAsString, useQueryState } from 'nuqs';
|
||||||
|
|
||||||
|
import { FILTER_KEYS } from '/@/renderer/features/shared/utils';
|
||||||
|
import { SortOrder } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export const useSortOrderFilter = (defaultValue?: string) => {
|
||||||
|
const [sortOrder, setSortOrder] = useQueryState(
|
||||||
|
FILTER_KEYS.SHARED.SORT_ORDER,
|
||||||
|
defaultValue ? parseAsString.withDefault(defaultValue) : parseAsString,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
[FILTER_KEYS.SHARED.SORT_ORDER]: (sortOrder as SortOrder) ?? undefined,
|
||||||
|
setSortOrder,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import z from 'zod';
|
||||||
|
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
import { Play } from '/@/shared/types/types';
|
import { Play } from '/@/shared/types/types';
|
||||||
|
|
||||||
@@ -20,9 +22,35 @@ export const PLAY_TYPES = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const FILTER_KEYS = {
|
export const customFiltersSchema = z.record(z.string(), z.any());
|
||||||
|
|
||||||
|
enum AlbumFilterKeys {
|
||||||
|
_CUSTOM = '_custom',
|
||||||
|
ARTIST_IDS = 'artistIds',
|
||||||
|
COMPILATION = 'compilation',
|
||||||
|
FAVORITE = 'favorite',
|
||||||
|
GENRE_ID = 'genreId',
|
||||||
|
GENRES = 'genres',
|
||||||
|
HAS_RATING = 'hasRating',
|
||||||
|
MAX_YEAR = 'maxYear',
|
||||||
|
MIN_YEAR = 'minYear',
|
||||||
|
RECENTLY_PLAYED = 'recentlyPlayed',
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SharedFilterKeys {
|
||||||
|
MUSIC_FOLDER_ID = 'musicFolderId',
|
||||||
|
SEARCH_TERM = 'searchTerm',
|
||||||
|
SORT_BY = 'sortBy',
|
||||||
|
SORT_ORDER = 'sortOrder',
|
||||||
|
}
|
||||||
|
|
||||||
|
const PaginationFilterKeys = {
|
||||||
CURRENT_PAGE: 'currentPage',
|
CURRENT_PAGE: 'currentPage',
|
||||||
SCROLL_OFFSET: 'scrollOffset',
|
SCROLL_OFFSET: 'scrollOffset',
|
||||||
SORT_BY: 'sortBy',
|
};
|
||||||
SORT_ORDER: 'sortOrder',
|
|
||||||
|
export const FILTER_KEYS = {
|
||||||
|
ALBUM: AlbumFilterKeys,
|
||||||
|
PAGINATION: PaginationFilterKeys,
|
||||||
|
SHARED: SharedFilterKeys,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user