New: Link Indexer name to info modal

#546
pull/1481/head
Qstick 2 years ago
parent ff16043a06
commit 99d315979e

@ -12,7 +12,7 @@ import { icons } from 'Helpers/Props';
import DeleteIndexerModal from 'Indexer/Delete/DeleteIndexerModal';
import EditIndexerModalConnector from 'Indexer/Edit/EditIndexerModalConnector';
import createIndexerIndexItemSelector from 'Indexer/Index/createIndexerIndexItemSelector';
import IndexerInfoModal from 'Indexer/Info/IndexerInfoModal';
import IndexerTitleLink from 'Indexer/IndexerTitleLink';
import titleCase from 'Utilities/String/titleCase';
import translate from 'Utilities/String/translate';
import CapabilitiesLabel from './CapabilitiesLabel';
@ -53,7 +53,6 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
fields.find((field) => field.name === 'baseUrl')?.value ??
(Array.isArray(indexerUrls) ? indexerUrls[0] : undefined);
const [isIndexerInfoModalOpen, setIsIndexerInfoModalOpen] = useState(false);
const [isEditIndexerModalOpen, setIsEditIndexerModalOpen] = useState(false);
const [isDeleteIndexerModalOpen, setIsDeleteIndexerModalOpen] =
useState(false);
@ -67,14 +66,6 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
setIsEditIndexerModalOpen(false);
}, [setIsEditIndexerModalOpen]);
const onIndexerInfoPress = useCallback(() => {
setIsIndexerInfoModalOpen(true);
}, [setIsIndexerInfoModalOpen]);
const onIndexerInfoModalClose = useCallback(() => {
setIsIndexerInfoModalOpen(false);
}, [setIsIndexerInfoModalOpen]);
const onDeleteIndexerPress = useCallback(() => {
setIsEditIndexerModalOpen(false);
setIsDeleteIndexerModalOpen(true);
@ -134,7 +125,10 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
if (name === 'sortName') {
return (
<VirtualTableRowCell key={name} className={styles[name]}>
{indexerName}
<IndexerTitleLink
indexerId={indexerId}
indexerName={indexerName}
/>
</VirtualTableRowCell>
);
}
@ -204,12 +198,6 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
key={column.name}
className={styles[column.name]}
>
<IconButton
name={icons.INFO}
title={translate('IndexerInfo')}
onPress={onIndexerInfoPress}
/>
{baseUrl ? (
<IconButton
className={styles.externalLink}
@ -238,12 +226,6 @@ function IndexerIndexRow(props: IndexerIndexRowProps) {
onDeleteIndexerPress={onDeleteIndexerPress}
/>
<IndexerInfoModal
indexerId={indexerId}
isOpen={isIndexerInfoModalOpen}
onModalClose={onIndexerInfoModalClose}
/>
<DeleteIndexerModal
isOpen={isDeleteIndexerModalOpen}
indexerId={indexerId}

@ -13,6 +13,12 @@ export interface IndexerCategory extends ModelBase {
export interface IndexerCapabilities extends ModelBase {
limitsMax: number;
limitsDefault: number;
supportsRawSearch: boolean;
searchParams: string[];
tvSearchParams: string[];
movieSearchParams: string[];
musicSearchParams: string[];
bookSearchParams: string[];
categories: IndexerCategory[];
}
@ -26,6 +32,9 @@ export interface IndexerField extends ModelBase {
interface Indexer extends ModelBase {
name: string;
description: string;
encoding: string;
language: string;
added: Date;
enable: boolean;
redirect: boolean;

@ -0,0 +1,8 @@
.link {
composes: link from '~Components/Link/Link.css';
&:hover {
color: var(--linkHoverColor);
text-decoration: underline;
}
}

@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'link': string;
}
export const cssExports: CssExports;
export default cssExports;

@ -0,0 +1,44 @@
import PropTypes from 'prop-types';
import React, { useCallback, useState } from 'react';
import Link from 'Components/Link/Link';
import IndexerInfoModal from './Info/IndexerInfoModal';
import styles from './IndexerTitleLink.css';
interface IndexerTitleLinkProps {
indexerName: string;
indexerId: number;
}
function IndexerTitleLink(props: IndexerTitleLinkProps) {
const { indexerName, indexerId } = props;
const [isIndexerInfoModalOpen, setIsIndexerInfoModalOpen] = useState(false);
const onIndexerInfoPress = useCallback(() => {
setIsIndexerInfoModalOpen(true);
}, [setIsIndexerInfoModalOpen]);
const onIndexerInfoModalClose = useCallback(() => {
setIsIndexerInfoModalOpen(false);
}, [setIsIndexerInfoModalOpen]);
return (
<div>
<Link className={styles.link} onPress={onIndexerInfoPress}>
{indexerName}
</Link>
<IndexerInfoModal
indexerId={indexerId}
isOpen={isIndexerInfoModalOpen}
onModalClose={onIndexerInfoModalClose}
/>
</div>
);
}
IndexerTitleLink.propTypes = {
indexerName: PropTypes.string.isRequired,
};
export default IndexerTitleLink;

@ -1,27 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import IndexerInfoModalContentConnector from './IndexerInfoModalContentConnector';
function IndexerInfoModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
size={sizes.MEDIUM}
isOpen={isOpen}
onModalClose={onModalClose}
>
<IndexerInfoModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
</Modal>
);
}
IndexerInfoModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default IndexerInfoModal;

@ -0,0 +1,25 @@
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import IndexerInfoModalContent from './IndexerInfoModalContent';
interface IndexerInfoModalProps {
isOpen: boolean;
indexerId: number;
onModalClose(): void;
}
function IndexerInfoModal(props: IndexerInfoModalProps) {
const { isOpen, onModalClose, indexerId } = props;
return (
<Modal size={sizes.MEDIUM} isOpen={isOpen} onModalClose={onModalClose}>
<IndexerInfoModalContent
indexerId={indexerId}
onModalClose={onModalClose}
/>
</Modal>
);
}
export default IndexerInfoModal;

@ -1,126 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
import FieldSet from 'Components/FieldSet';
import Link from 'Components/Link/Link';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import translate from 'Utilities/String/translate';
import styles from './IndexerInfoModalContent.css';
function IndexerInfoModalContent(props) {
const {
id,
name,
description,
encoding,
language,
indexerUrls,
fields,
protocol,
capabilities,
onModalClose
} = props;
const baseUrl = fields.find((field) => field.name === 'baseUrl')?.value ?? indexerUrls[0];
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{`${name}`}
</ModalHeader>
<ModalBody>
<FieldSet legend={translate('IndexerDetails')}>
<div className={styles.groups}>
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Id')}
data={id}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Description')}
data={description ? description : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Encoding')}
data={encoding ? encoding : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Language')}
data={language ?? '-'}
/>
<DescriptionListItemTitle>{translate('IndexerSite')}</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to={baseUrl}>{baseUrl}</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{`${protocol === 'usenet' ? 'Newznab' : 'Torznab'} Url`}</DescriptionListItemTitle>
<DescriptionListItemDescription>
{`${window.location.origin}${window.Prowlarr.urlBase}/${id}/api`}
</DescriptionListItemDescription>
</DescriptionList>
</div>
</FieldSet>
<FieldSet legend={translate('SearchCapabilities')}>
<div className={styles.groups}>
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('RawSearchSupported')}
data={capabilities.supportsRawSearch ? translate('Yes') : translate('No')}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('SearchTypes')}
data={capabilities.searchParams.length === 0 ? translate('NotSupported') : capabilities.searchParams[0]}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('TVSearchTypes')}
data={capabilities.tvSearchParams.length === 0 ? translate('NotSupported') : capabilities.tvSearchParams.join(', ')}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MovieSearchTypes')}
data={capabilities.movieSearchParams.length === 0 ? translate('NotSupported') : capabilities.movieSearchParams.join(', ')}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('BookSearchTypes')}
data={capabilities.bookSearchParams.length === 0 ? translate('NotSupported') : capabilities.bookSearchParams.join(', ')}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MusicSearchTypes')}
data={capabilities.musicSearchParams.length === 0 ? translate('NotSupported') : capabilities.musicSearchParams.join(', ')}
/>
</DescriptionList>
</div>
</FieldSet>
</ModalBody>
</ModalContent >
);
}
IndexerInfoModalContent.propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
encoding: PropTypes.string.isRequired,
language: PropTypes.string.isRequired,
indexerUrls: PropTypes.arrayOf(PropTypes.string).isRequired,
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
protocol: PropTypes.string.isRequired,
capabilities: PropTypes.object.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default IndexerInfoModalContent;

@ -0,0 +1,200 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
import FieldSet from 'Components/FieldSet';
import Label from 'Components/Label';
import Button from 'Components/Link/Button';
import Link from 'Components/Link/Link';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import Indexer from 'Indexer/Indexer';
import createIndexerSelector from 'Store/Selectors/createIndexerSelector';
import translate from 'Utilities/String/translate';
import styles from './IndexerInfoModalContent.css';
function createIndexerInfoItemSelector(indexerId: number) {
return createSelector(
createIndexerSelector(indexerId),
(indexer: Indexer) => {
return {
indexer,
};
}
);
}
interface IndexerInfoModalContentProps {
indexerId: number;
onModalClose(): void;
}
function IndexerInfoModalContent(props: IndexerInfoModalContentProps) {
const { indexer } = useSelector(
createIndexerInfoItemSelector(props.indexerId)
);
const {
id,
name,
description,
encoding,
language,
indexerUrls,
fields,
protocol,
capabilities,
} = indexer;
const { onModalClose } = props;
const baseUrl =
fields.find((field) => field.name === 'baseUrl')?.value ??
(Array.isArray(indexerUrls) ? indexerUrls[0] : undefined);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>{`${name}`}</ModalHeader>
<ModalBody>
<FieldSet legend={translate('IndexerDetails')}>
<div>
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Id')}
data={id}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Description')}
data={description ? description : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Encoding')}
data={encoding ? encoding : '-'}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('Language')}
data={language ?? '-'}
/>
<DescriptionListItemTitle>
{translate('IndexerSite')}
</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to={baseUrl}>{baseUrl.replace('api.', '')}</Link>
</DescriptionListItemDescription>
<DescriptionListItemTitle>{`${
protocol === 'usenet' ? 'Newznab' : 'Torznab'
} Url`}</DescriptionListItemTitle>
<DescriptionListItemDescription>
{`${window.location.origin}${window.Prowlarr.urlBase}/${id}/api`}
</DescriptionListItemDescription>
</DescriptionList>
</div>
</FieldSet>
<FieldSet legend={translate('SearchCapabilities')}>
<div>
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('RawSearchSupported')}
data={
capabilities.supportsRawSearch
? translate('Yes')
: translate('No')
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('SearchTypes')}
data={
capabilities.searchParams.length === 0 ? (
translate('NotSupported')
) : (
<Label kind={kinds.PRIMARY}>
{capabilities.searchParams[0]}
</Label>
)
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('TVSearchTypes')}
data={
capabilities.tvSearchParams.length === 0
? translate('NotSupported')
: capabilities.tvSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MovieSearchTypes')}
data={
capabilities.movieSearchParams.length === 0
? translate('NotSupported')
: capabilities.movieSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('BookSearchTypes')}
data={
capabilities.bookSearchParams.length === 0
? translate('NotSupported')
: capabilities.bookSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
}
/>
<DescriptionListItem
descriptionClassName={styles.description}
title={translate('MusicSearchTypes')}
data={
capabilities.musicSearchParams.length === 0
? translate('NotSupported')
: capabilities.musicSearchParams.map((p) => {
return (
<Label key={p} kind={kinds.PRIMARY}>
{p}
</Label>
);
})
}
/>
</DescriptionList>
</div>
</FieldSet>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>{translate('Close')}</Button>
</ModalFooter>
</ModalContent>
);
}
export default IndexerInfoModalContent;

@ -1,40 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createIndexerSelector from 'Store/Selectors/createIndexerSelector';
import IndexerInfoModalContent from './IndexerInfoModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.advancedSettings,
createIndexerSelector(),
(advancedSettings, indexer) => {
return {
advancedSettings,
...indexer
};
}
);
}
class IndexerInfoModalContentConnector extends Component {
//
// Render
render() {
return (
<IndexerInfoModalContent
{...this.props}
/>
);
}
}
IndexerInfoModalContentConnector.propTypes = {
indexerId: PropTypes.number,
onModalClose: PropTypes.func.isRequired
};
export default connect(createMapStateToProps)(IndexerInfoModalContentConnector);
Loading…
Cancel
Save