mirror of
https://github.com/Sonarr/Sonarr.git
synced 2025-04-24 22:37:06 -04:00
Various UI fixes
Fixed: Manual Import not working if no episodes are Missing Fixed: Allow spaces in Must/Must not contain fields Fixed: Show loading indicator when managing episodes and fetching data from the server Fixed: Series images not loading for all search results New: Auto focus search input when navigating to Add Series page
This commit is contained in:
parent
561fdef815
commit
102b8afd66
8 changed files with 114 additions and 56 deletions
|
@ -100,6 +100,7 @@ class AddNewSeries extends Component {
|
||||||
name="seriesLookup"
|
name="seriesLookup"
|
||||||
value={term}
|
value={term}
|
||||||
placeholder="eg. Breaking Bad, tvdb:####"
|
placeholder="eg. Breaking Bad, tvdb:####"
|
||||||
|
autoFocus={true}
|
||||||
onChange={this.onSearchInputChange}
|
onChange={this.onSearchInputChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -78,12 +78,14 @@ class AddNewSeriesSearchResult extends Component {
|
||||||
{...linkProps}
|
{...linkProps}
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
!isSmallScreen &&
|
isSmallScreen ?
|
||||||
<SeriesPoster
|
null :
|
||||||
className={styles.poster}
|
<SeriesPoster
|
||||||
images={images}
|
className={styles.poster}
|
||||||
size={250}
|
images={images}
|
||||||
/>
|
size={250}
|
||||||
|
overflow={true}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -91,18 +93,22 @@ class AddNewSeriesSearchResult extends Component {
|
||||||
{title}
|
{title}
|
||||||
|
|
||||||
{
|
{
|
||||||
!title.contains(year) && !!year &&
|
!title.contains(year) && year ?
|
||||||
<span className={styles.year}>({year})</span>
|
<span className={styles.year}>
|
||||||
|
({year})
|
||||||
|
</span> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isExistingSeries &&
|
isExistingSeries ?
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.alreadyExistsIcon}
|
className={styles.alreadyExistsIcon}
|
||||||
name={icons.CHECK_CIRCLE}
|
name={icons.CHECK_CIRCLE}
|
||||||
size={36}
|
size={36}
|
||||||
title="Already in your library"
|
title="Already in your library"
|
||||||
/>
|
/> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -115,27 +121,30 @@ class AddNewSeriesSearchResult extends Component {
|
||||||
</Label>
|
</Label>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!network &&
|
network ?
|
||||||
<Label size={sizes.LARGE}>
|
<Label size={sizes.LARGE}>
|
||||||
{network}
|
{network}
|
||||||
</Label>
|
</Label> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!seasonCount &&
|
seasonCount ?
|
||||||
<Label size={sizes.LARGE}>
|
<Label size={sizes.LARGE}>
|
||||||
{seasons}
|
{seasons}
|
||||||
</Label>
|
</Label> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
status === 'ended' &&
|
status === 'ended' ?
|
||||||
<Label
|
<Label
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
size={sizes.LARGE}
|
size={sizes.LARGE}
|
||||||
>
|
>
|
||||||
Ended
|
Ended
|
||||||
</Label>
|
</Label> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,11 @@ 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 { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
import SelectInput from 'Components/Form/SelectInput';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
import SelectInput from 'Components/Form/SelectInput';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
@ -140,6 +141,9 @@ class EpisodeFileEditorModalContent extends Component {
|
||||||
const {
|
const {
|
||||||
seasonNumber,
|
seasonNumber,
|
||||||
isDeleting,
|
isDeleting,
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
items,
|
items,
|
||||||
languages,
|
languages,
|
||||||
qualities,
|
qualities,
|
||||||
|
@ -182,14 +186,27 @@ class EpisodeFileEditorModalContent extends Component {
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{
|
{
|
||||||
!items.length &&
|
isFetching && !isPopulated ?
|
||||||
<div>
|
<LoadingIndicator /> :
|
||||||
No episode files to manage.
|
null
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!items.length &&
|
!isFetching && error ?
|
||||||
|
<div>{error}</div> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && !items.length ?
|
||||||
|
<div>
|
||||||
|
No episode files to manage.
|
||||||
|
</div>:
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
isPopulated && items.length ?
|
||||||
<Table
|
<Table
|
||||||
columns={columns}
|
columns={columns}
|
||||||
selectAll={true}
|
selectAll={true}
|
||||||
|
@ -212,7 +229,8 @@ class EpisodeFileEditorModalContent extends Component {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table> :
|
||||||
|
null
|
||||||
}
|
}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
|
@ -272,6 +290,9 @@ class EpisodeFileEditorModalContent extends Component {
|
||||||
EpisodeFileEditorModalContent.propTypes = {
|
EpisodeFileEditorModalContent.propTypes = {
|
||||||
seasonNumber: PropTypes.number,
|
seasonNumber: PropTypes.number,
|
||||||
isDeleting: PropTypes.bool.isRequired,
|
isDeleting: PropTypes.bool.isRequired,
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
qualities: PropTypes.arrayOf(PropTypes.object).isRequired,
|
qualities: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|
|
@ -10,20 +10,45 @@ import { deleteEpisodeFiles, updateEpisodeFiles } from 'Store/Actions/episodeFil
|
||||||
import { fetchLanguageProfileSchema, fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
import { fetchLanguageProfileSchema, fetchQualityProfileSchema } from 'Store/Actions/settingsActions';
|
||||||
import EpisodeFileEditorModalContent from './EpisodeFileEditorModalContent';
|
import EpisodeFileEditorModalContent from './EpisodeFileEditorModalContent';
|
||||||
|
|
||||||
|
function createSchemaSelector() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.languageProfiles,
|
||||||
|
(state) => state.settings.qualityProfiles,
|
||||||
|
(languageProfiles, qualityProfiles) => {
|
||||||
|
const languages = _.map(languageProfiles.schema.languages, 'language');
|
||||||
|
const qualities = getQualities(qualityProfiles.schema.items);
|
||||||
|
|
||||||
|
let error = null;
|
||||||
|
|
||||||
|
if (languageProfiles.schemaError) {
|
||||||
|
error = 'Unable to load languages';
|
||||||
|
} else if (qualityProfiles.schemaError) {
|
||||||
|
error = 'Unable to load qualities';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFetching: languageProfiles.isSchemaFetching || qualityProfiles.isSchemaFetching,
|
||||||
|
isPopulated: languageProfiles.isSchemaPopulated && qualityProfiles.isSchemaPopulated,
|
||||||
|
error,
|
||||||
|
languages,
|
||||||
|
qualities
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { seasonNumber }) => seasonNumber,
|
(state, { seasonNumber }) => seasonNumber,
|
||||||
(state) => state.episodes,
|
(state) => state.episodes,
|
||||||
(state) => state.episodeFiles,
|
(state) => state.episodeFiles,
|
||||||
(state) => state.settings.languageProfiles.schema,
|
createSchemaSelector(),
|
||||||
(state) => state.settings.qualityProfiles.schema,
|
|
||||||
createSeriesSelector(),
|
createSeriesSelector(),
|
||||||
(
|
(
|
||||||
seasonNumber,
|
seasonNumber,
|
||||||
episodes,
|
episodes,
|
||||||
episodeFiles,
|
episodeFiles,
|
||||||
languageProfilesSchema,
|
schema,
|
||||||
qualityProfileSchema,
|
|
||||||
series
|
series
|
||||||
) => {
|
) => {
|
||||||
const filtered = _.filter(episodes.items, (episode) => {
|
const filtered = _.filter(episodes.items, (episode) => {
|
||||||
|
@ -51,16 +76,12 @@ function createMapStateToProps() {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const languages = _.map(languageProfilesSchema.languages, 'language');
|
|
||||||
const qualities = getQualities(qualityProfileSchema.items);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
...schema,
|
||||||
items,
|
items,
|
||||||
seriesType: series.seriesType,
|
seriesType: series.seriesType,
|
||||||
isDeleting: episodeFiles.isDeleting,
|
isDeleting: episodeFiles.isDeleting,
|
||||||
isSaving: episodeFiles.isSaving,
|
isSaving: episodeFiles.isSaving
|
||||||
languages,
|
|
||||||
qualities
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -96,9 +117,6 @@ class EpisodeFileEditorModalContentConnector extends Component {
|
||||||
this.props.dispatchFetchQualityProfileSchema();
|
this.props.dispatchFetchQualityProfileSchema();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
|
@ -120,6 +138,9 @@ class EpisodeFileEditorModalContentConnector extends Component {
|
||||||
this.props.dispatchUpdateEpisodeFiles({ episodeFileIds, quality });
|
this.props.dispatchUpdateEpisodeFiles({ episodeFileIds, quality });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
dispatchFetchLanguageProfileSchema,
|
dispatchFetchLanguageProfileSchema,
|
||||||
|
|
|
@ -159,6 +159,7 @@ class SeriesImage extends Component {
|
||||||
style={style}
|
style={style}
|
||||||
src={url}
|
src={url}
|
||||||
onError={this.onError}
|
onError={this.onError}
|
||||||
|
onLoad={this.onLoad}
|
||||||
/>
|
/>
|
||||||
</LazyLoad>
|
</LazyLoad>
|
||||||
);
|
);
|
||||||
|
|
|
@ -31,7 +31,7 @@ class AddDownloadClientModalContent extends Component {
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Add DownloadClient
|
Add Download Client
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
|
|
|
@ -13,6 +13,9 @@ import FormLabel from 'Components/Form/FormLabel';
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
import styles from './EditReleaseProfileModalContent.css';
|
import styles from './EditReleaseProfileModalContent.css';
|
||||||
|
|
||||||
|
// Tab, enter, and comma
|
||||||
|
const tagInputDelimiters = [9, 13, 188];
|
||||||
|
|
||||||
function EditReleaseProfileModalContent(props) {
|
function EditReleaseProfileModalContent(props) {
|
||||||
const {
|
const {
|
||||||
isSaving,
|
isSaving,
|
||||||
|
@ -51,6 +54,7 @@ function EditReleaseProfileModalContent(props) {
|
||||||
helpText="The release must contain at least one of these terms (case insensitive)"
|
helpText="The release must contain at least one of these terms (case insensitive)"
|
||||||
kind={kinds.SUCCESS}
|
kind={kinds.SUCCESS}
|
||||||
placeholder="Add new restriction"
|
placeholder="Add new restriction"
|
||||||
|
delimiters={tagInputDelimiters}
|
||||||
{...required}
|
{...required}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
|
@ -65,6 +69,7 @@ function EditReleaseProfileModalContent(props) {
|
||||||
helpText="The release will be rejected if it contains one or more of terms (case insensitive)"
|
helpText="The release will be rejected if it contains one or more of terms (case insensitive)"
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
placeholder="Add new restriction"
|
placeholder="Add new restriction"
|
||||||
|
delimiters={tagInputDelimiters}
|
||||||
{...ignored}
|
{...ignored}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -265,13 +265,13 @@ class Missing extends Component {
|
||||||
onConfirm={this.onSearchAllMissingConfirmed}
|
onConfirm={this.onSearchAllMissingConfirmed}
|
||||||
onCancel={this.onConfirmSearchAllMissingModalClose}
|
onCancel={this.onConfirmSearchAllMissingModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<InteractiveImportModal
|
|
||||||
isOpen={isInteractiveImportModalOpen}
|
|
||||||
onModalClose={this.onInteractiveImportModalClose}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<InteractiveImportModal
|
||||||
|
isOpen={isInteractiveImportModalOpen}
|
||||||
|
onModalClose={this.onInteractiveImportModalClose}
|
||||||
|
/>
|
||||||
</PageContentBodyConnector>
|
</PageContentBodyConnector>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue