parent
ff3f52eb3f
commit
ec2dc34098
@ -1,37 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import AlbumDetailsModalContentConnector from './AlbumDetailsModalContentConnector';
|
||||
|
||||
class AlbumDetailsModal extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isOpen,
|
||||
onModalClose,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<AlbumDetailsModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumDetailsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AlbumDetailsModal;
|
@ -1,48 +0,0 @@
|
||||
.artistName {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
margin-top: -32px;
|
||||
}
|
||||
|
||||
.tabList {
|
||||
margin: 0 0 10px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tab {
|
||||
position: relative;
|
||||
bottom: -1px;
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid transparent;
|
||||
border-top: none;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selectedTab {
|
||||
border-color: $borderColor;
|
||||
border-radius: 0 0 5px 5px;
|
||||
background-color: rgba(239, 239, 239, 0.4);
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.tabPanel {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.openArtistButton {
|
||||
composes: button from 'Components/Link/Button.css';
|
||||
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.openButtons {
|
||||
margin-right: auto;
|
||||
}
|
@ -1,190 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||
import albumEntities from 'Album/albumEntities';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import AlbumHistoryConnector from './History/AlbumHistoryConnector';
|
||||
import AlbumSearchConnector from './Search/AlbumSearchConnector';
|
||||
import styles from './AlbumDetailsModalContent.css';
|
||||
|
||||
const tabs = [
|
||||
'history',
|
||||
'search'
|
||||
];
|
||||
|
||||
class AlbumDetailsModalContent extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
selectedTab: props.selectedTab
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onTabSelect = (index, lastIndex) => {
|
||||
this.setState({ selectedTab: tabs[index] });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
albumId,
|
||||
artistName,
|
||||
foreignArtistId,
|
||||
foreignAlbumId,
|
||||
artistMonitored,
|
||||
albumTitle,
|
||||
monitored,
|
||||
isSaving,
|
||||
showOpenArtistButton,
|
||||
showOpenAlbumButton,
|
||||
startInteractiveSearch,
|
||||
onMonitorAlbumPress,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const artistLink = `/artist/${foreignArtistId}`;
|
||||
const albumLink = `/album/${foreignAlbumId}`;
|
||||
|
||||
return (
|
||||
<ModalContent
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<ModalHeader>
|
||||
<MonitorToggleButton
|
||||
className={styles.toggleButton}
|
||||
id={albumId}
|
||||
monitored={monitored}
|
||||
size={18}
|
||||
isDisabled={!artistMonitored}
|
||||
isSaving={isSaving}
|
||||
onPress={onMonitorAlbumPress}
|
||||
/>
|
||||
|
||||
<span className={styles.artistName}>
|
||||
{artistName}
|
||||
</span>
|
||||
|
||||
<span className={styles.separator}>-</span>
|
||||
|
||||
{albumTitle}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Tabs
|
||||
className={styles.tabs}
|
||||
selectedIndex={tabs.indexOf(this.state.selectedTab)}
|
||||
onSelect={this.onTabSelect}
|
||||
>
|
||||
<TabList
|
||||
className={styles.tabList}
|
||||
>
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
selectedClassName={styles.selectedTab}
|
||||
>
|
||||
History
|
||||
</Tab>
|
||||
|
||||
<Tab
|
||||
className={styles.tab}
|
||||
selectedClassName={styles.selectedTab}
|
||||
>
|
||||
Search
|
||||
</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanel className={styles.tabPanel}>
|
||||
<AlbumHistoryConnector
|
||||
albumId={albumId}
|
||||
/>
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel className={styles.tabPanel}>
|
||||
<AlbumSearchConnector
|
||||
albumId={albumId}
|
||||
startInteractiveSearch={startInteractiveSearch}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter >
|
||||
<div className={styles.openButtons}>
|
||||
{
|
||||
showOpenArtistButton &&
|
||||
<Button
|
||||
className={styles.openArtistButton}
|
||||
to={artistLink}
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Open Artist
|
||||
</Button>
|
||||
}
|
||||
|
||||
{
|
||||
showOpenAlbumButton &&
|
||||
<Button
|
||||
className={styles.openAlbumButton}
|
||||
to={albumLink}
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Open Album
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumDetailsModalContent.propTypes = {
|
||||
albumId: PropTypes.number.isRequired,
|
||||
albumEntity: PropTypes.string.isRequired,
|
||||
artistId: PropTypes.number.isRequired,
|
||||
artistName: PropTypes.string.isRequired,
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
foreignAlbumId: PropTypes.string.isRequired,
|
||||
artistMonitored: PropTypes.bool.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
albumLabel: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
albumTitle: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
isSaving: PropTypes.bool,
|
||||
showOpenArtistButton: PropTypes.bool,
|
||||
showOpenAlbumButton: PropTypes.bool,
|
||||
selectedTab: PropTypes.string.isRequired,
|
||||
startInteractiveSearch: PropTypes.bool.isRequired,
|
||||
onMonitorAlbumPress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
AlbumDetailsModalContent.defaultProps = {
|
||||
selectedTab: 'history',
|
||||
albumLabel: ['Unknown'],
|
||||
albumEntity: albumEntities.ALBUMS,
|
||||
startInteractiveSearch: false
|
||||
};
|
||||
|
||||
export default AlbumDetailsModalContent;
|
@ -1,125 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
|
||||
import { toggleAlbumMonitored } from 'Store/Actions/albumActions';
|
||||
import createAlbumSelector from 'Store/Selectors/createAlbumSelector';
|
||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||
import albumEntities from 'Album/albumEntities';
|
||||
import { fetchTracks, clearTracks } from 'Store/Actions/trackActions';
|
||||
import AlbumDetailsModalContent from './AlbumDetailsModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createAlbumSelector(),
|
||||
createArtistSelector(),
|
||||
(album, artist) => {
|
||||
const {
|
||||
artistName,
|
||||
foreignArtistId,
|
||||
monitored: artistMonitored
|
||||
} = artist;
|
||||
|
||||
return {
|
||||
artistName,
|
||||
foreignArtistId,
|
||||
artistMonitored,
|
||||
...album
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
dispatchCancelFetchReleases() {
|
||||
dispatch(cancelFetchReleases());
|
||||
},
|
||||
|
||||
dispatchClearReleases() {
|
||||
dispatch(clearReleases());
|
||||
},
|
||||
|
||||
dispatchFetchTracks({ artistId, albumId }) {
|
||||
dispatch(fetchTracks({ artistId, albumId }));
|
||||
},
|
||||
|
||||
dispatchClearTracks() {
|
||||
dispatch(clearTracks());
|
||||
},
|
||||
|
||||
onMonitorAlbumPress(monitored) {
|
||||
const {
|
||||
albumId,
|
||||
albumEntity
|
||||
} = this.props;
|
||||
|
||||
dispatch(toggleAlbumMonitored({
|
||||
albumEntity,
|
||||
albumId,
|
||||
monitored
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class AlbumDetailsModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
componentDidMount() {
|
||||
this._populate();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Clear pending releases here so we can reshow the search
|
||||
// results even after switching tabs.
|
||||
this._unpopulate();
|
||||
this.props.dispatchCancelFetchReleases();
|
||||
this.props.dispatchClearReleases();
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
_populate() {
|
||||
const artistId = this.props.artistId;
|
||||
const albumId = this.props.albumId;
|
||||
this.props.dispatchFetchTracks({ artistId, albumId });
|
||||
}
|
||||
|
||||
_unpopulate() {
|
||||
this.props.dispatchClearTracks();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
dispatchClearReleases,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<AlbumDetailsModalContent {...otherProps} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumDetailsModalContentConnector.propTypes = {
|
||||
albumId: PropTypes.number.isRequired,
|
||||
albumEntity: PropTypes.string.isRequired,
|
||||
artistId: PropTypes.number.isRequired,
|
||||
dispatchFetchTracks: PropTypes.func.isRequired,
|
||||
dispatchClearTracks: PropTypes.func.isRequired,
|
||||
dispatchCancelFetchReleases: PropTypes.func.isRequired,
|
||||
dispatchClearReleases: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
AlbumDetailsModalContentConnector.defaultProps = {
|
||||
albumEntity: albumEntities.ALBUMS
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(AlbumDetailsModalContentConnector);
|
@ -1,20 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Link from 'Components/Link/Link';
|
||||
|
||||
function AlbumTitleDetailLink({ foreignAlbumId, title }) {
|
||||
const link = `/album/${foreignAlbumId}`;
|
||||
|
||||
return (
|
||||
<Link to={link}>
|
||||
{title}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
AlbumTitleDetailLink.propTypes = {
|
||||
foreignAlbumId: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default AlbumTitleDetailLink;
|
@ -1,68 +1,20 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import Link from 'Components/Link/Link';
|
||||
import AlbumDetailsModal from 'Album/AlbumDetailsModal';
|
||||
import styles from './AlbumTitleLink.css';
|
||||
|
||||
class AlbumTitleLink extends Component {
|
||||
function AlbumTitleLink({ foreignAlbumId, title }) {
|
||||
const link = `/album/${foreignAlbumId}`;
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isDetailsModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onLinkPress = () => {
|
||||
this.setState({ isDetailsModalOpen: true });
|
||||
}
|
||||
|
||||
onModalClose = () => {
|
||||
this.setState({ isDetailsModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
albumTitle,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Link
|
||||
className={styles.link}
|
||||
onPress={this.onLinkPress}
|
||||
>
|
||||
{albumTitle}
|
||||
</Link>
|
||||
|
||||
<AlbumDetailsModal
|
||||
isOpen={this.state.isDetailsModalOpen}
|
||||
albumTitle={albumTitle}
|
||||
{...otherProps}
|
||||
onModalClose={this.onModalClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link to={link}>
|
||||
{title}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
AlbumTitleLink.propTypes = {
|
||||
albumTitle: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
AlbumTitleLink.defaultProps = {
|
||||
showArtistButton: false
|
||||
foreignAlbumId: PropTypes.string.isRequired,
|
||||
title: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default AlbumTitleLink;
|
||||
|
@ -1,117 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import AlbumHistoryRow from './AlbumHistoryRow';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'eventType',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'sourceTitle',
|
||||
label: 'Source Title',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'language',
|
||||
label: 'Language',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'quality',
|
||||
label: 'Quality',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
label: 'Date',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'details',
|
||||
label: 'Details',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
label: 'Actions',
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
class AlbumHistory extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
onMarkAsFailedPress
|
||||
} = this.props;
|
||||
|
||||
const hasItems = !!items.length;
|
||||
|
||||
if (isFetching) {
|
||||
return (
|
||||
<LoadingIndicator />
|
||||
);
|
||||
}
|
||||
|
||||
if (!isFetching && !!error) {
|
||||
return (
|
||||
<div>Unable to load album history.</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isPopulated && !hasItems && !error) {
|
||||
return (
|
||||
<div>No album history.</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (isPopulated && hasItems && !error) {
|
||||
return (
|
||||
<Table
|
||||
columns={columns}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<AlbumHistoryRow
|
||||
key={item.id}
|
||||
{...item}
|
||||
onMarkAsFailedPress={onMarkAsFailedPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
AlbumHistory.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
AlbumHistory.defaultProps = {
|
||||
selectedTab: 'details'
|
||||
};
|
||||
|
||||
export default AlbumHistory;
|
@ -1,63 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchAlbumHistory, clearAlbumHistory, albumHistoryMarkAsFailed } from 'Store/Actions/albumHistoryActions';
|
||||
import AlbumHistory from './AlbumHistory';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.albumHistory,
|
||||
(albumHistory) => {
|
||||
return albumHistory;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchAlbumHistory,
|
||||
clearAlbumHistory,
|
||||
albumHistoryMarkAsFailed
|
||||
};
|
||||
|
||||
class AlbumHistoryConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchAlbumHistory({ albumId: this.props.albumId });
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearAlbumHistory();
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onMarkAsFailedPress = (historyId) => {
|
||||
this.props.albumHistoryMarkAsFailed({ historyId, albumId: this.props.albumId });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<AlbumHistory
|
||||
{...this.props}
|
||||
onMarkAsFailedPress={this.onMarkAsFailedPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumHistoryConnector.propTypes = {
|
||||
albumId: PropTypes.number.isRequired,
|
||||
fetchAlbumHistory: PropTypes.func.isRequired,
|
||||
clearAlbumHistory: PropTypes.func.isRequired,
|
||||
albumHistoryMarkAsFailed: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AlbumHistoryConnector);
|
@ -1,6 +0,0 @@
|
||||
.details,
|
||||
.actions {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 65px;
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import EpisodeLanguage from 'Album/EpisodeLanguage';
|
||||
import EpisodeQuality from 'Album/EpisodeQuality';
|
||||
import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector';
|
||||
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
|
||||
import styles from './AlbumHistoryRow.css';
|
||||
|
||||
function getTitle(eventType) {
|
||||
switch (eventType) {
|
||||
case 'grabbed': return 'Grabbed';
|
||||
case 'artistFolderImported': return 'Artist Folder Imported';
|
||||
case 'downloadFolderImported': return 'Download Folder Imported';
|
||||
case 'downloadFailed': return 'Download Failed';
|
||||
case 'trackFileDeleted': return 'Track File Deleted';
|
||||
case 'trackFileRenamed': return 'Track File Renamed';
|
||||
default: return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumHistoryRow extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isMarkAsFailedModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onMarkAsFailedPress = () => {
|
||||
this.setState({ isMarkAsFailedModalOpen: true });
|
||||
}
|
||||
|
||||
onConfirmMarkAsFailed = () => {
|
||||
this.props.onMarkAsFailedPress(this.props.id);
|
||||
this.setState({ isMarkAsFailedModalOpen: false });
|
||||
}
|
||||
|
||||
onMarkAsFailedModalClose = () => {
|
||||
this.setState({ isMarkAsFailedModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
eventType,
|
||||
sourceTitle,
|
||||
language,
|
||||
languageCutoffNotMet,
|
||||
quality,
|
||||
qualityCutoffNotMet,
|
||||
date,
|
||||
data
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isMarkAsFailedModalOpen
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<HistoryEventTypeCell
|
||||
eventType={eventType}
|
||||
data={data}
|
||||
/>
|
||||
|
||||
<TableRowCell>
|
||||
{sourceTitle}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
<EpisodeLanguage
|
||||
language={language}
|
||||
isCutoffNotMet={languageCutoffNotMet}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell>
|
||||
|
||||
<EpisodeQuality
|
||||
quality={quality}
|
||||
isCutoffNotMet={qualityCutoffNotMet}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<RelativeDateCellConnector
|
||||
date={date}
|
||||
/>
|
||||
|
||||
<TableRowCell className={styles.details}>
|
||||
<Popover
|
||||
anchor={
|
||||
<Icon
|
||||
name={icons.INFO}
|
||||
/>
|
||||
}
|
||||
title={getTitle(eventType)}
|
||||
body={
|
||||
<HistoryDetailsConnector
|
||||
eventType={eventType}
|
||||
sourceTitle={sourceTitle}
|
||||
data={data}
|
||||
/>
|
||||
}
|
||||
position={tooltipPositions.LEFT}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.actions}>
|
||||
{
|
||||
eventType === 'grabbed' &&
|
||||
<IconButton
|
||||
name={icons.REMOVE}
|
||||
onPress={this.onMarkAsFailedPress}
|
||||
/>
|
||||
}
|
||||
</TableRowCell>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isMarkAsFailedModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title="Mark as Failed"
|
||||
message={`Are you sure you want to mark '${sourceTitle}' as failed?`}
|
||||
confirmLabel="Mark as Failed"
|
||||
onConfirm={this.onConfirmMarkAsFailed}
|
||||
onCancel={this.onMarkAsFailedModalClose}
|
||||
/>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumHistoryRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
eventType: PropTypes.string.isRequired,
|
||||
sourceTitle: PropTypes.string.isRequired,
|
||||
language: PropTypes.object.isRequired,
|
||||
languageCutoffNotMet: PropTypes.bool.isRequired,
|
||||
quality: PropTypes.object.isRequired,
|
||||
qualityCutoffNotMet: PropTypes.bool.isRequired,
|
||||
date: PropTypes.string.isRequired,
|
||||
data: PropTypes.object.isRequired,
|
||||
onMarkAsFailedPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AlbumHistoryRow;
|
@ -1,16 +0,0 @@
|
||||
.buttonContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
composes: button from 'Components/Link/Button.css';
|
||||
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.buttonIcon {
|
||||
margin-right: 5px;
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Icon from 'Components/Icon';
|
||||
import styles from './AlbumSearch.css';
|
||||
|
||||
function AlbumSearch(props) {
|
||||
const {
|
||||
onQuickSearchPress,
|
||||
onInteractiveSearchPress
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={styles.buttonContainer}>
|
||||
<Button
|
||||
className={styles.button}
|
||||
size={sizes.LARGE}
|
||||
onPress={onQuickSearchPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.buttonIcon}
|
||||
name={icons.QUICK}
|
||||
/>
|
||||
|
||||
Quick Search
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={styles.buttonContainer}>
|
||||
<Button
|
||||
className={styles.button}
|
||||
kind={kinds.PRIMARY}
|
||||
size={sizes.LARGE}
|
||||
onPress={onInteractiveSearchPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.buttonIcon}
|
||||
name={icons.INTERACTIVE}
|
||||
/>
|
||||
|
||||
Interactive Search
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
AlbumSearch.propTypes = {
|
||||
onQuickSearchPress: PropTypes.func.isRequired,
|
||||
onInteractiveSearchPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AlbumSearch;
|
@ -1,90 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import * as commandNames from 'Commands/commandNames';
|
||||
import AlbumSearch from './AlbumSearch';
|
||||
import InteractiveAlbumSearchConnector from './InteractiveAlbumSearchConnector';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.releases,
|
||||
(releases) => {
|
||||
return {
|
||||
isPopulated: releases.isPopulated
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
executeCommand
|
||||
};
|
||||
|
||||
class AlbumSearchConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isInteractiveSearchOpen: props.startInteractiveSearch
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.isPopulated) {
|
||||
this.setState({ isInteractiveSearchOpen: true });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onQuickSearchPress = () => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.ALBUM_SEARCH,
|
||||
albumIds: [this.props.albumId]
|
||||
});
|
||||
|
||||
this.props.onModalClose();
|
||||
}
|
||||
|
||||
onInteractiveSearchPress = () => {
|
||||
this.setState({ isInteractiveSearchOpen: true });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
if (this.state.isInteractiveSearchOpen) {
|
||||
return (
|
||||
<InteractiveAlbumSearchConnector
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AlbumSearch
|
||||
{...this.props}
|
||||
onQuickSearchPress={this.onQuickSearchPress}
|
||||
onInteractiveSearchPress={this.onInteractiveSearchPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumSearchConnector.propTypes = {
|
||||
albumId: PropTypes.number.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
startInteractiveSearch: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired,
|
||||
executeCommand: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(AlbumSearchConnector);
|
@ -1,130 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { icons, sortDirections } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import Icon from 'Components/Icon';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import InteractiveAlbumSearchRow from './InteractiveAlbumSearchRow';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'protocol',
|
||||
label: 'Source',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'age',
|
||||
label: 'Age',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'indexer',
|
||||
label: 'Indexer',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: 'Size',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'peers',
|
||||
label: 'Peers',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'qualityWeight',
|
||||
label: 'Quality',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: React.createElement(Icon, { name: icons.DANGER }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseWeight',
|
||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
function InteractiveAlbumSearch(props) {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
onSortPress,
|
||||
onGrabPress
|
||||
} = props;
|
||||
|
||||
if (isFetching) {
|
||||
return <LoadingIndicator />;
|
||||
} else if (!isFetching && !!error) {
|
||||
return <div>Unable to load results for this album search. Try again later.</div>;
|
||||
} else if (isPopulated && !items.length) {
|
||||
return <div>No results found.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={onSortPress}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<InteractiveAlbumSearchRow
|
||||
key={item.guid}
|
||||
{...item}
|
||||
longDateFormat={longDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
onGrabPress={onGrabPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
InteractiveAlbumSearch.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.string,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
onGrabPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default InteractiveAlbumSearch;
|
@ -1,90 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { createSelector } from 'reselect';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import { fetchReleases, setReleasesSort, grabRelease } from 'Store/Actions/releaseActions';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import InteractiveAlbumSearch from './InteractiveAlbumSearch';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createClientSideCollectionSelector(),
|
||||
createUISettingsSelector(),
|
||||
(releases, uiSettings) => {
|
||||
return {
|
||||
longDateFormat: uiSettings.longDateFormat,
|
||||
timeFormat: uiSettings.timeFormat,
|
||||
...releases
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchReleases,
|
||||
setReleasesSort,
|
||||
grabRelease
|
||||
};
|
||||
|
||||
class InteractiveAlbumSearchConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
albumId,
|
||||
isPopulated
|
||||
} = this.props;
|
||||
|
||||
// If search results are not yet isPopulated fetch them,
|
||||
// otherwise re-show the existing props.
|
||||
|
||||
if (!isPopulated) {
|
||||
this.props.fetchReleases({
|
||||
albumId
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSortPress = (sortKey, sortDirection) => {
|
||||
this.props.setReleasesSort({ sortKey, sortDirection });
|
||||
}
|
||||
|
||||
onGrabPress = (guid, indexerId) => {
|
||||
this.props.grabRelease({ guid, indexerId });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<InteractiveAlbumSearch
|
||||
{...this.props}
|
||||
onSortPress={this.onSortPress}
|
||||
onGrabPress={this.onGrabPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InteractiveAlbumSearchConnector.propTypes = {
|
||||
albumId: PropTypes.number.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
fetchReleases: PropTypes.func.isRequired,
|
||||
setReleasesSort: PropTypes.func.isRequired,
|
||||
grabRelease: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'releases' }
|
||||
)(InteractiveAlbumSearchConnector);
|
@ -0,0 +1,31 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import InteractiveAlbumSearchModalContentConnector from './InteractiveAlbumSearchModalContentConnector';
|
||||
|
||||
function InteractiveAlbumSearchModal(props) {
|
||||
const {
|
||||
isOpen,
|
||||
onModalClose,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<InteractiveAlbumSearchModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
InteractiveAlbumSearchModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default InteractiveAlbumSearchModal;
|
@ -0,0 +1,169 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons, sortDirections } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Icon from 'Components/Icon';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import InteractiveAlbumSearchRow from './InteractiveAlbumSearchRow';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
name: 'protocol',
|
||||
label: 'Source',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'age',
|
||||
label: 'Age',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'indexer',
|
||||
label: 'Indexer',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
label: 'Size',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'peers',
|
||||
label: 'Peers',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'qualityWeight',
|
||||
label: 'Quality',
|
||||
isSortable: true,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'rejections',
|
||||
label: React.createElement(Icon, { name: icons.DANGER }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'releaseWeight',
|
||||
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
|
||||
isSortable: true,
|
||||
fixedSortDirection: sortDirections.ASCENDING,
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
class InteractiveAlbumSearchModalContent extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
longDateFormat,
|
||||
timeFormat,
|
||||
onSortPress,
|
||||
onGrabPress,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const hasItems = !!items.length;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Interactive Album Search
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to load releases.</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && !hasItems && !error &&
|
||||
<div>No results.</div>
|
||||
}
|
||||
|
||||
{
|
||||
isPopulated && hasItems && !error &&
|
||||
<Table
|
||||
columns={columns}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onSortPress={onSortPress}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<InteractiveAlbumSearchRow
|
||||
key={item.guid}
|
||||
{...item}
|
||||
longDateFormat={longDateFormat}
|
||||
timeFormat={timeFormat}
|
||||
onGrabPress={onGrabPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InteractiveAlbumSearchModalContent.propTypes = {
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.string,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
onGrabPress: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default InteractiveAlbumSearchModalContent;
|
@ -0,0 +1,109 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import connectSection from 'Store/connectSection';
|
||||
import { createSelector } from 'reselect';
|
||||
import { fetchReleases, clearReleases, cancelFetchReleases, setReleasesSort, grabRelease } from 'Store/Actions/releaseActions';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||
import InteractiveAlbumSearchModalContent from './InteractiveAlbumSearchModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createClientSideCollectionSelector(),
|
||||
createUISettingsSelector(),
|
||||
(releases, uiSettings) => {
|
||||
return {
|
||||
longDateFormat: uiSettings.longDateFormat,
|
||||
timeFormat: uiSettings.timeFormat,
|
||||
...releases
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
dispatchFetchReleases({ albumId }) {
|
||||
dispatch(fetchReleases({ albumId }));
|
||||
},
|
||||
|
||||
dispatchCancelFetchReleases() {
|
||||
dispatch(cancelFetchReleases());
|
||||
},
|
||||
|
||||
dispatchClearReleases() {
|
||||
dispatch(clearReleases());
|
||||
},
|
||||
|
||||
dispatchSetReleasesSort({ sortKey, sortDirection }) {
|
||||
dispatch(setReleasesSort({ sortKey, sortDirection }));
|
||||
},
|
||||
|
||||
dispatchGrabRelease({ guid, indexerId }) {
|
||||
dispatch(grabRelease({ guid, indexerId }));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class InteractiveAlbumSearchModalContentConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
const {
|
||||
albumId
|
||||
} = this.props;
|
||||
|
||||
this.props.dispatchFetchReleases({
|
||||
albumId
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.dispatchCancelFetchReleases();
|
||||
this.props.dispatchClearReleases();
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onSortPress = (sortKey, sortDirection) => {
|
||||
this.props.dispatchSetReleasesSort({ sortKey, sortDirection });
|
||||
}
|
||||
|
||||
onGrabPress = (guid, indexerId) => {
|
||||
this.props.dispatchGrabRelease({ guid, indexerId });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<InteractiveAlbumSearchModalContent
|
||||
{...this.props}
|
||||
onSortPress={this.onSortPress}
|
||||
onGrabPress={this.onGrabPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
InteractiveAlbumSearchModalContentConnector.propTypes = {
|
||||
albumId: PropTypes.number,
|
||||
dispatchFetchReleases: PropTypes.func.isRequired,
|
||||
dispatchClearReleases: PropTypes.func.isRequired,
|
||||
dispatchCancelFetchReleases: PropTypes.func.isRequired,
|
||||
dispatchSetReleasesSort: PropTypes.func.isRequired,
|
||||
dispatchGrabRelease: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
createMapDispatchToProps,
|
||||
undefined,
|
||||
undefined,
|
||||
{ section: 'releases' }
|
||||
)(InteractiveAlbumSearchModalContentConnector);
|
@ -1,81 +0,0 @@
|
||||
import moment from 'moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import isInNextWeek from 'Utilities/Date/isInNextWeek';
|
||||
import isToday from 'Utilities/Date/isToday';
|
||||
import isTomorrow from 'Utilities/Date/isTomorrow';
|
||||
import { kinds, sizes } from 'Helpers/Props';
|
||||
import Label from 'Components/Label';
|
||||
|
||||
function AlbumReleasing(props) {
|
||||
const {
|
||||
releaseDate,
|
||||
albumLabel,
|
||||
shortDateFormat,
|
||||
showRelativeDates
|
||||
} = props;
|
||||
|
||||
const networkLabel = (
|
||||
<Label
|
||||
kind={kinds.INFO}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
{albumLabel}
|
||||
</Label>
|
||||
);
|
||||
|
||||
if (!releaseDate) {
|
||||
return (
|
||||
<span>
|
||||
TBA on {networkLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (!showRelativeDates) {
|
||||
return (
|
||||
<span>
|
||||
{moment(releaseDate).format(shortDateFormat)} on {networkLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (isToday(releaseDate)) {
|
||||
return (
|
||||
<span>
|
||||
Today on {networkLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (isTomorrow(releaseDate)) {
|
||||
return (
|
||||
<span>
|
||||
Tomorrow on {networkLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (isInNextWeek(releaseDate)) {
|
||||
return (
|
||||
<span>
|
||||
{moment(releaseDate).format('dddd')} on {networkLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span>
|
||||
{moment(releaseDate).format(shortDateFormat)} on {networkLabel}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
AlbumReleasing.propTypes = {
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
albumLabel: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default AlbumReleasing;
|
@ -1,19 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import AlbumReleasing from './AlbumReleasing';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createUISettingsSelector(),
|
||||
(uiSettings) => {
|
||||
return _.pick(uiSettings, [
|
||||
'shortDateFormat',
|
||||
'showRelativeDates'
|
||||
]);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(AlbumReleasing);
|
@ -1,48 +0,0 @@
|
||||
.infoTitle {
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.overview,
|
||||
.files {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.filesHeader {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.filesHeader {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid $borderColor;
|
||||
}
|
||||
|
||||
.fileRow {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.path {
|
||||
@add-mixin truncate;
|
||||
|
||||
flex: 1 0 1px;
|
||||
}
|
||||
|
||||
.size,
|
||||
.quality {
|
||||
flex: 0 0 125px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
flex: 0 0 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
.size,
|
||||
.quality {
|
||||
flex: 0 0 80px;
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { kinds, sizes } from 'Helpers/Props';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import Label from 'Components/Label';
|
||||
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import AlbumReleasingConnector from './AlbumReleasingConnector';
|
||||
import TrackDetailRow from './TrackDetailRow';
|
||||
import styles from './AlbumSummary.css';
|
||||
|
||||
class AlbumSummary extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isRemoveTrackFileModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onRemoveTrackFilePress = () => {
|
||||
this.setState({ isRemoveTrackFileModalOpen: true });
|
||||
}
|
||||
|
||||
onConfirmRemoveTrackFile = () => {
|
||||
this.props.onDeleteTrackFile();
|
||||
this.setState({ isRemoveTrackFileModalOpen: false });
|
||||
}
|
||||
|
||||
onRemoveTrackFileModalClose = () => {
|
||||
this.setState({ isRemoveTrackFileModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
qualityProfileId,
|
||||
overview,
|
||||
releaseDate,
|
||||
albumLabel,
|
||||
path,
|
||||
items,
|
||||
size,
|
||||
quality,
|
||||
qualityCutoffNotMet,
|
||||
columns
|
||||
} = this.props;
|
||||
|
||||
const hasOverview = !!overview;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<span className={styles.infoTitle}>Releases</span>
|
||||
|
||||
<AlbumReleasingConnector
|
||||
releaseDate={releaseDate}
|
||||
albumLabel={albumLabel}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className={styles.infoTitle}>Quality Profile</span>
|
||||
|
||||
<Label
|
||||
kind={kinds.PRIMARY}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<QualityProfileNameConnector
|
||||
qualityProfileId={qualityProfileId}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className={styles.overview}>
|
||||
{
|
||||
hasOverview ?
|
||||
overview :
|
||||
'No album overview.'
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{
|
||||
<div className={styles.albums}>
|
||||
{
|
||||
items.length ?
|
||||
<Table
|
||||
columns={columns}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((item) => {
|
||||
return (
|
||||
<TrackDetailRow
|
||||
key={item.id}
|
||||
columns={columns}
|
||||
{...item}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table> :
|
||||
|
||||
<div className={styles.noAlbums}>
|
||||
No tracks in this group
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={this.state.isRemoveTrackFileModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title="Delete Track File"
|
||||
message={`Are you sure you want to delete '${path}'?`}
|
||||
confirmLabel="Delete"
|
||||
onConfirm={this.onConfirmRemoveTrackFile}
|
||||
onCancel={this.onRemoveTrackFileModalClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AlbumSummary.propTypes = {
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
overview: PropTypes.string,
|
||||
albumLabel: PropTypes.arrayOf(PropTypes.string),
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
path: PropTypes.string,
|
||||
size: PropTypes.number,
|
||||
quality: PropTypes.object,
|
||||
qualityCutoffNotMet: PropTypes.bool,
|
||||
onDeleteTrackFile: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AlbumSummary;
|
@ -1,46 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { deleteTrackFile } from 'Store/Actions/trackFileActions';
|
||||
import createAlbumSelector from 'Store/Selectors/createAlbumSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||
import AlbumSummary from './AlbumSummary';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.tracks,
|
||||
createAlbumSelector(),
|
||||
createCommandsSelector(),
|
||||
createDimensionsSelector(),
|
||||
createArtistSelector(),
|
||||
(tracks, album, commands, dimensions, artist) => {
|
||||
const filteredItems = _.filter(tracks.items, { albumId: album.id });
|
||||
const mediumSortedItems = _.orderBy(filteredItems, 'absoluteTrackNumber');
|
||||
const items = _.orderBy(mediumSortedItems, 'mediumNumber');
|
||||
|
||||
return {
|
||||
network: album.label,
|
||||
qualityProfileId: artist.qualityProfileId,
|
||||
releaseDate: album.releaseDate,
|
||||
overview: album.overview,
|
||||
items,
|
||||
columns: tracks.columns
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onDeleteTrackFile() {
|
||||
dispatch(deleteTrackFile({
|
||||
id: props.trackFileId,
|
||||
albumEntity: props.albumEntity
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(AlbumSummary);
|
@ -1,31 +0,0 @@
|
||||
.title {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monitored {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 42px;
|
||||
}
|
||||
|
||||
.trackNumber {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.audio {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.language,
|
||||
.video,
|
||||
.status {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 100px;
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
|
||||
import MediaInfoConnector from 'TrackFile/MediaInfoConnector';
|
||||
import * as mediaInfoTypes from 'TrackFile/mediaInfoTypes';
|
||||
import EpisodeStatusConnector from 'Album/EpisodeStatusConnector';
|
||||
|
||||
import styles from './TrackDetailRow.css';
|
||||
|
||||
class TrackDetailRow extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
mediumNumber,
|
||||
absoluteTrackNumber,
|
||||
duration,
|
||||
columns,
|
||||
trackFileId
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
{
|
||||
columns.map((column) => {
|
||||
const {
|
||||
name,
|
||||
isVisible
|
||||
} = column;
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (name === 'medium') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.trackNumber}
|
||||
>
|
||||
{mediumNumber}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'absoluteTrackNumber') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.trackNumber}
|
||||
>
|
||||
{absoluteTrackNumber}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'title') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.title}
|
||||
>
|
||||
{title}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'duration') {
|
||||
return (
|
||||
<TableRowCell key={name}>
|
||||
{
|
||||
formatTimeSpan(duration)
|
||||
}
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'audioInfo') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.audio}
|
||||
>
|
||||
<MediaInfoConnector
|
||||
type={mediaInfoTypes.AUDIO}
|
||||
trackFileId={trackFileId}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
if (name === 'status') {
|
||||
return (
|
||||
<TableRowCell
|
||||
key={name}
|
||||
className={styles.status}
|
||||
>
|
||||
<EpisodeStatusConnector
|
||||
albumId={id}
|
||||
trackFileId={trackFileId}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})
|
||||
}
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TrackDetailRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
title: PropTypes.string.isRequired,
|
||||
duration: PropTypes.number.isRequired,
|
||||
trackFileId: PropTypes.number.isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
mediumNumber: PropTypes.number.isRequired,
|
||||
absoluteTrackNumber: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
export default TrackDetailRow;
|
Loading…
Reference in new issue