mirror of
https://github.com/Prowlarr/Prowlarr.git
synced 2025-09-17 17:14:18 +02:00
Search and History Improvements
This commit is contained in:
@@ -46,7 +46,7 @@ class IndexersSelectInputConnector extends Component {
|
||||
|
||||
IndexersSelectInputConnector.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
indexerIds: PropTypes.number.isRequired,
|
||||
indexerIds: PropTypes.number,
|
||||
value: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
values: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
|
@@ -51,6 +51,12 @@
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.actionMenu {
|
||||
&:hover {
|
||||
color: #515253;
|
||||
}
|
||||
}
|
||||
|
||||
.translate {
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
@@ -63,7 +69,7 @@
|
||||
line-height: 60px;
|
||||
|
||||
&:hover {
|
||||
color: $toobarButtonHoverColor;
|
||||
color: #515253;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -158,8 +158,7 @@ MovieEditorFooter.propTypes = {
|
||||
saveError: PropTypes.object,
|
||||
isDeleting: PropTypes.bool.isRequired,
|
||||
deleteError: PropTypes.object,
|
||||
onSaveSelected: PropTypes.func.isRequired,
|
||||
onOrganizeMoviePress: PropTypes.func.isRequired
|
||||
onSaveSelected: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MovieEditorFooter;
|
||||
|
@@ -1,11 +0,0 @@
|
||||
.actions {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
.sourceTitle {
|
||||
composes: cell from '~Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
word-break: break-word;
|
||||
}
|
@@ -1,162 +0,0 @@
|
||||
import HistoryDetailsModal from 'Activity/History/Details/HistoryDetailsModal';
|
||||
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import MovieLanguage from 'Indexer/MovieLanguage';
|
||||
import MovieQuality from 'Indexer/MovieQuality';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './MovieHistoryRow.css';
|
||||
|
||||
class MovieHistoryRow extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isMarkAsFailedModalOpen: false,
|
||||
isDetailsModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onMarkAsFailedPress = () => {
|
||||
this.setState({ isMarkAsFailedModalOpen: true });
|
||||
}
|
||||
|
||||
onConfirmMarkAsFailed = () => {
|
||||
this.props.onMarkAsFailedPress(this.props.id);
|
||||
this.setState({ isMarkAsFailedModalOpen: false });
|
||||
}
|
||||
|
||||
onMarkAsFailedModalClose = () => {
|
||||
this.setState({ isMarkAsFailedModalOpen: false });
|
||||
}
|
||||
|
||||
onDetailsPress = () => {
|
||||
this.setState({ isDetailsModalOpen: true });
|
||||
}
|
||||
|
||||
onDetailsModalClose = () => {
|
||||
this.setState({ isDetailsModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
eventType,
|
||||
sourceTitle,
|
||||
quality,
|
||||
languages,
|
||||
qualityCutoffNotMet,
|
||||
date,
|
||||
data,
|
||||
isMarkingAsFailed,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
onMarkAsFailedPress
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isMarkAsFailedModalOpen
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<HistoryEventTypeCell
|
||||
eventType={eventType}
|
||||
data={data}
|
||||
/>
|
||||
|
||||
<TableRowCell className={styles.sourceTitle}>
|
||||
{sourceTitle}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
<MovieLanguage
|
||||
languages={languages}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
<MovieQuality
|
||||
quality={quality}
|
||||
isCutoffNotMet={qualityCutoffNotMet}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<RelativeDateCellConnector
|
||||
date={date}
|
||||
/>
|
||||
|
||||
<TableRowCell className={styles.actions}>
|
||||
<IconButton
|
||||
name={icons.INFO}
|
||||
onPress={this.onDetailsPress}
|
||||
/>
|
||||
|
||||
{
|
||||
eventType === 'grabbed' &&
|
||||
<IconButton
|
||||
title={translate('MarkAsFailed')}
|
||||
name={icons.REMOVE}
|
||||
onPress={this.onMarkAsFailedPress}
|
||||
/>
|
||||
}
|
||||
</TableRowCell>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isMarkAsFailedModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('MarkAsFailed')}
|
||||
message={translate('MarkAsFailedMessageText', [sourceTitle])}
|
||||
confirmLabel={translate('MarkAsFailed')}
|
||||
onConfirm={this.onConfirmMarkAsFailed}
|
||||
onCancel={this.onMarkAsFailedModalClose}
|
||||
/>
|
||||
|
||||
<HistoryDetailsModal
|
||||
isOpen={this.state.isDetailsModalOpen}
|
||||
eventType={eventType}
|
||||
sourceTitle={sourceTitle}
|
||||
data={data}
|
||||
isMarkingAsFailed={isMarkingAsFailed}
|
||||
shortDateFormat={shortDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
||||
onModalClose={this.onDetailsModalClose}
|
||||
/>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieHistoryRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
eventType: PropTypes.string.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
languages: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
quality: PropTypes.object.isRequired,
|
||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||
date: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
isMarkingAsFailed: PropTypes.bool,
|
||||
movie: PropTypes.object.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MovieHistoryRow;
|
@@ -1,27 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchHistory, markAsFailed } from 'Store/Actions/historyActions';
|
||||
import createIndexerSelector from 'Store/Selectors/createIndexerSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import MovieHistoryRow from './MovieHistoryRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createIndexerSelector(),
|
||||
createUISettingsSelector(),
|
||||
(movie, uiSettings) => {
|
||||
return {
|
||||
movie,
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
timeFormat: uiSettings.timeFormat
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchHistory,
|
||||
markAsFailed
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieHistoryRow);
|
@@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
import MovieHistoryTableContentConnector from './MovieHistoryTableContentConnector';
|
||||
|
||||
function MovieHistoryTable(props) {
|
||||
const {
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<MovieHistoryTableContentConnector
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
MovieHistoryTable.propTypes = {
|
||||
};
|
||||
|
||||
export default MovieHistoryTable;
|
@@ -1,5 +0,0 @@
|
||||
.blankpad {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
padding-left: 2em;
|
||||
}
|
@@ -1,114 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import MovieHistoryRowConnector from './MovieHistoryRowConnector';
|
||||
import styles from './MovieHistoryTableContent.css';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'eventType',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'sourceTitle',
|
||||
label: translate('SourceTitle'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'languages',
|
||||
label: translate('Languages'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: translate('Quality'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'customFormats',
|
||||
label: translate('CustomFormats'),
|
||||
isSortable: false,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
label: translate('Date'),
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
label: React.createElement(IconButton, { name: icons.ADVANCED_SETTINGS }),
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
class MovieHistoryTableContent extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
onMarkAsFailedPress
|
||||
} = this.props;
|
||||
|
||||
const hasItems = !!items.length;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div className={styles.blankpad}>Unable to load history</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !hasItems && !error &&
|
||||
<div className={styles.blankpad}>No history</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && hasItems && !error &&
|
||||
<Table columns={columns}>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<MovieHistoryRowConnector
|
||||
key={item.id}
|
||||
{...item}
|
||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieHistoryTableContent.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MovieHistoryTableContent;
|
@@ -1,55 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { movieHistoryMarkAsFailed } from 'Store/Actions/movieHistoryActions';
|
||||
import MovieHistoryTableContent from './MovieHistoryTableContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.movieHistory,
|
||||
(movieHistory) => {
|
||||
return movieHistory;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
movieHistoryMarkAsFailed
|
||||
};
|
||||
|
||||
class MovieHistoryTableContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onMarkAsFailedPress = (historyId) => {
|
||||
const {
|
||||
movieId
|
||||
} = this.props;
|
||||
|
||||
this.props.movieHistoryMarkAsFailed({
|
||||
historyId,
|
||||
movieId
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MovieHistoryTableContent
|
||||
{...this.props}
|
||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieHistoryTableContentConnector.propTypes = {
|
||||
movieId: PropTypes.number.isRequired,
|
||||
movieHistoryMarkAsFailed: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieHistoryTableContentConnector);
|
@@ -15,11 +15,11 @@ function createMapStateToProps() {
|
||||
createIndexerClientSideCollectionItemsSelector('indexerIndex'),
|
||||
createDimensionsSelector(),
|
||||
(
|
||||
movies,
|
||||
indexers,
|
||||
dimensionsState
|
||||
) => {
|
||||
return {
|
||||
...movies,
|
||||
...indexers,
|
||||
isSmallScreen: dimensionsState.isSmallScreen
|
||||
};
|
||||
}
|
||||
|
@@ -30,7 +30,7 @@ function SearchIndexSortMenu(props) {
|
||||
</SortMenuItem>
|
||||
|
||||
<SortMenuItem
|
||||
name="sortTitle"
|
||||
name="title"
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onPress={onSortSelect}
|
||||
|
@@ -4,6 +4,7 @@ import IndexersSelectInputConnector from 'Components/Form/IndexersSelectInputCon
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||
import SearchFooterLabel from './SearchFooterLabel';
|
||||
import styles from './SearchFooter.css';
|
||||
|
||||
class SearchFooter extends Component {
|
||||
@@ -61,9 +62,13 @@ class SearchFooter extends Component {
|
||||
return (
|
||||
<PageContentFooter>
|
||||
<div className={styles.inputContainer}>
|
||||
<SearchFooterLabel
|
||||
label={'Query'}
|
||||
isSaving={false}
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
name='searchQuery'
|
||||
placeholder='Query'
|
||||
value={searchQuery}
|
||||
isDisabled={isFetching}
|
||||
onChange={this.onInputChange}
|
||||
@@ -71,6 +76,11 @@ class SearchFooter extends Component {
|
||||
</div>
|
||||
|
||||
<div className={styles.indexerContainer}>
|
||||
<SearchFooterLabel
|
||||
label={'Indexers'}
|
||||
isSaving={false}
|
||||
/>
|
||||
|
||||
<IndexersSelectInputConnector
|
||||
name='indexerIds'
|
||||
placeholder='Indexers'
|
||||
|
8
frontend/src/Search/SearchFooterLabel.css
Normal file
8
frontend/src/Search/SearchFooterLabel.css
Normal file
@@ -0,0 +1,8 @@
|
||||
.label {
|
||||
margin-bottom: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.savingIcon {
|
||||
margin-left: 8px;
|
||||
}
|
40
frontend/src/Search/SearchFooterLabel.js
Normal file
40
frontend/src/Search/SearchFooterLabel.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import SpinnerIcon from 'Components/SpinnerIcon';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import styles from './SearchFooterLabel.css';
|
||||
|
||||
function SearchFooterLabel(props) {
|
||||
const {
|
||||
className,
|
||||
label,
|
||||
isSaving
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{label}
|
||||
|
||||
{
|
||||
isSaving &&
|
||||
<SpinnerIcon
|
||||
className={styles.savingIcon}
|
||||
name={icons.SPINNER}
|
||||
isSpinning={true}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SearchFooterLabel.propTypes = {
|
||||
className: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
SearchFooterLabel.defaultProps = {
|
||||
className: styles.label
|
||||
};
|
||||
|
||||
export default SearchFooterLabel;
|
@@ -11,6 +11,7 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
||||
import NoIndexer from 'Indexer/NoIndexer';
|
||||
import * as keyCodes from 'Utilities/Constants/keyCodes';
|
||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||
@@ -83,7 +84,7 @@ class SearchIndex extends Component {
|
||||
} = this.props;
|
||||
|
||||
// Reset if not sorting by sortTitle
|
||||
if (sortKey !== 'sortTitle') {
|
||||
if (sortKey !== 'title') {
|
||||
this.setState({ jumpBarItems: { order: [] } });
|
||||
return;
|
||||
}
|
||||
@@ -161,6 +162,7 @@ class SearchIndex extends Component {
|
||||
onScroll,
|
||||
onSortSelect,
|
||||
onFilterSelect,
|
||||
hasIndexers,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
@@ -174,8 +176,6 @@ class SearchIndex extends Component {
|
||||
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
||||
const hasNoIndexer = !totalItems;
|
||||
|
||||
console.log(hasNoIndexer);
|
||||
|
||||
return (
|
||||
<PageContent>
|
||||
<PageToolbar>
|
||||
@@ -247,7 +247,12 @@ class SearchIndex extends Component {
|
||||
}
|
||||
|
||||
{
|
||||
!error && !isFetching && !items.length &&
|
||||
!error && !isFetching && !hasIndexers &&
|
||||
<NoIndexer />
|
||||
}
|
||||
|
||||
{
|
||||
!error && !isFetching && hasIndexers && !items.length &&
|
||||
<NoSearchResults totalItems={totalItems} />
|
||||
}
|
||||
</PageContentBody>
|
||||
@@ -286,7 +291,8 @@ SearchIndex.propTypes = {
|
||||
onSortSelect: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
onSearchPress: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired
|
||||
onScroll: PropTypes.func.isRequired,
|
||||
hasIndexers: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SearchIndex;
|
||||
|
@@ -6,19 +6,23 @@ import withScrollPosition from 'Components/withScrollPosition';
|
||||
import { fetchReleases, setReleasesFilter, setReleasesSort, setReleasesTableOption } from 'Store/Actions/releaseActions';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createIndexerClientSideCollectionItemsSelector from 'Store/Selectors/createIndexerClientSideCollectionItemsSelector';
|
||||
import createReleaseClientSideCollectionItemsSelector from 'Store/Selectors/createReleaseClientSideCollectionItemsSelector';
|
||||
import SearchIndex from './SearchIndex';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createIndexerClientSideCollectionItemsSelector('indexerIndex'),
|
||||
createReleaseClientSideCollectionItemsSelector('releases'),
|
||||
createDimensionsSelector(),
|
||||
(
|
||||
movies,
|
||||
indexers,
|
||||
releases,
|
||||
dimensionsState
|
||||
) => {
|
||||
return {
|
||||
...movies,
|
||||
...releases,
|
||||
hasIndexers: indexers.items.length > 0,
|
||||
isSmallScreen: dimensionsState.isSmallScreen
|
||||
};
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.status {
|
||||
.protocol {
|
||||
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
|
||||
|
||||
flex: 0 0 60px;
|
||||
|
@@ -5,7 +5,7 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status {
|
||||
.protocol {
|
||||
composes: cell;
|
||||
|
||||
flex: 0 0 60px;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { setMovieSort } from 'Store/Actions/indexerIndexActions';
|
||||
import { setReleasesSort } from 'Store/Actions/releaseActions';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import SearchIndexTable from './SearchIndexTable';
|
||||
|
||||
@@ -23,7 +23,7 @@ function createMapStateToProps() {
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onSortPress(sortKey) {
|
||||
dispatch(setMovieSort({ sortKey }));
|
||||
dispatch(setReleasesSort({ sortKey }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -27,6 +27,7 @@ class AddIndexerItem extends Component {
|
||||
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
implementation,
|
||||
implementationName,
|
||||
infoLink,
|
||||
@@ -47,7 +48,7 @@ class AddIndexerItem extends Component {
|
||||
|
||||
<div className={styles.overlay}>
|
||||
<div className={styles.name}>
|
||||
{implementationName}
|
||||
{name}
|
||||
</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
@@ -101,6 +102,7 @@ class AddIndexerItem extends Component {
|
||||
}
|
||||
|
||||
AddIndexerItem.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
implementation: PropTypes.string.isRequired,
|
||||
implementationName: PropTypes.string.isRequired,
|
||||
infoLink: PropTypes.string.isRequired,
|
||||
|
@@ -88,33 +88,6 @@ class UISettings extends Component {
|
||||
id="uiSettings"
|
||||
{...otherProps}
|
||||
>
|
||||
<FieldSet legend={translate('Calendar')}>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SettingsFirstDayOfWeek')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="firstDayOfWeek"
|
||||
values={firstDayOfWeekOptions}
|
||||
onChange={onInputChange}
|
||||
{...settings.firstDayOfWeek}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SettingsWeekColumnHeader')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="calendarWeekColumnHeader"
|
||||
values={weekColumnOptions}
|
||||
onChange={onInputChange}
|
||||
helpText={translate('SettingsWeekColumnHeaderHelpText')}
|
||||
{...settings.calendarWeekColumnHeader}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend={translate('Dates')}>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('SettingsShortDateFormat')}</FormLabel>
|
||||
|
@@ -60,25 +60,25 @@ export const defaultState = {
|
||||
name: 'protocol',
|
||||
label: translate('Protocol'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'privacy',
|
||||
label: translate('Privacy'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'added',
|
||||
label: translate('Added'),
|
||||
isSortable: true,
|
||||
isVisible: false
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'capabilities',
|
||||
label: 'Capabilities',
|
||||
label: 'Categories',
|
||||
isSortable: false,
|
||||
isVisible: false
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
|
@@ -26,7 +26,7 @@ export const defaultState = {
|
||||
isPopulated: false,
|
||||
error: null,
|
||||
items: [],
|
||||
sortKey: 'releaseWeight',
|
||||
sortKey: 'title',
|
||||
sortDirection: sortDirections.ASCENDING,
|
||||
|
||||
columns: [
|
||||
|
@@ -113,8 +113,8 @@ module.exports = {
|
||||
//
|
||||
// Toolbar
|
||||
|
||||
toobarButtonHoverColor: '#ffc230',
|
||||
toobarButtonSelectedColor: '#ffc230',
|
||||
toobarButtonHoverColor: '#e66000',
|
||||
toobarButtonSelectedColor: '#e66000',
|
||||
|
||||
//
|
||||
// Scroller
|
||||
|
@@ -17,7 +17,7 @@ class MoreInfo extends Component {
|
||||
<DescriptionList>
|
||||
<DescriptionListItemTitle>Home page</DescriptionListItemTitle>
|
||||
<DescriptionListItemDescription>
|
||||
<Link to="https://prowlarr.video/">prowlarr.video</Link>
|
||||
<Link to="https://prowlarr.com/">prowlarr.com</Link>
|
||||
</DescriptionListItemDescription>
|
||||
|
||||
<DescriptionListItemTitle>Discord</DescriptionListItemTitle>
|
||||
|
@@ -13,11 +13,10 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
|
||||
private static readonly Regex BeginningThe = new Regex(@"^the\s", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
public List<string> SceneTitles { get; set; }
|
||||
public virtual bool UserInvokedSearch { get; set; }
|
||||
public virtual bool InteractiveSearch { get; set; }
|
||||
public List<int> IndexerIds { get; set; }
|
||||
public string ImdbId { get; set; }
|
||||
public int TmdbId { get; set; }
|
||||
public List<int> IndexerIds { get; set; }
|
||||
|
||||
public List<string> QueryTitles => SceneTitles.Select(GetQueryTitle).ToList();
|
||||
|
||||
|
@@ -15,8 +15,8 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
{
|
||||
public interface ISearchForNzb
|
||||
{
|
||||
List<ReleaseInfo> Search(string query, List<int> indexerIds, bool userInvokedSearch, bool interactiveSearch);
|
||||
NewznabResults Search(NewznabRequest request, List<int> indexerIds, bool userInvokedSearch, bool interactiveSearch);
|
||||
List<ReleaseInfo> Search(string query, List<int> indexerIds, bool interactiveSearch);
|
||||
NewznabResults Search(NewznabRequest request, List<int> indexerIds, bool interactiveSearch);
|
||||
}
|
||||
|
||||
public class NzbSearchService : ISearchForNzb
|
||||
@@ -34,26 +34,25 @@ namespace NzbDrone.Core.IndexerSearch
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public List<ReleaseInfo> Search(string query, List<int> indexerIds, bool userInvokedSearch, bool interactiveSearch)
|
||||
public List<ReleaseInfo> Search(string query, List<int> indexerIds, bool interactiveSearch)
|
||||
{
|
||||
var searchSpec = Get<MovieSearchCriteria>(query, indexerIds, userInvokedSearch, interactiveSearch);
|
||||
var searchSpec = Get<MovieSearchCriteria>(query, indexerIds, interactiveSearch);
|
||||
|
||||
return Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec);
|
||||
}
|
||||
|
||||
public NewznabResults Search(NewznabRequest request, List<int> indexerIds, bool userInvokedSearch, bool interactiveSearch)
|
||||
public NewznabResults Search(NewznabRequest request, List<int> indexerIds, bool interactiveSearch)
|
||||
{
|
||||
var searchSpec = Get<MovieSearchCriteria>(request.q, indexerIds, userInvokedSearch, interactiveSearch);
|
||||
var searchSpec = Get<MovieSearchCriteria>(request.q, indexerIds, interactiveSearch);
|
||||
|
||||
return new NewznabResults { Releases = Dispatch(indexer => indexer.Fetch(searchSpec), searchSpec) };
|
||||
}
|
||||
|
||||
private TSpec Get<TSpec>(string query, List<int> indexerIds, bool userInvokedSearch, bool interactiveSearch)
|
||||
private TSpec Get<TSpec>(string query, List<int> indexerIds, bool interactiveSearch)
|
||||
where TSpec : SearchCriteriaBase, new()
|
||||
{
|
||||
var spec = new TSpec()
|
||||
{
|
||||
UserInvokedSearch = userInvokedSearch,
|
||||
InteractiveSearch = interactiveSearch
|
||||
};
|
||||
|
||||
|
@@ -53,10 +53,10 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
yield return GetDefinition("Nzb-Tortuga", GetSettings("https://www.nzb-tortuga.com"));
|
||||
yield return GetDefinition("Nzb.su", GetSettings("https://api.nzb.su"));
|
||||
yield return GetDefinition("NZBCat", GetSettings("https://nzb.cat"));
|
||||
yield return GetDefinition("NZBFinder.ws", GetSettings("https://nzbfinder.ws", categories: new[] { 2030, 2040, 2045, 2050, 2060, 2070, 2080, 2090 }));
|
||||
yield return GetDefinition("NZBFinder.ws", GetSettings("https://nzbfinder.ws"));
|
||||
yield return GetDefinition("NZBgeek", GetSettings("https://api.nzbgeek.info"));
|
||||
yield return GetDefinition("nzbplanet.net", GetSettings("https://api.nzbplanet.net"));
|
||||
yield return GetDefinition("omgwtfnzbs", GetSettings("https://api.omgwtfnzbs.me", categories: new[] { 2000, 2020, 2030, 2040, 2045, 2050, 2070 }));
|
||||
yield return GetDefinition("omgwtfnzbs", GetSettings("https://api.omgwtfnzbs.me"));
|
||||
yield return GetDefinition("OZnzb.com", GetSettings("https://api.oznzb.com"));
|
||||
yield return GetDefinition("SimplyNZBs", GetSettings("https://simplynzbs.com"));
|
||||
yield return GetDefinition("Tabula Rasa", GetSettings("https://www.tabula-rasa.pw", apiPath: @"/api/v1/api"));
|
||||
|
@@ -19,6 +19,27 @@ namespace NzbDrone.Core.Indexers.Rarbg
|
||||
public override DownloadProtocol Protocol => DownloadProtocol.Torrent;
|
||||
|
||||
public override IndexerPrivacy Privacy => IndexerPrivacy.Public;
|
||||
|
||||
public override IndexerCapabilities Capabilities => new IndexerCapabilities
|
||||
{
|
||||
TvSearchParams = new List<TvSearchParam>
|
||||
{
|
||||
TvSearchParam.Q, TvSearchParam.Season, TvSearchParam.Ep
|
||||
},
|
||||
MovieSearchParams = new List<MovieSearchParam>
|
||||
{
|
||||
MovieSearchParam.Q, MovieSearchParam.ImdbId
|
||||
},
|
||||
MusicSearchParams = new List<MusicSearchParam>
|
||||
{
|
||||
MusicSearchParam.Q
|
||||
},
|
||||
BookSearchParams = new List<BookSearchParam>
|
||||
{
|
||||
BookSearchParam.Q
|
||||
}
|
||||
};
|
||||
|
||||
public override TimeSpan RateLimit => TimeSpan.FromSeconds(2);
|
||||
|
||||
public Rarbg(IRarbgTokenProvider tokenProvider, IHttpClient httpClient, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger)
|
||||
|
@@ -17,6 +17,7 @@
|
||||
<PackageReference Include="System.Data.SQLite.Core.Servarr" Version="1.0.113.0-0" />
|
||||
<PackageReference Include="System.Text.Json" Version="4.7.2" />
|
||||
<PackageReference Include="MonoTorrent" Version="1.0.19" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.1.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\NzbDrone.Common\Prowlarr.Common.csproj" />
|
||||
|
@@ -20,7 +20,7 @@ namespace Prowlarr.Api.V1.History
|
||||
GetResourcePaged = GetHistory;
|
||||
|
||||
Get("/since", x => GetHistorySince());
|
||||
Get("/movie", x => GetMovieHistory());
|
||||
Get("/indexer", x => GetIndexerHistory());
|
||||
}
|
||||
|
||||
protected HistoryResource MapToResource(NzbDrone.Core.History.History model, bool includeMovie)
|
||||
@@ -75,26 +75,26 @@ namespace Prowlarr.Api.V1.History
|
||||
return _historyService.Since(date, eventType).Select(h => MapToResource(h, includeMovie)).ToList();
|
||||
}
|
||||
|
||||
private List<HistoryResource> GetMovieHistory()
|
||||
private List<HistoryResource> GetIndexerHistory()
|
||||
{
|
||||
var queryMovieId = Request.Query.MovieId;
|
||||
var queryIndexerId = Request.Query.IndexerId;
|
||||
var queryEventType = Request.Query.EventType;
|
||||
|
||||
if (!queryMovieId.HasValue)
|
||||
if (!queryIndexerId.HasValue)
|
||||
{
|
||||
throw new BadRequestException("movieId is missing");
|
||||
throw new BadRequestException("indexerId is missing");
|
||||
}
|
||||
|
||||
int movieId = Convert.ToInt32(queryMovieId.Value);
|
||||
int indexerId = Convert.ToInt32(queryIndexerId.Value);
|
||||
HistoryEventType? eventType = null;
|
||||
var includeMovie = Request.GetBooleanQueryParameter("includeMovie");
|
||||
var includeIndexer = Request.GetBooleanQueryParameter("includeIndexer");
|
||||
|
||||
if (queryEventType.HasValue)
|
||||
{
|
||||
eventType = (HistoryEventType)Convert.ToInt32(queryEventType.Value);
|
||||
}
|
||||
|
||||
return _historyService.GetByIndexerId(movieId, eventType).Select(h => MapToResource(h, includeMovie)).ToList();
|
||||
return _historyService.GetByIndexerId(indexerId, eventType).Select(h => MapToResource(h, includeIndexer)).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -62,8 +62,10 @@ namespace Prowlarr.Api.V1.Indexers
|
||||
Response response = indexerInstance.GetCapabilities().ToXml();
|
||||
response.ContentType = "application/rss+xml";
|
||||
return response;
|
||||
case "tvsearch":
|
||||
case "music":
|
||||
case "movie":
|
||||
Response movieResponse = _nzbSearchService.Search(request, new List<int> { indexer.Id }, true, false).ToXml();
|
||||
Response movieResponse = _nzbSearchService.Search(request, new List<int> { indexer.Id }, false).ToXml();
|
||||
movieResponse.ContentType = "application/rss+xml";
|
||||
return movieResponse;
|
||||
default:
|
||||
|
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Nancy.ModelBinding;
|
||||
using NLog;
|
||||
@@ -50,7 +49,7 @@ namespace Prowlarr.Api.V1.Search
|
||||
{
|
||||
try
|
||||
{
|
||||
var decisions = _nzbSearhService.Search(query, indexerIds, true, true);
|
||||
var decisions = _nzbSearhService.Search(query, indexerIds, true);
|
||||
|
||||
return MapDecisions(decisions);
|
||||
}
|
||||
|
Reference in New Issue
Block a user