mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
New: Show indexer categories in Add Indexer modal
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
import AddIndexerModalContentConnector from './AddIndexerModalContentConnector';
|
import AddIndexerModalContentConnector from './AddIndexerModalContentConnector';
|
||||||
import styles from './AddIndexerModal.css';
|
import styles from './AddIndexerModal.css';
|
||||||
|
|
||||||
@@ -8,6 +9,7 @@ function AddIndexerModal({ isOpen, onModalClose, onSelectIndexer, ...otherProps
|
|||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
|
size={sizes.EXTRA_LARGE}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
className={styles.modal}
|
className={styles.modal}
|
||||||
>
|
>
|
||||||
|
@@ -16,7 +16,7 @@ import TableBody from 'Components/Table/TableBody';
|
|||||||
import { kinds, scrollDirections } from 'Helpers/Props';
|
import { kinds, scrollDirections } from 'Helpers/Props';
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import SelectIndexerRowConnector from './SelectIndexerRowConnector';
|
import SelectIndexerRow from './SelectIndexerRow';
|
||||||
import styles from './AddIndexerModalContent.css';
|
import styles from './AddIndexerModalContent.css';
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@@ -49,6 +49,12 @@ const columns = [
|
|||||||
label: () => translate('Privacy'),
|
label: () => translate('Privacy'),
|
||||||
isSortable: true,
|
isSortable: true,
|
||||||
isVisible: true
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'categories',
|
||||||
|
label: () => translate('Categories'),
|
||||||
|
isSortable: false,
|
||||||
|
isVisible: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -260,7 +266,7 @@ class AddIndexerModalContent extends Component {
|
|||||||
<TableBody>
|
<TableBody>
|
||||||
{
|
{
|
||||||
filteredIndexers.map((indexer) => (
|
filteredIndexers.map((indexer) => (
|
||||||
<SelectIndexerRowConnector
|
<SelectIndexerRow
|
||||||
key={`${indexer.implementation}-${indexer.name}`}
|
key={`${indexer.implementation}-${indexer.name}`}
|
||||||
implementation={indexer.implementation}
|
implementation={indexer.implementation}
|
||||||
implementationName={indexer.implementationName}
|
implementationName={indexer.implementationName}
|
||||||
|
@@ -1,15 +1,18 @@
|
|||||||
|
import { some } from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { fetchIndexerSchema, selectIndexerSchema, setIndexerSchemaSort } from 'Store/Actions/indexerActions';
|
import { fetchIndexerSchema, selectIndexerSchema, setIndexerSchemaSort } from 'Store/Actions/indexerActions';
|
||||||
|
import createAllIndexersSelector from 'Store/Selectors/createAllIndexersSelector';
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import AddIndexerModalContent from './AddIndexerModalContent';
|
import AddIndexerModalContent from './AddIndexerModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createClientSideCollectionSelector('indexers.schema'),
|
createClientSideCollectionSelector('indexers.schema'),
|
||||||
(indexers) => {
|
createAllIndexersSelector(),
|
||||||
|
(indexers, allIndexers) => {
|
||||||
const {
|
const {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
@@ -19,11 +22,19 @@ function createMapStateToProps() {
|
|||||||
sortKey
|
sortKey
|
||||||
} = indexers;
|
} = indexers;
|
||||||
|
|
||||||
|
const indexerList = items.map((item) => {
|
||||||
|
const { definitionName } = item;
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
isExistingIndexer: some(allIndexers, { definitionName })
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
error,
|
error,
|
||||||
indexers: items,
|
indexers: indexerList,
|
||||||
sortKey,
|
sortKey,
|
||||||
sortDirection
|
sortDirection
|
||||||
};
|
};
|
||||||
|
@@ -1,90 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|
||||||
import TableRowButton from 'Components/Table/TableRowButton';
|
|
||||||
import { icons } from 'Helpers/Props';
|
|
||||||
import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
|
|
||||||
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './SelectIndexerRow.css';
|
|
||||||
|
|
||||||
class SelectIndexerRow extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onPress = () => {
|
|
||||||
const {
|
|
||||||
implementation,
|
|
||||||
implementationName,
|
|
||||||
name
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
this.props.onIndexerSelect({ implementation, implementationName, name });
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
protocol,
|
|
||||||
privacy,
|
|
||||||
name,
|
|
||||||
language,
|
|
||||||
description,
|
|
||||||
isExistingIndexer
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRowButton onPress={this.onPress}>
|
|
||||||
<TableRowCell className={styles.protocol}>
|
|
||||||
<ProtocolLabel
|
|
||||||
protocol={protocol}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
{name}
|
|
||||||
{
|
|
||||||
isExistingIndexer ?
|
|
||||||
<Icon
|
|
||||||
className={styles.alreadyExistsIcon}
|
|
||||||
name={icons.CHECK_CIRCLE}
|
|
||||||
size={15}
|
|
||||||
title={translate('IndexerAlreadySetup')}
|
|
||||||
/> :
|
|
||||||
null
|
|
||||||
}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
{language}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
{description}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell>
|
|
||||||
{translate(firstCharToUpper(privacy))}
|
|
||||||
</TableRowCell>
|
|
||||||
</TableRowButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SelectIndexerRow.propTypes = {
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
protocol: PropTypes.string.isRequired,
|
|
||||||
privacy: PropTypes.string.isRequired,
|
|
||||||
language: PropTypes.string.isRequired,
|
|
||||||
description: PropTypes.string.isRequired,
|
|
||||||
implementation: PropTypes.string.isRequired,
|
|
||||||
implementationName: PropTypes.string.isRequired,
|
|
||||||
onIndexerSelect: PropTypes.func.isRequired,
|
|
||||||
isExistingIndexer: PropTypes.bool.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SelectIndexerRow;
|
|
75
frontend/src/Indexer/Add/SelectIndexerRow.tsx
Normal file
75
frontend/src/Indexer/Add/SelectIndexerRow.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
|
import TableRowButton from 'Components/Table/TableRowButton';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import CapabilitiesLabel from 'Indexer/Index/Table/CapabilitiesLabel';
|
||||||
|
import ProtocolLabel from 'Indexer/Index/Table/ProtocolLabel';
|
||||||
|
import { IndexerCapabilities } from 'Indexer/Indexer';
|
||||||
|
import firstCharToUpper from 'Utilities/String/firstCharToUpper';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './SelectIndexerRow.css';
|
||||||
|
|
||||||
|
interface SelectIndexerRowProps {
|
||||||
|
name: string;
|
||||||
|
protocol: string;
|
||||||
|
privacy: string;
|
||||||
|
language: string;
|
||||||
|
description: string;
|
||||||
|
capabilities: IndexerCapabilities;
|
||||||
|
implementation: string;
|
||||||
|
implementationName: string;
|
||||||
|
isExistingIndexer: boolean;
|
||||||
|
onIndexerSelect(...args: unknown[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectIndexerRow(props: SelectIndexerRowProps) {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
protocol,
|
||||||
|
privacy,
|
||||||
|
language,
|
||||||
|
description,
|
||||||
|
capabilities,
|
||||||
|
implementation,
|
||||||
|
implementationName,
|
||||||
|
isExistingIndexer,
|
||||||
|
onIndexerSelect,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const onPress = useCallback(() => {
|
||||||
|
onIndexerSelect({ implementation, implementationName, name });
|
||||||
|
}, [implementation, implementationName, name, onIndexerSelect]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRowButton onPress={onPress}>
|
||||||
|
<TableRowCell className={styles.protocol}>
|
||||||
|
<ProtocolLabel protocol={protocol} />
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
{name}
|
||||||
|
{isExistingIndexer ? (
|
||||||
|
<Icon
|
||||||
|
className={styles.alreadyExistsIcon}
|
||||||
|
name={icons.CHECK_CIRCLE}
|
||||||
|
size={15}
|
||||||
|
title={translate('IndexerAlreadySetup')}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>{language}</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>{description}</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>{translate(firstCharToUpper(privacy))}</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell>
|
||||||
|
<CapabilitiesLabel capabilities={capabilities} />
|
||||||
|
</TableRowCell>
|
||||||
|
</TableRowButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectIndexerRow;
|
@@ -1,18 +0,0 @@
|
|||||||
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import createExistingIndexerSelector from 'Store/Selectors/createExistingIndexerSelector';
|
|
||||||
import SelectIndexerRow from './SelectIndexerRow';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
createExistingIndexerSelector(),
|
|
||||||
(isExistingIndexer, dimensions) => {
|
|
||||||
return {
|
|
||||||
isExistingIndexer
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps)(SelectIndexerRow);
|
|
@@ -1,3 +1,4 @@
|
|||||||
|
import { uniqBy } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import { IndexerCapabilities } from 'Indexer/Indexer';
|
import { IndexerCapabilities } from 'Indexer/Indexer';
|
||||||
@@ -23,14 +24,18 @@ function CapabilitiesLabel(props: CapabilitiesLabelProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const nameList = Array.from(
|
const indexerCategories = uniqBy(filteredList, 'id').sort(
|
||||||
new Set(filteredList.map((item) => item.name).sort())
|
(a, b) => a.id - b.id
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{nameList.map((category) => {
|
{indexerCategories.map((category) => {
|
||||||
return <Label key={category}>{category}</Label>;
|
return (
|
||||||
|
<Label key={category.id} title={`${category.id}`}>
|
||||||
|
{category.name}
|
||||||
|
</Label>
|
||||||
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{filteredList.length === 0 ? <Label>{'None'}</Label> : null}
|
{filteredList.length === 0 ? <Label>{'None'}</Label> : null}
|
||||||
|
@@ -1,43 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Label from 'Components/Label';
|
|
||||||
import Tooltip from 'Components/Tooltip/Tooltip';
|
|
||||||
import { kinds, tooltipPositions } from 'Helpers/Props';
|
|
||||||
|
|
||||||
function CategoryLabel({ categories }) {
|
|
||||||
const sortedCategories = categories.filter((cat) => cat.name !== undefined).sort((c) => c.id);
|
|
||||||
|
|
||||||
if (categories?.length === 0) {
|
|
||||||
return (
|
|
||||||
<Tooltip
|
|
||||||
anchor={<Label kind={kinds.DANGER}>Unknown</Label>}
|
|
||||||
tooltip="Please report this issue to the GitHub as this shouldn't be happening"
|
|
||||||
position={tooltipPositions.LEFT}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
{
|
|
||||||
sortedCategories.map((category) => {
|
|
||||||
return (
|
|
||||||
<Label key={category.name}>
|
|
||||||
{category.name}
|
|
||||||
</Label>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
CategoryLabel.defaultProps = {
|
|
||||||
categories: []
|
|
||||||
};
|
|
||||||
|
|
||||||
CategoryLabel.propTypes = {
|
|
||||||
categories: PropTypes.arrayOf(PropTypes.object).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CategoryLabel;
|
|
36
frontend/src/Search/Table/CategoryLabel.tsx
Normal file
36
frontend/src/Search/Table/CategoryLabel.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Label from 'Components/Label';
|
||||||
|
import Tooltip from 'Components/Tooltip/Tooltip';
|
||||||
|
import { kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
|
import { IndexerCategory } from 'Indexer/Indexer';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
|
||||||
|
interface CategoryLabelProps {
|
||||||
|
categories: IndexerCategory[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function CategoryLabel({ categories = [] }: CategoryLabelProps) {
|
||||||
|
if (categories?.length === 0) {
|
||||||
|
return (
|
||||||
|
<Tooltip
|
||||||
|
anchor={<Label kind={kinds.DANGER}>{translate('Unknown')}</Label>}
|
||||||
|
tooltip="Please report this issue to the GitHub as this shouldn't be happening"
|
||||||
|
position={tooltipPositions.LEFT}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedCategories = categories
|
||||||
|
.filter((cat) => cat.name !== undefined)
|
||||||
|
.sort((a, b) => a.id - b.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{sortedCategories.map((category) => {
|
||||||
|
return <Label key={category.id}>{category.name}</Label>;
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CategoryLabel;
|
Reference in New Issue
Block a user