mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-16 10:03:51 -07:00
New: Calendar filtering by tags
Closes #3658 Closes #4211 Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
This commit is contained in:
parent
c7faf7cc25
commit
a4af75b60c
7 changed files with 160 additions and 15 deletions
|
@ -1,5 +1,6 @@
|
|||
import AlbumAppState from './AlbumAppState';
|
||||
import ArtistAppState, { ArtistIndexAppState } from './ArtistAppState';
|
||||
import CalendarAppState from './CalendarAppState';
|
||||
import HistoryAppState from './HistoryAppState';
|
||||
import QueueAppState from './QueueAppState';
|
||||
import SettingsAppState from './SettingsAppState';
|
||||
|
@ -52,6 +53,7 @@ interface AppState {
|
|||
app: AppSectionState;
|
||||
artist: ArtistAppState;
|
||||
artistIndex: ArtistIndexAppState;
|
||||
calendar: CalendarAppState;
|
||||
history: HistoryAppState;
|
||||
queue: QueueAppState;
|
||||
settings: SettingsAppState;
|
||||
|
|
10
frontend/src/App/State/CalendarAppState.ts
Normal file
10
frontend/src/App/State/CalendarAppState.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import Album from 'Album/Album';
|
||||
import AppSectionState, {
|
||||
AppSectionFilterState,
|
||||
} from 'App/State/AppSectionState';
|
||||
|
||||
interface CalendarAppState
|
||||
extends AppSectionState<Album>,
|
||||
AppSectionFilterState<Album> {}
|
||||
|
||||
export default CalendarAppState;
|
54
frontend/src/Calendar/CalendarFilterModal.tsx
Normal file
54
frontend/src/Calendar/CalendarFilterModal.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import FilterModal from 'Components/Filter/FilterModal';
|
||||
import { setCalendarFilter } from 'Store/Actions/calendarActions';
|
||||
|
||||
function createCalendarSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.calendar.items,
|
||||
(calendar) => {
|
||||
return calendar;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createFilterBuilderPropsSelector() {
|
||||
return createSelector(
|
||||
(state: AppState) => state.calendar.filterBuilderProps,
|
||||
(filterBuilderProps) => {
|
||||
return filterBuilderProps;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
interface CalendarFilterModalProps {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
export default function CalendarFilterModal(props: CalendarFilterModalProps) {
|
||||
const sectionItems = useSelector(createCalendarSelector());
|
||||
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
|
||||
const customFilterType = 'calendar';
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const dispatchSetFilter = useCallback(
|
||||
(payload: unknown) => {
|
||||
dispatch(setCalendarFilter(payload));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<FilterModal
|
||||
// TODO: Don't spread all the props
|
||||
{...props}
|
||||
sectionItems={sectionItems}
|
||||
filterBuilderProps={filterBuilderProps}
|
||||
customFilterType={customFilterType}
|
||||
dispatchSetFilter={dispatchSetFilter}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -14,6 +14,7 @@ import { align, icons } from 'Helpers/Props';
|
|||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import CalendarConnector from './CalendarConnector';
|
||||
import CalendarFilterModal from './CalendarFilterModal';
|
||||
import CalendarLinkModal from './iCal/CalendarLinkModal';
|
||||
import LegendConnector from './Legend/LegendConnector';
|
||||
import CalendarOptionsModal from './Options/CalendarOptionsModal';
|
||||
|
@ -78,6 +79,7 @@ class CalendarPage extends Component {
|
|||
const {
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
hasArtist,
|
||||
artistError,
|
||||
artistIsFetching,
|
||||
|
@ -137,7 +139,8 @@ class CalendarPage extends Component {
|
|||
isDisabled={!hasArtist}
|
||||
selectedFilterKey={selectedFilterKey}
|
||||
filters={filters}
|
||||
customFilters={[]}
|
||||
customFilters={customFilters}
|
||||
filterModalConnectorComponent={CalendarFilterModal}
|
||||
onFilterSelect={onFilterSelect}
|
||||
/>
|
||||
</PageToolbarSection>
|
||||
|
@ -204,6 +207,7 @@ class CalendarPage extends Component {
|
|||
CalendarPage.propTypes = {
|
||||
selectedFilterKey: PropTypes.string.isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
hasArtist: PropTypes.bool.isRequired,
|
||||
artistError: PropTypes.object,
|
||||
artistIsFetching: PropTypes.bool.isRequired,
|
||||
|
|
|
@ -5,6 +5,7 @@ import * as commandNames from 'Commands/commandNames';
|
|||
import withCurrentPage from 'Components/withCurrentPage';
|
||||
import { searchMissing, setCalendarDaysCount, setCalendarFilter } from 'Store/Actions/calendarActions';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createArtistCountSelector from 'Store/Selectors/createArtistCountSelector';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||
|
@ -59,6 +60,7 @@ function createMapStateToProps() {
|
|||
return createSelector(
|
||||
(state) => state.calendar.selectedFilterKey,
|
||||
(state) => state.calendar.filters,
|
||||
createCustomFiltersSelector('calendar'),
|
||||
createArtistCountSelector(),
|
||||
createUISettingsSelector(),
|
||||
createMissingAlbumIdsSelector(),
|
||||
|
@ -67,6 +69,7 @@ function createMapStateToProps() {
|
|||
(
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
artistCount,
|
||||
uiSettings,
|
||||
missingAlbumIds,
|
||||
|
@ -76,6 +79,7 @@ function createMapStateToProps() {
|
|||
return {
|
||||
selectedFilterKey,
|
||||
filters,
|
||||
customFilters,
|
||||
colorImpairedMode: uiSettings.enableColorImpairedMode,
|
||||
hasArtist: !!artistCount.count,
|
||||
artistError: artistCount.error,
|
||||
|
|
|
@ -4,9 +4,10 @@ import { createAction } from 'redux-actions';
|
|||
import { batchActions } from 'redux-batched-actions';
|
||||
import * as calendarViews from 'Calendar/calendarViews';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import { filterTypes } from 'Helpers/Props';
|
||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes } from 'Helpers/Props';
|
||||
import { createThunk, handleThunks } from 'Store/thunks';
|
||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
||||
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import { set, update } from './baseActions';
|
||||
import { executeCommandHelper } from './commandActions';
|
||||
|
@ -54,8 +55,8 @@ export const defaultState = {
|
|||
label: () => translate('All'),
|
||||
filters: [
|
||||
{
|
||||
key: 'monitored',
|
||||
value: false,
|
||||
key: 'unmonitored',
|
||||
value: [true],
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
|
@ -65,19 +66,35 @@ export const defaultState = {
|
|||
label: () => translate('MonitoredOnly'),
|
||||
filters: [
|
||||
{
|
||||
key: 'monitored',
|
||||
value: true,
|
||||
key: 'unmonitored',
|
||||
value: [false],
|
||||
type: filterTypes.EQUAL
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
filterBuilderProps: [
|
||||
{
|
||||
name: 'unmonitored',
|
||||
label: () => translate('IncludeUnmonitored'),
|
||||
type: filterBuilderTypes.EQUAL,
|
||||
valueType: filterBuilderValueTypes.BOOL
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
label: () => translate('Tags'),
|
||||
type: filterBuilderTypes.CONTAINS,
|
||||
valueType: filterBuilderValueTypes.TAG
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const persistState = [
|
||||
'calendar.view',
|
||||
'calendar.selectedFilterKey',
|
||||
'calendar.options'
|
||||
'calendar.options',
|
||||
'calendar.customFilters'
|
||||
];
|
||||
|
||||
//
|
||||
|
@ -189,6 +206,10 @@ function isRangePopulated(start, end, state) {
|
|||
return false;
|
||||
}
|
||||
|
||||
function getCustomFilters(state, type) {
|
||||
return state.customFilters.items.filter((customFilter) => customFilter.type === type);
|
||||
}
|
||||
|
||||
//
|
||||
// Action Creators
|
||||
|
||||
|
@ -210,7 +231,8 @@ export const actionHandlers = handleThunks({
|
|||
[FETCH_CALENDAR]: function(getState, payload, dispatch) {
|
||||
const state = getState();
|
||||
const calendar = state.calendar;
|
||||
const unmonitored = calendar.selectedFilterKey === 'all';
|
||||
const customFilters = getCustomFilters(state, section);
|
||||
const selectedFilters = findSelectedFilters(calendar.selectedFilterKey, calendar.filters, customFilters);
|
||||
|
||||
const {
|
||||
time = calendar.time,
|
||||
|
@ -237,13 +259,26 @@ export const actionHandlers = handleThunks({
|
|||
|
||||
dispatch(set(attrs));
|
||||
|
||||
const requestParams = {
|
||||
start,
|
||||
end
|
||||
};
|
||||
|
||||
selectedFilters.forEach((selectedFilter) => {
|
||||
if (selectedFilter.key === 'unmonitored') {
|
||||
requestParams.unmonitored = selectedFilter.value.includes(true);
|
||||
}
|
||||
|
||||
if (selectedFilter.key === 'tags') {
|
||||
requestParams.tags = selectedFilter.value.join(',');
|
||||
}
|
||||
});
|
||||
|
||||
requestParams.unmonitored = requestParams.unmonitored ?? false;
|
||||
|
||||
const promise = createAjaxRequest({
|
||||
url: '/calendar',
|
||||
data: {
|
||||
unmonitored,
|
||||
start,
|
||||
end
|
||||
}
|
||||
data: requestParams
|
||||
}).request;
|
||||
|
||||
promise.done((data) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue