mirror of https://github.com/lidarr/Lidarr
parent
3beac03c00
commit
54e9f88648
|
@ -119,6 +119,7 @@ class HistoryRow extends Component {
|
||||||
artistId={artist.id}
|
artistId={artist.id}
|
||||||
albumTitle={album.title}
|
albumTitle={album.title}
|
||||||
showOpenArtistButton={true}
|
showOpenArtistButton={true}
|
||||||
|
showOpenAlbumButton={true}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
|
|
|
@ -161,6 +161,7 @@ class QueueRow extends Component {
|
||||||
trackFileId={album.trackFileId}
|
trackFileId={album.trackFileId}
|
||||||
albumTitle={album.title}
|
albumTitle={album.title}
|
||||||
showOpenArtistButton={true}
|
showOpenArtistButton={true}
|
||||||
|
showOpenAlbumButton={true}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
|
|
|
@ -78,6 +78,10 @@ class AddNewArtistSearchResult extends Component {
|
||||||
isSmallScreen
|
isSmallScreen
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isNewAddArtistModalOpen
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
const linkProps = isExistingArtist ? { to: `/artist/${foreignArtistId}` } : { onPress: this.onPress };
|
const linkProps = isExistingArtist ? { to: `/artist/${foreignArtistId}` } : { onPress: this.onPress };
|
||||||
let albums = '1 Album';
|
let albums = '1 Album';
|
||||||
|
|
||||||
|
@ -88,78 +92,78 @@ class AddNewArtistSearchResult extends Component {
|
||||||
const height = calculateHeight(230, isSmallScreen);
|
const height = calculateHeight(230, isSmallScreen);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<div>
|
||||||
className={styles.searchResult}
|
<Link
|
||||||
{...linkProps}
|
className={styles.searchResult}
|
||||||
>
|
{...linkProps}
|
||||||
{
|
>
|
||||||
!isSmallScreen &&
|
{
|
||||||
|
!isSmallScreen &&
|
||||||
<ArtistPoster
|
<ArtistPoster
|
||||||
className={styles.poster}
|
className={styles.poster}
|
||||||
images={images}
|
images={images}
|
||||||
size={250}
|
size={250}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.name}>
|
<div className={styles.name}>
|
||||||
{artistName}
|
{artistName}
|
||||||
|
|
||||||
{
|
{
|
||||||
!name.contains(year) && !!year &&
|
!name.contains(year) && !!year &&
|
||||||
<span className={styles.year}>({year})</span>
|
<span className={styles.year}>({year})</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!disambiguation &&
|
!!disambiguation &&
|
||||||
<span className={styles.year}>({disambiguation})</span>
|
<span className={styles.year}>({disambiguation})</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
isExistingArtist &&
|
isExistingArtist &&
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.alreadyExistsIcon}
|
className={styles.alreadyExistsIcon}
|
||||||
name={icons.CHECK_CIRCLE}
|
name={icons.CHECK_CIRCLE}
|
||||||
size={36}
|
size={36}
|
||||||
title="Already in your library"
|
title="Already in your library"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label size={sizes.LARGE}>
|
||||||
|
<HeartRating
|
||||||
|
rating={ratings.value}
|
||||||
|
iconSize={13}
|
||||||
/>
|
/>
|
||||||
}
|
</Label>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
{
|
||||||
<Label size={sizes.LARGE}>
|
!!artistType &&
|
||||||
<HeartRating
|
<Label size={sizes.LARGE}>
|
||||||
rating={ratings.value}
|
{artistType}
|
||||||
iconSize={13}
|
</Label>
|
||||||
/>
|
}
|
||||||
</Label>
|
|
||||||
|
|
||||||
{
|
{
|
||||||
!!artistType &&
|
!!albumCount &&
|
||||||
<Label size={sizes.LARGE}>
|
<Label size={sizes.LARGE}>
|
||||||
{artistType}
|
{albums}
|
||||||
</Label>
|
</Label>
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
!!albumCount &&
|
status === 'ended' &&
|
||||||
<Label size={sizes.LARGE}>
|
<Label
|
||||||
{albums}
|
kind={kinds.DANGER}
|
||||||
</Label>
|
size={sizes.LARGE}
|
||||||
}
|
>
|
||||||
|
Ended
|
||||||
|
</Label>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
{
|
|
||||||
status === 'ended' &&
|
|
||||||
<Label
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
size={sizes.LARGE}
|
|
||||||
>
|
|
||||||
Ended
|
|
||||||
</Label>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<div
|
<div
|
||||||
className={styles.overview}
|
className={styles.overview}
|
||||||
style={{
|
style={{
|
||||||
|
@ -173,10 +177,10 @@ class AddNewArtistSearchResult extends Component {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
|
|
||||||
<AddNewArtistModal
|
<AddNewArtistModal
|
||||||
isOpen={this.state.isNewAddArtistModalOpen && !isExistingArtist}
|
isOpen={isNewAddArtistModalOpen && !isExistingArtist}
|
||||||
foreignArtistId={foreignArtistId}
|
foreignArtistId={foreignArtistId}
|
||||||
artistName={artistName}
|
artistName={artistName}
|
||||||
year={year}
|
year={year}
|
||||||
|
@ -184,7 +188,7 @@ class AddNewArtistSearchResult extends Component {
|
||||||
images={images}
|
images={images}
|
||||||
onModalClose={this.onAddArtistModalClose}
|
onModalClose={this.onAddArtistModalClose}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import ReactDOM from 'react-dom';
|
||||||
import TetherComponent from 'react-tether';
|
import TetherComponent from 'react-tether';
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import SpinnerIcon from 'Components/SpinnerIcon';
|
|
||||||
import FormInputButton from 'Components/Form/FormInputButton';
|
import FormInputButton from 'Components/Form/FormInputButton';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
|
|
@ -43,7 +43,7 @@ class ImportArtistSelectFolderConnector extends Component {
|
||||||
const newRootFolders = _.differenceBy(items, prevProps.items, (item) => item.id);
|
const newRootFolders = _.differenceBy(items, prevProps.items, (item) => item.id);
|
||||||
|
|
||||||
if (newRootFolders.length === 1) {
|
if (newRootFolders.length === 1) {
|
||||||
this.props.push(`${window.Sonarr.urlBase}/add/import/${newRootFolders[0].id}`);
|
this.props.push(`${window.Lidarr.urlBase}/add/import/${newRootFolders[0].id}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,3 +42,7 @@
|
||||||
|
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.openButtons {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
|
@ -45,17 +45,20 @@ class AlbumDetailsModalContent extends Component {
|
||||||
albumId,
|
albumId,
|
||||||
artistName,
|
artistName,
|
||||||
foreignArtistId,
|
foreignArtistId,
|
||||||
|
foreignAlbumId,
|
||||||
artistMonitored,
|
artistMonitored,
|
||||||
albumTitle,
|
albumTitle,
|
||||||
monitored,
|
monitored,
|
||||||
isSaving,
|
isSaving,
|
||||||
showOpenArtistButton,
|
showOpenArtistButton,
|
||||||
|
showOpenAlbumButton,
|
||||||
startInteractiveSearch,
|
startInteractiveSearch,
|
||||||
onMonitorAlbumPress,
|
onMonitorAlbumPress,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const artistLink = `/artist/${foreignArtistId}`;
|
const artistLink = `/artist/${foreignArtistId}`;
|
||||||
|
const albumLink = `/album/${foreignAlbumId}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent
|
<ModalContent
|
||||||
|
@ -121,18 +124,30 @@ class AlbumDetailsModalContent extends Component {
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter >
|
||||||
{
|
<div className={styles.openButtons}>
|
||||||
showOpenArtistButton &&
|
{
|
||||||
<Button
|
showOpenArtistButton &&
|
||||||
className={styles.openArtistButton}
|
<Button
|
||||||
to={artistLink}
|
className={styles.openArtistButton}
|
||||||
onPress={onModalClose}
|
to={artistLink}
|
||||||
>
|
onPress={onModalClose}
|
||||||
Open Artist
|
>
|
||||||
</Button>
|
Open Artist
|
||||||
}
|
</Button>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
showOpenAlbumButton &&
|
||||||
|
<Button
|
||||||
|
className={styles.openAlbumButton}
|
||||||
|
to={albumLink}
|
||||||
|
onPress={onModalClose}
|
||||||
|
>
|
||||||
|
Open Album
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<Button
|
<Button
|
||||||
onPress={onModalClose}
|
onPress={onModalClose}
|
||||||
>
|
>
|
||||||
|
@ -150,6 +165,7 @@ AlbumDetailsModalContent.propTypes = {
|
||||||
artistId: PropTypes.number.isRequired,
|
artistId: PropTypes.number.isRequired,
|
||||||
artistName: PropTypes.string.isRequired,
|
artistName: PropTypes.string.isRequired,
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
foreignArtistId: PropTypes.string.isRequired,
|
||||||
|
foreignAlbumId: PropTypes.string.isRequired,
|
||||||
artistMonitored: PropTypes.bool.isRequired,
|
artistMonitored: PropTypes.bool.isRequired,
|
||||||
releaseDate: PropTypes.string.isRequired,
|
releaseDate: PropTypes.string.isRequired,
|
||||||
albumLabel: PropTypes.arrayOf(PropTypes.string).isRequired,
|
albumLabel: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
@ -157,6 +173,7 @@ AlbumDetailsModalContent.propTypes = {
|
||||||
monitored: PropTypes.bool.isRequired,
|
monitored: PropTypes.bool.isRequired,
|
||||||
isSaving: PropTypes.bool,
|
isSaving: PropTypes.bool,
|
||||||
showOpenArtistButton: PropTypes.bool,
|
showOpenArtistButton: PropTypes.bool,
|
||||||
|
showOpenAlbumButton: PropTypes.bool,
|
||||||
selectedTab: PropTypes.string.isRequired,
|
selectedTab: PropTypes.string.isRequired,
|
||||||
startInteractiveSearch: PropTypes.bool.isRequired,
|
startInteractiveSearch: PropTypes.bool.isRequired,
|
||||||
onMonitorAlbumPress: PropTypes.func.isRequired,
|
onMonitorAlbumPress: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -5,7 +5,6 @@ import IconButton from 'Components/Link/IconButton';
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import AlbumDetailsModal from './AlbumDetailsModal';
|
import AlbumDetailsModal from './AlbumDetailsModal';
|
||||||
import EditAlbumModalConnector from './Edit/EditAlbumModalConnector';
|
|
||||||
import styles from './AlbumSearchCell.css';
|
import styles from './AlbumSearchCell.css';
|
||||||
|
|
||||||
class AlbumSearchCell extends Component {
|
class AlbumSearchCell extends Component {
|
||||||
|
@ -17,8 +16,7 @@ class AlbumSearchCell extends Component {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isDetailsModalOpen: false,
|
isDetailsModalOpen: false
|
||||||
isEditAlbumModalOpen: false
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,14 +31,6 @@ class AlbumSearchCell extends Component {
|
||||||
this.setState({ isDetailsModalOpen: false });
|
this.setState({ isDetailsModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
onEditAlbumPress = () => {
|
|
||||||
this.setState({ isEditAlbumModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onEditAlbumModalClose = () => {
|
|
||||||
this.setState({ isEditAlbumModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
|
@ -67,12 +57,6 @@ class AlbumSearchCell extends Component {
|
||||||
onPress={this.onManualSearchPress}
|
onPress={this.onManualSearchPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IconButton
|
|
||||||
name={icons.EDIT}
|
|
||||||
title="Edit Album"
|
|
||||||
onPress={this.onEditAlbumPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AlbumDetailsModal
|
<AlbumDetailsModal
|
||||||
isOpen={this.state.isDetailsModalOpen}
|
isOpen={this.state.isDetailsModalOpen}
|
||||||
albumId={albumId}
|
albumId={albumId}
|
||||||
|
@ -84,12 +68,6 @@ class AlbumSearchCell extends Component {
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EditAlbumModalConnector
|
|
||||||
isOpen={this.state.isEditAlbumModalOpen}
|
|
||||||
albumId={albumId}
|
|
||||||
artistId={artistId}
|
|
||||||
onModalClose={this.onEditAlbumModalClose}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,9 @@ import AlbumSearchCell from './AlbumSearchCell';
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { albumId }) => albumId,
|
(state, { albumId }) => albumId,
|
||||||
(state, { sceneSeasonNumber }) => sceneSeasonNumber,
|
|
||||||
createArtistSelector(),
|
createArtistSelector(),
|
||||||
createCommandsSelector(),
|
createCommandsSelector(),
|
||||||
(albumId, sceneSeasonNumber, artist, commands) => {
|
(albumId, artist, commands) => {
|
||||||
const isSearching = _.some(commands, (command) => {
|
const isSearching = _.some(commands, (command) => {
|
||||||
const albumSearch = command.name === commandNames.ALBUM_SEARCH;
|
const albumSearch = command.name === commandNames.ALBUM_SEARCH;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import Icon from 'Components/Icon';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Label from 'Components/Label';
|
import Label from 'Components/Label';
|
||||||
import AlbumCover from 'Album/AlbumCover';
|
import AlbumCover from 'Album/AlbumCover';
|
||||||
|
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||||
import EditAlbumModalConnector from 'Album/Edit/EditAlbumModalConnector';
|
import EditAlbumModalConnector from 'Album/Edit/EditAlbumModalConnector';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import PageContent from 'Components/Page/PageContent';
|
import PageContent from 'Components/Page/PageContent';
|
||||||
|
@ -50,6 +51,7 @@ class AlbumDetails extends Component {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isOrganizeModalOpen: false,
|
||||||
isArtistHistoryModalOpen: false,
|
isArtistHistoryModalOpen: false,
|
||||||
isManageTracksOpen: false,
|
isManageTracksOpen: false,
|
||||||
isEditAlbumModalOpen: false,
|
isEditAlbumModalOpen: false,
|
||||||
|
@ -62,6 +64,14 @@ class AlbumDetails extends Component {
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
|
onOrganizePress = () => {
|
||||||
|
this.setState({ isOrganizeModalOpen: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
onOrganizeModalClose = () => {
|
||||||
|
this.setState({ isOrganizeModalOpen: false });
|
||||||
|
}
|
||||||
|
|
||||||
onEditAlbumPress = () => {
|
onEditAlbumPress = () => {
|
||||||
this.setState({ isEditAlbumModalOpen: true });
|
this.setState({ isEditAlbumModalOpen: true });
|
||||||
}
|
}
|
||||||
|
@ -135,6 +145,7 @@ class AlbumDetails extends Component {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
isOrganizeModalOpen,
|
||||||
isArtistHistoryModalOpen,
|
isArtistHistoryModalOpen,
|
||||||
isEditAlbumModalOpen,
|
isEditAlbumModalOpen,
|
||||||
isManageTracksOpen,
|
isManageTracksOpen,
|
||||||
|
@ -164,6 +175,12 @@ class AlbumDetails extends Component {
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
<PageToolbarSeparator />
|
||||||
|
|
||||||
|
<PageToolbarButton
|
||||||
|
label="Preview Rename"
|
||||||
|
iconName={icons.ORGANIZE}
|
||||||
|
onPress={this.onOrganizePress}
|
||||||
|
/>
|
||||||
|
|
||||||
<PageToolbarButton
|
<PageToolbarButton
|
||||||
label="Manage Tracks"
|
label="Manage Tracks"
|
||||||
iconName={icons.TRACK_FILE}
|
iconName={icons.TRACK_FILE}
|
||||||
|
@ -364,6 +381,13 @@ class AlbumDetails extends Component {
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<OrganizePreviewModalConnector
|
||||||
|
isOpen={isOrganizeModalOpen}
|
||||||
|
artistId={artist.id}
|
||||||
|
albumId={id}
|
||||||
|
onModalClose={this.onOrganizeModalClose}
|
||||||
|
/>
|
||||||
|
|
||||||
<TrackFileEditorModal
|
<TrackFileEditorModal
|
||||||
isOpen={isManageTracksOpen}
|
isOpen={isManageTracksOpen}
|
||||||
artistId={artist.id}
|
artistId={artist.id}
|
||||||
|
|
|
@ -26,7 +26,7 @@ class EditAlbumModalContent extends Component {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -2,41 +2,9 @@ import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import DocumentTitle from 'react-document-title';
|
import DocumentTitle from 'react-document-title';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import { Route, Redirect } from 'react-router-dom';
|
|
||||||
import { ConnectedRouter } from 'react-router-redux';
|
import { ConnectedRouter } from 'react-router-redux';
|
||||||
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
|
|
||||||
import NotFound from 'Components/NotFound';
|
|
||||||
import Switch from 'Components/Router/Switch';
|
|
||||||
import PageConnector from 'Components/Page/PageConnector';
|
import PageConnector from 'Components/Page/PageConnector';
|
||||||
import ArtistIndexConnector from 'Artist/Index/ArtistIndexConnector';
|
import AppRoutes from './AppRoutes';
|
||||||
import AddNewArtistConnector from 'AddArtist/AddNewArtist/AddNewArtistConnector';
|
|
||||||
import ImportArtist from 'AddArtist/ImportArtist/ImportArtist';
|
|
||||||
import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector';
|
|
||||||
import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector';
|
|
||||||
import ArtistDetailsPageConnector from 'Artist/Details/ArtistDetailsPageConnector';
|
|
||||||
import AlbumDetailsPageConnector from 'Album/Details/AlbumDetailsPageConnector';
|
|
||||||
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
|
|
||||||
import HistoryConnector from 'Activity/History/HistoryConnector';
|
|
||||||
import QueueConnector from 'Activity/Queue/QueueConnector';
|
|
||||||
import BlacklistConnector from 'Activity/Blacklist/BlacklistConnector';
|
|
||||||
import MissingConnector from 'Wanted/Missing/MissingConnector';
|
|
||||||
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
|
|
||||||
import Settings from 'Settings/Settings';
|
|
||||||
import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementConnector';
|
|
||||||
import Profiles from 'Settings/Profiles/Profiles';
|
|
||||||
import Quality from 'Settings/Quality/Quality';
|
|
||||||
import IndexerSettings from 'Settings/Indexers/IndexerSettings';
|
|
||||||
import DownloadClientSettings from 'Settings/DownloadClients/DownloadClientSettings';
|
|
||||||
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
|
|
||||||
import MetadataSettings from 'Settings/Metadata/MetadataSettings';
|
|
||||||
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
|
||||||
import UISettingsConnector from 'Settings/UI/UISettingsConnector';
|
|
||||||
import Status from 'System/Status/Status';
|
|
||||||
import TasksConnector from 'System/Tasks/TasksConnector';
|
|
||||||
import BackupsConnector from 'System/Backup/BackupsConnector';
|
|
||||||
import UpdatesConnector from 'System/Updates/UpdatesConnector';
|
|
||||||
import LogsTableConnector from 'System/Events/LogsTableConnector';
|
|
||||||
import Logs from 'System/Logs/Logs';
|
|
||||||
|
|
||||||
function App({ store, history }) {
|
function App({ store, history }) {
|
||||||
return (
|
return (
|
||||||
|
@ -44,205 +12,7 @@ function App({ store, history }) {
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<ConnectedRouter history={history}>
|
<ConnectedRouter history={history}>
|
||||||
<PageConnector>
|
<PageConnector>
|
||||||
<Switch>
|
<AppRoutes app={App} />
|
||||||
{/*
|
|
||||||
Artist
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<Route
|
|
||||||
exact={true}
|
|
||||||
path="/"
|
|
||||||
component={ArtistIndexConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{
|
|
||||||
window.Sonarr.urlBase &&
|
|
||||||
<Route
|
|
||||||
exact={true}
|
|
||||||
path="/"
|
|
||||||
addUrlBase={false}
|
|
||||||
render={() => {
|
|
||||||
return (
|
|
||||||
<Redirect
|
|
||||||
to={getPathWithUrlBase('/')}
|
|
||||||
component={App}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/add/new"
|
|
||||||
component={AddNewArtistConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/add/import"
|
|
||||||
component={ImportArtist}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/artisteditor"
|
|
||||||
component={ArtistEditorConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/albumstudio"
|
|
||||||
component={AlbumStudioConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/artist/:foreignArtistId"
|
|
||||||
component={ArtistDetailsPageConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/album/:foreignAlbumId"
|
|
||||||
component={AlbumDetailsPageConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/*
|
|
||||||
Calendar
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/calendar"
|
|
||||||
component={CalendarPageConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/*
|
|
||||||
Activity
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/activity/history"
|
|
||||||
component={HistoryConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/activity/queue"
|
|
||||||
component={QueueConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/activity/blacklist"
|
|
||||||
component={BlacklistConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/*
|
|
||||||
Wanted
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/wanted/missing"
|
|
||||||
component={MissingConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/wanted/cutoffunmet"
|
|
||||||
component={CutoffUnmetConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/*
|
|
||||||
Settings
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<Route
|
|
||||||
exact={true}
|
|
||||||
path="/settings"
|
|
||||||
component={Settings}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/settings/mediamanagement"
|
|
||||||
component={MediaManagementConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/settings/profiles"
|
|
||||||
component={Profiles}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/settings/quality"
|
|
||||||
component={Quality}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/settings/indexers"
|
|
||||||
component={IndexerSettings}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/settings/downloadclients"
|
|
||||||
component={DownloadClientSettings}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/settings/connect"
|
|
||||||
component={NotificationSettings}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/settings/metadata"
|
|
||||||
component={MetadataSettings}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/settings/general"
|
|
||||||
component={GeneralSettingsConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/settings/ui"
|
|
||||||
component={UISettingsConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/*
|
|
||||||
System
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/system/status"
|
|
||||||
component={Status}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/system/tasks"
|
|
||||||
component={TasksConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/system/backup"
|
|
||||||
component={BackupsConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/system/updates"
|
|
||||||
component={UpdatesConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/system/events"
|
|
||||||
component={LogsTableConnector}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="/system/logs/files"
|
|
||||||
component={Logs}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/*
|
|
||||||
Not Found
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<Route
|
|
||||||
path="*"
|
|
||||||
component={NotFound}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</Switch>
|
|
||||||
</PageConnector>
|
</PageConnector>
|
||||||
</ConnectedRouter>
|
</ConnectedRouter>
|
||||||
</Provider>
|
</Provider>
|
||||||
|
|
|
@ -0,0 +1,249 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import { Route, Redirect } from 'react-router-dom';
|
||||||
|
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
|
||||||
|
import NotFound from 'Components/NotFound';
|
||||||
|
import Switch from 'Components/Router/Switch';
|
||||||
|
import ArtistIndexConnector from 'Artist/Index/ArtistIndexConnector';
|
||||||
|
import AddNewArtistConnector from 'AddArtist/AddNewArtist/AddNewArtistConnector';
|
||||||
|
import ImportArtist from 'AddArtist/ImportArtist/ImportArtist';
|
||||||
|
import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector';
|
||||||
|
import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector';
|
||||||
|
import ArtistDetailsPageConnector from 'Artist/Details/ArtistDetailsPageConnector';
|
||||||
|
import AlbumDetailsPageConnector from 'Album/Details/AlbumDetailsPageConnector';
|
||||||
|
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
|
||||||
|
import HistoryConnector from 'Activity/History/HistoryConnector';
|
||||||
|
import QueueConnector from 'Activity/Queue/QueueConnector';
|
||||||
|
import BlacklistConnector from 'Activity/Blacklist/BlacklistConnector';
|
||||||
|
import MissingConnector from 'Wanted/Missing/MissingConnector';
|
||||||
|
import CutoffUnmetConnector from 'Wanted/CutoffUnmet/CutoffUnmetConnector';
|
||||||
|
import Settings from 'Settings/Settings';
|
||||||
|
import MediaManagementConnector from 'Settings/MediaManagement/MediaManagementConnector';
|
||||||
|
import Profiles from 'Settings/Profiles/Profiles';
|
||||||
|
import Quality from 'Settings/Quality/Quality';
|
||||||
|
import IndexerSettings from 'Settings/Indexers/IndexerSettings';
|
||||||
|
import DownloadClientSettings from 'Settings/DownloadClients/DownloadClientSettings';
|
||||||
|
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
|
||||||
|
import MetadataSettings from 'Settings/Metadata/MetadataSettings';
|
||||||
|
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
||||||
|
import UISettingsConnector from 'Settings/UI/UISettingsConnector';
|
||||||
|
import Status from 'System/Status/Status';
|
||||||
|
import TasksConnector from 'System/Tasks/TasksConnector';
|
||||||
|
import BackupsConnector from 'System/Backup/BackupsConnector';
|
||||||
|
import UpdatesConnector from 'System/Updates/UpdatesConnector';
|
||||||
|
import LogsTableConnector from 'System/Events/LogsTableConnector';
|
||||||
|
import Logs from 'System/Logs/Logs';
|
||||||
|
|
||||||
|
function AppRoutes(props) {
|
||||||
|
const {
|
||||||
|
app
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
{/*
|
||||||
|
Artist
|
||||||
|
*/}
|
||||||
|
|
||||||
|
<Route
|
||||||
|
exact={true}
|
||||||
|
path="/"
|
||||||
|
component={ArtistIndexConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
window.Lidarr.urlBase &&
|
||||||
|
<Route
|
||||||
|
exact={true}
|
||||||
|
path="/"
|
||||||
|
addUrlBase={false}
|
||||||
|
render={() => {
|
||||||
|
return (
|
||||||
|
<Redirect
|
||||||
|
to={getPathWithUrlBase('/')}
|
||||||
|
component={app}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/add/new"
|
||||||
|
component={AddNewArtistConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/add/import"
|
||||||
|
component={ImportArtist}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/artisteditor"
|
||||||
|
component={ArtistEditorConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/albumstudio"
|
||||||
|
component={AlbumStudioConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/artist/:foreignArtistId"
|
||||||
|
component={ArtistDetailsPageConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/album/:foreignAlbumId"
|
||||||
|
component={AlbumDetailsPageConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
Calendar
|
||||||
|
*/}
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/calendar"
|
||||||
|
component={CalendarPageConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
Activity
|
||||||
|
*/}
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/activity/history"
|
||||||
|
component={HistoryConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/activity/queue"
|
||||||
|
component={QueueConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/activity/blacklist"
|
||||||
|
component={BlacklistConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
Wanted
|
||||||
|
*/}
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/wanted/missing"
|
||||||
|
component={MissingConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/wanted/cutoffunmet"
|
||||||
|
component={CutoffUnmetConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
Settings
|
||||||
|
*/}
|
||||||
|
|
||||||
|
<Route
|
||||||
|
exact={true}
|
||||||
|
path="/settings"
|
||||||
|
component={Settings}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/mediamanagement"
|
||||||
|
component={MediaManagementConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/profiles"
|
||||||
|
component={Profiles}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/quality"
|
||||||
|
component={Quality}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/indexers"
|
||||||
|
component={IndexerSettings}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/downloadclients"
|
||||||
|
component={DownloadClientSettings}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/connect"
|
||||||
|
component={NotificationSettings}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/metadata"
|
||||||
|
component={MetadataSettings}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/general"
|
||||||
|
component={GeneralSettingsConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/ui"
|
||||||
|
component={UISettingsConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
System
|
||||||
|
*/}
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/system/status"
|
||||||
|
component={Status}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/system/tasks"
|
||||||
|
component={TasksConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/system/backup"
|
||||||
|
component={BackupsConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/system/updates"
|
||||||
|
component={UpdatesConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/system/events"
|
||||||
|
component={LogsTableConnector}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/system/logs/files"
|
||||||
|
component={Logs}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/*
|
||||||
|
Not Found
|
||||||
|
*/}
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="*"
|
||||||
|
component={NotFound}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AppRoutes.propTypes = {
|
||||||
|
app: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppRoutes;
|
|
@ -33,7 +33,7 @@ function createMapDispatchToProps(dispatch, props) {
|
||||||
},
|
},
|
||||||
|
|
||||||
onSeeChangesPress() {
|
onSeeChangesPress() {
|
||||||
window.location = `${window.Sonarr.urlBase}/system/updates`;
|
window.location = `${window.Lidarr.urlBase}/system/updates`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ class ArtistDetailsPageConnector extends Component {
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (!this.props.foreignArtistId) {
|
if (!this.props.foreignArtistId) {
|
||||||
this.props.push(`${window.Sonarr.urlBase}/`);
|
this.props.push(`${window.Lidarr.urlBase}/`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ class EditArtistModalContent extends Component {
|
||||||
this.props.onSavePress(true);
|
this.props.onSavePress(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { icons } from 'Helpers/Props';
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
import fonts from 'Styles/Variables/fonts';
|
import fonts from 'Styles/Variables/fonts';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
import ArtistPoster from 'Artist/ArtistPoster';
|
import ArtistPoster from 'Artist/ArtistPoster';
|
||||||
|
|
|
@ -5,6 +5,5 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.statusIcon {
|
.statusIcon {
|
||||||
margin-right: 6px;
|
width: 20px !important;
|
||||||
width: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,16 +17,9 @@ function createMapStateToProps() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch) {
|
const mapDispatchToProps = {
|
||||||
return {
|
onNavigatePrevious: gotoCalendarPreviousRange,
|
||||||
onNavigatePrevious() {
|
onNavigateNext: gotoCalendarNextRange
|
||||||
dispatch(gotoCalendarPreviousRange());
|
};
|
||||||
},
|
|
||||||
|
|
||||||
onNavigateNext() {
|
export default connect(createMapStateToProps, mapDispatchToProps)(CalendarDays);
|
||||||
dispatch(gotoCalendarNextRange());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(CalendarDays);
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ function getUrls(state) {
|
||||||
tags
|
tags
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
let icalUrl = `${window.location.host}${window.Sonarr.urlBase}/feed/v1/calendar/Lidarr.ics?`;
|
let icalUrl = `${window.location.host}${window.Lidarr.urlBase}/feed/v1/calendar/Lidarr.ics?`;
|
||||||
|
|
||||||
if (unmonitored) {
|
if (unmonitored) {
|
||||||
icalUrl += 'unmonitored=true&';
|
icalUrl += 'unmonitored=true&';
|
||||||
|
@ -40,7 +40,7 @@ function getUrls(state) {
|
||||||
icalUrl += `tags=${tags.toString()}&`;
|
icalUrl += `tags=${tags.toString()}&`;
|
||||||
}
|
}
|
||||||
|
|
||||||
icalUrl += `apikey=${window.Sonarr.apiKey}`;
|
icalUrl += `apikey=${window.Lidarr.apiKey}`;
|
||||||
|
|
||||||
const iCalHttpUrl = `${window.location.protocol}//${icalUrl}`;
|
const iCalHttpUrl = `${window.location.protocol}//${icalUrl}`;
|
||||||
const iCalWebCalUrl = `webcal://${icalUrl}`;
|
const iCalWebCalUrl = `webcal://${icalUrl}`;
|
||||||
|
|
|
@ -14,3 +14,8 @@
|
||||||
.scroller {
|
.scroller {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
|
@ -3,11 +3,12 @@ import React, { Component } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { scrollDirections } from 'Helpers/Props';
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import Scroller from 'Components/Scroller/Scroller';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import Scroller from 'Components/Scroller/Scroller';
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import PathInput from 'Components/Form/PathInput';
|
import PathInput from 'Components/Form/PathInput';
|
||||||
|
@ -43,12 +44,15 @@ class FileBrowserModalContent extends Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps, prevState) {
|
||||||
const {
|
const {
|
||||||
currentPath
|
currentPath
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (currentPath !== this.state.currentPath) {
|
if (
|
||||||
|
currentPath !== this.state.currentPath &&
|
||||||
|
currentPath !== prevState.currentPath
|
||||||
|
) {
|
||||||
this.setState({ currentPath });
|
this.setState({ currentPath });
|
||||||
this._scrollerNode.scrollTop = 0;
|
this._scrollerNode.scrollTop = 0;
|
||||||
}
|
}
|
||||||
|
@ -91,6 +95,9 @@ class FileBrowserModalContent extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
parent,
|
parent,
|
||||||
directories,
|
directories,
|
||||||
files,
|
files,
|
||||||
|
@ -125,61 +132,77 @@ class FileBrowserModalContent extends Component {
|
||||||
ref={this.setScrollerRef}
|
ref={this.setScrollerRef}
|
||||||
className={styles.scroller}
|
className={styles.scroller}
|
||||||
>
|
>
|
||||||
<Table columns={columns}>
|
{
|
||||||
<TableBody>
|
!!error &&
|
||||||
{
|
<div>Error loading contents</div>
|
||||||
emptyParent &&
|
}
|
||||||
<FileBrowserRow
|
|
||||||
type="computer"
|
|
||||||
name="My Computer"
|
|
||||||
path={parent}
|
|
||||||
onPress={this.onRowPress}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
!emptyParent && parent &&
|
isPopulated && !error &&
|
||||||
<FileBrowserRow
|
<Table columns={columns}>
|
||||||
type="parent"
|
<TableBody>
|
||||||
name="..."
|
{
|
||||||
path={parent}
|
emptyParent &&
|
||||||
onPress={this.onRowPress}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
directories.map((directory) => {
|
|
||||||
return (
|
|
||||||
<FileBrowserRow
|
<FileBrowserRow
|
||||||
key={directory.path}
|
type="computer"
|
||||||
type={directory.type}
|
name="My Computer"
|
||||||
name={directory.name}
|
path={parent}
|
||||||
path={directory.path}
|
|
||||||
onPress={this.onRowPress}
|
onPress={this.onRowPress}
|
||||||
/>
|
/>
|
||||||
);
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
files.map((file) => {
|
!emptyParent && parent &&
|
||||||
return (
|
|
||||||
<FileBrowserRow
|
<FileBrowserRow
|
||||||
key={file.path}
|
type="parent"
|
||||||
type={file.type}
|
name="..."
|
||||||
name={file.name}
|
path={parent}
|
||||||
path={file.path}
|
|
||||||
onPress={this.onRowPress}
|
onPress={this.onRowPress}
|
||||||
/>
|
/>
|
||||||
);
|
}
|
||||||
})
|
|
||||||
}
|
{
|
||||||
</TableBody>
|
directories.map((directory) => {
|
||||||
</Table>
|
return (
|
||||||
|
<FileBrowserRow
|
||||||
|
key={directory.path}
|
||||||
|
type={directory.type}
|
||||||
|
name={directory.name}
|
||||||
|
path={directory.path}
|
||||||
|
onPress={this.onRowPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
files.map((file) => {
|
||||||
|
return (
|
||||||
|
<FileBrowserRow
|
||||||
|
key={file.path}
|
||||||
|
type={file.type}
|
||||||
|
name={file.name}
|
||||||
|
path={file.path}
|
||||||
|
onPress={this.onRowPress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
</Scroller>
|
</Scroller>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
{
|
||||||
|
isFetching &&
|
||||||
|
<LoadingIndicator
|
||||||
|
className={styles.loading}
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onPress={onModalClose}
|
onPress={onModalClose}
|
||||||
>
|
>
|
||||||
|
@ -200,6 +223,9 @@ class FileBrowserModalContent extends Component {
|
||||||
FileBrowserModalContent.propTypes = {
|
FileBrowserModalContent.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
|
isFetching: PropTypes.bool.isRequired,
|
||||||
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
parent: PropTypes.string,
|
parent: PropTypes.string,
|
||||||
currentPath: PropTypes.string.isRequired,
|
currentPath: PropTypes.string.isRequired,
|
||||||
directories: PropTypes.arrayOf(PropTypes.object).isRequired,
|
directories: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|
|
@ -11,6 +11,9 @@ function createMapStateToProps() {
|
||||||
(state) => state.paths,
|
(state) => state.paths,
|
||||||
(paths) => {
|
(paths) => {
|
||||||
const {
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
parent,
|
parent,
|
||||||
currentPath,
|
currentPath,
|
||||||
directories,
|
directories,
|
||||||
|
@ -22,6 +25,9 @@ function createMapStateToProps() {
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
parent,
|
parent,
|
||||||
currentPath,
|
currentPath,
|
||||||
directories,
|
directories,
|
||||||
|
|
|
@ -8,12 +8,23 @@ class NumberInput extends Component {
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onChange = ({ name, value }) => {
|
onChange = ({ name, value }) => {
|
||||||
|
const {
|
||||||
|
min,
|
||||||
|
max
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
let newValue = null;
|
let newValue = null;
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
newValue = this.props.isFloat ? parseFloat(value) : parseInt(value);
|
newValue = this.props.isFloat ? parseFloat(value) : parseInt(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (min != null && newValue < min) {
|
||||||
|
newValue = min;
|
||||||
|
} else if (max != null && newValue > max) {
|
||||||
|
newValue = max;
|
||||||
|
}
|
||||||
|
|
||||||
this.props.onChange({
|
this.props.onChange({
|
||||||
name,
|
name,
|
||||||
value: newValue
|
value: newValue
|
||||||
|
@ -40,6 +51,8 @@ class NumberInput extends Component {
|
||||||
|
|
||||||
NumberInput.propTypes = {
|
NumberInput.propTypes = {
|
||||||
value: PropTypes.number,
|
value: PropTypes.number,
|
||||||
|
min: PropTypes.number,
|
||||||
|
max: PropTypes.number,
|
||||||
isFloat: PropTypes.bool.isRequired,
|
isFloat: PropTypes.bool.isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
onChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
@ -98,7 +98,7 @@ class ClipboardButton extends Component {
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
<span className={showStateIcon && styles.showStateIcon}>
|
<span className={showStateIcon ? styles.showStateIcon : undefined}>
|
||||||
{
|
{
|
||||||
showSuccess &&
|
showSuccess &&
|
||||||
<span className={styles.stateIconContainer}>
|
<span className={styles.stateIconContainer}>
|
||||||
|
|
|
@ -41,7 +41,8 @@ IconButton.propTypes = {
|
||||||
};
|
};
|
||||||
|
|
||||||
IconButton.defaultProps = {
|
IconButton.defaultProps = {
|
||||||
className: styles.button
|
className: styles.button,
|
||||||
|
size: 12
|
||||||
};
|
};
|
||||||
|
|
||||||
export default IconButton;
|
export default IconButton;
|
||||||
|
|
|
@ -47,13 +47,13 @@ class Link extends Component {
|
||||||
el = 'a';
|
el = 'a';
|
||||||
linkProps.href = to;
|
linkProps.href = to;
|
||||||
linkProps.target = target || '_self';
|
linkProps.target = target || '_self';
|
||||||
} else if (to.startsWith(window.Sonarr.urlBase)) {
|
} else if (to.startsWith(window.Lidarr.urlBase)) {
|
||||||
el = RouterLink;
|
el = RouterLink;
|
||||||
linkProps.to = to;
|
linkProps.to = to;
|
||||||
linkProps.target = target;
|
linkProps.target = target;
|
||||||
} else {
|
} else {
|
||||||
el = RouterLink;
|
el = RouterLink;
|
||||||
linkProps.to = `${window.Sonarr.urlBase}/${to.replace(/^\//, '')}`;
|
linkProps.to = `${window.Lidarr.urlBase}/${to.replace(/^\//, '')}`;
|
||||||
linkProps.target = target;
|
linkProps.target = target;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,6 @@
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
left: 100%;
|
left: 100%;
|
||||||
opacity: 0;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ class SpinnerErrorButton extends Component {
|
||||||
isSpinning={isSpinning}
|
isSpinning={isSpinning}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
<span className={showIcon && styles.showIcon}>
|
<span className={showIcon ? styles.showIcon : undefined}>
|
||||||
{
|
{
|
||||||
showIcon &&
|
showIcon &&
|
||||||
<span className={styles.iconContainer}>
|
<span className={styles.iconContainer}>
|
||||||
|
|
|
@ -16,6 +16,7 @@ function SpinnerIconButton(props) {
|
||||||
<IconButton
|
<IconButton
|
||||||
name={isSpinning ? (spinningName || name) : name}
|
name={isSpinning ? (spinningName || name) : name}
|
||||||
isDisabled={isDisabled || isSpinning}
|
isDisabled={isDisabled || isSpinning}
|
||||||
|
isSpinning={isSpinning}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import Portal from 'react-portal';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import elementClass from 'element-class';
|
import elementClass from 'element-class';
|
||||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||||
|
@ -27,6 +26,8 @@ class Modal extends Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
this._node = document.getElementById('modal-root');
|
||||||
|
this._backgroundRef = null;
|
||||||
this._modalId = getUniqueElememtId();
|
this._modalId = getUniqueElememtId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,6 +58,10 @@ class Modal extends Component {
|
||||||
//
|
//
|
||||||
// Control
|
// Control
|
||||||
|
|
||||||
|
_setBackgroundRef = (ref) => {
|
||||||
|
this._backgroundRef = ref;
|
||||||
|
}
|
||||||
|
|
||||||
_openModal() {
|
_openModal() {
|
||||||
openModals.push(this._modalId);
|
openModals.push(this._modalId);
|
||||||
window.addEventListener('keydown', this.onKeyDown);
|
window.addEventListener('keydown', this.onKeyDown);
|
||||||
|
@ -79,9 +84,9 @@ class Modal extends Component {
|
||||||
const targetElement = this._findEventTarget(event);
|
const targetElement = this._findEventTarget(event);
|
||||||
|
|
||||||
if (targetElement) {
|
if (targetElement) {
|
||||||
const modalElement = ReactDOM.findDOMNode(this.refs.modal);
|
const backgroundElement = ReactDOM.findDOMNode(this._backgroundRef);
|
||||||
|
|
||||||
return !modalElement || !modalElement.contains(targetElement);
|
return backgroundElement.isEqualNode(targetElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -138,10 +143,6 @@ class Modal extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClosePress = (event) => {
|
|
||||||
this.props.onModalClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
|
@ -155,36 +156,32 @@ class Modal extends Component {
|
||||||
isOpen
|
isOpen
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
if (!isOpen) {
|
||||||
<Portal
|
return null;
|
||||||
isOpened={isOpen}
|
}
|
||||||
|
|
||||||
|
return ReactDOM.createPortal(
|
||||||
|
<div
|
||||||
|
className={styles.modalContainer}
|
||||||
>
|
>
|
||||||
<div>
|
<div
|
||||||
{
|
ref={this._setBackgroundRef}
|
||||||
isOpen &&
|
className={backdropClassName}
|
||||||
<div
|
onMouseDown={this.onBackdropBeginPress}
|
||||||
className={styles.modalContainer}
|
onMouseUp={this.onBackdropEndPress}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={backdropClassName}
|
className={classNames(
|
||||||
onMouseDown={this.onBackdropBeginPress}
|
className,
|
||||||
onMouseUp={this.onBackdropEndPress}
|
styles[size]
|
||||||
>
|
)}
|
||||||
<div
|
style={style}
|
||||||
ref="modal"
|
>
|
||||||
className={classNames(
|
{children}
|
||||||
className,
|
</div>
|
||||||
styles[size]
|
|
||||||
)}
|
|
||||||
style={style}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</Portal>
|
</div>,
|
||||||
|
this._node
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ function NotFound({ message }) {
|
||||||
|
|
||||||
<img
|
<img
|
||||||
className={styles.image}
|
className={styles.image}
|
||||||
src={`${window.Sonarr.urlBase}/Content/Images/404.png`}
|
src={`${window.Lidarr.urlBase}/Content/Images/404.png`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
|
|
|
@ -19,11 +19,11 @@ function createMapStateToProps() {
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
return {
|
return {
|
||||||
onGoToArtist(foreignArtistId) {
|
onGoToArtist(foreignArtistId) {
|
||||||
dispatch(push(`${window.Sonarr.urlBase}/artist/${foreignArtistId}`));
|
dispatch(push(`${window.Lidarr.urlBase}/artist/${foreignArtistId}`));
|
||||||
},
|
},
|
||||||
|
|
||||||
onGoToAddNewArtist(query) {
|
onGoToAddNewArtist(query) {
|
||||||
dispatch(push(`${window.Sonarr.urlBase}/add/new?term=${encodeURIComponent(query)}`));
|
dispatch(push(`${window.Lidarr.urlBase}/add/new?term=${encodeURIComponent(query)}`));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,10 @@ class PageHeader extends Component {
|
||||||
return (
|
return (
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className={styles.logoContainer}>
|
<div className={styles.logoContainer}>
|
||||||
<Link to={`${window.Sonarr.urlBase}/`}>
|
<Link to={`${window.Lidarr.urlBase}/`}>
|
||||||
<img
|
<img
|
||||||
className={styles.logo}
|
className={styles.logo}
|
||||||
src={`${window.Sonarr.urlBase}/Content/Images/logo.svg`}
|
src={`${window.Lidarr.urlBase}/Content/Images/logo.svg`}
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,6 +74,7 @@ class PageHeader extends Component {
|
||||||
className={styles.donate}
|
className={styles.donate}
|
||||||
name={icons.HEART}
|
name={icons.HEART}
|
||||||
to="https://www.paypal.me/Lidarr"
|
to="https://www.paypal.me/Lidarr"
|
||||||
|
size={14}
|
||||||
/>
|
/>
|
||||||
<PageHeaderActionsMenuConnector
|
<PageHeaderActionsMenuConnector
|
||||||
onKeyboardShortcutsPress={this.onOpenKeyboardShortcutsModal}
|
onKeyboardShortcutsPress={this.onOpenKeyboardShortcutsModal}
|
||||||
|
|
|
@ -61,7 +61,7 @@ function PageHeaderActionsMenu(props) {
|
||||||
{
|
{
|
||||||
formsAuth &&
|
formsAuth &&
|
||||||
<MenuItem
|
<MenuItem
|
||||||
to={`${window.Sonarr.urlBase}/logout`}
|
to={`${window.Lidarr.urlBase}/logout`}
|
||||||
noRouter={true}
|
noRouter={true}
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
|
|
|
@ -15,7 +15,7 @@ import LoadingPage from './LoadingPage';
|
||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
|
|
||||||
function testLocalStorage() {
|
function testLocalStorage() {
|
||||||
const key = 'sonarrTest';
|
const key = 'lidarrTest';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(key, key);
|
localStorage.setItem(key, key);
|
||||||
|
@ -64,7 +64,7 @@ function createMapStateToProps() {
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
return {
|
return {
|
||||||
dispatchFetchSeries() {
|
dispatchFetchArtist() {
|
||||||
dispatch(fetchArtist());
|
dispatch(fetchArtist());
|
||||||
},
|
},
|
||||||
dispatchFetchTags() {
|
dispatchFetchTags() {
|
||||||
|
@ -109,7 +109,7 @@ class PageConnector extends Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (!this.props.isPopulated) {
|
if (!this.props.isPopulated) {
|
||||||
this.props.dispatchFetchSeries();
|
this.props.dispatchFetchArtist();
|
||||||
this.props.dispatchFetchTags();
|
this.props.dispatchFetchTags();
|
||||||
this.props.dispatchFetchQualityProfiles();
|
this.props.dispatchFetchQualityProfiles();
|
||||||
this.props.dispatchFetchLanguageProfiles();
|
this.props.dispatchFetchLanguageProfiles();
|
||||||
|
@ -133,7 +133,7 @@ class PageConnector extends Component {
|
||||||
const {
|
const {
|
||||||
isPopulated,
|
isPopulated,
|
||||||
hasError,
|
hasError,
|
||||||
dispatchFetchSeries,
|
dispatchFetchArtist,
|
||||||
dispatchFetchTags,
|
dispatchFetchTags,
|
||||||
dispatchFetchQualityProfiles,
|
dispatchFetchQualityProfiles,
|
||||||
dispatchFetchLanguageProfiles,
|
dispatchFetchLanguageProfiles,
|
||||||
|
@ -171,7 +171,7 @@ PageConnector.propTypes = {
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
hasError: PropTypes.bool.isRequired,
|
hasError: PropTypes.bool.isRequired,
|
||||||
isSidebarVisible: PropTypes.bool.isRequired,
|
isSidebarVisible: PropTypes.bool.isRequired,
|
||||||
dispatchFetchSeries: PropTypes.func.isRequired,
|
dispatchFetchArtist: PropTypes.func.isRequired,
|
||||||
dispatchFetchTags: PropTypes.func.isRequired,
|
dispatchFetchTags: PropTypes.func.isRequired,
|
||||||
dispatchFetchQualityProfiles: PropTypes.func.isRequired,
|
dispatchFetchQualityProfiles: PropTypes.func.isRequired,
|
||||||
dispatchFetchLanguageProfiles: PropTypes.func.isRequired,
|
dispatchFetchLanguageProfiles: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -415,7 +415,7 @@ class PageSidebar extends Component {
|
||||||
transform
|
transform
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const urlBase = window.Sonarr.urlBase;
|
const urlBase = window.Lidarr.urlBase;
|
||||||
const pathname = urlBase ? location.pathname.substr(urlBase.length) || '/' : location.pathname;
|
const pathname = urlBase ? location.pathname.substr(urlBase.length) || '/' : location.pathname;
|
||||||
const activeParent = getActiveParent(pathname);
|
const activeParent = getActiveParent(pathname);
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ class SignalRConnector extends Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
console.log('Starting signalR');
|
console.log('Starting signalR');
|
||||||
|
|
||||||
this.signalRconnection = $.connection('/signalr', { apiKey: window.Sonarr.apiKey });
|
this.signalRconnection = $.connection('/signalr', { apiKey: window.Lidarr.apiKey });
|
||||||
|
|
||||||
this.signalRconnection.stateChanged(this.onStateChanged);
|
this.signalRconnection.stateChanged(this.onStateChanged);
|
||||||
this.signalRconnection.received(this.onReceived);
|
this.signalRconnection.received(this.onReceived);
|
||||||
|
@ -232,11 +232,12 @@ class SignalRConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTrackFile = (body) => {
|
handleTrackFile = (body) => {
|
||||||
|
const section = 'trackFiles';
|
||||||
|
|
||||||
if (body.action === 'updated') {
|
if (body.action === 'updated') {
|
||||||
this.props.updateItem({
|
this.props.updateItem({ section, ...body.resource });
|
||||||
section: 'trackFiles',
|
} else if (body.action === 'deleted') {
|
||||||
...body.resource
|
this.props.removeItem({ section, id: body.resource.id });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,7 +336,7 @@ class SignalRConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onReconnecting = () => {
|
onReconnecting = () => {
|
||||||
if (window.Sonarr.unloading) {
|
if (window.Lidarr.unloading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,7 +350,7 @@ class SignalRConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onDisconnected = () => {
|
onDisconnected = () => {
|
||||||
if (window.Sonarr.unloading) {
|
if (window.Lidarr.unloading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,15 @@ function SpinnerIcon(props) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
name={isSpinning ? (spinningName || name) : name}
|
name={isSpinning ? (spinningName || name) : name}
|
||||||
|
isSpinning={isSpinning}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
SpinnerIcon.propTypes = {
|
SpinnerIcon.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.object.isRequired,
|
||||||
spinningName: PropTypes.string.isRequired,
|
spinningName: PropTypes.object.isRequired,
|
||||||
isSpinning: PropTypes.bool.isRequired
|
isSpinning: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ function TableOptionsColumn(props) {
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={!isModifiable && styles.notDragable}>
|
<div className={isModifiable ? undefined : styles.notDragable}>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
styles.column,
|
styles.column,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
.button {
|
||||||
|
composes: toolbarButton from 'Components/Page/Toolbar/PageToolbarButton.css';
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labelContainer {
|
||||||
|
composes: labelContainer from 'Components/Page/Toolbar/PageToolbarButton.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
composes: label from 'Components/Page/Toolbar/PageToolbarButton.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicatorContainer {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicatorBackground {
|
||||||
|
color: $themeDarkColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enabled {
|
||||||
|
color: $successColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
color: $dangerColor;
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import Link from 'Components/Link/Link';
|
||||||
|
import styles from './AdvancedSettingsButton.css';
|
||||||
|
|
||||||
|
function AdvancedSettingsButton(props) {
|
||||||
|
const {
|
||||||
|
advancedSettings,
|
||||||
|
onAdvancedSettingsPress
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
className={styles.button}
|
||||||
|
title={advancedSettings ? 'Shown, click to hide' : 'Hidden, click to show'}
|
||||||
|
onPress={onAdvancedSettingsPress}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
name={icons.ADVANCED_SETTINGS}
|
||||||
|
size={21}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
styles.indicatorContainer,
|
||||||
|
'fa-layers fa-fw'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
className={styles.indicatorBackground}
|
||||||
|
name={icons.CIRCLE}
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
className={advancedSettings ? styles.enabled : styles.disabled}
|
||||||
|
name={advancedSettings ? icons.CHECK : icons.CLOSE}
|
||||||
|
size={10}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div className={styles.labelContainer}>
|
||||||
|
<div className={styles.label}>
|
||||||
|
{advancedSettings ? 'Hide Advanced' : 'Show Advanced'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdvancedSettingsButton.propTypes = {
|
||||||
|
advancedSettings: PropTypes.bool.isRequired,
|
||||||
|
onAdvancedSettingsPress: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdvancedSettingsButton;
|
|
@ -14,7 +14,10 @@ class DownloadClientSettings extends Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
this._saveCallback = null;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isSaving: false,
|
||||||
hasPendingChanges: false
|
hasPendingChanges: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,28 +25,34 @@ class DownloadClientSettings extends Component {
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
setDownloadClientOptionsRef = (ref) => {
|
onChildMounted = (saveCallback) => {
|
||||||
this._downloadClientOptions = ref;
|
this._saveCallback = saveCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
onHasPendingChange = (hasPendingChanges) => {
|
onChildStateChange = (payload) => {
|
||||||
this.setState({
|
this.setState(payload);
|
||||||
hasPendingChanges
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSavePress = () => {
|
onSavePress = () => {
|
||||||
this._downloadClientOptions.getWrappedInstance().save();
|
if (this._saveCallback) {
|
||||||
|
this._saveCallback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
isSaving,
|
||||||
|
hasPendingChanges
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent title="Download Client Settings">
|
<PageContent title="Download Client Settings">
|
||||||
<SettingsToolbarConnector
|
<SettingsToolbarConnector
|
||||||
hasPendingChanges={this.state.hasPendingChanges}
|
isSaving={isSaving}
|
||||||
|
hasPendingChanges={hasPendingChanges}
|
||||||
onSavePress={this.onSavePress}
|
onSavePress={this.onSavePress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -51,8 +60,8 @@ class DownloadClientSettings extends Component {
|
||||||
<DownloadClientsConnector />
|
<DownloadClientsConnector />
|
||||||
|
|
||||||
<DownloadClientOptionsConnector
|
<DownloadClientOptionsConnector
|
||||||
ref={this.setDownloadClientOptionsRef}
|
onChildMounted={this.onChildMounted}
|
||||||
onHasPendingChange={this.onHasPendingChange}
|
onChildStateChange={this.onChildStateChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RemotePathMappingsConnector />
|
<RemotePathMappingsConnector />
|
||||||
|
|
|
@ -60,6 +60,7 @@ class DownloadClient extends Component {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={styles.downloadClient}
|
className={styles.downloadClient}
|
||||||
|
overlayContent={true}
|
||||||
onPress={this.onEditDownloadClientPress}
|
onPress={this.onEditDownloadClientPress}
|
||||||
>
|
>
|
||||||
<div className={styles.name}>
|
<div className={styles.name}>
|
||||||
|
|
|
@ -21,10 +21,10 @@ function createMapStateToProps() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchDownloadClientOptions,
|
dispatchFetchDownloadClientOptions: fetchDownloadClientOptions,
|
||||||
setDownloadClientOptionsValue,
|
dispatchSetDownloadClientOptionsValue: setDownloadClientOptionsValue,
|
||||||
saveDownloadClientOptions,
|
dispatchSaveDownloadClientOptions: saveDownloadClientOptions,
|
||||||
clearPendingChanges
|
dispatchClearPendingChanges: clearPendingChanges
|
||||||
};
|
};
|
||||||
|
|
||||||
class DownloadClientOptionsConnector extends Component {
|
class DownloadClientOptionsConnector extends Component {
|
||||||
|
@ -33,31 +33,43 @@ class DownloadClientOptionsConnector extends Component {
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchDownloadClientOptions();
|
const {
|
||||||
|
dispatchFetchDownloadClientOptions,
|
||||||
|
dispatchSaveDownloadClientOptions,
|
||||||
|
onChildMounted
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
dispatchFetchDownloadClientOptions();
|
||||||
|
onChildMounted(dispatchSaveDownloadClientOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.hasPendingChanges !== prevProps.hasPendingChanges) {
|
const {
|
||||||
this.props.onHasPendingChange(this.props.hasPendingChanges);
|
hasPendingChanges,
|
||||||
|
isSaving,
|
||||||
|
onChildStateChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
prevProps.isSaving !== isSaving ||
|
||||||
|
prevProps.hasPendingChanges !== hasPendingChanges
|
||||||
|
) {
|
||||||
|
onChildStateChange({
|
||||||
|
isSaving,
|
||||||
|
hasPendingChanges
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.clearPendingChanges({ section: this.props.section });
|
this.props.dispatchClearPendingChanges({ section: this.props.section });
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
save = () => {
|
|
||||||
this.props.saveDownloadClientOptions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onInputChange = ({ name, value }) => {
|
onInputChange = ({ name, value }) => {
|
||||||
this.props.setDownloadClientOptionsValue({ name, value });
|
this.props.dispatchSetDownloadClientOptionsValue({ name, value });
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -75,18 +87,20 @@ class DownloadClientOptionsConnector extends Component {
|
||||||
|
|
||||||
DownloadClientOptionsConnector.propTypes = {
|
DownloadClientOptionsConnector.propTypes = {
|
||||||
section: PropTypes.string.isRequired,
|
section: PropTypes.string.isRequired,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
hasPendingChanges: PropTypes.bool.isRequired,
|
hasPendingChanges: PropTypes.bool.isRequired,
|
||||||
fetchDownloadClientOptions: PropTypes.func.isRequired,
|
dispatchFetchDownloadClientOptions: PropTypes.func.isRequired,
|
||||||
setDownloadClientOptionsValue: PropTypes.func.isRequired,
|
dispatchSetDownloadClientOptionsValue: PropTypes.func.isRequired,
|
||||||
saveDownloadClientOptions: PropTypes.func.isRequired,
|
dispatchSaveDownloadClientOptions: PropTypes.func.isRequired,
|
||||||
clearPendingChanges: PropTypes.func.isRequired,
|
dispatchClearPendingChanges: PropTypes.func.isRequired,
|
||||||
onHasPendingChange: PropTypes.func.isRequired
|
onChildMounted: PropTypes.func.isRequired,
|
||||||
|
onChildStateChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connectSection(
|
||||||
createMapStateToProps,
|
createMapStateToProps,
|
||||||
mapDispatchToProps,
|
mapDispatchToProps,
|
||||||
undefined,
|
undefined,
|
||||||
{ withRef: true },
|
undefined,
|
||||||
{ section: 'settings.downloadClientOptions' }
|
{ section: 'settings.downloadClientOptions' }
|
||||||
)(DownloadClientOptionsConnector);
|
)(DownloadClientOptionsConnector);
|
||||||
|
|
|
@ -49,6 +49,8 @@ function HostSettings(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="port"
|
name="port"
|
||||||
|
min={1}
|
||||||
|
max={65535}
|
||||||
helpTextWarning="Requires restart to take effect"
|
helpTextWarning="Requires restart to take effect"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...port}
|
{...port}
|
||||||
|
@ -95,6 +97,8 @@ function HostSettings(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="sslPort"
|
name="sslPort"
|
||||||
|
min={1}
|
||||||
|
max={65535}
|
||||||
helpTextWarning="Requires restart to take effect"
|
helpTextWarning="Requires restart to take effect"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...sslPort}
|
{...sslPort}
|
||||||
|
|
|
@ -74,6 +74,8 @@ function ProxySettings(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="proxyPort"
|
name="proxyPort"
|
||||||
|
min={1}
|
||||||
|
max={65535}
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...proxyPort}
|
{...proxyPort}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -14,7 +14,10 @@ class IndexerSettings extends Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
this._saveCallback = null;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isSaving: false,
|
||||||
hasPendingChanges: false
|
hasPendingChanges: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,28 +25,34 @@ class IndexerSettings extends Component {
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
setIndexerOptionsRef = (ref) => {
|
onChildMounted = (saveCallback) => {
|
||||||
this._indexerOptions = ref;
|
this._saveCallback = saveCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
onHasPendingChange = (hasPendingChanges) => {
|
onChildStateChange = (payload) => {
|
||||||
this.setState({
|
this.setState(payload);
|
||||||
hasPendingChanges
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSavePress = () => {
|
onSavePress = () => {
|
||||||
this._indexerOptions.getWrappedInstance().save();
|
if (this._saveCallback) {
|
||||||
|
this._saveCallback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
isSaving,
|
||||||
|
hasPendingChanges
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent title="Indexer Settings">
|
<PageContent title="Indexer Settings">
|
||||||
<SettingsToolbarConnector
|
<SettingsToolbarConnector
|
||||||
hasPendingChanges={this.state.hasPendingChanges}
|
isSaving={isSaving}
|
||||||
|
hasPendingChanges={hasPendingChanges}
|
||||||
onSavePress={this.onSavePress}
|
onSavePress={this.onSavePress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -51,8 +60,8 @@ class IndexerSettings extends Component {
|
||||||
<IndexersConnector />
|
<IndexersConnector />
|
||||||
|
|
||||||
<IndexerOptionsConnector
|
<IndexerOptionsConnector
|
||||||
ref={this.setIndexerOptionsRef}
|
onChildMounted={this.onChildMounted}
|
||||||
onHasPendingChange={this.onHasPendingChange}
|
onChildStateChange={this.onChildStateChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<RestrictionsConnector />
|
<RestrictionsConnector />
|
||||||
|
|
|
@ -76,6 +76,7 @@ class Indexer extends Component {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={styles.indexer}
|
className={styles.indexer}
|
||||||
|
overlayContent={true}
|
||||||
onPress={this.onEditIndexerPress}
|
onPress={this.onEditIndexerPress}
|
||||||
>
|
>
|
||||||
<div className={styles.name}>
|
<div className={styles.name}>
|
||||||
|
|
|
@ -41,6 +41,7 @@ function IndexerOptions(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="minimumAge"
|
name="minimumAge"
|
||||||
|
min={0}
|
||||||
helpText="Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider."
|
helpText="Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider."
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...settings.minimumAge}
|
{...settings.minimumAge}
|
||||||
|
@ -53,6 +54,7 @@ function IndexerOptions(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="maximumSize"
|
name="maximumSize"
|
||||||
|
min={0}
|
||||||
helpText="Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited."
|
helpText="Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited."
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...settings.maximumSize}
|
{...settings.maximumSize}
|
||||||
|
@ -65,6 +67,7 @@ function IndexerOptions(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="retention"
|
name="retention"
|
||||||
|
min={0}
|
||||||
helpText="Usenet only: Set to zero to set for unlimited retention"
|
helpText="Usenet only: Set to zero to set for unlimited retention"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...settings.retention}
|
{...settings.retention}
|
||||||
|
@ -80,6 +83,7 @@ function IndexerOptions(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="rssSyncInterval"
|
name="rssSyncInterval"
|
||||||
|
min={0}
|
||||||
helpText="Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)"
|
helpText="Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)"
|
||||||
helpTextWarning="This will apply to all indexers, please follow the rules set forth by them"
|
helpTextWarning="This will apply to all indexers, please follow the rules set forth by them"
|
||||||
helpLink="https://github.com/Sonarr/Sonarr/wiki/RSS-Sync"
|
helpLink="https://github.com/Sonarr/Sonarr/wiki/RSS-Sync"
|
||||||
|
|
|
@ -21,10 +21,10 @@ function createMapStateToProps() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchIndexerOptions,
|
dispatchFetchIndexerOptions: fetchIndexerOptions,
|
||||||
setIndexerOptionsValue,
|
dispatchSetIndexerOptionsValue: setIndexerOptionsValue,
|
||||||
saveIndexerOptions,
|
dispatchSaveIndexerOptions: saveIndexerOptions,
|
||||||
clearPendingChanges
|
dispatchClearPendingChanges: clearPendingChanges
|
||||||
};
|
};
|
||||||
|
|
||||||
class IndexerOptionsConnector extends Component {
|
class IndexerOptionsConnector extends Component {
|
||||||
|
@ -33,31 +33,43 @@ class IndexerOptionsConnector extends Component {
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchIndexerOptions();
|
const {
|
||||||
|
dispatchFetchIndexerOptions,
|
||||||
|
dispatchSaveIndexerOptions,
|
||||||
|
onChildMounted
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
dispatchFetchIndexerOptions();
|
||||||
|
onChildMounted(dispatchSaveIndexerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
if (this.props.hasPendingChanges !== prevProps.hasPendingChanges) {
|
const {
|
||||||
this.props.onHasPendingChange(this.props.hasPendingChanges);
|
hasPendingChanges,
|
||||||
|
isSaving,
|
||||||
|
onChildStateChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
prevProps.isSaving !== isSaving ||
|
||||||
|
prevProps.hasPendingChanges !== hasPendingChanges
|
||||||
|
) {
|
||||||
|
onChildStateChange({
|
||||||
|
isSaving,
|
||||||
|
hasPendingChanges
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.clearPendingChanges({ section: this.props.section });
|
this.props.dispatchClearPendingChanges({ section: this.props.section });
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
save = () => {
|
|
||||||
this.props.saveIndexerOptions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onInputChange = ({ name, value }) => {
|
onInputChange = ({ name, value }) => {
|
||||||
this.props.setIndexerOptionsValue({ name, value });
|
this.props.dispatchSetIndexerOptionsValue({ name, value });
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -75,18 +87,20 @@ class IndexerOptionsConnector extends Component {
|
||||||
|
|
||||||
IndexerOptionsConnector.propTypes = {
|
IndexerOptionsConnector.propTypes = {
|
||||||
section: PropTypes.string.isRequired,
|
section: PropTypes.string.isRequired,
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
hasPendingChanges: PropTypes.bool.isRequired,
|
hasPendingChanges: PropTypes.bool.isRequired,
|
||||||
fetchIndexerOptions: PropTypes.func.isRequired,
|
dispatchFetchIndexerOptions: PropTypes.func.isRequired,
|
||||||
setIndexerOptionsValue: PropTypes.func.isRequired,
|
dispatchSetIndexerOptionsValue: PropTypes.func.isRequired,
|
||||||
saveIndexerOptions: PropTypes.func.isRequired,
|
dispatchSaveIndexerOptions: PropTypes.func.isRequired,
|
||||||
clearPendingChanges: PropTypes.func.isRequired,
|
dispatchClearPendingChanges: PropTypes.func.isRequired,
|
||||||
onHasPendingChange: PropTypes.func.isRequired
|
onChildMounted: PropTypes.func.isRequired,
|
||||||
|
onChildStateChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connectSection(
|
||||||
createMapStateToProps,
|
createMapStateToProps,
|
||||||
mapDispatchToProps,
|
mapDispatchToProps,
|
||||||
undefined,
|
undefined,
|
||||||
{ withRef: true },
|
undefined,
|
||||||
{ section: 'settings.indexerOptions' }
|
{ section: 'settings.indexerOptions' }
|
||||||
)(IndexerOptionsConnector);
|
)(IndexerOptionsConnector);
|
||||||
|
|
|
@ -64,6 +64,7 @@ class Restriction extends Component {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={styles.restriction}
|
className={styles.restriction}
|
||||||
|
overlayContent={true}
|
||||||
onPress={this.onEditRestrictionPress}
|
onPress={this.onEditRestrictionPress}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -52,6 +52,7 @@ class Metadata extends Component {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={styles.metadata}
|
className={styles.metadata}
|
||||||
|
overlayContent={true}
|
||||||
onPress={this.onEditMetadataPress}
|
onPress={this.onEditMetadataPress}
|
||||||
>
|
>
|
||||||
<div className={styles.name}>
|
<div className={styles.name}>
|
||||||
|
|
|
@ -79,6 +79,7 @@ class Notification extends Component {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
className={styles.notification}
|
className={styles.notification}
|
||||||
|
overlayContent={true}
|
||||||
onPress={this.onEditNotificationPress}
|
onPress={this.onEditNotificationPress}
|
||||||
>
|
>
|
||||||
<div className={styles.name}>
|
<div className={styles.name}>
|
||||||
|
|
|
@ -101,7 +101,7 @@ function EditLanguageProfileModalContent(props) {
|
||||||
id &&
|
id &&
|
||||||
<div
|
<div
|
||||||
className={styles.deleteButtonContainer}
|
className={styles.deleteButtonContainer}
|
||||||
title={isInUse && 'Can\'t delete a language profile that is attached to a artist'}
|
title={isInUse ? 'Can\'t delete a language profile that is attached to a artist' : undefined}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
|
|
|
@ -97,7 +97,7 @@ function EditMetadataProfileModalContent(props) {
|
||||||
id &&
|
id &&
|
||||||
<div
|
<div
|
||||||
className={styles.deleteButtonContainer}
|
className={styles.deleteButtonContainer}
|
||||||
title={isInUse && 'Can\'t delete a metadata profile that is attached to a artist'}
|
title={isInUse ? 'Can\'t delete a metadata profile that is attached to a artist' : undefined}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
|
|
|
@ -200,7 +200,7 @@ class EditQualityProfileModalContent extends Component {
|
||||||
id &&
|
id &&
|
||||||
<div
|
<div
|
||||||
className={styles.deleteButtonContainer}
|
className={styles.deleteButtonContainer}
|
||||||
title={isInUse && 'Can\'t delete a quality profile that is attached to a artist'}
|
title={isInUse ? 'Can\'t delete a quality profile that is attached to a artist' : undefined}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
kind={kinds.DANGER}
|
kind={kinds.DANGER}
|
||||||
|
|
|
@ -28,6 +28,29 @@ function getValue(value) {
|
||||||
|
|
||||||
class QualityDefinition extends Component {
|
class QualityDefinition extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this._forceUpdateTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
// A hack to deal with a bug in the slider component until a fix for it
|
||||||
|
// lands and an updated version is available.
|
||||||
|
// See: https://github.com/mpowaga/react-slider/issues/115
|
||||||
|
|
||||||
|
this._forceUpdateTimeout = setTimeout(() => this.forceUpdate(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this._forceUpdateTimeout) {
|
||||||
|
clearTimeout(this._forceUpdateTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
|
@ -131,6 +154,8 @@ class QualityDefinition extends Component {
|
||||||
<NumberInput
|
<NumberInput
|
||||||
className={styles.sizeInput}
|
className={styles.sizeInput}
|
||||||
name={`${id}.min`}
|
name={`${id}.min`}
|
||||||
|
min={slider.min}
|
||||||
|
max={maxSize ? maxSize - 10 : slider.max - 10}
|
||||||
value={minSize || slider.min}
|
value={minSize || slider.min}
|
||||||
isFloat={true}
|
isFloat={true}
|
||||||
onChange={this.onMinSizeChange}
|
onChange={this.onMinSizeChange}
|
||||||
|
@ -143,6 +168,7 @@ class QualityDefinition extends Component {
|
||||||
<NumberInput
|
<NumberInput
|
||||||
className={styles.sizeInput}
|
className={styles.sizeInput}
|
||||||
name={`${id}.max`}
|
name={`${id}.max`}
|
||||||
|
min={minSize + 10}
|
||||||
value={maxSize || slider.max}
|
value={maxSize || slider.max}
|
||||||
isFloat={true}
|
isFloat={true}
|
||||||
onChange={this.onMaxSizeChange}
|
onChange={this.onMaxSizeChange}
|
||||||
|
|
|
@ -40,7 +40,7 @@ class QualityDefinitionConnector extends Component {
|
||||||
this.props.setQualityDefinitionValue({ id, name: 'minSize', value: minSize });
|
this.props.setQualityDefinitionValue({ id, name: 'minSize', value: minSize });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minSize !== currentMaxSize) {
|
if (maxSize !== currentMaxSize) {
|
||||||
this.props.setQualityDefinitionValue({ id, name: 'maxSize', value: maxSize });
|
this.props.setQualityDefinitionValue({ id, name: 'maxSize', value: maxSize });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,8 @@ function createMapStateToProps() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchQualityDefinitions,
|
dispatchFetchQualityDefinitions: fetchQualityDefinitions,
|
||||||
saveQualityDefinitions
|
dispatchSaveQualityDefinitions: saveQualityDefinitions
|
||||||
};
|
};
|
||||||
|
|
||||||
class QualityDefinitionsConnector extends Component {
|
class QualityDefinitionsConnector extends Component {
|
||||||
|
@ -36,26 +36,36 @@ class QualityDefinitionsConnector extends Component {
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.fetchQualityDefinitions();
|
this.props.dispatchFetchQualityDefinitions();
|
||||||
|
|
||||||
|
const {
|
||||||
|
dispatchFetchQualityDefinitions,
|
||||||
|
dispatchSaveQualityDefinitions,
|
||||||
|
onChildMounted
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
dispatchFetchQualityDefinitions();
|
||||||
|
onChildMounted(dispatchSaveQualityDefinitions);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const {
|
const {
|
||||||
hasPendingChanges
|
hasPendingChanges,
|
||||||
|
isSaving,
|
||||||
|
onChildStateChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (hasPendingChanges !== prevProps.hasPendingChanges) {
|
if (
|
||||||
this.props.onHasPendingChange(hasPendingChanges);
|
prevProps.isSaving !== isSaving ||
|
||||||
|
prevProps.hasPendingChanges !== hasPendingChanges
|
||||||
|
) {
|
||||||
|
onChildStateChange({
|
||||||
|
isSaving,
|
||||||
|
hasPendingChanges
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
save = () => {
|
|
||||||
this.props.saveQualityDefinitions();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
|
@ -69,10 +79,12 @@ class QualityDefinitionsConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
QualityDefinitionsConnector.propTypes = {
|
QualityDefinitionsConnector.propTypes = {
|
||||||
|
isSaving: PropTypes.bool.isRequired,
|
||||||
hasPendingChanges: PropTypes.bool.isRequired,
|
hasPendingChanges: PropTypes.bool.isRequired,
|
||||||
fetchQualityDefinitions: PropTypes.func.isRequired,
|
dispatchFetchQualityDefinitions: PropTypes.func.isRequired,
|
||||||
saveQualityDefinitions: PropTypes.func.isRequired,
|
dispatchSaveQualityDefinitions: PropTypes.func.isRequired,
|
||||||
onHasPendingChange: PropTypes.func.isRequired
|
onChildMounted: PropTypes.func.isRequired,
|
||||||
|
onChildStateChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps, null, { withRef: true })(QualityDefinitionsConnector);
|
export default connect(createMapStateToProps, mapDispatchToProps, null, { withRef: true })(QualityDefinitionsConnector);
|
||||||
|
|
|
@ -12,7 +12,10 @@ class Quality extends Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
|
this._saveCallback = null;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isSaving: false,
|
||||||
hasPendingChanges: false
|
hasPendingChanges: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -20,35 +23,41 @@ class Quality extends Component {
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
setQualityDefinitionsRef = (ref) => {
|
onChildMounted = (saveCallback) => {
|
||||||
this._qualityDefinitions = ref;
|
this._saveCallback = saveCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
onHasPendingChange = (hasPendingChanges) => {
|
onChildStateChange = (payload) => {
|
||||||
this.setState({
|
this.setState(payload);
|
||||||
hasPendingChanges
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSavePress = () => {
|
onSavePress = () => {
|
||||||
this._qualityDefinitions.getWrappedInstance().save();
|
if (this._saveCallback) {
|
||||||
|
this._saveCallback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
isSaving,
|
||||||
|
hasPendingChanges
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContent title="Quality Settings">
|
<PageContent title="Quality Settings">
|
||||||
<SettingsToolbarConnector
|
<SettingsToolbarConnector
|
||||||
hasPendingChanges={this.state.hasPendingChanges}
|
isSaving={isSaving}
|
||||||
|
hasPendingChanges={hasPendingChanges}
|
||||||
onSavePress={this.onSavePress}
|
onSavePress={this.onSavePress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PageContentBodyConnector>
|
<PageContentBodyConnector>
|
||||||
<QualityDefinitionsConnector
|
<QualityDefinitionsConnector
|
||||||
ref={this.setQualityDefinitionsRef}
|
onChildMounted={this.onChildMounted}
|
||||||
onHasPendingChange={this.onHasPendingChange}
|
onChildStateChange={this.onChildStateChange}
|
||||||
/>
|
/>
|
||||||
</PageContentBodyConnector>
|
</PageContentBodyConnector>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
.advancedSettings {
|
|
||||||
composes: toolbarButton from 'Components/Page/Toolbar/PageToolbarButton.css';
|
|
||||||
}
|
|
||||||
|
|
||||||
.advancedSettingsEnabled {
|
|
||||||
color: $toobarButtonHoverColor;
|
|
||||||
}
|
|
|
@ -1,13 +1,12 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import classNames from 'classnames';
|
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts';
|
import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts';
|
||||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||||
import PendingChangesModal from './PendingChangesModal';
|
import PendingChangesModal from './PendingChangesModal';
|
||||||
import styles from './SettingsToolbar.css';
|
import AdvancedSettingsButton from './AdvancedSettingsButton';
|
||||||
|
|
||||||
class SettingsToolbar extends Component {
|
class SettingsToolbar extends Component {
|
||||||
|
|
||||||
|
@ -53,14 +52,9 @@ class SettingsToolbar extends Component {
|
||||||
return (
|
return (
|
||||||
<PageToolbar>
|
<PageToolbar>
|
||||||
<PageToolbarSection>
|
<PageToolbarSection>
|
||||||
<PageToolbarButton
|
<AdvancedSettingsButton
|
||||||
label={advancedSettings ? 'Hide Advanced' : 'Show Advanced'}
|
advancedSettings={advancedSettings}
|
||||||
className={classNames(
|
onAdvancedSettingsPress={onAdvancedSettingsPress}
|
||||||
styles.advancedSettings,
|
|
||||||
advancedSettings && styles.advancedSettingsEnabled
|
|
||||||
)}
|
|
||||||
iconName={icons.ADVANCED_SETTINGS}
|
|
||||||
onPress={onAdvancedSettingsPress}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
if (window.Sonarr.analytics) {
|
if (window.Lidarr.analytics) {
|
||||||
const d = document;
|
const d = document;
|
||||||
const g = d.createElement('script');
|
const g = d.createElement('script');
|
||||||
const s = d.getElementsByTagName('script')[0];
|
const s = d.getElementsByTagName('script')[0];
|
||||||
|
|
|
@ -7,7 +7,7 @@ import updateSectionState from 'Utilities/State/updateSectionState';
|
||||||
import { createThunk } from 'Store/thunks';
|
import { createThunk } from 'Store/thunks';
|
||||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||||
import createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
|
import createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
|
||||||
import { clearPendingChanges, update } from 'Store/Actions/baseActions';
|
import { clearPendingChanges, set, update } from 'Store/Actions/baseActions';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Variables
|
// Variables
|
||||||
|
@ -70,6 +70,11 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isSaving: true
|
||||||
|
}));
|
||||||
|
|
||||||
const promise = $.ajax({
|
const promise = $.ajax({
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
url: '/qualityDefinition/update',
|
url: '/qualityDefinition/update',
|
||||||
|
@ -78,10 +83,24 @@ export default {
|
||||||
|
|
||||||
promise.done((data) => {
|
promise.done((data) => {
|
||||||
dispatch(batchActions([
|
dispatch(batchActions([
|
||||||
|
set({
|
||||||
|
section,
|
||||||
|
isSaving: false,
|
||||||
|
saveError: null
|
||||||
|
}),
|
||||||
|
|
||||||
update({ section, data }),
|
update({ section, data }),
|
||||||
clearPendingChanges({ section })
|
clearPendingChanges({ section })
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
promise.fail((xhr) => {
|
||||||
|
dispatch(set({
|
||||||
|
section,
|
||||||
|
isSaving: false,
|
||||||
|
saveError: xhr
|
||||||
|
}));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ export const defaultState = {
|
||||||
messages: {
|
messages: {
|
||||||
items: []
|
items: []
|
||||||
},
|
},
|
||||||
version: window.Sonarr.version,
|
version: window.Lidarr.version,
|
||||||
isUpdated: false,
|
isUpdated: false,
|
||||||
isConnected: true,
|
isConnected: true,
|
||||||
isReconnecting: false,
|
isReconnecting: false,
|
||||||
|
|
|
@ -176,25 +176,23 @@ export const actionHandlers = handleThunks({
|
||||||
|
|
||||||
[FETCH_QUEUE_STATUS]: createFetchHandler(status, '/queue/status'),
|
[FETCH_QUEUE_STATUS]: createFetchHandler(status, '/queue/status'),
|
||||||
|
|
||||||
[FETCH_QUEUE_DETAILS]: function(payload) {
|
[FETCH_QUEUE_DETAILS]: function(getState, payload, dispatch) {
|
||||||
return function(dispatch, getState) {
|
let params = payload;
|
||||||
let params = payload;
|
|
||||||
|
|
||||||
// If the payload params are empty try to get params from state.
|
// If the payload params are empty try to get params from state.
|
||||||
|
|
||||||
if (params && !_.isEmpty(params)) {
|
if (params && !_.isEmpty(params)) {
|
||||||
dispatch(set({ section: details, params }));
|
dispatch(set({ section: details, params }));
|
||||||
} else {
|
} else {
|
||||||
params = getState().queue.details.params;
|
params = getState().queue.details.params;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure there are params before trying to fetch the queue
|
// Ensure there are params before trying to fetch the queue
|
||||||
// so we don't make a bad request to the server.
|
// so we don't make a bad request to the server.
|
||||||
|
|
||||||
if (params && !_.isEmpty(params)) {
|
if (params && !_.isEmpty(params)) {
|
||||||
fetchQueueDetailsHelper(getState, params, dispatch);
|
fetchQueueDetailsHelper(getState, params, dispatch);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
...createServerSideCollectionHandlers(
|
...createServerSideCollectionHandlers(
|
||||||
|
|
|
@ -87,7 +87,7 @@ const config = {
|
||||||
slicer,
|
slicer,
|
||||||
serialize,
|
serialize,
|
||||||
merge,
|
merge,
|
||||||
key: 'sonarr'
|
key: 'lidarr'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default persistState(paths, config);
|
export default persistState(paths, config);
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default function sentryMiddleware() {
|
||||||
version,
|
version,
|
||||||
release,
|
release,
|
||||||
isProduction
|
isProduction
|
||||||
} = window.Sonarr;
|
} = window.Lidarr;
|
||||||
|
|
||||||
if (!analytics) {
|
if (!analytics) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export default function getPathWithUrlBase(path) {
|
export default function getPathWithUrlBase(path) {
|
||||||
return `${window.Sonarr.urlBase}${path}`;
|
return `${window.Lidarr.urlBase}${path}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ function CutoffUnmetRow(props) {
|
||||||
albumEntity={albumEntities.WANTED_CUTOFF_UNMET}
|
albumEntity={albumEntities.WANTED_CUTOFF_UNMET}
|
||||||
albumTitle={title}
|
albumTitle={title}
|
||||||
showOpenArtistButton={true}
|
showOpenArtistButton={true}
|
||||||
|
showOpenAlbumButton={true}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
|
|
|
@ -81,6 +81,7 @@ function MissingRow(props) {
|
||||||
albumEntity={albumEntities.WANTED_MISSING}
|
albumEntity={albumEntities.WANTED_MISSING}
|
||||||
albumTitle={title}
|
albumTitle={title}
|
||||||
showOpenArtistButton={true}
|
showOpenArtistButton={true}
|
||||||
|
showOpenAlbumButton={true}
|
||||||
/>
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
|
|
|
@ -53,7 +53,7 @@
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.Sonarr = {
|
window.Lidarr = {
|
||||||
apiRoot: 'API_ROOT',
|
apiRoot: 'API_ROOT',
|
||||||
apiKey: 'API_KEY',
|
apiKey: 'API_KEY',
|
||||||
release: 'APP_RELEASE',
|
release: 'APP_RELEASE',
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
|
|
||||||
const absUrlRegex = /^(https?:)?\/\//i;
|
const absUrlRegex = /^(https?:)?\/\//i;
|
||||||
const apiRoot = window.Sonarr.apiRoot;
|
const apiRoot = window.Lidarr.apiRoot;
|
||||||
const urlBase = window.Sonarr.urlBase;
|
const urlBase = window.Lidarr.urlBase;
|
||||||
|
|
||||||
function isRelative(xhr) {
|
function isRelative(xhr) {
|
||||||
return !absUrlRegex.test(xhr.url);
|
return !absUrlRegex.test(xhr.url);
|
||||||
|
@ -31,7 +31,7 @@ function addRootUrl(xhr) {
|
||||||
|
|
||||||
function addApiKey(xhr) {
|
function addApiKey(xhr) {
|
||||||
xhr.headers = xhr.headers || {};
|
xhr.headers = xhr.headers || {};
|
||||||
xhr.headers['X-Api-Key'] = window.Sonarr.apiKey;
|
xhr.headers['X-Api-Key'] = window.Lidarr.apiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* eslint no-undef: 0 */
|
/* eslint no-undef: 0 */
|
||||||
import 'Shims/jquery';
|
import 'Shims/jquery';
|
||||||
|
|
||||||
__webpack_public_path__ = `${window.Sonarr.urlBase}/`;
|
__webpack_public_path__ = `${window.Lidarr.urlBase}/`;
|
||||||
|
|
73
package.json
73
package.json
|
@ -33,87 +33,86 @@
|
||||||
"classnames": "2.2.5",
|
"classnames": "2.2.5",
|
||||||
"clipboard": "1.7.1",
|
"clipboard": "1.7.1",
|
||||||
"create-react-class": "^15.6.2",
|
"create-react-class": "^15.6.2",
|
||||||
"css-loader": "0.28.7",
|
"css-loader": "0.28.9",
|
||||||
"del": "3.0.0",
|
"del": "3.0.0",
|
||||||
"element-class": "0.2.2",
|
"element-class": "0.2.2",
|
||||||
"esformatter": "0.10.0",
|
"esformatter": "0.10.0",
|
||||||
"eslint": "4.8.0",
|
"eslint": "4.16.0",
|
||||||
"eslint-loader": "1.9.0",
|
"eslint-loader": "1.9.0",
|
||||||
"eslint-plugin-filenames": "1.2.0",
|
"eslint-plugin-filenames": "1.2.0",
|
||||||
"eslint-plugin-react": "7.4.0",
|
"eslint-plugin-react": "7.5.1",
|
||||||
"esprint": "0.4.0",
|
"esprint": "0.4.0",
|
||||||
"extract-text-webpack-plugin": "3.0.1",
|
"extract-text-webpack-plugin": "3.0.2",
|
||||||
"file-loader": "1.1.5",
|
"file-loader": "1.1.6",
|
||||||
"filesize": "3.5.10",
|
"filesize": "3.5.11",
|
||||||
"gulp": "3.9.1",
|
"gulp": "3.9.1",
|
||||||
"gulp-cached": "1.1.1",
|
"gulp-cached": "1.1.1",
|
||||||
"gulp-clean-css": "3.9.0",
|
"gulp-clean-css": "3.9.2",
|
||||||
"gulp-concat": "2.6.1",
|
"gulp-concat": "2.6.1",
|
||||||
"gulp-declare": "0.3.0",
|
"gulp-declare": "0.3.0",
|
||||||
"gulp-livereload": "3.8.1",
|
"gulp-livereload": "3.8.1",
|
||||||
"gulp-postcss": "7.0.0",
|
"gulp-postcss": "7.0.1",
|
||||||
"gulp-print": "2.0.1",
|
"gulp-print": "2.0.1",
|
||||||
"gulp-sourcemaps": "2.6.1",
|
"gulp-sourcemaps": "2.6.3",
|
||||||
"gulp-stripbom": "1.0.4",
|
"gulp-stripbom": "1.0.4",
|
||||||
"gulp-util": "3.0.8",
|
"gulp-util": "3.0.8",
|
||||||
"gulp-watch": "4.3.11",
|
"gulp-watch": "5.0.0",
|
||||||
"gulp-wrap": "0.13.0",
|
"gulp-wrap": "0.13.0",
|
||||||
"history": "4.7.2",
|
"history": "4.7.2",
|
||||||
"jdu": "1.0.0",
|
"jdu": "1.0.0",
|
||||||
"jquery": "3.2.1",
|
"jquery": "3.3.1",
|
||||||
"loader-utils": "^1.1.0",
|
"loader-utils": "^1.1.0",
|
||||||
"lodash": "4.17.4",
|
"lodash": "4.17.4",
|
||||||
"mobile-detect": "1.3.7",
|
"mobile-detect": "1.4.1",
|
||||||
"moment": "2.18.1",
|
"moment": "2.20.1",
|
||||||
"mousetrap": "1.6.1",
|
"mousetrap": "1.6.1",
|
||||||
"normalize.css": "7.0.0",
|
"normalize.css": "7.0.0",
|
||||||
"postcss-loader": "2.0.6",
|
"postcss-loader": "2.0.10",
|
||||||
"postcss-mixins": "6.1.1",
|
"postcss-mixins": "6.2.0",
|
||||||
"postcss-nested": "2.1.2",
|
"postcss-nested": "3.0.0",
|
||||||
"postcss-simple-vars": "4.1.0",
|
"postcss-simple-vars": "4.1.0",
|
||||||
"prop-types": "15.6.0",
|
"prop-types": "15.6.0",
|
||||||
"qs": "6.5.1",
|
"qs": "6.5.1",
|
||||||
"query-string": "5.0.0",
|
"query-string": "5.0.1",
|
||||||
"raven-for-redux": "1.0.0",
|
"raven-for-redux": "1.0.0",
|
||||||
"raven-js": "3.17.0",
|
"raven-js": "3.17.0",
|
||||||
"react": "15.6.0",
|
"react": "16.2.0",
|
||||||
"react-addons-shallow-compare": "15.6.2",
|
"react-addons-shallow-compare": "15.6.2",
|
||||||
"react-async-script": "0.9.1",
|
"react-async-script": "0.9.1",
|
||||||
"react-autosuggest": "9.3.2",
|
"react-autosuggest": "9.3.2",
|
||||||
"react-custom-scrollbars": "4.1.2",
|
"react-custom-scrollbars": "4.2.1",
|
||||||
"react-dnd": "2.5.4",
|
"react-dnd": "2.5.4",
|
||||||
"react-dnd-html5-backend": "2.5.4",
|
"react-dnd-html5-backend": "2.5.4",
|
||||||
"react-document-title": "2.0.3",
|
"react-document-title": "2.0.3",
|
||||||
"react-dom": "15.6.0",
|
"react-dom": "16.2.0",
|
||||||
"react-google-recaptcha": "0.9.7",
|
"react-google-recaptcha": "0.9.9",
|
||||||
"react-lazyload": "2.2.7",
|
"react-lazyload": "2.3.0",
|
||||||
"react-measure": "1.4.7",
|
"react-measure": "1.4.7",
|
||||||
"react-portal": "3.1.0",
|
|
||||||
"react-redux": "5.0.6",
|
"react-redux": "5.0.6",
|
||||||
"react-router-dom": "4.2.2",
|
"react-router-dom": "4.2.2",
|
||||||
"react-router-redux": "5.0.0-alpha.6",
|
"react-router-redux": "5.0.0-alpha.9",
|
||||||
"react-slider": "0.9.0",
|
"react-slider": "0.9.0",
|
||||||
"react-tabs": "2.1.0",
|
"react-tabs": "2.2.1",
|
||||||
"react-tag-autocomplete": "5.4.1",
|
"react-tag-autocomplete": "5.5.0",
|
||||||
"react-tether": "0.5.7",
|
"react-tether": "0.6.1",
|
||||||
"react-text-truncate": "0.12.0",
|
"react-text-truncate": "0.12.1",
|
||||||
"react-virtualized": "9.10.1",
|
"react-virtualized": "9.18.0",
|
||||||
"redux": "3.7.2",
|
"redux": "3.7.2",
|
||||||
"redux-actions": "2.2.1",
|
"redux-actions": "2.2.1",
|
||||||
"redux-batched-actions": "0.2.0",
|
"redux-batched-actions": "0.2.1",
|
||||||
"redux-localstorage": "0.4.1",
|
"redux-localstorage": "0.4.1",
|
||||||
"redux-thunk": "2.2.0",
|
"redux-thunk": "2.2.0",
|
||||||
"require-nocache": "1.0.0",
|
"require-nocache": "1.0.0",
|
||||||
"reselect": "3.0.1",
|
"reselect": "3.0.1",
|
||||||
"run-sequence": "2.2.0",
|
"run-sequence": "2.2.1",
|
||||||
"signalr": "2.2.2",
|
"signalr": "2.2.2",
|
||||||
"streamqueue": "1.1.1",
|
"streamqueue": "1.1.2",
|
||||||
"style-loader": "0.19.0",
|
"style-loader": "0.19.1",
|
||||||
"stylelint": "8.2.0",
|
"stylelint": "8.4.0",
|
||||||
"stylelint-order": "0.7.0",
|
"stylelint-order": "0.8.0",
|
||||||
"tar.gz": "1.0.5",
|
"tar.gz": "1.0.7",
|
||||||
"url-loader": "0.6.2",
|
"url-loader": "0.6.2",
|
||||||
"webpack": "3.6.0",
|
"webpack": "3.10.0",
|
||||||
"webpack-stream": "^4.0.0"
|
"webpack-stream": "^4.0.0"
|
||||||
},
|
},
|
||||||
"main": "index.js"
|
"main": "index.js"
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Nancy.Responses;
|
using Nancy.Responses;
|
||||||
|
using NzbDrone.Common.TPL;
|
||||||
using NzbDrone.Core.Datastore.Events;
|
using NzbDrone.Core.Datastore.Events;
|
||||||
using NzbDrone.Core.Download.Pending;
|
using NzbDrone.Core.Download.Pending;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
@ -16,12 +17,18 @@ namespace Lidarr.Api.V1.Queue
|
||||||
{
|
{
|
||||||
private readonly IQueueService _queueService;
|
private readonly IQueueService _queueService;
|
||||||
private readonly IPendingReleaseService _pendingReleaseService;
|
private readonly IPendingReleaseService _pendingReleaseService;
|
||||||
|
private readonly Debouncer _broadcastDebounce;
|
||||||
|
|
||||||
|
|
||||||
public QueueStatusModule(IBroadcastSignalRMessage broadcastSignalRMessage, IQueueService queueService, IPendingReleaseService pendingReleaseService)
|
public QueueStatusModule(IBroadcastSignalRMessage broadcastSignalRMessage, IQueueService queueService, IPendingReleaseService pendingReleaseService)
|
||||||
: base(broadcastSignalRMessage, "queue/status")
|
: base(broadcastSignalRMessage, "queue/status")
|
||||||
{
|
{
|
||||||
_queueService = queueService;
|
_queueService = queueService;
|
||||||
_pendingReleaseService = pendingReleaseService;
|
_pendingReleaseService = pendingReleaseService;
|
||||||
|
|
||||||
|
_broadcastDebounce = new Debouncer(BroadcastChange, TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
|
|
||||||
Get["/"] = x => GetQueueStatusResponse();
|
Get["/"] = x => GetQueueStatusResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,25 +39,38 @@ namespace Lidarr.Api.V1.Queue
|
||||||
|
|
||||||
private QueueStatusResource GetQueueStatus()
|
private QueueStatusResource GetQueueStatus()
|
||||||
{
|
{
|
||||||
|
_broadcastDebounce.Pause();
|
||||||
|
|
||||||
var queue = _queueService.GetQueue();
|
var queue = _queueService.GetQueue();
|
||||||
var pending = _pendingReleaseService.GetPendingQueue();
|
var pending = _pendingReleaseService.GetPendingQueue();
|
||||||
|
|
||||||
return new QueueStatusResource
|
var resource = new QueueStatusResource
|
||||||
{
|
{
|
||||||
Count = queue.Count + pending.Count,
|
Count = queue.Count + pending.Count,
|
||||||
Errors = queue.Any(q => q.TrackedDownloadStatus.Equals("Error", StringComparison.InvariantCultureIgnoreCase)),
|
Errors = queue.Any(q => q.TrackedDownloadStatus.Equals("Error", StringComparison.InvariantCultureIgnoreCase)),
|
||||||
Warnings = queue.Any(q => q.TrackedDownloadStatus.Equals("Warning", StringComparison.InvariantCultureIgnoreCase))
|
Warnings = queue.Any(q => q.TrackedDownloadStatus.Equals("Warning", StringComparison.InvariantCultureIgnoreCase))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_broadcastDebounce.Resume();
|
||||||
|
|
||||||
|
return resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BroadcastChange()
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Updated, GetQueueStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(QueueUpdatedEvent message)
|
public void Handle(QueueUpdatedEvent message)
|
||||||
{
|
{
|
||||||
BroadcastResourceChange(ModelAction.Updated, GetQueueStatus());
|
_broadcastDebounce.Execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Handle(PendingReleasesUpdatedEvent message)
|
public void Handle(PendingReleasesUpdatedEvent message)
|
||||||
{
|
{
|
||||||
BroadcastResourceChange(ModelAction.Updated, GetQueueStatus());
|
_broadcastDebounce.Execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,9 @@ using HttpStatusCode = System.Net.HttpStatusCode;
|
||||||
|
|
||||||
namespace Lidarr.Api.V1.TrackFiles
|
namespace Lidarr.Api.V1.TrackFiles
|
||||||
{
|
{
|
||||||
public class TrackModule : LidarrRestModuleWithSignalR<TrackFileResource, TrackFile>,
|
public class TrackFileModule : LidarrRestModuleWithSignalR<TrackFileResource, TrackFile>,
|
||||||
IHandle<TrackFileAddedEvent>
|
IHandle<TrackFileAddedEvent>,
|
||||||
|
IHandle<TrackFileDeletedEvent>
|
||||||
{
|
{
|
||||||
private readonly IMediaFileService _mediaFileService;
|
private readonly IMediaFileService _mediaFileService;
|
||||||
private readonly IDeleteMediaFiles _mediaFileDeletionService;
|
private readonly IDeleteMediaFiles _mediaFileDeletionService;
|
||||||
|
@ -26,7 +27,7 @@ namespace Lidarr.Api.V1.TrackFiles
|
||||||
private readonly IAlbumService _albumService;
|
private readonly IAlbumService _albumService;
|
||||||
private readonly IUpgradableSpecification _upgradableSpecification;
|
private readonly IUpgradableSpecification _upgradableSpecification;
|
||||||
|
|
||||||
public TrackModule(IBroadcastSignalRMessage signalRBroadcaster,
|
public TrackFileModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||||
IMediaFileService mediaFileService,
|
IMediaFileService mediaFileService,
|
||||||
IDeleteMediaFiles mediaFileDeletionService,
|
IDeleteMediaFiles mediaFileDeletionService,
|
||||||
IArtistService artistService,
|
IArtistService artistService,
|
||||||
|
@ -170,5 +171,11 @@ namespace Lidarr.Api.V1.TrackFiles
|
||||||
{
|
{
|
||||||
BroadcastResourceChange(ModelAction.Updated, message.TrackFile.Id);
|
BroadcastResourceChange(ModelAction.Updated, message.TrackFile.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Handle(TrackFileDeletedEvent message)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Deleted, message.TrackFile.Id);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Datastore.Events;
|
using NzbDrone.Core.Datastore.Events;
|
||||||
using NzbDrone.Core.DecisionEngine;
|
using NzbDrone.Core.DecisionEngine;
|
||||||
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
using NzbDrone.Core.Music.Events;
|
using NzbDrone.Core.Music.Events;
|
||||||
|
@ -15,7 +16,8 @@ namespace Lidarr.Api.V1.Tracks
|
||||||
{
|
{
|
||||||
public abstract class TrackModuleWithSignalR : LidarrRestModuleWithSignalR<TrackResource, Track>,
|
public abstract class TrackModuleWithSignalR : LidarrRestModuleWithSignalR<TrackResource, Track>,
|
||||||
IHandle<TrackInfoRefreshedEvent>,
|
IHandle<TrackInfoRefreshedEvent>,
|
||||||
IHandle<TrackImportedEvent>
|
IHandle<TrackImportedEvent>,
|
||||||
|
IHandle<TrackFileDeletedEvent>
|
||||||
{
|
{
|
||||||
protected readonly ITrackService _trackService;
|
protected readonly ITrackService _trackService;
|
||||||
protected readonly IArtistService _artistService;
|
protected readonly IArtistService _artistService;
|
||||||
|
@ -131,5 +133,13 @@ namespace Lidarr.Api.V1.Tracks
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Handle(TrackFileDeletedEvent message)
|
||||||
|
{
|
||||||
|
foreach (var track in message.TrackFile.Tracks.Value)
|
||||||
|
{
|
||||||
|
BroadcastResourceChange(ModelAction.Deleted, track.Id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,15 @@ namespace Lidarr.Http
|
||||||
|
|
||||||
protected void BroadcastResourceChange(ModelAction action, int id)
|
protected void BroadcastResourceChange(ModelAction action, int id)
|
||||||
{
|
{
|
||||||
var resource = GetResourceById(id);
|
if (action == ModelAction.Deleted)
|
||||||
BroadcastResourceChange(action, resource);
|
{
|
||||||
|
BroadcastResourceChange(action, new TResource { Id = id });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var resource = GetResourceById(id);
|
||||||
|
BroadcastResourceChange(action, resource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,10 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
.Returns(new TrackFileMoveResult());
|
.Returns(new TrackFileMoveResult());
|
||||||
|
|
||||||
_downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
|
_downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
|
||||||
|
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
|
||||||
|
.Returns(new List<TrackFile>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -198,5 +202,19 @@ namespace NzbDrone.Core.Test.MediaFiles
|
||||||
Mocker.GetMock<IUpgradeMediaFiles>()
|
Mocker.GetMock<IUpgradeMediaFiles>()
|
||||||
.Verify(v => v.UpgradeTrackFile(It.IsAny<TrackFile>(), _approvedDecisions.First().LocalTrack, false), Times.Once());
|
.Verify(v => v.UpgradeTrackFile(It.IsAny<TrackFile>(), _approvedDecisions.First().LocalTrack, false), Times.Once());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_delete_existing_metadata_files_with_the_same_path()
|
||||||
|
{
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
|
||||||
|
.Returns(Builder<TrackFile>.CreateListOfSize(1).BuildList());
|
||||||
|
|
||||||
|
Subject.Import(new List<ImportDecision> { _approvedDecisions.First() }, false);
|
||||||
|
|
||||||
|
Mocker.GetMock<IMediaFileService>()
|
||||||
|
.Verify(v => v.Delete(It.IsAny<TrackFile>(), DeleteMediaFileReason.ManualOverride), Times.Once());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
|
|
||||||
var monoVersion = _platformInfo.Version;
|
var monoVersion = _platformInfo.Version;
|
||||||
|
|
||||||
if (monoVersion == new Version("4.4.0") || monoVersion == new Version("4.4.1"))
|
if (monoVersion == new Version("4.4") || monoVersion == new Version("4.4.1"))
|
||||||
{
|
{
|
||||||
_logger.Debug("Mono version {0}", monoVersion);
|
_logger.Debug("Mono version {0}", monoVersion);
|
||||||
return new HealthCheck(GetType(), HealthCheckResult.Error, $"Your Mono version {monoVersion} has a bug that causes issues connecting to indexers/download clients. You should upgrade to a higher version");
|
return new HealthCheck(GetType(), HealthCheckResult.Error, $"Your Mono version {monoVersion} has a bug that causes issues connecting to indexers/download clients. You should upgrade to a higher version");
|
||||||
|
@ -34,7 +34,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
|
||||||
|
|
||||||
if (monoVersion >= new Version("4.4"))
|
if (monoVersion >= new Version("4.4"))
|
||||||
{
|
{
|
||||||
_logger.Debug("Mono version is 4.6 or better: {0}", monoVersion);
|
_logger.Debug("Mono version is 4.4 or better: {0}", monoVersion);
|
||||||
return new HealthCheck(GetType());
|
return new HealthCheck(GetType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -248,6 +248,12 @@ namespace NzbDrone.Core.History
|
||||||
_logger.Debug("Removing track file from DB as part of cleanup routine, not creating history event.");
|
_logger.Debug("Removing track file from DB as part of cleanup routine, not creating history event.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (message.Reason == DeleteMediaFileReason.ManualOverride)
|
||||||
|
{
|
||||||
|
_logger.Debug("Removing track file from DB as part of manual override of existing file, not creating history event.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
foreach (var track in message.TrackFile.Tracks.Value)
|
foreach (var track in message.TrackFile.Tracks.Value)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
namespace NzbDrone.Core.MediaFiles
|
namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
public enum DeleteMediaFileReason
|
public enum DeleteMediaFileReason
|
||||||
{
|
{
|
||||||
MissingFromDisk,
|
MissingFromDisk,
|
||||||
Manual,
|
Manual,
|
||||||
Upgrade,
|
Upgrade,
|
||||||
NoLinkedEpisodes
|
NoLinkedEpisodes,
|
||||||
|
ManualOverride
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
|
@ -11,6 +11,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
List<TrackFile> GetFilesByArtist(int artistId);
|
List<TrackFile> GetFilesByArtist(int artistId);
|
||||||
List<TrackFile> GetFilesByAlbum(int albumId);
|
List<TrackFile> GetFilesByAlbum(int albumId);
|
||||||
List<TrackFile> GetFilesWithoutMediaInfo();
|
List<TrackFile> GetFilesWithoutMediaInfo();
|
||||||
|
List<TrackFile> GetFilesWithRelativePath(int artistId, string relativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,5 +36,13 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
{
|
{
|
||||||
return Query.Where(c => c.AlbumId == albumId).ToList();
|
return Query.Where(c => c.AlbumId == albumId).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<TrackFile> GetFilesWithRelativePath(int artistId, string relativePath)
|
||||||
|
{
|
||||||
|
return Query.Where(c => c.ArtistId == artistId)
|
||||||
|
.AndWhere(c => c.RelativePath == relativePath)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
List<string> FilterExistingFiles(List<string> files, Artist artist);
|
List<string> FilterExistingFiles(List<string> files, Artist artist);
|
||||||
TrackFile Get(int id);
|
TrackFile Get(int id);
|
||||||
List<TrackFile> Get(IEnumerable<int> ids);
|
List<TrackFile> Get(IEnumerable<int> ids);
|
||||||
|
List<TrackFile> GetFilesWithRelativePath(int artistId, string relativePath);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +99,11 @@ namespace NzbDrone.Core.MediaFiles
|
||||||
return _mediaFileRepository.Get(ids).ToList();
|
return _mediaFileRepository.Get(ids).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<TrackFile> GetFilesWithRelativePath(int artistId, string relativePath)
|
||||||
|
{
|
||||||
|
return _mediaFileRepository.GetFilesWithRelativePath(artistId, relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
public void HandleAsync(ArtistDeletedEvent message)
|
public void HandleAsync(ArtistDeletedEvent message)
|
||||||
{
|
{
|
||||||
var files = GetFilesByArtist(message.Artist.Id);
|
var files = GetFilesByArtist(message.Artist.Id);
|
||||||
|
|
|
@ -147,7 +147,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Debug()
|
Logger.Debug()
|
||||||
.Message("Unknown audio format: '{0}' in '{1}'.", string.Join(", ", audioFormat, audioCodecID, audioProfile, audioCodecLibrary))
|
.Message("Unknown audio format: '{0}'.", string.Join(", ", audioFormat, audioCodecID, audioProfile, audioCodecLibrary))
|
||||||
.WriteSentryWarn("UnknownAudioFormat", mediaInfo.ContainerFormat, audioFormat, audioCodecID)
|
.WriteSentryWarn("UnknownAudioFormat", mediaInfo.ContainerFormat, audioFormat, audioCodecID)
|
||||||
.Write();
|
.Write();
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,15 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
trackFile.RelativePath = localTrack.Artist.Path.GetRelativePath(trackFile.Path);
|
trackFile.RelativePath = localTrack.Artist.Path.GetRelativePath(trackFile.Path);
|
||||||
|
|
||||||
|
// Delete existing files from the DB mapped to this path
|
||||||
|
var previousFiles = _mediaFileService.GetFilesWithRelativePath(localTrack.Artist.Id, trackFile.RelativePath);
|
||||||
|
|
||||||
|
foreach (var previousFile in previousFiles)
|
||||||
|
{
|
||||||
|
_mediaFileService.Delete(previousFile, DeleteMediaFileReason.ManualOverride);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_mediaFileService.Add(trackFile);
|
_mediaFileService.Add(trackFile);
|
||||||
|
|
Loading…
Reference in New Issue