Redesign sidebar / header and other misc. improvements (#24)

* Remove 1920px max width

* Fix position of list controls menu

* Match size and color of search input

* Adjust library header sizing

* Move app menu to sidebar

* Increase row buffer on play queue list

* Fix query builder styles

* Fix playerbar slider track bg

* Adjust titlebar styles

* Fix invalid modal prop

* Various adjustments to detail pages

* Fix sidebar height calculation

* Fix list null indicators, add filter indicator

* Adjust playqueue styles

* Fix jellyfin releaseYear normalization

* Suppress browser context menu on ag-grid

* Add radius to drawer queue -- normalize layout

* Add modal styles to provider theme

* Fix playlist song list pagination

* Add disc number to albums with more than one disc

* Fix query builder boolean values

* Adjust input placeholder color

* Properly handle rating/favorite from context menu on table

* Conform dropdown menu styles to context menu

* Increase sort type select width

* Fix drawer queue radius

* Change primary color

* Prevent volume wheel from invalid values

* Add icons to query builder dropdowns

* Update notification styles

* Update scrollbar thumb styles

* Remove "add to playlist" on smart playlists

* Fix "add to playlist" from context menu
This commit is contained in:
Jeff 2023-02-07 22:47:23 -08:00 committed by GitHub
parent d2c0d4c11f
commit 9f2e873366
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
80 changed files with 1427 additions and 1101 deletions

View file

@ -45,25 +45,25 @@ export const JellyfinSongFilters = ({ handleFilterChange }: JellyfinSongFiltersP
},
];
const handleMinYearFilter = debounce((e: number | undefined) => {
if (e && (e < 1700 || e > 2300)) return;
const handleMinYearFilter = debounce((e: number | string) => {
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
const updatedFilters = setFilters({
jfParams: {
...filter.jfParams,
includeItemTypes: 'Audio',
minYear: e,
minYear: e === '' ? undefined : (e as number),
},
});
handleFilterChange(updatedFilters);
}, 500);
const handleMaxYearFilter = debounce((e: number | undefined) => {
if (e && (e < 1700 || e > 2300)) return;
const handleMaxYearFilter = debounce((e: number | string) => {
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
const updatedFilters = setFilters({
jfParams: {
...filter.jfParams,
includeItemTypes: 'Audio',
maxYear: e,
maxYear: e === '' ? undefined : (e as number),
},
});
handleFilterChange(updatedFilters);
@ -99,17 +99,17 @@ export const JellyfinSongFilters = ({ handleFilterChange }: JellyfinSongFiltersP
<Group grow>
<NumberInput
required
defaultValue={filter.jfParams?.minYear}
label="From year"
max={2300}
min={1700}
value={filter.jfParams?.minYear}
onChange={handleMinYearFilter}
/>
<NumberInput
defaultValue={filter.jfParams?.maxYear}
label="To year"
max={2300}
min={1700}
value={filter.jfParams?.maxYear}
onChange={handleMaxYearFilter}
/>
</Group>

View file

@ -46,11 +46,11 @@ export const NavidromeSongFilters = ({ handleFilterChange }: NavidromeSongFilter
},
];
const handleYearFilter = debounce((e: number | undefined) => {
const handleYearFilter = debounce((e: number | string) => {
const updatedFilters = setFilters({
ndParams: {
...filter.ndParams,
year: e,
year: e === '' ? undefined : (e as number),
},
});
@ -80,7 +80,7 @@ export const NavidromeSongFilters = ({ handleFilterChange }: NavidromeSongFilter
min={0}
value={filter.ndParams?.year}
width={50}
onChange={handleYearFilter}
onChange={(e) => handleYearFilter(e)}
/>
<Select
clearable

View file

@ -87,7 +87,7 @@ export const SongListContent = ({ customFilters, itemCount, tableRef }: SongList
);
const songs = api.normalize.songList(songsRes, server);
params.successCallback(songs?.items || [], songsRes?.totalRecordCount);
params.successCallback(songs?.items || [], songsRes?.totalRecordCount || 0);
},
rowCount: undefined,
};

View file

@ -1,4 +1,4 @@
import { useCallback, ChangeEvent, MutableRefObject, MouseEvent } from 'react';
import { useCallback, useMemo, ChangeEvent, MutableRefObject, MouseEvent } from 'react';
import { IDatasource } from '@ag-grid-community/core';
import { Flex, Group, Stack } from '@mantine/core';
import { openModal } from '@mantine/modals';
@ -6,9 +6,13 @@ import {
RiSortAsc,
RiSortDesc,
RiFolder2Line,
RiFilter3Line,
RiMoreFill,
RiSettings3Fill,
RiPlayFill,
RiAddBoxFill,
RiAddCircleFill,
RiRefreshLine,
RiFilterFill,
} from 'react-icons/ri';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
@ -138,7 +142,7 @@ export const SongListHeaderFilters = ({
);
const songs = api.normalize.songList(songsRes, server);
params.successCallback(songs?.items || [], songsRes?.totalRecordCount);
params.successCallback(songs?.items || [], songsRes?.totalRecordCount || 0);
},
rowCount: undefined,
};
@ -274,6 +278,22 @@ export const SongListHeaderFilters = ({
});
};
const isFilterApplied = useMemo(() => {
const isNavidromeFilterApplied =
server?.type === ServerType.NAVIDROME &&
page.filter.ndParams &&
Object.values(page.filter.ndParams).some((value) => value !== undefined);
const isJellyfinFilterApplied =
server?.type === ServerType.JELLYFIN &&
page.filter.jfParams &&
Object.values(page.filter.jfParams)
.filter((value) => value !== 'Audio') // Don't account for includeItemTypes: Audio
.some((value) => value !== undefined);
return isNavidromeFilterApplied || isJellyfinFilterApplied;
}, [page.filter.jfParams, page.filter.ndParams, server?.type]);
return (
<Flex justify="space-between">
<Group
@ -350,15 +370,6 @@ export const SongListHeaderFilters = ({
</DropdownMenu.Dropdown>
</DropdownMenu>
)}
<Button
compact
fw="600"
size="md"
variant="subtle"
onClick={handleOpenFiltersModal}
>
{cq.isSm ? 'Filters' : <RiFilter3Line size="1.3rem" />}
</Button>
<DropdownMenu position="bottom-start">
<DropdownMenu.Target>
<Button
@ -371,20 +382,49 @@ export const SongListHeaderFilters = ({
</Button>
</DropdownMenu.Target>
<DropdownMenu.Dropdown>
<DropdownMenu.Item onClick={() => handlePlay(Play.NOW)}>Play</DropdownMenu.Item>
<DropdownMenu.Item onClick={() => handlePlay(Play.LAST)}>
<DropdownMenu.Item
icon={<RiPlayFill />}
onClick={() => handlePlay(Play.NOW)}
>
Play
</DropdownMenu.Item>
<DropdownMenu.Item
icon={<RiAddBoxFill />}
onClick={() => handlePlay(Play.LAST)}
>
Add to queue
</DropdownMenu.Item>
<DropdownMenu.Item onClick={() => handlePlay(Play.NEXT)}>
<DropdownMenu.Item
icon={<RiAddCircleFill />}
onClick={() => handlePlay(Play.NEXT)}
>
Add to queue next
</DropdownMenu.Item>
<DropdownMenu.Divider />
<DropdownMenu.Item onClick={handleRefresh}>Refresh</DropdownMenu.Item>
<DropdownMenu.Item
icon={<RiRefreshLine />}
onClick={handleRefresh}
>
Refresh
</DropdownMenu.Item>
</DropdownMenu.Dropdown>
</DropdownMenu>
</Group>
<Group>
<DropdownMenu position="bottom-start">
<Group
noWrap
spacing="sm"
>
<Button
compact
size="md"
sx={{ svg: { fill: isFilterApplied ? 'var(--primary-color) !important' : undefined } }}
tooltip={{ label: 'Filters' }}
variant="subtle"
onClick={handleOpenFiltersModal}
>
<RiFilterFill size="1.3rem" />
</Button>
<DropdownMenu position="bottom-end">
<DropdownMenu.Target>
<Button
compact

View file

@ -72,7 +72,7 @@ export const SongListHeader = ({
);
const songs = api.normalize.songList(songsRes, server);
params.successCallback(songs?.items || [], songsRes?.totalRecordCount);
params.successCallback(songs?.items || [], songsRes?.totalRecordCount || 0);
},
rowCount: undefined,
};
@ -114,13 +114,11 @@ export const SongListHeader = ({
<PageHeader backgroundColor="var(--titlebar-bg)">
<Flex
justify="space-between"
py="1rem"
w="100%"
>
<LibraryHeaderBar>
<Group>
<LibraryHeaderBar.PlayButton onClick={() => handlePlay(playButtonBehavior)} />
<LibraryHeaderBar.Title>{title || 'Tracks'}</LibraryHeaderBar.Title>
</Group>
<LibraryHeaderBar.PlayButton onClick={() => handlePlay(playButtonBehavior)} />
<LibraryHeaderBar.Title>{title || 'Tracks'}</LibraryHeaderBar.Title>
<Paper
fw="600"
px="1rem"