Skip to content

Commit

Permalink
New: Wanted Cutoff/Missing
Browse files Browse the repository at this point in the history
  • Loading branch information
mynameisbogdan committed May 13, 2024
1 parent 9798202 commit 152f50a
Show file tree
Hide file tree
Showing 37 changed files with 2,267 additions and 88 deletions.
16 changes: 16 additions & 0 deletions frontend/src/App/AppRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import Status from 'System/Status/Status';
import Tasks from 'System/Tasks/Tasks';
import UpdatesConnector from 'System/Updates/UpdatesConnector';
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
import MissingConnector from 'Wanted/Missing/MissingConnector';

function AppRoutes(props) {
const {
Expand Down Expand Up @@ -121,6 +123,20 @@ function AppRoutes(props) {
component={BlocklistConnector}
/>

{/*
Wanted
*/}

<Route
path="/wanted/missing"
component={MissingConnector}
/>

<Route
path="/wanted/cutoffunmet"
component={CutoffUnmetConnector}
/>

{/*
Settings
*/}
Expand Down
16 changes: 16 additions & 0 deletions frontend/src/Components/Page/Sidebar/PageSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ const links = [
]
},

{
iconName: icons.WARNING,
title: () => translate('Wanted'),
to: '/wanted/missing',
children: [
{
title: () => translate('Missing'),
to: '/wanted/missing'
},
{
title: () => translate('CutoffUnmet'),
to: '/wanted/cutoffunmet'
}
]
},

{
iconName: icons.SETTINGS,
title: () => translate('Settings'),
Expand Down
20 changes: 20 additions & 0 deletions frontend/src/Components/SignalRConnector.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,26 @@ class SignalRConnector extends Component {
this.props.dispatchSetVersion({ version });
};

handleWantedCutoff = (body) => {
if (body.action === 'updated') {
this.props.dispatchUpdateItem({
section: 'wanted.cutoffUnmet',
updateOnly: true,
...body.resource
});
}
};

handleWantedMissing = (body) => {
if (body.action === 'updated') {
this.props.dispatchUpdateItem({
section: 'wanted.missing',
updateOnly: true,
...body.resource
});
}
};

handleSystemTask = () => {
this.props.dispatchFetchCommands();
};
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/Movie/MovieSearchCell.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.movieSearchCell {
composes: cell from '~Components/Table/Cells/TableRowCell.css';

width: 70px;
white-space: nowrap;
}
7 changes: 7 additions & 0 deletions frontend/src/Movie/MovieSearchCell.css.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'movieSearchCell': string;
}
export const cssExports: CssExports;
export default cssExports;
81 changes: 81 additions & 0 deletions frontend/src/Movie/MovieSearchCell.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import MovieInteractiveSearchModalConnector from './Search/MovieInteractiveSearchModalConnector';
import styles from './MovieSearchCell.css';

class MovieSearchCell extends Component {

//
// Lifecycle

constructor(props, context) {
super(props, context);

this.state = {
isInteractiveSearchModalOpen: false
};
}

//
// Listeners

onManualSearchPress = () => {
this.setState({ isInteractiveSearchModalOpen: true });
};

onInteractiveSearchModalClose = () => {
this.setState({ isInteractiveSearchModalOpen: false });
};

//
// Render

render() {
const {
movieId,
movieTitle,
isSearching,
onSearchPress,
...otherProps
} = this.props;

return (
<TableRowCell className={styles.movieSearchCell}>
<SpinnerIconButton
name={icons.SEARCH}
isSpinning={isSearching}
onPress={onSearchPress}
title={translate('AutomaticSearch')}
/>

<IconButton
name={icons.INTERACTIVE}
onPress={this.onManualSearchPress}
title={translate('InteractiveSearch')}
/>

<MovieInteractiveSearchModalConnector
isOpen={this.state.isInteractiveSearchModalOpen}
movieId={movieId}
movieTitle={movieTitle}
onModalClose={this.onInteractiveSearchModalClose}
{...otherProps}
/>
</TableRowCell>
);
}
}

MovieSearchCell.propTypes = {
movieId: PropTypes.number.isRequired,
movieTitle: PropTypes.string.isRequired,
isSearching: PropTypes.bool.isRequired,
onSearchPress: PropTypes.func.isRequired
};

export default MovieSearchCell;
48 changes: 48 additions & 0 deletions frontend/src/Movie/MovieSearchCellConnector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames';
import MovieSearchCell from 'Movie/MovieSearchCell';
import { executeCommand } from 'Store/Actions/commandActions';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createMovieSelector from 'Store/Selectors/createMovieSelector';
import { isCommandExecuting } from 'Utilities/Command';

function createMapStateToProps() {
return createSelector(
(state, { movieId }) => movieId,
createMovieSelector(),
createCommandsSelector(),
(movieId, movie, commands) => {
const isSearching = commands.some((command) => {
const movieSearch = command.name === commandNames.MOVIE_SEARCH;

if (!movieSearch) {
return false;
}

return (
isCommandExecuting(command) &&
command.body.movieIds.indexOf(movieId) > -1
);
});

return {
movieMonitored: movie.monitored,
isSearching
};
}
);
}

function createMapDispatchToProps(dispatch, props) {
return {
onSearchPress(name, path) {
dispatch(executeCommand({
name: commandNames.MOVIE_SEARCH,
movieIds: [props.movieId]
}));
}
};
}

export default connect(createMapStateToProps, createMapDispatchToProps)(MovieSearchCell);
4 changes: 4 additions & 0 deletions frontend/src/Movie/MovieStatus.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.center {
display: flex;
justify-content: center;
}
7 changes: 7 additions & 0 deletions frontend/src/Movie/MovieStatus.css.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'center': string;
}
export const cssExports: CssExports;
export default cssExports;
115 changes: 115 additions & 0 deletions frontend/src/Movie/MovieStatus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import PropTypes from 'prop-types';
import React from 'react';
import QueueDetails from 'Activity/Queue/QueueDetails';
import Icon from 'Components/Icon';
import ProgressBar from 'Components/ProgressBar';
import { icons, kinds, sizes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
import MovieQuality from './MovieQuality';
import styles from './MovieStatus.css';

function MovieStatus(props) {
const {
isAvailable,
monitored,
grabbed,
queueItem,
movieFile
} = props;

const hasMovieFile = !!movieFile;
const isQueued = !!queueItem;

if (isQueued) {
const {
sizeleft,
size
} = queueItem;

const progress = size ? (100 - sizeleft / size * 100) : 0;

return (
<div className={styles.center}>
<QueueDetails
{...queueItem}
progressBar={
<ProgressBar
progress={progress}
kind={kinds.PURPLE}
size={sizes.MEDIUM}
/>
}
/>
</div>
);
}

if (grabbed) {
return (
<div className={styles.center}>
<Icon
name={icons.DOWNLOADING}
title={translate('MovieIsDownloading')}
/>
</div>
);
}

if (hasMovieFile) {
const quality = movieFile.quality;
const isCutoffNotMet = movieFile.qualityCutoffNotMet;

return (
<div className={styles.center}>
<MovieQuality
quality={quality}
size={movieFile.size}
isCutoffNotMet={isCutoffNotMet}
title={translate('MovieDownloaded')}
/>
</div>
);
}

if (!monitored) {
return (
<div className={styles.center}>
<Icon
name={icons.UNMONITORED}
kind={kinds.DISABLED}
title={translate('MovieIsNotMonitored')}
/>
</div>
);
}

if (isAvailable) {
return (
<div className={styles.center}>
<Icon
name={icons.MISSING}
title={translate('MovieMissingFromDisk')}
/>
</div>
);
}

return (
<div className={styles.center}>
<Icon
name={icons.NOT_AIRED}
title={translate('MovieIsNotAvailable')}
/>
</div>
);
}

MovieStatus.propTypes = {
isAvailable: PropTypes.bool.isRequired,
monitored: PropTypes.bool.isRequired,
grabbed: PropTypes.bool,
queueItem: PropTypes.object,
movieFile: PropTypes.object
};

export default MovieStatus;

0 comments on commit 152f50a

Please sign in to comment.