mirror of
https://github.com/Sonarr/Sonarr.git
synced 2025-04-24 22:37:06 -04:00
Fixed: Various issues with unknown items in queue
This commit is contained in:
parent
7e33261ccc
commit
21a92b62fd
15 changed files with 330 additions and 242 deletions
|
@ -5,7 +5,7 @@ import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
|
||||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||||
import selectAll from 'Utilities/Table/selectAll';
|
import selectAll from 'Utilities/Table/selectAll';
|
||||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
import toggleSelected from 'Utilities/Table/toggleSelected';
|
||||||
import { icons } from 'Helpers/Props';
|
import { align, icons } from 'Helpers/Props';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
@ -16,6 +16,7 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||||
|
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||||
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
|
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
|
||||||
import QueueOptionsConnector from './QueueOptionsConnector';
|
import QueueOptionsConnector from './QueueOptionsConnector';
|
||||||
import QueueRowConnector from './QueueRowConnector';
|
import QueueRowConnector from './QueueRowConnector';
|
||||||
|
@ -43,16 +44,18 @@ class Queue extends Component {
|
||||||
// before episodes start fetching or when episodes start fetching.
|
// before episodes start fetching or when episodes start fetching.
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(
|
this.props.isFetching &&
|
||||||
this.props.isFetching &&
|
nextProps.isPopulated &&
|
||||||
nextProps.isPopulated &&
|
hasDifferentItems(this.props.items, nextProps.items) &&
|
||||||
hasDifferentItems(this.props.items, nextProps.items)
|
nextProps.items.some((e) => e.episodeId)
|
||||||
) ||
|
|
||||||
(!this.props.isEpisodesFetching && nextProps.isEpisodesFetching)
|
|
||||||
) {
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.props.isEpisodesFetching && nextProps.isEpisodesFetching) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +142,7 @@ class Queue extends Component {
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const isRefreshing = isFetching || isEpisodesFetching || isCheckForFinishedDownloadExecuting;
|
const isRefreshing = isFetching || isEpisodesFetching || isCheckForFinishedDownloadExecuting;
|
||||||
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length);
|
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length || items.every((e) => !e.episodeId));
|
||||||
const hasError = error || episodesError;
|
const hasError = error || episodesError;
|
||||||
const selectedCount = this.getSelectedIds().length;
|
const selectedCount = this.getSelectedIds().length;
|
||||||
const disableSelectedActions = selectedCount === 0;
|
const disableSelectedActions = selectedCount === 0;
|
||||||
|
@ -173,6 +176,21 @@ class Queue extends Component {
|
||||||
onPress={this.onRemoveSelectedPress}
|
onPress={this.onRemoveSelectedPress}
|
||||||
/>
|
/>
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
|
|
||||||
|
<PageToolbarSection
|
||||||
|
alignContent={align.RIGHT}
|
||||||
|
>
|
||||||
|
<TableOptionsModalWrapper
|
||||||
|
columns={columns}
|
||||||
|
{...otherProps}
|
||||||
|
optionsComponent={QueueOptionsConnector}
|
||||||
|
>
|
||||||
|
<PageToolbarButton
|
||||||
|
label="Options"
|
||||||
|
iconName={icons.TABLE}
|
||||||
|
/>
|
||||||
|
</TableOptionsModalWrapper>
|
||||||
|
</PageToolbarSection>
|
||||||
</PageToolbar>
|
</PageToolbar>
|
||||||
|
|
||||||
<PageContentBodyConnector>
|
<PageContentBodyConnector>
|
||||||
|
|
|
@ -10,6 +10,7 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
||||||
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
|
||||||
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
|
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
|
||||||
|
import EpisodeLanguage from 'Episode/EpisodeLanguage';
|
||||||
import EpisodeQuality from 'Episode/EpisodeQuality';
|
import EpisodeQuality from 'Episode/EpisodeQuality';
|
||||||
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
|
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
|
||||||
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
|
||||||
|
@ -71,6 +72,7 @@ class QueueRow extends Component {
|
||||||
errorMessage,
|
errorMessage,
|
||||||
series,
|
series,
|
||||||
episode,
|
episode,
|
||||||
|
language,
|
||||||
quality,
|
quality,
|
||||||
protocol,
|
protocol,
|
||||||
indexer,
|
indexer,
|
||||||
|
@ -204,6 +206,16 @@ class QueueRow extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'language') {
|
||||||
|
return (
|
||||||
|
<TableRowCell key={name}>
|
||||||
|
<EpisodeLanguage
|
||||||
|
language={language}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'quality') {
|
if (name === 'quality') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell key={name}>
|
<TableRowCell key={name}>
|
||||||
|
@ -340,6 +352,7 @@ QueueRow.propTypes = {
|
||||||
errorMessage: PropTypes.string,
|
errorMessage: PropTypes.string,
|
||||||
series: PropTypes.object,
|
series: PropTypes.object,
|
||||||
episode: PropTypes.object,
|
episode: PropTypes.object,
|
||||||
|
language: PropTypes.object.isRequired,
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
protocol: PropTypes.string.isRequired,
|
protocol: PropTypes.string.isRequired,
|
||||||
indexer: PropTypes.string,
|
indexer: PropTypes.string,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import { icons, scrollDirections } from 'Helpers/Props';
|
import { icons, scrollDirections } from 'Helpers/Props';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Scroller from 'Components/Scroller/Scroller';
|
import Scroller from 'Components/Scroller/Scroller';
|
||||||
import TableOptionsModal from 'Components/Table/TableOptions/TableOptionsModal';
|
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||||
import TableHeader from './TableHeader';
|
import TableHeader from './TableHeader';
|
||||||
import TableHeaderCell from './TableHeaderCell';
|
import TableHeaderCell from './TableHeaderCell';
|
||||||
import TableSelectAllHeaderCell from './TableSelectAllHeaderCell';
|
import TableSelectAllHeaderCell from './TableSelectAllHeaderCell';
|
||||||
|
@ -25,119 +25,88 @@ function getTableHeaderCellProps(props) {
|
||||||
}, {});
|
}, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
class Table extends Component {
|
function Table(props) {
|
||||||
|
const {
|
||||||
|
className,
|
||||||
|
selectAll,
|
||||||
|
columns,
|
||||||
|
optionsComponent,
|
||||||
|
pageSize,
|
||||||
|
canModifyColumns,
|
||||||
|
children,
|
||||||
|
onSortPress,
|
||||||
|
onTableOptionChange,
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
//
|
return (
|
||||||
// Lifecycle
|
<Scroller
|
||||||
|
className={styles.tableContainer}
|
||||||
|
scrollDirection={scrollDirections.HORIZONTAL}
|
||||||
|
>
|
||||||
|
<table className={className}>
|
||||||
|
<TableHeader>
|
||||||
|
{
|
||||||
|
selectAll &&
|
||||||
|
<TableSelectAllHeaderCell {...otherProps} />
|
||||||
|
}
|
||||||
|
|
||||||
constructor(props, context) {
|
{
|
||||||
super(props, context);
|
columns.map((column) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
isVisible
|
||||||
|
} = column;
|
||||||
|
|
||||||
this.state = {
|
if (!isVisible) {
|
||||||
isTableOptionsModalOpen: false
|
return null;
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
|
||||||
//
|
if (
|
||||||
// Listeners
|
(name === 'actions' || name === 'details') &&
|
||||||
|
onTableOptionChange
|
||||||
onTableOptionsPress = () => {
|
) {
|
||||||
this.setState({ isTableOptionsModalOpen: true });
|
return (
|
||||||
}
|
<TableHeaderCell
|
||||||
|
key={name}
|
||||||
onTableOptionsModalClose = () => {
|
className={styles[name]}
|
||||||
this.setState({ isTableOptionsModalOpen: false });
|
name={name}
|
||||||
}
|
isSortable={false}
|
||||||
|
{...otherProps}
|
||||||
//
|
>
|
||||||
// Render
|
<TableOptionsModalWrapper
|
||||||
|
columns={columns}
|
||||||
render() {
|
optionsComponent={optionsComponent}
|
||||||
const {
|
pageSize={pageSize}
|
||||||
className,
|
canModifyColumns={canModifyColumns}
|
||||||
selectAll,
|
onTableOptionChange={onTableOptionChange}
|
||||||
columns,
|
|
||||||
optionsComponent,
|
|
||||||
pageSize,
|
|
||||||
canModifyColumns,
|
|
||||||
children,
|
|
||||||
onSortPress,
|
|
||||||
onTableOptionChange,
|
|
||||||
...otherProps
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Scroller
|
|
||||||
className={styles.tableContainer}
|
|
||||||
scrollDirection={scrollDirections.HORIZONTAL}
|
|
||||||
>
|
|
||||||
<table className={className}>
|
|
||||||
<TableHeader>
|
|
||||||
{
|
|
||||||
selectAll &&
|
|
||||||
<TableSelectAllHeaderCell {...otherProps} />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
columns.map((column) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
isVisible
|
|
||||||
} = column;
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((name === 'actions' || name === 'details') && onTableOptionChange) {
|
|
||||||
return (
|
|
||||||
<TableHeaderCell
|
|
||||||
key={name}
|
|
||||||
className={styles[name]}
|
|
||||||
name={name}
|
|
||||||
isSortable={false}
|
|
||||||
{...otherProps}
|
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
name={icons.ADVANCED_SETTINGS}
|
name={icons.ADVANCED_SETTINGS}
|
||||||
onPress={this.onTableOptionsPress}
|
|
||||||
/>
|
/>
|
||||||
</TableHeaderCell>
|
</TableOptionsModalWrapper>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableHeaderCell
|
|
||||||
key={column.name}
|
|
||||||
onSortPress={onSortPress}
|
|
||||||
{...getTableHeaderCellProps(otherProps)}
|
|
||||||
{...column}
|
|
||||||
>
|
|
||||||
{column.label}
|
|
||||||
</TableHeaderCell>
|
</TableHeaderCell>
|
||||||
);
|
);
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
{
|
return (
|
||||||
!!onTableOptionChange &&
|
<TableHeaderCell
|
||||||
<TableOptionsModal
|
key={column.name}
|
||||||
isOpen={this.state.isTableOptionsModalOpen}
|
onSortPress={onSortPress}
|
||||||
columns={columns}
|
{...getTableHeaderCellProps(otherProps)}
|
||||||
optionsComponent={optionsComponent}
|
{...column}
|
||||||
pageSize={pageSize}
|
>
|
||||||
canModifyColumns={canModifyColumns}
|
{column.label}
|
||||||
onTableOptionChange={onTableOptionChange}
|
</TableHeaderCell>
|
||||||
onModalClose={this.onTableOptionsModalClose}
|
);
|
||||||
/>
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
{children}
|
{children}
|
||||||
</table>
|
</table>
|
||||||
</Scroller>
|
</Scroller>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Table.propTypes = {
|
Table.propTypes = {
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component, Fragment } from 'react';
|
||||||
|
import TableOptionsModal from './TableOptionsModal';
|
||||||
|
|
||||||
|
class TableOptionsModalWrapper extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
isTableOptionsModalOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onTableOptionsPress = () => {
|
||||||
|
this.setState({ isTableOptionsModalOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onTableOptionsModalClose = () => {
|
||||||
|
this.setState({ isTableOptionsModalOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
columns,
|
||||||
|
children,
|
||||||
|
...otherProps
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{
|
||||||
|
React.cloneElement(children, { onPress: this.onTableOptionsPress })
|
||||||
|
}
|
||||||
|
|
||||||
|
<TableOptionsModal
|
||||||
|
{...otherProps}
|
||||||
|
isOpen={this.state.isTableOptionsModalOpen}
|
||||||
|
columns={columns}
|
||||||
|
onModalClose={this.onTableOptionsModalClose}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TableOptionsModalWrapper.propTypes = {
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
children: PropTypes.node.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TableOptionsModalWrapper;
|
|
@ -86,6 +86,7 @@ import {
|
||||||
faStop as fasStop,
|
faStop as fasStop,
|
||||||
faSync as fasSync,
|
faSync as fasSync,
|
||||||
faTags as fasTags,
|
faTags as fasTags,
|
||||||
|
faTable as fasTable,
|
||||||
faTh as fasTh,
|
faTh as fasTh,
|
||||||
faThList as fasThList,
|
faThList as fasThList,
|
||||||
faTrashAlt as fasTrashAlt,
|
faTrashAlt as fasTrashAlt,
|
||||||
|
@ -188,6 +189,7 @@ export const SORT_DESCENDING = fasSortDown;
|
||||||
export const SPINNER = fasSpinner;
|
export const SPINNER = fasSpinner;
|
||||||
export const SUBTRACT = fasMinus;
|
export const SUBTRACT = fasMinus;
|
||||||
export const SYSTEM = fasLaptop;
|
export const SYSTEM = fasLaptop;
|
||||||
|
export const TABLE = fasTable;
|
||||||
export const TAGS = fasTags;
|
export const TAGS = fasTags;
|
||||||
export const TBA = fasQuestionCircle;
|
export const TBA = fasQuestionCircle;
|
||||||
export const TEST = fasVial;
|
export const TEST = fasVial;
|
||||||
|
|
|
@ -1,108 +1,78 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
|
||||||
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
|
||||||
import TableOptionsModal from 'Components/Table/TableOptions/TableOptionsModal';
|
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||||
import SeriesIndexTableOptionsConnector from './SeriesIndexTableOptionsConnector';
|
import SeriesIndexTableOptionsConnector from './SeriesIndexTableOptionsConnector';
|
||||||
import styles from './SeriesIndexHeader.css';
|
import styles from './SeriesIndexHeader.css';
|
||||||
|
|
||||||
class SeriesIndexHeader extends Component {
|
function SeriesIndexHeader(props) {
|
||||||
|
const {
|
||||||
|
showBanners,
|
||||||
|
columns,
|
||||||
|
onTableOptionChange,
|
||||||
|
...otherProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
//
|
return (
|
||||||
// Lifecycle
|
<VirtualTableHeader>
|
||||||
|
{
|
||||||
|
columns.map((column) => {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
label,
|
||||||
|
isSortable,
|
||||||
|
isVisible
|
||||||
|
} = column;
|
||||||
|
|
||||||
constructor(props, context) {
|
if (!isVisible) {
|
||||||
super(props, context);
|
return null;
|
||||||
|
}
|
||||||
this.state = {
|
|
||||||
isTableOptionsModalOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onTableOptionsPress = () => {
|
|
||||||
this.setState({ isTableOptionsModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onTableOptionsModalClose = () => {
|
|
||||||
this.setState({ isTableOptionsModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
showBanners,
|
|
||||||
columns,
|
|
||||||
onTableOptionChange,
|
|
||||||
...otherProps
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<VirtualTableHeader>
|
|
||||||
{
|
|
||||||
columns.map((column) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
label,
|
|
||||||
isSortable,
|
|
||||||
isVisible
|
|
||||||
} = column;
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'actions') {
|
|
||||||
return (
|
|
||||||
<VirtualTableHeaderCell
|
|
||||||
key={name}
|
|
||||||
className={styles[name]}
|
|
||||||
name={name}
|
|
||||||
isSortable={false}
|
|
||||||
{...otherProps}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
name={icons.ADVANCED_SETTINGS}
|
|
||||||
onPress={this.onTableOptionsPress}
|
|
||||||
/>
|
|
||||||
</VirtualTableHeaderCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (name === 'actions') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableHeaderCell
|
<VirtualTableHeaderCell
|
||||||
key={name}
|
key={name}
|
||||||
className={classNames(
|
className={styles[name]}
|
||||||
styles[name],
|
|
||||||
name === 'sortTitle' && showBanners && styles.banner
|
|
||||||
)}
|
|
||||||
name={name}
|
name={name}
|
||||||
isSortable={isSortable}
|
isSortable={false}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
{label}
|
|
||||||
|
<TableOptionsModalWrapper
|
||||||
|
columns={columns}
|
||||||
|
optionsComponent={SeriesIndexTableOptionsConnector}
|
||||||
|
onTableOptionChange={onTableOptionChange}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
name={icons.ADVANCED_SETTINGS}
|
||||||
|
/>
|
||||||
|
</TableOptionsModalWrapper>
|
||||||
</VirtualTableHeaderCell>
|
</VirtualTableHeaderCell>
|
||||||
);
|
);
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
<TableOptionsModal
|
return (
|
||||||
isOpen={this.state.isTableOptionsModalOpen}
|
<VirtualTableHeaderCell
|
||||||
columns={columns}
|
key={name}
|
||||||
optionsComponent={SeriesIndexTableOptionsConnector}
|
className={classNames(
|
||||||
onTableOptionChange={onTableOptionChange}
|
styles[name],
|
||||||
onModalClose={this.onTableOptionsModalClose}
|
name === 'sortTitle' && showBanners && styles.banner
|
||||||
/>
|
)}
|
||||||
</VirtualTableHeader>
|
name={name}
|
||||||
);
|
isSortable={isSortable}
|
||||||
}
|
{...otherProps}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</VirtualTableHeaderCell>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</VirtualTableHeader>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SeriesIndexHeader.propTypes = {
|
SeriesIndexHeader.propTypes = {
|
||||||
|
|
|
@ -84,6 +84,12 @@ export const defaultState = {
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: false
|
isVisible: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'language',
|
||||||
|
label: 'Language',
|
||||||
|
isSortable: true,
|
||||||
|
isVisible: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'quality',
|
name: 'quality',
|
||||||
label: 'Quality',
|
label: 'Quality',
|
||||||
|
|
|
@ -10,7 +10,11 @@ function createQueueItemSelector() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return details.find((item) => {
|
return details.find((item) => {
|
||||||
return item.episode.id === episodeId;
|
if (item.episode) {
|
||||||
|
return item.episode.id === episodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
@ -124,11 +124,10 @@ namespace NzbDrone.Core.Download.TrackedDownloads
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Track it so it can be displayed in the queue even though we can't determine which serires it is for
|
||||||
if (trackedDownload.RemoteEpisode == null)
|
if (trackedDownload.RemoteEpisode == null)
|
||||||
{
|
{
|
||||||
_logger.Trace("No Episode found for download '{0}', not tracking.", trackedDownload.DownloadItem.Title);
|
_logger.Trace("No Episode found for download '{0}'", trackedDownload.DownloadItem.Title);
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Core.Lifecycle;
|
using NzbDrone.Core.Lifecycle;
|
||||||
|
@ -16,6 +16,7 @@ namespace NzbDrone.Core.Profiles.Languages
|
||||||
List<LanguageProfile> All();
|
List<LanguageProfile> All();
|
||||||
LanguageProfile Get(int id);
|
LanguageProfile Get(int id);
|
||||||
bool Exists(int id);
|
bool Exists(int id);
|
||||||
|
LanguageProfile GetDefaultProfile(string name, Language cutoff = null, params Language[] allowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LanguageProfileService : ILanguageProfileService, IHandle<ApplicationStartedEvent>
|
public class LanguageProfileService : ILanguageProfileService, IHandle<ApplicationStartedEvent>
|
||||||
|
@ -66,6 +67,25 @@ namespace NzbDrone.Core.Profiles.Languages
|
||||||
return _profileRepository.Exists(id);
|
return _profileRepository.Exists(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LanguageProfile GetDefaultProfile(string name, Language cutoff = null, params Language[] allowed)
|
||||||
|
{
|
||||||
|
var orderedLanguages = Language.All
|
||||||
|
.Where(l => l != Language.Unknown)
|
||||||
|
.OrderByDescending(l => l.Name)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
orderedLanguages.Insert(0, Language.Unknown);
|
||||||
|
|
||||||
|
var languages = orderedLanguages.Select(v => new LanguageProfileItem { Language = v, Allowed = false })
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new LanguageProfile
|
||||||
|
{
|
||||||
|
Cutoff = Language.Unknown,
|
||||||
|
Languages = languages
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private LanguageProfile AddDefaultProfile(string name, Language cutoff, params Language[] allowed)
|
private LanguageProfile AddDefaultProfile(string name, Language cutoff, params Language[] allowed)
|
||||||
{
|
{
|
||||||
var languages = Language.All
|
var languages = Language.All
|
||||||
|
@ -92,4 +112,4 @@ namespace NzbDrone.Core.Profiles.Languages
|
||||||
AddDefaultProfile("English", Language.English, Language.English);
|
AddDefaultProfile("English", Language.English, Language.English);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Download.TrackedDownloads;
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.Languages;
|
||||||
using NzbDrone.Core.Parser.Model;
|
using NzbDrone.Core.Parser.Model;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
@ -13,6 +14,7 @@ namespace NzbDrone.Core.Queue
|
||||||
{
|
{
|
||||||
public Series Series { get; set; }
|
public Series Series { get; set; }
|
||||||
public Episode Episode { get; set; }
|
public Episode Episode { get; set; }
|
||||||
|
public Language Language { get; set; }
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { get; set; }
|
||||||
public decimal Size { get; set; }
|
public decimal Size { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
|
|
@ -3,7 +3,9 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NzbDrone.Common.Crypto;
|
using NzbDrone.Common.Crypto;
|
||||||
using NzbDrone.Core.Download.TrackedDownloads;
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
|
using NzbDrone.Core.Languages;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Queue
|
namespace NzbDrone.Core.Queue
|
||||||
|
@ -42,7 +44,7 @@ namespace NzbDrone.Core.Queue
|
||||||
|
|
||||||
private IEnumerable<Queue> MapQueue(TrackedDownload trackedDownload)
|
private IEnumerable<Queue> MapQueue(TrackedDownload trackedDownload)
|
||||||
{
|
{
|
||||||
if (trackedDownload.RemoteEpisode.Episodes != null && trackedDownload.RemoteEpisode.Episodes.Any())
|
if (trackedDownload.RemoteEpisode?.Episodes != null && trackedDownload.RemoteEpisode.Episodes.Any())
|
||||||
{
|
{
|
||||||
foreach (var episode in trackedDownload.RemoteEpisode.Episodes)
|
foreach (var episode in trackedDownload.RemoteEpisode.Episodes)
|
||||||
{
|
{
|
||||||
|
@ -59,9 +61,10 @@ namespace NzbDrone.Core.Queue
|
||||||
{
|
{
|
||||||
var queue = new Queue
|
var queue = new Queue
|
||||||
{
|
{
|
||||||
Series = trackedDownload.RemoteEpisode.Series,
|
Series = trackedDownload.RemoteEpisode?.Series,
|
||||||
Episode = episode,
|
Episode = episode,
|
||||||
Quality = trackedDownload.RemoteEpisode.ParsedEpisodeInfo.Quality,
|
Language = trackedDownload.RemoteEpisode?.ParsedEpisodeInfo.Language ?? Language.Unknown,
|
||||||
|
Quality = trackedDownload.RemoteEpisode?.ParsedEpisodeInfo.Quality ?? new QualityModel(Quality.Unknown),
|
||||||
Title = Parser.Parser.RemoveFileExtension(trackedDownload.DownloadItem.Title),
|
Title = Parser.Parser.RemoveFileExtension(trackedDownload.DownloadItem.Title),
|
||||||
Size = trackedDownload.DownloadItem.TotalSize,
|
Size = trackedDownload.DownloadItem.TotalSize,
|
||||||
Sizeleft = trackedDownload.DownloadItem.RemainingSize,
|
Sizeleft = trackedDownload.DownloadItem.RemainingSize,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
using System.Linq;
|
|
||||||
using NzbDrone.Core.Profiles.Languages;
|
using NzbDrone.Core.Profiles.Languages;
|
||||||
using Sonarr.Http;
|
using Sonarr.Http;
|
||||||
|
|
||||||
|
@ -6,32 +5,19 @@ namespace Sonarr.Api.V3.Profiles.Language
|
||||||
{
|
{
|
||||||
public class LanguageProfileSchemaModule : SonarrRestModule<LanguageProfileResource>
|
public class LanguageProfileSchemaModule : SonarrRestModule<LanguageProfileResource>
|
||||||
{
|
{
|
||||||
|
private readonly LanguageProfileService _languageProfileService;
|
||||||
|
|
||||||
public LanguageProfileSchemaModule()
|
public LanguageProfileSchemaModule(LanguageProfileService languageProfileService)
|
||||||
: base("/languageprofile/schema")
|
: base("/languageprofile/schema")
|
||||||
{
|
{
|
||||||
|
_languageProfileService = languageProfileService;
|
||||||
GetResourceSingle = GetAll;
|
GetResourceSingle = GetAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LanguageProfileResource GetAll()
|
private LanguageProfileResource GetAll()
|
||||||
{
|
{
|
||||||
var orderedLanguages = NzbDrone.Core.Languages.Language.All
|
var profile = _languageProfileService.GetDefaultProfile(string.Empty);
|
||||||
.Where(l => l != NzbDrone.Core.Languages.Language.Unknown)
|
|
||||||
.OrderByDescending(l => l.Name)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
orderedLanguages.Insert(0, NzbDrone.Core.Languages.Language.Unknown);
|
|
||||||
|
|
||||||
var languages = orderedLanguages.Select(v => new LanguageProfileItem {Language = v, Allowed = false})
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var profile = new LanguageProfile
|
|
||||||
{
|
|
||||||
Cutoff = NzbDrone.Core.Languages.Language.Unknown,
|
|
||||||
Languages = languages
|
|
||||||
};
|
|
||||||
|
|
||||||
return profile.ToResource();
|
return profile.ToResource();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Datastore.Events;
|
using NzbDrone.Core.Datastore.Events;
|
||||||
using NzbDrone.Core.Download.Pending;
|
using NzbDrone.Core.Download.Pending;
|
||||||
|
using NzbDrone.Core.Languages;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
using NzbDrone.Core.Profiles.Languages;
|
||||||
|
using NzbDrone.Core.Profiles.Qualities;
|
||||||
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Queue;
|
using NzbDrone.Core.Queue;
|
||||||
using NzbDrone.SignalR;
|
using NzbDrone.SignalR;
|
||||||
using Sonarr.Http;
|
using Sonarr.Http;
|
||||||
|
@ -18,18 +21,23 @@ namespace Sonarr.Api.V3.Queue
|
||||||
{
|
{
|
||||||
private readonly IQueueService _queueService;
|
private readonly IQueueService _queueService;
|
||||||
private readonly IPendingReleaseService _pendingReleaseService;
|
private readonly IPendingReleaseService _pendingReleaseService;
|
||||||
private readonly IConfigService _configService;
|
|
||||||
|
private readonly LanguageComparer LANGUAGE_COMPARER;
|
||||||
|
private readonly QualityModelComparer QUALITY_COMPARER;
|
||||||
|
|
||||||
public QueueModule(IBroadcastSignalRMessage broadcastSignalRMessage,
|
public QueueModule(IBroadcastSignalRMessage broadcastSignalRMessage,
|
||||||
IQueueService queueService,
|
IQueueService queueService,
|
||||||
IPendingReleaseService pendingReleaseService,
|
IPendingReleaseService pendingReleaseService,
|
||||||
IConfigService configService)
|
ILanguageProfileService languageProfileService,
|
||||||
|
QualityProfileService qualityProfileService)
|
||||||
: base(broadcastSignalRMessage)
|
: base(broadcastSignalRMessage)
|
||||||
{
|
{
|
||||||
_queueService = queueService;
|
_queueService = queueService;
|
||||||
_pendingReleaseService = pendingReleaseService;
|
_pendingReleaseService = pendingReleaseService;
|
||||||
_configService = configService;
|
|
||||||
GetResourcePaged = GetQueue;
|
GetResourcePaged = GetQueue;
|
||||||
|
|
||||||
|
LANGUAGE_COMPARER = new LanguageComparer(languageProfileService.GetDefaultProfile(string.Empty));
|
||||||
|
QUALITY_COMPARER = new QualityModelComparer(qualityProfileService.GetDefaultProfile(string.Empty));
|
||||||
}
|
}
|
||||||
|
|
||||||
private PagingResource<QueueResource> GetQueue(PagingResource<QueueResource> pagingResource)
|
private PagingResource<QueueResource> GetQueue(PagingResource<QueueResource> pagingResource)
|
||||||
|
@ -55,38 +63,60 @@ namespace Sonarr.Api.V3.Queue
|
||||||
|
|
||||||
if (pagingSpec.SortKey == "episode")
|
if (pagingSpec.SortKey == "episode")
|
||||||
{
|
{
|
||||||
ordered = ascending ? fullQueue.OrderBy(q => q.Episode.SeasonNumber).ThenBy(q => q.Episode.EpisodeNumber) :
|
ordered = ascending
|
||||||
fullQueue.OrderByDescending(q => q.Episode.SeasonNumber).ThenByDescending(q => q.Episode.EpisodeNumber);
|
? fullQueue.OrderBy(q => q.Episode?.SeasonNumber).ThenBy(q => q.Episode?.EpisodeNumber)
|
||||||
|
: fullQueue.OrderByDescending(q => q.Episode?.SeasonNumber)
|
||||||
|
.ThenByDescending(q => q.Episode?.EpisodeNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (pagingSpec.SortKey == "timeleft")
|
else if (pagingSpec.SortKey == "timeleft")
|
||||||
{
|
{
|
||||||
ordered = ascending ? fullQueue.OrderBy(q => q.Timeleft, new TimeleftComparer()) :
|
ordered = ascending
|
||||||
fullQueue.OrderByDescending(q => q.Timeleft, new TimeleftComparer());
|
? fullQueue.OrderBy(q => q.Timeleft, new TimeleftComparer())
|
||||||
|
: fullQueue.OrderByDescending(q => q.Timeleft, new TimeleftComparer());
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (pagingSpec.SortKey == "estimatedCompletionTime")
|
else if (pagingSpec.SortKey == "estimatedCompletionTime")
|
||||||
{
|
{
|
||||||
ordered = ascending ? fullQueue.OrderBy(q => q.EstimatedCompletionTime, new EstimatedCompletionTimeComparer()) :
|
ordered = ascending
|
||||||
fullQueue.OrderByDescending(q => q.EstimatedCompletionTime, new EstimatedCompletionTimeComparer());
|
? fullQueue.OrderBy(q => q.EstimatedCompletionTime, new EstimatedCompletionTimeComparer())
|
||||||
|
: fullQueue.OrderByDescending(q => q.EstimatedCompletionTime,
|
||||||
|
new EstimatedCompletionTimeComparer());
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (pagingSpec.SortKey == "protocol")
|
else if (pagingSpec.SortKey == "protocol")
|
||||||
{
|
{
|
||||||
ordered = ascending ? fullQueue.OrderBy(q => q.Protocol) :
|
ordered = ascending
|
||||||
fullQueue.OrderByDescending(q => q.Protocol);
|
? fullQueue.OrderBy(q => q.Protocol)
|
||||||
|
: fullQueue.OrderByDescending(q => q.Protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (pagingSpec.SortKey == "indexer")
|
else if (pagingSpec.SortKey == "indexer")
|
||||||
{
|
{
|
||||||
ordered = ascending ? fullQueue.OrderBy(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase) :
|
ordered = ascending
|
||||||
fullQueue.OrderByDescending(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase);
|
? fullQueue.OrderBy(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase)
|
||||||
|
: fullQueue.OrderByDescending(q => q.Indexer, StringComparer.InvariantCultureIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (pagingSpec.SortKey == "downloadClient")
|
else if (pagingSpec.SortKey == "downloadClient")
|
||||||
{
|
{
|
||||||
ordered = ascending ? fullQueue.OrderBy(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase) :
|
ordered = ascending
|
||||||
fullQueue.OrderByDescending(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase);
|
? fullQueue.OrderBy(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase)
|
||||||
|
: fullQueue.OrderByDescending(q => q.DownloadClient, StringComparer.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (pagingSpec.SortKey == "language")
|
||||||
|
{
|
||||||
|
ordered = ascending
|
||||||
|
? fullQueue.OrderBy(q => q.Language, LANGUAGE_COMPARER)
|
||||||
|
: fullQueue.OrderByDescending(q => q.Language, LANGUAGE_COMPARER);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (pagingSpec.SortKey == "quality")
|
||||||
|
{
|
||||||
|
ordered = ascending
|
||||||
|
? fullQueue.OrderBy(q => q.Quality, QUALITY_COMPARER)
|
||||||
|
: fullQueue.OrderByDescending(q => q.Quality, QUALITY_COMPARER);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -113,13 +143,15 @@ namespace Sonarr.Api.V3.Queue
|
||||||
switch (pagingSpec.SortKey)
|
switch (pagingSpec.SortKey)
|
||||||
{
|
{
|
||||||
case "series.sortTitle":
|
case "series.sortTitle":
|
||||||
return q => q.Series.SortTitle;
|
return q => q.Series?.SortTitle;
|
||||||
case "episode":
|
case "episode":
|
||||||
return q => q.Episode;
|
return q => q.Episode;
|
||||||
case "episode.airDateUtc":
|
case "episode.airDateUtc":
|
||||||
return q => q.Episode.AirDateUtc;
|
return q => q.Episode.AirDateUtc;
|
||||||
case "episode.title":
|
case "episode.title":
|
||||||
return q => q.Episode.Title;
|
return q => q.Episode.Title;
|
||||||
|
case "language":
|
||||||
|
return q => q.Language;
|
||||||
case "quality":
|
case "quality":
|
||||||
return q => q.Quality;
|
return q => q.Quality;
|
||||||
case "progress":
|
case "progress":
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NzbDrone.Core.Download.TrackedDownloads;
|
using NzbDrone.Core.Download.TrackedDownloads;
|
||||||
using NzbDrone.Core.Indexers;
|
using NzbDrone.Core.Indexers;
|
||||||
|
using NzbDrone.Core.Languages;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using Sonarr.Api.V3.Episodes;
|
using Sonarr.Api.V3.Episodes;
|
||||||
using Sonarr.Api.V3.Series;
|
using Sonarr.Api.V3.Series;
|
||||||
|
@ -16,6 +17,7 @@ namespace Sonarr.Api.V3.Queue
|
||||||
public int? EpisodeId { get; set; }
|
public int? EpisodeId { get; set; }
|
||||||
public SeriesResource Series { get; set; }
|
public SeriesResource Series { get; set; }
|
||||||
public EpisodeResource Episode { get; set; }
|
public EpisodeResource Episode { get; set; }
|
||||||
|
public Language Language { get; set; }
|
||||||
public QualityModel Quality { get; set; }
|
public QualityModel Quality { get; set; }
|
||||||
public decimal Size { get; set; }
|
public decimal Size { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
@ -45,6 +47,7 @@ namespace Sonarr.Api.V3.Queue
|
||||||
EpisodeId = model.Episode?.Id,
|
EpisodeId = model.Episode?.Id,
|
||||||
Series = includeSeries && model.Series != null ? model.Series.ToResource() : null,
|
Series = includeSeries && model.Series != null ? model.Series.ToResource() : null,
|
||||||
Episode = includeEpisode && model.Episode != null ? model.Episode.ToResource() : null,
|
Episode = includeEpisode && model.Episode != null ? model.Episode.ToResource() : null,
|
||||||
|
Language = model.Language,
|
||||||
Quality = model.Quality,
|
Quality = model.Quality,
|
||||||
Size = model.Size,
|
Size = model.Size,
|
||||||
Title = model.Title,
|
Title = model.Title,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue