Remove Season Pass

This commit is contained in:
Mark McDowall 2023-01-30 19:15:40 -08:00 committed by Mark McDowall
parent 67232290e5
commit 6fce6c2bbc
14 changed files with 9 additions and 908 deletions

View File

@ -9,7 +9,6 @@ import ImportSeries from 'AddSeries/ImportSeries/ImportSeries';
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
import NotFound from 'Components/NotFound';
import Switch from 'Components/Router/Switch';
import SeasonPassConnector from 'SeasonPass/SeasonPassConnector';
import SeriesDetailsPageConnector from 'Series/Details/SeriesDetailsPageConnector';
import SeriesIndex from 'Series/Index/SeriesIndex';
import CustomFormatSettingsConnector from 'Settings/CustomFormats/CustomFormatSettingsConnector';
@ -95,7 +94,15 @@ function AppRoutes(props) {
<Route
path="/seasonpass"
component={SeasonPassConnector}
exact={true}
render={() => {
return (
<Redirect
to={getPathWithUrlBase('/')}
component={app}
/>
);
}}
/>
<Route

View File

@ -31,10 +31,6 @@ const links = [
{
title: 'Library Import',
to: '/add/import'
},
{
title: 'Season Pass',
to: '/seasonpass'
}
]
},

View File

@ -1,217 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import { align, sortDirections } from 'Helpers/Props';
import NoSeries from 'Series/NoSeries';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import SeasonPassFilterModalConnector from './SeasonPassFilterModalConnector';
import SeasonPassFooter from './SeasonPassFooter';
import SeasonPassRowConnector from './SeasonPassRowConnector';
const columns = [
{
name: 'status',
isVisible: true
},
{
name: 'sortTitle',
label: 'Title',
isSortable: true,
isVisible: true
},
{
name: 'monitored',
isVisible: true
},
{
name: 'seasonCount',
label: 'Seasons',
isSortable: true,
isVisible: true
}
];
class SeasonPass extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
allSelected: false,
allUnselected: false,
lastToggled: null,
selectedState: {}
};
}
componentDidUpdate(prevProps) {
const {
isSaving,
saveError
} = this.props;
if (prevProps.isSaving && !isSaving && !saveError) {
this.onSelectAllChange({ value: false });
}
}
//
// Control
getSelectedIds = () => {
return getSelectedIds(this.state.selectedState);
};
//
// Listeners
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
};
onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey);
});
};
onUpdateSelectedPress = (changes) => {
this.props.onUpdateSelectedPress({
seriesIds: this.getSelectedIds(),
...changes
});
};
//
// Render
render() {
const {
isFetching,
isPopulated,
error,
totalItems,
items,
selectedFilterKey,
filters,
customFilters,
sortKey,
sortDirection,
isSaving,
saveError,
onSortPress,
onFilterSelect
} = this.props;
const {
allSelected,
allUnselected,
selectedState
} = this.state;
return (
<PageContent title="Season Pass">
<PageToolbar>
<PageToolbarSection />
<PageToolbarSection alignContent={align.RIGHT}>
<FilterMenu
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={SeasonPassFilterModalConnector}
onFilterSelect={onFilterSelect}
/>
</PageToolbarSection>
</PageToolbar>
<PageContentBody>
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
<div>Unable to load the calendar</div>
}
{
!error && isPopulated && !!items.length &&
<div>
<Table
columns={columns}
sortKey={sortKey}
sortDirection={sortDirection}
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
onSortPress={onSortPress}
onSelectAllChange={this.onSelectAllChange}
>
<TableBody>
{
items.map((item) => {
return (
<SeasonPassRowConnector
key={item.id}
seriesId={item.id}
isSelected={selectedState[item.id]}
onSelectedChange={this.onSelectedChange}
/>
);
})
}
</TableBody>
</Table>
</div>
}
{
!error && isPopulated && !items.length &&
<NoSeries totalItems={totalItems} />
}
</PageContentBody>
<SeasonPassFooter
selectedCount={this.getSelectedIds().length}
isSaving={isSaving}
saveError={saveError}
onUpdateSelectedPress={this.onUpdateSelectedPress}
/>
</PageContent>
);
}
}
SeasonPass.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
totalItems: PropTypes.number.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
onSortPress: PropTypes.func.isRequired,
onFilterSelect: PropTypes.func.isRequired,
onUpdateSelectedPress: PropTypes.func.isRequired
};
export default SeasonPass;

View File

@ -1,64 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { saveSeasonPass, setSeasonPassFilter, setSeasonPassSort } from 'Store/Actions/seasonPassActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import SeasonPass from './SeasonPass';
function createMapStateToProps() {
return createSelector(
createClientSideCollectionSelector('series', 'seasonPass'),
(series) => {
return {
...series
};
}
);
}
const mapDispatchToProps = {
setSeasonPassSort,
setSeasonPassFilter,
saveSeasonPass
};
class SeasonPassConnector extends Component {
//
// Listeners
onSortPress = (sortKey) => {
this.props.setSeasonPassSort({ sortKey });
};
onFilterSelect = (selectedFilterKey) => {
this.props.setSeasonPassFilter({ selectedFilterKey });
};
onUpdateSelectedPress = (payload) => {
this.props.saveSeasonPass(payload);
};
//
// Render
render() {
return (
<SeasonPass
{...this.props}
onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect}
onUpdateSelectedPress={this.onUpdateSelectedPress}
/>
);
}
}
SeasonPassConnector.propTypes = {
setSeasonPassSort: PropTypes.func.isRequired,
setSeasonPassFilter: PropTypes.func.isRequired,
saveSeasonPass: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(SeasonPassConnector);

View File

@ -1,24 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import FilterModal from 'Components/Filter/FilterModal';
import { setSeasonPassFilter } from 'Store/Actions/seasonPassActions';
function createMapStateToProps() {
return createSelector(
(state) => state.series.items,
(state) => state.seasonPass.filterBuilderProps,
(sectionItems, filterBuilderProps) => {
return {
sectionItems,
filterBuilderProps,
customFilterType: 'series'
};
}
);
}
const mapDispatchToProps = {
dispatchSetFilter: setSeasonPassFilter
};
export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal);

View File

@ -1,14 +0,0 @@
.inputContainer {
margin-right: 20px;
}
.label {
margin-bottom: 3px;
font-weight: bold;
}
.updateSelectedButton {
composes: button from '~Components/Link/SpinnerButton.css';
height: 35px;
}

View File

@ -1,145 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import MonitorEpisodesSelectInput from 'Components/Form/MonitorEpisodesSelectInput';
import SelectInput from 'Components/Form/SelectInput';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import { kinds } from 'Helpers/Props';
import styles from './SeasonPassFooter.css';
const NO_CHANGE = 'noChange';
class SeasonPassFooter extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
monitored: NO_CHANGE,
monitor: NO_CHANGE
};
}
componentDidUpdate(prevProps) {
const {
isSaving,
saveError
} = prevProps;
if (prevProps.isSaving && !isSaving && !saveError) {
this.setState({
monitored: NO_CHANGE,
monitor: NO_CHANGE
});
}
}
//
// Listeners
onInputChange = ({ name, value }) => {
this.setState({ [name]: value });
};
onUpdateSelectedPress = () => {
const {
monitor,
monitored
} = this.state;
const changes = {};
if (monitored !== NO_CHANGE) {
changes.monitored = monitored === 'monitored';
}
if (monitor !== NO_CHANGE) {
changes.monitor = monitor;
}
this.props.onUpdateSelectedPress(changes);
};
//
// Render
render() {
const {
selectedCount,
isSaving
} = this.props;
const {
monitored,
monitor
} = this.state;
const monitoredOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'monitored', value: 'Monitored' },
{ key: 'unmonitored', value: 'Unmonitored' }
];
const noChanges = monitored === NO_CHANGE && monitor === NO_CHANGE;
return (
<PageContentFooter>
<div className={styles.inputContainer}>
<div className={styles.label}>
Monitor Series
</div>
<SelectInput
name="monitored"
value={monitored}
values={monitoredOptions}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<div className={styles.label}>
Monitor Episodes
</div>
<MonitorEpisodesSelectInput
name="monitor"
value={monitor}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div>
<div className={styles.label}>
{selectedCount} Series Selected
</div>
<SpinnerButton
className={styles.updateSelectedButton}
kind={kinds.PRIMARY}
isSpinning={isSaving}
isDisabled={!selectedCount || noChanges}
onPress={this.onUpdateSelectedPress}
>
Update Selected
</SpinnerButton>
</div>
</PageContentFooter>
);
}
}
SeasonPassFooter.propTypes = {
selectedCount: PropTypes.number.isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
onUpdateSelectedPress: PropTypes.func.isRequired
};
export default SeasonPassFooter;

View File

@ -1,20 +0,0 @@
.status,
.monitored {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 50px;
}
.title {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 1px;
white-space: nowrap;
}
.seasons {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
display: flex;
flex-wrap: wrap;
}

View File

@ -1,103 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Icon from 'Components/Icon';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import TableRow from 'Components/Table/TableRow';
import { getSeriesStatusDetails } from 'Series/SeriesStatus';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import SeasonPassSeason from './SeasonPassSeason';
import styles from './SeasonPassRow.css';
class SeasonPassRow extends Component {
//
// Render
render() {
const {
seriesId,
monitored,
status,
title,
titleSlug,
seasons,
isSaving,
isSelected,
onSelectedChange,
onSeriesMonitoredPress,
onSeasonMonitoredPress
} = this.props;
const statusDetails = getSeriesStatusDetails(status);
return (
<TableRow>
<TableSelectCell
id={seriesId}
isSelected={isSelected}
onSelectedChange={onSelectedChange}
/>
<TableRowCell className={styles.status}>
<Icon
className={styles.statusIcon}
name={statusDetails.icon}
title={statusDetails.title}
/>
</TableRowCell>
<TableRowCell className={styles.title}>
<SeriesTitleLink
titleSlug={titleSlug}
title={title}
/>
</TableRowCell>
<TableRowCell className={styles.monitored}>
<MonitorToggleButton
monitored={monitored}
isSaving={isSaving}
onPress={onSeriesMonitoredPress}
/>
</TableRowCell>
<TableRowCell className={styles.seasons}>
{
seasons.map((season) => {
return (
<SeasonPassSeason
key={season.seasonNumber}
{...season}
onSeasonMonitoredPress={onSeasonMonitoredPress}
/>
);
})
}
</TableRowCell>
</TableRow>
);
}
}
SeasonPassRow.propTypes = {
seriesId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired,
status: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired,
seasons: PropTypes.arrayOf(PropTypes.object).isRequired,
isSaving: PropTypes.bool.isRequired,
isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired,
onSeriesMonitoredPress: PropTypes.func.isRequired,
onSeasonMonitoredPress: PropTypes.func.isRequired
};
SeasonPassRow.defaultProps = {
isSaving: false
};
export default SeasonPassRow;

View File

@ -1,77 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { toggleSeasonMonitored, toggleSeriesMonitored } from 'Store/Actions/seriesActions';
import createSeriesSelector from 'Store/Selectors/createSeriesSelector';
import SeasonPassRow from './SeasonPassRow';
function createMapStateToProps() {
return createSelector(
createSeriesSelector(),
(series) => {
return _.pick(series, [
'status',
'titleSlug',
'title',
'monitored',
'seasons',
'isSaving'
]);
}
);
}
const mapDispatchToProps = {
toggleSeriesMonitored,
toggleSeasonMonitored
};
class SeasonPassRowConnector extends Component {
//
// Listeners
onSeriesMonitoredPress = () => {
const {
seriesId,
monitored
} = this.props;
this.props.toggleSeriesMonitored({
seriesId,
monitored: !monitored
});
};
onSeasonMonitoredPress = (seasonNumber, monitored) => {
this.props.toggleSeasonMonitored({
seriesId: this.props.seriesId,
seasonNumber,
monitored
});
};
//
// Render
render() {
return (
<SeasonPassRow
{...this.props}
onSeriesMonitoredPress={this.onSeriesMonitoredPress}
onSeasonMonitoredPress={this.onSeasonMonitoredPress}
/>
);
}
}
SeasonPassRowConnector.propTypes = {
seriesId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired,
toggleSeriesMonitored: PropTypes.func.isRequired,
toggleSeasonMonitored: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(SeasonPassRowConnector);

View File

@ -1,25 +0,0 @@
.season {
display: flex;
align-items: stretch;
overflow: hidden;
margin: 2px 4px;
border: 1px solid var(--borderColor);
border-radius: 4px;
background-color: var(--seasonBackgroundColor);
cursor: default;
}
.info {
padding: 0 4px;
}
.episodes {
padding: 0 4px;
background-color: var(--episodesBackgroundColor);
color: var(--defaultColor);
}
.allEpisodes {
background-color: #e0ffe0;
color: var(--darkGray);
}

View File

@ -1,88 +0,0 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import padNumber from 'Utilities/Number/padNumber';
import styles from './SeasonPassSeason.css';
class SeasonPassSeason extends Component {
//
// Listeners
onSeasonMonitoredPress = () => {
const {
seasonNumber,
monitored
} = this.props;
this.props.onSeasonMonitoredPress(seasonNumber, !monitored);
};
//
// Render
render() {
const {
seasonNumber,
monitored,
statistics,
isSaving
} = this.props;
const {
episodeFileCount,
totalEpisodeCount,
percentOfEpisodes
} = statistics;
return (
<div className={styles.season}>
<div className={styles.info}>
<MonitorToggleButton
monitored={monitored}
isSaving={isSaving}
onPress={this.onSeasonMonitoredPress}
/>
<span>
{
seasonNumber === 0 ? 'Specials' : `S${padNumber(seasonNumber, 2)}`
}
</span>
</div>
<div
className={classNames(
styles.episodes,
percentOfEpisodes === 100 && styles.allEpisodes
)}
title={`${episodeFileCount}/${totalEpisodeCount} episodes downloaded`}
>
{
totalEpisodeCount === 0 ? '0/0' : `${episodeFileCount}/${totalEpisodeCount}`
}
</div>
</div>
);
}
}
SeasonPassSeason.propTypes = {
seasonNumber: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired,
statistics: PropTypes.object.isRequired,
isSaving: PropTypes.bool.isRequired,
onSeasonMonitoredPress: PropTypes.func.isRequired
};
SeasonPassSeason.defaultProps = {
isSaving: false,
statistics: {
episodeFileCount: 0,
totalEpisodeCount: 0,
percentOfEpisodes: 0
}
};
export default SeasonPassSeason;

View File

@ -18,7 +18,6 @@ import * as providerOptions from './providerOptionActions';
import * as queue from './queueActions';
import * as releases from './releaseActions';
import * as rootFolders from './rootFolderActions';
import * as seasonPass from './seasonPassActions';
import * as series from './seriesActions';
import * as seriesHistory from './seriesHistoryActions';
import * as seriesIndex from './seriesIndexActions';
@ -48,7 +47,6 @@ export default [
queue,
releases,
rootFolders,
seasonPass,
series,
seriesHistory,
seriesIndex,

View File

@ -1,123 +0,0 @@
import { createAction } from 'redux-actions';
import { sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import { set } from './baseActions';
import createHandleActions from './Creators/createHandleActions';
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
import { fetchSeries, filterBuilderProps, filterPredicates, filters } from './seriesActions';
//
// Variables
export const section = 'seasonPass';
//
// State
export const defaultState = {
isSaving: false,
saveError: null,
sortKey: 'sortTitle',
sortDirection: sortDirections.ASCENDING,
secondarySortKey: 'sortTitle',
secondarySortDirection: sortDirections.ASCENDING,
selectedFilterKey: 'all',
filters,
filterPredicates,
filterBuilderProps
};
export const persistState = [
'seasonPass.sortKey',
'seasonPass.sortDirection',
'seasonPass.selectedFilterKey',
'seasonPass.customFilters'
];
//
// Actions Types
export const SET_SEASON_PASS_SORT = 'seasonPass/setSeasonPassSort';
export const SET_SEASON_PASS_FILTER = 'seasonPass/setSeasonPassFilter';
export const SAVE_SEASON_PASS = 'seasonPass/saveSeasonPass';
//
// Action Creators
export const setSeasonPassSort = createAction(SET_SEASON_PASS_SORT);
export const setSeasonPassFilter = createAction(SET_SEASON_PASS_FILTER);
export const saveSeasonPass = createThunk(SAVE_SEASON_PASS);
//
// Action Handlers
export const actionHandlers = handleThunks({
[SAVE_SEASON_PASS]: function(getState, payload, dispatch) {
const {
seriesIds,
monitored,
monitor
} = payload;
const series = [];
seriesIds.forEach((id) => {
const seriesToUpdate = { id };
if (payload.hasOwnProperty('monitored')) {
seriesToUpdate.monitored = monitored;
}
series.push(seriesToUpdate);
});
dispatch(set({
section,
isSaving: true
}));
const promise = createAjaxRequest({
url: '/seasonPass',
method: 'POST',
data: JSON.stringify({
series,
monitoringOptions: { monitor }
}),
dataType: 'json'
}).request;
promise.done((data) => {
dispatch(fetchSeries());
dispatch(set({
section,
isSaving: false,
saveError: null
}));
});
promise.fail((xhr) => {
dispatch(set({
section,
isSaving: false,
saveError: xhr
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[SET_SEASON_PASS_SORT]: createSetClientSideCollectionSortReducer(section),
[SET_SEASON_PASS_FILTER]: createSetClientSideCollectionFilterReducer(section)
}, defaultState, section);