New: Interactive Search from Album Detail Page

pull/6/head
Qstick 6 years ago
parent ff3f52eb3f
commit ec2dc34098

@ -5,7 +5,6 @@ import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import albumEntities from 'Album/albumEntities';
import AlbumTitleLink from 'Album/AlbumTitleLink';
import EpisodeLanguage from 'Album/EpisodeLanguage';
import EpisodeQuality from 'Album/EpisodeQuality';
@ -53,7 +52,6 @@ class HistoryRow extends Component {
render() {
const {
albumId,
artist,
album,
track,
@ -114,12 +112,8 @@ class HistoryRow extends Component {
return (
<TableRowCell key={name}>
<AlbumTitleLink
albumId={albumId}
albumEntity={albumEntities.ALBUMS}
artistId={artist.id}
albumTitle={album.title}
showOpenArtistButton={true}
showOpenAlbumButton={true}
foreignAlbumId={album.foreignAlbumId}
title={album.title}
/>
</TableRowCell>
);

@ -156,12 +156,8 @@ class QueueRow extends Component {
return (
<TableRowCell key={name}>
<AlbumTitleLink
albumId={album.id}
artistId={artist.id}
trackFileId={album.trackFileId}
albumTitle={album.title}
showOpenArtistButton={true}
showOpenAlbumButton={true}
foreignAlbumId={album.foreignAlbumId}
title={album.title}
/>
</TableRowCell>
);

@ -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);

@ -4,7 +4,7 @@ import { icons } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import AlbumDetailsModal from './AlbumDetailsModal';
import InteractiveAlbumSearchModal from './Search/InteractiveAlbumSearchModal';
import styles from './AlbumSearchCell.css';
class AlbumSearchCell extends Component {
@ -37,8 +37,6 @@ class AlbumSearchCell extends Component {
render() {
const {
albumId,
artistId,
albumTitle,
isSearching,
onSearchPress,
...otherProps
@ -57,13 +55,9 @@ class AlbumSearchCell extends Component {
onPress={this.onManualSearchPress}
/>
<AlbumDetailsModal
<InteractiveAlbumSearchModal
isOpen={this.state.isDetailsModalOpen}
albumId={albumId}
artistId={artistId}
albumTitle={albumTitle}
selectedTab="search"
startInteractiveSearch={true}
onModalClose={this.onDetailsModalClose}
{...otherProps}
/>

@ -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;

@ -22,6 +22,7 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import AlbumDetailsMediumConnector from './AlbumDetailsMediumConnector';
import ArtistHistoryModal from 'Artist/History/ArtistHistoryModal';
import InteractiveAlbumSearchModal from 'Album/Search/InteractiveAlbumSearchModal';
import TrackFileEditorModal from 'TrackFile/Editor/TrackFileEditorModal';
import styles from './AlbumDetails.css';
@ -53,6 +54,7 @@ class AlbumDetails extends Component {
this.state = {
isOrganizeModalOpen: false,
isArtistHistoryModalOpen: false,
isInteractiveSearchModalOpen: false,
isManageTracksOpen: false,
isEditAlbumModalOpen: false,
allExpanded: false,
@ -88,6 +90,14 @@ class AlbumDetails extends Component {
this.setState({ isManageTracksOpen: false });
}
onInteractiveSearchPress = () => {
this.setState({ isInteractiveSearchModalOpen: true });
}
onInteractiveSearchModalClose = () => {
this.setState({ isInteractiveSearchModalOpen: false });
}
onArtistHistoryPress = () => {
this.setState({ isArtistHistoryModalOpen: true });
}
@ -147,6 +157,7 @@ class AlbumDetails extends Component {
const {
isOrganizeModalOpen,
isArtistHistoryModalOpen,
isInteractiveSearchModalOpen,
isEditAlbumModalOpen,
isManageTracksOpen,
allExpanded,
@ -173,6 +184,12 @@ class AlbumDetails extends Component {
onPress={onSearchPress}
/>
<PageToolbarButton
label="Interactive Search"
iconName={icons.INTERACTIVE}
onPress={this.onInteractiveSearchPress}
/>
<PageToolbarSeparator />
<PageToolbarButton
@ -396,6 +413,12 @@ class AlbumDetails extends Component {
onModalClose={this.onManageTracksModalClose}
/>
<InteractiveAlbumSearchModal
isOpen={isInteractiveSearchModalOpen}
albumId={id}
onModalClose={this.onInteractiveSearchModalClose}
/>
<ArtistHistoryModal
isOpen={isArtistHistoryModalOpen}
artistId={artist.id}

@ -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;

@ -8,7 +8,7 @@ import Label from 'Components/Label';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import formatTimeSpan from 'Utilities/Date/formatTimeSpan';
import AlbumSearchCellConnector from 'Album/AlbumSearchCellConnector';
import AlbumTitleDetailLink from 'Album/AlbumTitleDetailLink';
import AlbumTitleLink from 'Album/AlbumTitleLink';
import styles from './AlbumRow.css';
function getTrackCountKind(monitored, trackFileCount, trackCount) {
@ -121,7 +121,7 @@ class AlbumRow extends Component {
key={name}
className={styles.title}
>
<AlbumTitleDetailLink
<AlbumTitleLink
title={title}
foreignAlbumId={foreignAlbumId}
/>

@ -12,7 +12,7 @@ import VirtualTableRow from 'Components/Table/VirtualTableRow';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import ArtistNameLink from 'Artist/ArtistNameLink';
import AlbumTitleDetailLink from 'Album/AlbumTitleDetailLink';
import AlbumTitleLink from 'Album/AlbumTitleLink';
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
import ArtistStatusCell from './ArtistStatusCell';
@ -181,7 +181,7 @@ class ArtistIndexRow extends Component {
key={name}
className={styles[name]}
>
<AlbumTitleDetailLink
<AlbumTitleLink
title={nextAlbum.title}
foreignAlbumId={nextAlbum.foreignAlbumId}
/>
@ -205,7 +205,7 @@ class ArtistIndexRow extends Component {
key={name}
className={styles[name]}
>
<AlbumTitleDetailLink
<AlbumTitleLink
title={lastAlbum.title}
foreignAlbumId={lastAlbum.foreignAlbumId}
/>

@ -7,8 +7,6 @@ import { icons } from 'Helpers/Props';
import getStatusStyle from 'Calendar/getStatusStyle';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import albumEntities from 'Album/albumEntities';
import AlbumDetailsModal from 'Album/AlbumDetailsModal';
import CalendarEventQueueDetails from 'Calendar/Events/CalendarEventQueueDetails';
import styles from './AgendaEvent.css';
@ -109,16 +107,6 @@ class AgendaEvent extends Component {
/>
}
</Link>
<AlbumDetailsModal
isOpen={this.state.isDetailsModalOpen}
albumId={id}
albumEntity={albumEntities.CALENDAR}
artistId={artist.id}
albumTitle={title}
showOpenArtistButton={true}
onModalClose={this.onDetailsModalClose}
/>
</div>
);
}

@ -7,7 +7,6 @@ import getStatusStyle from 'Calendar/getStatusStyle';
import albumEntities from 'Album/albumEntities';
import Icon from 'Components/Icon';
import Link from 'Components/Link/Link';
import AlbumDetailsModal from 'Album/AlbumDetailsModal';
import CalendarEventQueueDetails from './CalendarEventQueueDetails';
import styles from './CalendarEvent.css';
@ -107,16 +106,6 @@ class CalendarEvent extends Component {
</div>
</div>
</Link>
<AlbumDetailsModal
isOpen={this.state.isDetailsModalOpen}
albumId={id}
albumEntity={albumEntities.CALENDAR}
artistId={artist.id}
albumTitle={title}
showOpenArtistButton={true}
onModalClose={this.onDetailsModalClose}
/>
</div>
);
}

@ -18,6 +18,7 @@ function CutoffUnmetRow(props) {
trackFileId,
artist,
releaseDate,
foreignAlbumId,
albumType,
title,
isSelected,
@ -59,12 +60,8 @@ function CutoffUnmetRow(props) {
return (
<TableRowCell key={name}>
<AlbumTitleLink
albumId={id}
artistId={artist.id}
albumEntity={albumEntities.WANTED_CUTOFF_UNMET}
albumTitle={title}
showOpenArtistButton={true}
showOpenAlbumButton={true}
foreignAlbumId={foreignAlbumId}
title={title}
/>
</TableRowCell>
);
@ -140,6 +137,7 @@ CutoffUnmetRow.propTypes = {
trackFileId: PropTypes.number,
artist: PropTypes.object.isRequired,
releaseDate: PropTypes.string.isRequired,
foreignAlbumId: PropTypes.string.isRequired,
albumType: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
isSelected: PropTypes.bool,

@ -18,6 +18,7 @@ function MissingRow(props) {
artist,
releaseDate,
albumType,
foreignAlbumId,
title,
isSelected,
columns,
@ -76,12 +77,8 @@ function MissingRow(props) {
return (
<TableRowCell key={name}>
<AlbumTitleLink
albumId={id}
artistId={artist.id}
albumEntity={albumEntities.WANTED_MISSING}
albumTitle={title}
showOpenArtistButton={true}
showOpenAlbumButton={true}
foreignAlbumId={foreignAlbumId}
title={title}
/>
</TableRowCell>
);
@ -144,6 +141,7 @@ MissingRow.propTypes = {
// trackFileId: PropTypes.number,
artist: PropTypes.object.isRequired,
releaseDate: PropTypes.string.isRequired,
foreignAlbumId: PropTypes.string.isRequired,
albumType: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
isSelected: PropTypes.bool,

Loading…
Cancel
Save