mirror of https://github.com/lidarr/Lidarr
parent
3beac03c00
commit
54e9f88648
|
@ -119,6 +119,7 @@ class HistoryRow extends Component {
|
|||
artistId={artist.id}
|
||||
albumTitle={album.title}
|
||||
showOpenArtistButton={true}
|
||||
showOpenAlbumButton={true}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
|
|
|
@ -161,6 +161,7 @@ class QueueRow extends Component {
|
|||
trackFileId={album.trackFileId}
|
||||
albumTitle={album.title}
|
||||
showOpenArtistButton={true}
|
||||
showOpenAlbumButton={true}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
|
|
|
@ -78,6 +78,10 @@ class AddNewArtistSearchResult extends Component {
|
|||
isSmallScreen
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isNewAddArtistModalOpen
|
||||
} = this.state;
|
||||
|
||||
const linkProps = isExistingArtist ? { to: `/artist/${foreignArtistId}` } : { onPress: this.onPress };
|
||||
let albums = '1 Album';
|
||||
|
||||
|
@ -88,78 +92,78 @@ class AddNewArtistSearchResult extends Component {
|
|||
const height = calculateHeight(230, isSmallScreen);
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={styles.searchResult}
|
||||
{...linkProps}
|
||||
>
|
||||
{
|
||||
!isSmallScreen &&
|
||||
<div>
|
||||
<Link
|
||||
className={styles.searchResult}
|
||||
{...linkProps}
|
||||
>
|
||||
{
|
||||
!isSmallScreen &&
|
||||
<ArtistPoster
|
||||
className={styles.poster}
|
||||
images={images}
|
||||
size={250}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
<div>
|
||||
<div className={styles.name}>
|
||||
{artistName}
|
||||
<div>
|
||||
<div className={styles.name}>
|
||||
{artistName}
|
||||
|
||||
{
|
||||
!name.contains(year) && !!year &&
|
||||
{
|
||||
!name.contains(year) && !!year &&
|
||||
<span className={styles.year}>({year})</span>
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
!!disambiguation &&
|
||||
{
|
||||
!!disambiguation &&
|
||||
<span className={styles.year}>({disambiguation})</span>
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
isExistingArtist &&
|
||||
<Icon
|
||||
className={styles.alreadyExistsIcon}
|
||||
name={icons.CHECK_CIRCLE}
|
||||
size={36}
|
||||
title="Already in your library"
|
||||
{
|
||||
isExistingArtist &&
|
||||
<Icon
|
||||
className={styles.alreadyExistsIcon}
|
||||
name={icons.CHECK_CIRCLE}
|
||||
size={36}
|
||||
title="Already in your library"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label size={sizes.LARGE}>
|
||||
<HeartRating
|
||||
rating={ratings.value}
|
||||
iconSize={13}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</Label>
|
||||
|
||||
<div>
|
||||
<Label size={sizes.LARGE}>
|
||||
<HeartRating
|
||||
rating={ratings.value}
|
||||
iconSize={13}
|
||||
/>
|
||||
</Label>
|
||||
{
|
||||
!!artistType &&
|
||||
<Label size={sizes.LARGE}>
|
||||
{artistType}
|
||||
</Label>
|
||||
}
|
||||
|
||||
{
|
||||
!!artistType &&
|
||||
<Label size={sizes.LARGE}>
|
||||
{artistType}
|
||||
</Label>
|
||||
}
|
||||
{
|
||||
!!albumCount &&
|
||||
<Label size={sizes.LARGE}>
|
||||
{albums}
|
||||
</Label>
|
||||
}
|
||||
|
||||
{
|
||||
!!albumCount &&
|
||||
<Label size={sizes.LARGE}>
|
||||
{albums}
|
||||
</Label>
|
||||
}
|
||||
{
|
||||
status === 'ended' &&
|
||||
<Label
|
||||
kind={kinds.DANGER}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Ended
|
||||
</Label>
|
||||
}
|
||||
</div>
|
||||
|
||||
{
|
||||
status === 'ended' &&
|
||||
<Label
|
||||
kind={kinds.DANGER}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
Ended
|
||||
</Label>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div
|
||||
className={styles.overview}
|
||||
style={{
|
||||
|
@ -173,10 +177,10 @@ class AddNewArtistSearchResult extends Component {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<AddNewArtistModal
|
||||
isOpen={this.state.isNewAddArtistModalOpen && !isExistingArtist}
|
||||
isOpen={isNewAddArtistModalOpen && !isExistingArtist}
|
||||
foreignArtistId={foreignArtistId}
|
||||
artistName={artistName}
|
||||
year={year}
|
||||
|
@ -184,7 +188,7 @@ class AddNewArtistSearchResult extends Component {
|
|||
images={images}
|
||||
onModalClose={this.onAddArtistModalClose}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import ReactDOM from 'react-dom';
|
|||
import TetherComponent from 'react-tether';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import Icon from 'Components/Icon';
|
||||
import SpinnerIcon from 'Components/SpinnerIcon';
|
||||
import FormInputButton from 'Components/Form/FormInputButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
|
|
|
@ -43,7 +43,7 @@ class ImportArtistSelectFolderConnector extends Component {
|
|||
const newRootFolders = _.differenceBy(items, prevProps.items, (item) => item.id);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.openButtons {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
|
|
@ -45,17 +45,20 @@ class AlbumDetailsModalContent extends Component {
|
|||
albumId,
|
||||
artistName,
|
||||
foreignArtistId,
|
||||
foreignAlbumId,
|
||||
artistMonitored,
|
||||
albumTitle,
|
||||
monitored,
|
||||
isSaving,
|
||||
showOpenArtistButton,
|
||||
showOpenAlbumButton,
|
||||
startInteractiveSearch,
|
||||
onMonitorAlbumPress,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const artistLink = `/artist/${foreignArtistId}`;
|
||||
const albumLink = `/album/${foreignAlbumId}`;
|
||||
|
||||
return (
|
||||
<ModalContent
|
||||
|
@ -121,18 +124,30 @@ class AlbumDetailsModalContent extends Component {
|
|||
</Tabs>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
{
|
||||
showOpenArtistButton &&
|
||||
<Button
|
||||
className={styles.openArtistButton}
|
||||
to={artistLink}
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Open Artist
|
||||
</Button>
|
||||
}
|
||||
<ModalFooter >
|
||||
<div className={styles.openButtons}>
|
||||
{
|
||||
showOpenArtistButton &&
|
||||
<Button
|
||||
className={styles.openArtistButton}
|
||||
to={artistLink}
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Open Artist
|
||||
</Button>
|
||||
}
|
||||
|
||||
{
|
||||
showOpenAlbumButton &&
|
||||
<Button
|
||||
className={styles.openAlbumButton}
|
||||
to={albumLink}
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Open Album
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
|
@ -150,6 +165,7 @@ AlbumDetailsModalContent.propTypes = {
|
|||
artistId: PropTypes.number.isRequired,
|
||||
artistName: PropTypes.string.isRequired,
|
||||
foreignArtistId: PropTypes.string.isRequired,
|
||||
foreignAlbumId: PropTypes.string.isRequired,
|
||||
artistMonitored: PropTypes.bool.isRequired,
|
||||
releaseDate: PropTypes.string.isRequired,
|
||||
albumLabel: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
|
@ -157,6 +173,7 @@ AlbumDetailsModalContent.propTypes = {
|
|||
monitored: PropTypes.bool.isRequired,
|
||||
isSaving: PropTypes.bool,
|
||||
showOpenArtistButton: PropTypes.bool,
|
||||
showOpenAlbumButton: PropTypes.bool,
|
||||
selectedTab: PropTypes.string.isRequired,
|
||||
startInteractiveSearch: PropTypes.bool.isRequired,
|
||||
onMonitorAlbumPress: PropTypes.func.isRequired,
|
||||
|
|
|
@ -5,7 +5,6 @@ import IconButton from 'Components/Link/IconButton';
|
|||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import AlbumDetailsModal from './AlbumDetailsModal';
|
||||
import EditAlbumModalConnector from './Edit/EditAlbumModalConnector';
|
||||
import styles from './AlbumSearchCell.css';
|
||||
|
||||
class AlbumSearchCell extends Component {
|
||||
|
@ -17,8 +16,7 @@ class AlbumSearchCell extends Component {
|
|||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isDetailsModalOpen: false,
|
||||
isEditAlbumModalOpen: false
|
||||
isDetailsModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,14 +31,6 @@ class AlbumSearchCell extends Component {
|
|||
this.setState({ isDetailsModalOpen: false });
|
||||
}
|
||||
|
||||
onEditAlbumPress = () => {
|
||||
this.setState({ isEditAlbumModalOpen: true });
|
||||
}
|
||||
|
||||
onEditAlbumModalClose = () => {
|
||||
this.setState({ isEditAlbumModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
|
@ -67,12 +57,6 @@ class AlbumSearchCell extends Component {
|
|||
onPress={this.onManualSearchPress}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
name={icons.EDIT}
|
||||
title="Edit Album"
|
||||
onPress={this.onEditAlbumPress}
|
||||
/>
|
||||
|
||||
<AlbumDetailsModal
|
||||
isOpen={this.state.isDetailsModalOpen}
|
||||
albumId={albumId}
|
||||
|
@ -84,12 +68,6 @@ class AlbumSearchCell extends Component {
|
|||
{...otherProps}
|
||||
/>
|
||||
|
||||
<EditAlbumModalConnector
|
||||
isOpen={this.state.isEditAlbumModalOpen}
|
||||
albumId={albumId}
|
||||
artistId={artistId}
|
||||
onModalClose={this.onEditAlbumModalClose}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -10,10 +10,9 @@ import AlbumSearchCell from './AlbumSearchCell';
|
|||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { albumId }) => albumId,
|
||||
(state, { sceneSeasonNumber }) => sceneSeasonNumber,
|
||||
createArtistSelector(),
|
||||
createCommandsSelector(),
|
||||
(albumId, sceneSeasonNumber, artist, commands) => {
|
||||
(albumId, artist, commands) => {
|
||||
const isSearching = _.some(commands, (command) => {
|
||||
const albumSearch = command.name === commandNames.ALBUM_SEARCH;
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import Icon from 'Components/Icon';
|
|||
import IconButton from 'Components/Link/IconButton';
|
||||
import Label from 'Components/Label';
|
||||
import AlbumCover from 'Album/AlbumCover';
|
||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||
import EditAlbumModalConnector from 'Album/Edit/EditAlbumModalConnector';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
|
@ -50,6 +51,7 @@ class AlbumDetails extends Component {
|
|||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isOrganizeModalOpen: false,
|
||||
isArtistHistoryModalOpen: false,
|
||||
isManageTracksOpen: false,
|
||||
isEditAlbumModalOpen: false,
|
||||
|
@ -62,6 +64,14 @@ class AlbumDetails extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
onOrganizePress = () => {
|
||||
this.setState({ isOrganizeModalOpen: true });
|
||||
}
|
||||
|
||||
onOrganizeModalClose = () => {
|
||||
this.setState({ isOrganizeModalOpen: false });
|
||||
}
|
||||
|
||||
onEditAlbumPress = () => {
|
||||
this.setState({ isEditAlbumModalOpen: true });
|
||||
}
|
||||
|
@ -135,6 +145,7 @@ class AlbumDetails extends Component {
|
|||
} = this.props;
|
||||
|
||||
const {
|
||||
isOrganizeModalOpen,
|
||||
isArtistHistoryModalOpen,
|
||||
isEditAlbumModalOpen,
|
||||
isManageTracksOpen,
|
||||
|
@ -164,6 +175,12 @@ class AlbumDetails extends Component {
|
|||
|
||||
<PageToolbarSeparator />
|
||||
|
||||
<PageToolbarButton
|
||||
label="Preview Rename"
|
||||
iconName={icons.ORGANIZE}
|
||||
onPress={this.onOrganizePress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label="Manage Tracks"
|
||||
iconName={icons.TRACK_FILE}
|
||||
|
@ -364,6 +381,13 @@ class AlbumDetails extends Component {
|
|||
|
||||
</div>
|
||||
|
||||
<OrganizePreviewModalConnector
|
||||
isOpen={isOrganizeModalOpen}
|
||||
artistId={artist.id}
|
||||
albumId={id}
|
||||
onModalClose={this.onOrganizeModalClose}
|
||||
/>
|
||||
|
||||
<TrackFileEditorModal
|
||||
isOpen={isManageTracksOpen}
|
||||
artistId={artist.id}
|
||||
|
|
|
@ -26,7 +26,7 @@ class EditAlbumModalContent extends Component {
|
|||
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
|
|
|
@ -2,41 +2,9 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
import DocumentTitle from 'react-document-title';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Route, Redirect } from 'react-router-dom';
|
||||
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 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';
|
||||
import AppRoutes from './AppRoutes';
|
||||
|
||||
function App({ store, history }) {
|
||||
return (
|
||||
|
@ -44,205 +12,7 @@ function App({ store, history }) {
|
|||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
<PageConnector>
|
||||
<Switch>
|
||||
{/*
|
||||
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>
|
||||
<AppRoutes app={App} />
|
||||
</PageConnector>
|
||||
</ConnectedRouter>
|
||||
</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() {
|
||||
window.location = `${window.Sonarr.urlBase}/system/updates`;
|
||||
window.location = `${window.Lidarr.urlBase}/system/updates`;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ class ArtistDetailsPageConnector extends Component {
|
|||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!this.props.foreignArtistId) {
|
||||
this.props.push(`${window.Sonarr.urlBase}/`);
|
||||
this.props.push(`${window.Lidarr.urlBase}/`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ class EditArtistModalContent extends Component {
|
|||
this.props.onSavePress(true);
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
|
|
|
@ -5,7 +5,6 @@ import { icons } from 'Helpers/Props';
|
|||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import fonts from 'Styles/Variables/fonts';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Icon from 'Components/Icon';
|
||||
import Link from 'Components/Link/Link';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import ArtistPoster from 'Artist/ArtistPoster';
|
||||
|
|
|
@ -5,6 +5,5 @@
|
|||
}
|
||||
|
||||
.statusIcon {
|
||||
margin-right: 6px;
|
||||
width: 20px;
|
||||
width: 20px !important;
|
||||
}
|
||||
|
|
|
@ -17,16 +17,9 @@ function createMapStateToProps() {
|
|||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch) {
|
||||
return {
|
||||
onNavigatePrevious() {
|
||||
dispatch(gotoCalendarPreviousRange());
|
||||
},
|
||||
const mapDispatchToProps = {
|
||||
onNavigatePrevious: gotoCalendarPreviousRange,
|
||||
onNavigateNext: gotoCalendarNextRange
|
||||
};
|
||||
|
||||
onNavigateNext() {
|
||||
dispatch(gotoCalendarNextRange());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(CalendarDays);
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(CalendarDays);
|
||||
|
|
|
@ -22,7 +22,7 @@ function getUrls(state) {
|
|||
tags
|
||||
} = 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) {
|
||||
icalUrl += 'unmonitored=true&';
|
||||
|
@ -40,7 +40,7 @@ function getUrls(state) {
|
|||
icalUrl += `tags=${tags.toString()}&`;
|
||||
}
|
||||
|
||||
icalUrl += `apikey=${window.Sonarr.apiKey}`;
|
||||
icalUrl += `apikey=${window.Lidarr.apiKey}`;
|
||||
|
||||
const iCalHttpUrl = `${window.location.protocol}//${icalUrl}`;
|
||||
const iCalWebCalUrl = `webcal://${icalUrl}`;
|
||||
|
|
|
@ -14,3 +14,8 @@
|
|||
.scroller {
|
||||
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 { scrollDirections } from 'Helpers/Props';
|
||||
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 ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import Scroller from 'Components/Scroller/Scroller';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import PathInput from 'Components/Form/PathInput';
|
||||
|
@ -43,12 +44,15 @@ class FileBrowserModalContent extends Component {
|
|||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
currentPath
|
||||
} = this.props;
|
||||
|
||||
if (currentPath !== this.state.currentPath) {
|
||||
if (
|
||||
currentPath !== this.state.currentPath &&
|
||||
currentPath !== prevState.currentPath
|
||||
) {
|
||||
this.setState({ currentPath });
|
||||
this._scrollerNode.scrollTop = 0;
|
||||
}
|
||||
|
@ -91,6 +95,9 @@ class FileBrowserModalContent extends Component {
|
|||
|
||||
render() {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
parent,
|
||||
directories,
|
||||
files,
|
||||
|
@ -125,61 +132,77 @@ class FileBrowserModalContent extends Component {
|
|||
ref={this.setScrollerRef}
|
||||
className={styles.scroller}
|
||||
>
|
||||
<Table columns={columns}>
|
||||
<TableBody>
|
||||
{
|
||||
emptyParent &&
|
||||
<FileBrowserRow
|
||||
type="computer"
|
||||
name="My Computer"
|
||||
path={parent}
|
||||
onPress={this.onRowPress}
|
||||
/>
|
||||
}
|
||||
{
|
||||
!!error &&
|
||||
<div>Error loading contents</div>
|
||||
}
|
||||
|
||||
{
|
||||
!emptyParent && parent &&
|
||||
<FileBrowserRow
|
||||
type="parent"
|
||||
name="..."
|
||||
path={parent}
|
||||
onPress={this.onRowPress}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
directories.map((directory) => {
|
||||
return (
|
||||
{
|
||||
isPopulated && !error &&
|
||||
<Table columns={columns}>
|
||||
<TableBody>
|
||||
{
|
||||
emptyParent &&
|
||||
<FileBrowserRow
|
||||
key={directory.path}
|
||||
type={directory.type}
|
||||
name={directory.name}
|
||||
path={directory.path}
|
||||
type="computer"
|
||||
name="My Computer"
|
||||
path={parent}
|
||||
onPress={this.onRowPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
files.map((file) => {
|
||||
return (
|
||||
{
|
||||
!emptyParent && parent &&
|
||||
<FileBrowserRow
|
||||
key={file.path}
|
||||
type={file.type}
|
||||
name={file.name}
|
||||
path={file.path}
|
||||
type="parent"
|
||||
name="..."
|
||||
path={parent}
|
||||
onPress={this.onRowPress}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
}
|
||||
|
||||
{
|
||||
directories.map((directory) => {
|
||||
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>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator
|
||||
className={styles.loading}
|
||||
size={20}
|
||||
/>
|
||||
}
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
|
@ -200,6 +223,9 @@ class FileBrowserModalContent extends Component {
|
|||
FileBrowserModalContent.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
parent: PropTypes.string,
|
||||
currentPath: PropTypes.string.isRequired,
|
||||
directories: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
|
|
|
@ -11,6 +11,9 @@ function createMapStateToProps() {
|
|||
(state) => state.paths,
|
||||
(paths) => {
|
||||
const {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
parent,
|
||||
currentPath,
|
||||
directories,
|
||||
|
@ -22,6 +25,9 @@ function createMapStateToProps() {
|
|||
});
|
||||
|
||||
return {
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
parent,
|
||||
currentPath,
|
||||
directories,
|
||||
|
|
|
@ -8,12 +8,23 @@ class NumberInput extends Component {
|
|||
// Listeners
|
||||
|
||||
onChange = ({ name, value }) => {
|
||||
const {
|
||||
min,
|
||||
max
|
||||
} = this.props;
|
||||
|
||||
let newValue = null;
|
||||
|
||||
if (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({
|
||||
name,
|
||||
value: newValue
|
||||
|
@ -40,6 +51,8 @@ class NumberInput extends Component {
|
|||
|
||||
NumberInput.propTypes = {
|
||||
value: PropTypes.number,
|
||||
min: PropTypes.number,
|
||||
max: PropTypes.number,
|
||||
isFloat: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
|
|
@ -98,7 +98,7 @@ class ClipboardButton extends Component {
|
|||
className={styles.button}
|
||||
{...otherProps}
|
||||
>
|
||||
<span className={showStateIcon && styles.showStateIcon}>
|
||||
<span className={showStateIcon ? styles.showStateIcon : undefined}>
|
||||
{
|
||||
showSuccess &&
|
||||
<span className={styles.stateIconContainer}>
|
||||
|
|
|
@ -41,7 +41,8 @@ IconButton.propTypes = {
|
|||
};
|
||||
|
||||
IconButton.defaultProps = {
|
||||
className: styles.button
|
||||
className: styles.button,
|
||||
size: 12
|
||||
};
|
||||
|
||||
export default IconButton;
|
||||
|
|
|
@ -47,13 +47,13 @@ class Link extends Component {
|
|||
el = 'a';
|
||||
linkProps.href = to;
|
||||
linkProps.target = target || '_self';
|
||||
} else if (to.startsWith(window.Sonarr.urlBase)) {
|
||||
} else if (to.startsWith(window.Lidarr.urlBase)) {
|
||||
el = RouterLink;
|
||||
linkProps.to = to;
|
||||
linkProps.target = target;
|
||||
} else {
|
||||
el = RouterLink;
|
||||
linkProps.to = `${window.Sonarr.urlBase}/${to.replace(/^\//, '')}`;
|
||||
linkProps.to = `${window.Lidarr.urlBase}/${to.replace(/^\//, '')}`;
|
||||
linkProps.target = target;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,6 @@
|
|||
|
||||
.label {
|
||||
left: 100%;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ class SpinnerErrorButton extends Component {
|
|||
isSpinning={isSpinning}
|
||||
{...otherProps}
|
||||
>
|
||||
<span className={showIcon && styles.showIcon}>
|
||||
<span className={showIcon ? styles.showIcon : undefined}>
|
||||
{
|
||||
showIcon &&
|
||||
<span className={styles.iconContainer}>
|
||||
|
|
|
@ -16,6 +16,7 @@ function SpinnerIconButton(props) {
|
|||
<IconButton
|
||||
name={isSpinning ? (spinningName || name) : name}
|
||||
isDisabled={isDisabled || isSpinning}
|
||||
isSpinning={isSpinning}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Portal from 'react-portal';
|
||||
import classNames from 'classnames';
|
||||
import elementClass from 'element-class';
|
||||
import getUniqueElememtId from 'Utilities/getUniqueElementId';
|
||||
|
@ -27,6 +26,8 @@ class Modal extends Component {
|
|||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this._node = document.getElementById('modal-root');
|
||||
this._backgroundRef = null;
|
||||
this._modalId = getUniqueElememtId();
|
||||
}
|
||||
|
||||
|
@ -57,6 +58,10 @@ class Modal extends Component {
|
|||
//
|
||||
// Control
|
||||
|
||||
_setBackgroundRef = (ref) => {
|
||||
this._backgroundRef = ref;
|
||||
}
|
||||
|
||||
_openModal() {
|
||||
openModals.push(this._modalId);
|
||||
window.addEventListener('keydown', this.onKeyDown);
|
||||
|
@ -79,9 +84,9 @@ class Modal extends Component {
|
|||
const targetElement = this._findEventTarget(event);
|
||||
|
||||
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;
|
||||
|
@ -138,10 +143,6 @@ class Modal extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
onClosePress = (event) => {
|
||||
this.props.onModalClose();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
|
@ -155,36 +156,32 @@ class Modal extends Component {
|
|||
isOpen
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Portal
|
||||
isOpened={isOpen}
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<div
|
||||
className={styles.modalContainer}
|
||||
>
|
||||
<div>
|
||||
{
|
||||
isOpen &&
|
||||
<div
|
||||
className={styles.modalContainer}
|
||||
>
|
||||
<div
|
||||
className={backdropClassName}
|
||||
onMouseDown={this.onBackdropBeginPress}
|
||||
onMouseUp={this.onBackdropEndPress}
|
||||
>
|
||||
<div
|
||||
ref="modal"
|
||||
className={classNames(
|
||||
className,
|
||||
styles[size]
|
||||
)}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div
|
||||
ref={this._setBackgroundRef}
|
||||
className={backdropClassName}
|
||||
onMouseDown={this.onBackdropBeginPress}
|
||||
onMouseUp={this.onBackdropEndPress}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
className,
|
||||
styles[size]
|
||||
)}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</Portal>
|
||||
</div>,
|
||||
this._node
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ function NotFound({ message }) {
|
|||
|
||||
<img
|
||||
className={styles.image}
|
||||
src={`${window.Sonarr.urlBase}/Content/Images/404.png`}
|
||||
src={`${window.Lidarr.urlBase}/Content/Images/404.png`}
|
||||
/>
|
||||
</div>
|
||||
</PageContent>
|
||||
|
|
|
@ -19,11 +19,11 @@ function createMapStateToProps() {
|
|||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onGoToArtist(foreignArtistId) {
|
||||
dispatch(push(`${window.Sonarr.urlBase}/artist/${foreignArtistId}`));
|
||||
dispatch(push(`${window.Lidarr.urlBase}/artist/${foreignArtistId}`));
|
||||
},
|
||||
|
||||
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 (
|
||||
<div className={styles.header}>
|
||||
<div className={styles.logoContainer}>
|
||||
<Link to={`${window.Sonarr.urlBase}/`}>
|
||||
<Link to={`${window.Lidarr.urlBase}/`}>
|
||||
<img
|
||||
className={styles.logo}
|
||||
src={`${window.Sonarr.urlBase}/Content/Images/logo.svg`}
|
||||
src={`${window.Lidarr.urlBase}/Content/Images/logo.svg`}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -74,6 +74,7 @@ class PageHeader extends Component {
|
|||
className={styles.donate}
|
||||
name={icons.HEART}
|
||||
to="https://www.paypal.me/Lidarr"
|
||||
size={14}
|
||||
/>
|
||||
<PageHeaderActionsMenuConnector
|
||||
onKeyboardShortcutsPress={this.onOpenKeyboardShortcutsModal}
|
||||
|
|
|
@ -61,7 +61,7 @@ function PageHeaderActionsMenu(props) {
|
|||
{
|
||||
formsAuth &&
|
||||
<MenuItem
|
||||
to={`${window.Sonarr.urlBase}/logout`}
|
||||
to={`${window.Lidarr.urlBase}/logout`}
|
||||
noRouter={true}
|
||||
>
|
||||
<Icon
|
||||
|
|
|
@ -15,7 +15,7 @@ import LoadingPage from './LoadingPage';
|
|||
import Page from './Page';
|
||||
|
||||
function testLocalStorage() {
|
||||
const key = 'sonarrTest';
|
||||
const key = 'lidarrTest';
|
||||
|
||||
try {
|
||||
localStorage.setItem(key, key);
|
||||
|
@ -64,7 +64,7 @@ function createMapStateToProps() {
|
|||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
dispatchFetchSeries() {
|
||||
dispatchFetchArtist() {
|
||||
dispatch(fetchArtist());
|
||||
},
|
||||
dispatchFetchTags() {
|
||||
|
@ -109,7 +109,7 @@ class PageConnector extends Component {
|
|||
|
||||
componentDidMount() {
|
||||
if (!this.props.isPopulated) {
|
||||
this.props.dispatchFetchSeries();
|
||||
this.props.dispatchFetchArtist();
|
||||
this.props.dispatchFetchTags();
|
||||
this.props.dispatchFetchQualityProfiles();
|
||||
this.props.dispatchFetchLanguageProfiles();
|
||||
|
@ -133,7 +133,7 @@ class PageConnector extends Component {
|
|||
const {
|
||||
isPopulated,
|
||||
hasError,
|
||||
dispatchFetchSeries,
|
||||
dispatchFetchArtist,
|
||||
dispatchFetchTags,
|
||||
dispatchFetchQualityProfiles,
|
||||
dispatchFetchLanguageProfiles,
|
||||
|
@ -171,7 +171,7 @@ PageConnector.propTypes = {
|
|||
isPopulated: PropTypes.bool.isRequired,
|
||||
hasError: PropTypes.bool.isRequired,
|
||||
isSidebarVisible: PropTypes.bool.isRequired,
|
||||
dispatchFetchSeries: PropTypes.func.isRequired,
|
||||
dispatchFetchArtist: PropTypes.func.isRequired,
|
||||
dispatchFetchTags: PropTypes.func.isRequired,
|
||||
dispatchFetchQualityProfiles: PropTypes.func.isRequired,
|
||||
dispatchFetchLanguageProfiles: PropTypes.func.isRequired,
|
||||
|
|
|
@ -415,7 +415,7 @@ class PageSidebar extends Component {
|
|||
transform
|
||||
} = this.state;
|
||||
|
||||
const urlBase = window.Sonarr.urlBase;
|
||||
const urlBase = window.Lidarr.urlBase;
|
||||
const pathname = urlBase ? location.pathname.substr(urlBase.length) || '/' : location.pathname;
|
||||
const activeParent = getActiveParent(pathname);
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ class SignalRConnector extends Component {
|
|||
componentDidMount() {
|
||||
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.received(this.onReceived);
|
||||
|
@ -232,11 +232,12 @@ class SignalRConnector extends Component {
|
|||
}
|
||||
|
||||
handleTrackFile = (body) => {
|
||||
const section = 'trackFiles';
|
||||
|
||||
if (body.action === 'updated') {
|
||||
this.props.updateItem({
|
||||
section: 'trackFiles',
|
||||
...body.resource
|
||||
});
|
||||
this.props.updateItem({ section, ...body.resource });
|
||||
} else if (body.action === 'deleted') {
|
||||
this.props.removeItem({ section, id: body.resource.id });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,7 +336,7 @@ class SignalRConnector extends Component {
|
|||
}
|
||||
|
||||
onReconnecting = () => {
|
||||
if (window.Sonarr.unloading) {
|
||||
if (window.Lidarr.unloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -349,7 +350,7 @@ class SignalRConnector extends Component {
|
|||
}
|
||||
|
||||
onDisconnected = () => {
|
||||
if (window.Sonarr.unloading) {
|
||||
if (window.Lidarr.unloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,14 +14,15 @@ function SpinnerIcon(props) {
|
|||
return (
|
||||
<Icon
|
||||
name={isSpinning ? (spinningName || name) : name}
|
||||
isSpinning={isSpinning}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
SpinnerIcon.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
spinningName: PropTypes.string.isRequired,
|
||||
name: PropTypes.object.isRequired,
|
||||
spinningName: PropTypes.object.isRequired,
|
||||
isSpinning: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ function TableOptionsColumn(props) {
|
|||
} = props;
|
||||
|
||||
return (
|
||||
<div className={!isModifiable && styles.notDragable}>
|
||||
<div className={isModifiable ? undefined : styles.notDragable}>
|
||||
<div
|
||||
className={classNames(
|
||||
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) {
|
||||
super(props, context);
|
||||
|
||||
this._saveCallback = null;
|
||||
|
||||
this.state = {
|
||||
isSaving: false,
|
||||
hasPendingChanges: false
|
||||
};
|
||||
}
|
||||
|
@ -22,28 +25,34 @@ class DownloadClientSettings extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
setDownloadClientOptionsRef = (ref) => {
|
||||
this._downloadClientOptions = ref;
|
||||
onChildMounted = (saveCallback) => {
|
||||
this._saveCallback = saveCallback;
|
||||
}
|
||||
|
||||
onHasPendingChange = (hasPendingChanges) => {
|
||||
this.setState({
|
||||
hasPendingChanges
|
||||
});
|
||||
onChildStateChange = (payload) => {
|
||||
this.setState(payload);
|
||||
}
|
||||
|
||||
onSavePress = () => {
|
||||
this._downloadClientOptions.getWrappedInstance().save();
|
||||
if (this._saveCallback) {
|
||||
this._saveCallback();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isSaving,
|
||||
hasPendingChanges
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<PageContent title="Download Client Settings">
|
||||
<SettingsToolbarConnector
|
||||
hasPendingChanges={this.state.hasPendingChanges}
|
||||
isSaving={isSaving}
|
||||
hasPendingChanges={hasPendingChanges}
|
||||
onSavePress={this.onSavePress}
|
||||
/>
|
||||
|
||||
|
@ -51,8 +60,8 @@ class DownloadClientSettings extends Component {
|
|||
<DownloadClientsConnector />
|
||||
|
||||
<DownloadClientOptionsConnector
|
||||
ref={this.setDownloadClientOptionsRef}
|
||||
onHasPendingChange={this.onHasPendingChange}
|
||||
onChildMounted={this.onChildMounted}
|
||||
onChildStateChange={this.onChildStateChange}
|
||||
/>
|
||||
|
||||
<RemotePathMappingsConnector />
|
||||
|
|
|
@ -60,6 +60,7 @@ class DownloadClient extends Component {
|
|||
return (
|
||||
<Card
|
||||
className={styles.downloadClient}
|
||||
overlayContent={true}
|
||||
onPress={this.onEditDownloadClientPress}
|
||||
>
|
||||
<div className={styles.name}>
|
||||
|
|
|
@ -21,10 +21,10 @@ function createMapStateToProps() {
|
|||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchDownloadClientOptions,
|
||||
setDownloadClientOptionsValue,
|
||||
saveDownloadClientOptions,
|
||||
clearPendingChanges
|
||||
dispatchFetchDownloadClientOptions: fetchDownloadClientOptions,
|
||||
dispatchSetDownloadClientOptionsValue: setDownloadClientOptionsValue,
|
||||
dispatchSaveDownloadClientOptions: saveDownloadClientOptions,
|
||||
dispatchClearPendingChanges: clearPendingChanges
|
||||
};
|
||||
|
||||
class DownloadClientOptionsConnector extends Component {
|
||||
|
@ -33,31 +33,43 @@ class DownloadClientOptionsConnector extends Component {
|
|||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchDownloadClientOptions();
|
||||
const {
|
||||
dispatchFetchDownloadClientOptions,
|
||||
dispatchSaveDownloadClientOptions,
|
||||
onChildMounted
|
||||
} = this.props;
|
||||
|
||||
dispatchFetchDownloadClientOptions();
|
||||
onChildMounted(dispatchSaveDownloadClientOptions);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.hasPendingChanges !== prevProps.hasPendingChanges) {
|
||||
this.props.onHasPendingChange(this.props.hasPendingChanges);
|
||||
const {
|
||||
hasPendingChanges,
|
||||
isSaving,
|
||||
onChildStateChange
|
||||
} = this.props;
|
||||
|
||||
if (
|
||||
prevProps.isSaving !== isSaving ||
|
||||
prevProps.hasPendingChanges !== hasPendingChanges
|
||||
) {
|
||||
onChildStateChange({
|
||||
isSaving,
|
||||
hasPendingChanges
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearPendingChanges({ section: this.props.section });
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
save = () => {
|
||||
this.props.saveDownloadClientOptions();
|
||||
this.props.dispatchClearPendingChanges({ section: this.props.section });
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onInputChange = ({ name, value }) => {
|
||||
this.props.setDownloadClientOptionsValue({ name, value });
|
||||
this.props.dispatchSetDownloadClientOptionsValue({ name, value });
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -75,18 +87,20 @@ class DownloadClientOptionsConnector extends Component {
|
|||
|
||||
DownloadClientOptionsConnector.propTypes = {
|
||||
section: PropTypes.string.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
hasPendingChanges: PropTypes.bool.isRequired,
|
||||
fetchDownloadClientOptions: PropTypes.func.isRequired,
|
||||
setDownloadClientOptionsValue: PropTypes.func.isRequired,
|
||||
saveDownloadClientOptions: PropTypes.func.isRequired,
|
||||
clearPendingChanges: PropTypes.func.isRequired,
|
||||
onHasPendingChange: PropTypes.func.isRequired
|
||||
dispatchFetchDownloadClientOptions: PropTypes.func.isRequired,
|
||||
dispatchSetDownloadClientOptionsValue: PropTypes.func.isRequired,
|
||||
dispatchSaveDownloadClientOptions: PropTypes.func.isRequired,
|
||||
dispatchClearPendingChanges: PropTypes.func.isRequired,
|
||||
onChildMounted: PropTypes.func.isRequired,
|
||||
onChildStateChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
{ withRef: true },
|
||||
undefined,
|
||||
{ section: 'settings.downloadClientOptions' }
|
||||
)(DownloadClientOptionsConnector);
|
||||
|
|
|
@ -49,6 +49,8 @@ function HostSettings(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="port"
|
||||
min={1}
|
||||
max={65535}
|
||||
helpTextWarning="Requires restart to take effect"
|
||||
onChange={onInputChange}
|
||||
{...port}
|
||||
|
@ -95,6 +97,8 @@ function HostSettings(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="sslPort"
|
||||
min={1}
|
||||
max={65535}
|
||||
helpTextWarning="Requires restart to take effect"
|
||||
onChange={onInputChange}
|
||||
{...sslPort}
|
||||
|
|
|
@ -74,6 +74,8 @@ function ProxySettings(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="proxyPort"
|
||||
min={1}
|
||||
max={65535}
|
||||
onChange={onInputChange}
|
||||
{...proxyPort}
|
||||
/>
|
||||
|
|
|
@ -14,7 +14,10 @@ class IndexerSettings extends Component {
|
|||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this._saveCallback = null;
|
||||
|
||||
this.state = {
|
||||
isSaving: false,
|
||||
hasPendingChanges: false
|
||||
};
|
||||
}
|
||||
|
@ -22,28 +25,34 @@ class IndexerSettings extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
setIndexerOptionsRef = (ref) => {
|
||||
this._indexerOptions = ref;
|
||||
onChildMounted = (saveCallback) => {
|
||||
this._saveCallback = saveCallback;
|
||||
}
|
||||
|
||||
onHasPendingChange = (hasPendingChanges) => {
|
||||
this.setState({
|
||||
hasPendingChanges
|
||||
});
|
||||
onChildStateChange = (payload) => {
|
||||
this.setState(payload);
|
||||
}
|
||||
|
||||
onSavePress = () => {
|
||||
this._indexerOptions.getWrappedInstance().save();
|
||||
if (this._saveCallback) {
|
||||
this._saveCallback();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isSaving,
|
||||
hasPendingChanges
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<PageContent title="Indexer Settings">
|
||||
<SettingsToolbarConnector
|
||||
hasPendingChanges={this.state.hasPendingChanges}
|
||||
isSaving={isSaving}
|
||||
hasPendingChanges={hasPendingChanges}
|
||||
onSavePress={this.onSavePress}
|
||||
/>
|
||||
|
||||
|
@ -51,8 +60,8 @@ class IndexerSettings extends Component {
|
|||
<IndexersConnector />
|
||||
|
||||
<IndexerOptionsConnector
|
||||
ref={this.setIndexerOptionsRef}
|
||||
onHasPendingChange={this.onHasPendingChange}
|
||||
onChildMounted={this.onChildMounted}
|
||||
onChildStateChange={this.onChildStateChange}
|
||||
/>
|
||||
|
||||
<RestrictionsConnector />
|
||||
|
|
|
@ -76,6 +76,7 @@ class Indexer extends Component {
|
|||
return (
|
||||
<Card
|
||||
className={styles.indexer}
|
||||
overlayContent={true}
|
||||
onPress={this.onEditIndexerPress}
|
||||
>
|
||||
<div className={styles.name}>
|
||||
|
|
|
@ -41,6 +41,7 @@ function IndexerOptions(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
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."
|
||||
onChange={onInputChange}
|
||||
{...settings.minimumAge}
|
||||
|
@ -53,6 +54,7 @@ function IndexerOptions(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="maximumSize"
|
||||
min={0}
|
||||
helpText="Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited."
|
||||
onChange={onInputChange}
|
||||
{...settings.maximumSize}
|
||||
|
@ -65,6 +67,7 @@ function IndexerOptions(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="retention"
|
||||
min={0}
|
||||
helpText="Usenet only: Set to zero to set for unlimited retention"
|
||||
onChange={onInputChange}
|
||||
{...settings.retention}
|
||||
|
@ -80,6 +83,7 @@ function IndexerOptions(props) {
|
|||
<FormInputGroup
|
||||
type={inputTypes.NUMBER}
|
||||
name="rssSyncInterval"
|
||||
min={0}
|
||||
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"
|
||||
helpLink="https://github.com/Sonarr/Sonarr/wiki/RSS-Sync"
|
||||
|
|
|
@ -21,10 +21,10 @@ function createMapStateToProps() {
|
|||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchIndexerOptions,
|
||||
setIndexerOptionsValue,
|
||||
saveIndexerOptions,
|
||||
clearPendingChanges
|
||||
dispatchFetchIndexerOptions: fetchIndexerOptions,
|
||||
dispatchSetIndexerOptionsValue: setIndexerOptionsValue,
|
||||
dispatchSaveIndexerOptions: saveIndexerOptions,
|
||||
dispatchClearPendingChanges: clearPendingChanges
|
||||
};
|
||||
|
||||
class IndexerOptionsConnector extends Component {
|
||||
|
@ -33,31 +33,43 @@ class IndexerOptionsConnector extends Component {
|
|||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchIndexerOptions();
|
||||
const {
|
||||
dispatchFetchIndexerOptions,
|
||||
dispatchSaveIndexerOptions,
|
||||
onChildMounted
|
||||
} = this.props;
|
||||
|
||||
dispatchFetchIndexerOptions();
|
||||
onChildMounted(dispatchSaveIndexerOptions);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.hasPendingChanges !== prevProps.hasPendingChanges) {
|
||||
this.props.onHasPendingChange(this.props.hasPendingChanges);
|
||||
const {
|
||||
hasPendingChanges,
|
||||
isSaving,
|
||||
onChildStateChange
|
||||
} = this.props;
|
||||
|
||||
if (
|
||||
prevProps.isSaving !== isSaving ||
|
||||
prevProps.hasPendingChanges !== hasPendingChanges
|
||||
) {
|
||||
onChildStateChange({
|
||||
isSaving,
|
||||
hasPendingChanges
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearPendingChanges({ section: this.props.section });
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
save = () => {
|
||||
this.props.saveIndexerOptions();
|
||||
this.props.dispatchClearPendingChanges({ section: this.props.section });
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onInputChange = ({ name, value }) => {
|
||||
this.props.setIndexerOptionsValue({ name, value });
|
||||
this.props.dispatchSetIndexerOptionsValue({ name, value });
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -75,18 +87,20 @@ class IndexerOptionsConnector extends Component {
|
|||
|
||||
IndexerOptionsConnector.propTypes = {
|
||||
section: PropTypes.string.isRequired,
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
hasPendingChanges: PropTypes.bool.isRequired,
|
||||
fetchIndexerOptions: PropTypes.func.isRequired,
|
||||
setIndexerOptionsValue: PropTypes.func.isRequired,
|
||||
saveIndexerOptions: PropTypes.func.isRequired,
|
||||
clearPendingChanges: PropTypes.func.isRequired,
|
||||
onHasPendingChange: PropTypes.func.isRequired
|
||||
dispatchFetchIndexerOptions: PropTypes.func.isRequired,
|
||||
dispatchSetIndexerOptionsValue: PropTypes.func.isRequired,
|
||||
dispatchSaveIndexerOptions: PropTypes.func.isRequired,
|
||||
dispatchClearPendingChanges: PropTypes.func.isRequired,
|
||||
onChildMounted: PropTypes.func.isRequired,
|
||||
onChildStateChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connectSection(
|
||||
createMapStateToProps,
|
||||
mapDispatchToProps,
|
||||
undefined,
|
||||
{ withRef: true },
|
||||
undefined,
|
||||
{ section: 'settings.indexerOptions' }
|
||||
)(IndexerOptionsConnector);
|
||||
|
|
|
@ -64,6 +64,7 @@ class Restriction extends Component {
|
|||
return (
|
||||
<Card
|
||||
className={styles.restriction}
|
||||
overlayContent={true}
|
||||
onPress={this.onEditRestrictionPress}
|
||||
>
|
||||
<div>
|
||||
|
|
|
@ -52,6 +52,7 @@ class Metadata extends Component {
|
|||
return (
|
||||
<Card
|
||||
className={styles.metadata}
|
||||
overlayContent={true}
|
||||
onPress={this.onEditMetadataPress}
|
||||
>
|
||||
<div className={styles.name}>
|
||||
|
|
|
@ -79,6 +79,7 @@ class Notification extends Component {
|
|||
return (
|
||||
<Card
|
||||
className={styles.notification}
|
||||
overlayContent={true}
|
||||
onPress={this.onEditNotificationPress}
|
||||
>
|
||||
<div className={styles.name}>
|
||||
|
|
|
@ -101,7 +101,7 @@ function EditLanguageProfileModalContent(props) {
|
|||
id &&
|
||||
<div
|
||||
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
|
||||
kind={kinds.DANGER}
|
||||
|
|
|
@ -97,7 +97,7 @@ function EditMetadataProfileModalContent(props) {
|
|||
id &&
|
||||
<div
|
||||
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
|
||||
kind={kinds.DANGER}
|
||||
|
|
|
@ -200,7 +200,7 @@ class EditQualityProfileModalContent extends Component {
|
|||
id &&
|
||||
<div
|
||||
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
|
||||
kind={kinds.DANGER}
|
||||
|
|
|
@ -28,6 +28,29 @@ function getValue(value) {
|
|||
|
||||
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
|
||||
|
||||
|
@ -131,6 +154,8 @@ class QualityDefinition extends Component {
|
|||
<NumberInput
|
||||
className={styles.sizeInput}
|
||||
name={`${id}.min`}
|
||||
min={slider.min}
|
||||
max={maxSize ? maxSize - 10 : slider.max - 10}
|
||||
value={minSize || slider.min}
|
||||
isFloat={true}
|
||||
onChange={this.onMinSizeChange}
|
||||
|
@ -143,6 +168,7 @@ class QualityDefinition extends Component {
|
|||
<NumberInput
|
||||
className={styles.sizeInput}
|
||||
name={`${id}.max`}
|
||||
min={minSize + 10}
|
||||
value={maxSize || slider.max}
|
||||
isFloat={true}
|
||||
onChange={this.onMaxSizeChange}
|
||||
|
|
|
@ -40,7 +40,7 @@ class QualityDefinitionConnector extends Component {
|
|||
this.props.setQualityDefinitionValue({ id, name: 'minSize', value: minSize });
|
||||
}
|
||||
|
||||
if (minSize !== currentMaxSize) {
|
||||
if (maxSize !== currentMaxSize) {
|
||||
this.props.setQualityDefinitionValue({ id, name: 'maxSize', value: maxSize });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,8 @@ function createMapStateToProps() {
|
|||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchQualityDefinitions,
|
||||
saveQualityDefinitions
|
||||
dispatchFetchQualityDefinitions: fetchQualityDefinitions,
|
||||
dispatchSaveQualityDefinitions: saveQualityDefinitions
|
||||
};
|
||||
|
||||
class QualityDefinitionsConnector extends Component {
|
||||
|
@ -36,26 +36,36 @@ class QualityDefinitionsConnector extends Component {
|
|||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchQualityDefinitions();
|
||||
this.props.dispatchFetchQualityDefinitions();
|
||||
|
||||
const {
|
||||
dispatchFetchQualityDefinitions,
|
||||
dispatchSaveQualityDefinitions,
|
||||
onChildMounted
|
||||
} = this.props;
|
||||
|
||||
dispatchFetchQualityDefinitions();
|
||||
onChildMounted(dispatchSaveQualityDefinitions);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
hasPendingChanges
|
||||
hasPendingChanges,
|
||||
isSaving,
|
||||
onChildStateChange
|
||||
} = this.props;
|
||||
|
||||
if (hasPendingChanges !== prevProps.hasPendingChanges) {
|
||||
this.props.onHasPendingChange(hasPendingChanges);
|
||||
if (
|
||||
prevProps.isSaving !== isSaving ||
|
||||
prevProps.hasPendingChanges !== hasPendingChanges
|
||||
) {
|
||||
onChildStateChange({
|
||||
isSaving,
|
||||
hasPendingChanges
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
save = () => {
|
||||
this.props.saveQualityDefinitions();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
|
@ -69,10 +79,12 @@ class QualityDefinitionsConnector extends Component {
|
|||
}
|
||||
|
||||
QualityDefinitionsConnector.propTypes = {
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
hasPendingChanges: PropTypes.bool.isRequired,
|
||||
fetchQualityDefinitions: PropTypes.func.isRequired,
|
||||
saveQualityDefinitions: PropTypes.func.isRequired,
|
||||
onHasPendingChange: PropTypes.func.isRequired
|
||||
dispatchFetchQualityDefinitions: PropTypes.func.isRequired,
|
||||
dispatchSaveQualityDefinitions: PropTypes.func.isRequired,
|
||||
onChildMounted: PropTypes.func.isRequired,
|
||||
onChildStateChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps, null, { withRef: true })(QualityDefinitionsConnector);
|
||||
|
|
|
@ -12,7 +12,10 @@ class Quality extends Component {
|
|||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this._saveCallback = null;
|
||||
|
||||
this.state = {
|
||||
isSaving: false,
|
||||
hasPendingChanges: false
|
||||
};
|
||||
}
|
||||
|
@ -20,35 +23,41 @@ class Quality extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
setQualityDefinitionsRef = (ref) => {
|
||||
this._qualityDefinitions = ref;
|
||||
onChildMounted = (saveCallback) => {
|
||||
this._saveCallback = saveCallback;
|
||||
}
|
||||
|
||||
onHasPendingChange = (hasPendingChanges) => {
|
||||
this.setState({
|
||||
hasPendingChanges
|
||||
});
|
||||
onChildStateChange = (payload) => {
|
||||
this.setState(payload);
|
||||
}
|
||||
|
||||
onSavePress = () => {
|
||||
this._qualityDefinitions.getWrappedInstance().save();
|
||||
if (this._saveCallback) {
|
||||
this._saveCallback();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isSaving,
|
||||
hasPendingChanges
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<PageContent title="Quality Settings">
|
||||
<SettingsToolbarConnector
|
||||
hasPendingChanges={this.state.hasPendingChanges}
|
||||
isSaving={isSaving}
|
||||
hasPendingChanges={hasPendingChanges}
|
||||
onSavePress={this.onSavePress}
|
||||
/>
|
||||
|
||||
<PageContentBodyConnector>
|
||||
<QualityDefinitionsConnector
|
||||
ref={this.setQualityDefinitionsRef}
|
||||
onHasPendingChange={this.onHasPendingChange}
|
||||
onChildMounted={this.onChildMounted}
|
||||
onChildStateChange={this.onChildStateChange}
|
||||
/>
|
||||
</PageContentBodyConnector>
|
||||
</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 React, { Component } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts';
|
||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import PendingChangesModal from './PendingChangesModal';
|
||||
import styles from './SettingsToolbar.css';
|
||||
import AdvancedSettingsButton from './AdvancedSettingsButton';
|
||||
|
||||
class SettingsToolbar extends Component {
|
||||
|
||||
|
@ -53,14 +52,9 @@ class SettingsToolbar extends Component {
|
|||
return (
|
||||
<PageToolbar>
|
||||
<PageToolbarSection>
|
||||
<PageToolbarButton
|
||||
label={advancedSettings ? 'Hide Advanced' : 'Show Advanced'}
|
||||
className={classNames(
|
||||
styles.advancedSettings,
|
||||
advancedSettings && styles.advancedSettingsEnabled
|
||||
)}
|
||||
iconName={icons.ADVANCED_SETTINGS}
|
||||
onPress={onAdvancedSettingsPress}
|
||||
<AdvancedSettingsButton
|
||||
advancedSettings={advancedSettings}
|
||||
onAdvancedSettingsPress={onAdvancedSettingsPress}
|
||||
/>
|
||||
|
||||
{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
if (window.Sonarr.analytics) {
|
||||
if (window.Lidarr.analytics) {
|
||||
const d = document;
|
||||
const g = d.createElement('script');
|
||||
const s = d.getElementsByTagName('script')[0];
|
||||
|
|
|
@ -7,7 +7,7 @@ import updateSectionState from 'Utilities/State/updateSectionState';
|
|||
import { createThunk } from 'Store/thunks';
|
||||
import createFetchHandler from 'Store/Actions/Creators/createFetchHandler';
|
||||
import createSaveHandler from 'Store/Actions/Creators/createSaveHandler';
|
||||
import { clearPendingChanges, update } from 'Store/Actions/baseActions';
|
||||
import { clearPendingChanges, set, update } from 'Store/Actions/baseActions';
|
||||
|
||||
//
|
||||
// Variables
|
||||
|
@ -70,6 +70,11 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: true
|
||||
}));
|
||||
|
||||
const promise = $.ajax({
|
||||
method: 'PUT',
|
||||
url: '/qualityDefinition/update',
|
||||
|
@ -78,10 +83,24 @@ export default {
|
|||
|
||||
promise.done((data) => {
|
||||
dispatch(batchActions([
|
||||
set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: null
|
||||
}),
|
||||
|
||||
update({ section, data }),
|
||||
clearPendingChanges({ section })
|
||||
]));
|
||||
});
|
||||
|
||||
promise.fail((xhr) => {
|
||||
dispatch(set({
|
||||
section,
|
||||
isSaving: false,
|
||||
saveError: xhr
|
||||
}));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ export const defaultState = {
|
|||
messages: {
|
||||
items: []
|
||||
},
|
||||
version: window.Sonarr.version,
|
||||
version: window.Lidarr.version,
|
||||
isUpdated: false,
|
||||
isConnected: true,
|
||||
isReconnecting: false,
|
||||
|
|
|
@ -176,25 +176,23 @@ export const actionHandlers = handleThunks({
|
|||
|
||||
[FETCH_QUEUE_STATUS]: createFetchHandler(status, '/queue/status'),
|
||||
|
||||
[FETCH_QUEUE_DETAILS]: function(payload) {
|
||||
return function(dispatch, getState) {
|
||||
let params = payload;
|
||||
[FETCH_QUEUE_DETAILS]: function(getState, payload, dispatch) {
|
||||
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)) {
|
||||
dispatch(set({ section: details, params }));
|
||||
} else {
|
||||
params = getState().queue.details.params;
|
||||
}
|
||||
if (params && !_.isEmpty(params)) {
|
||||
dispatch(set({ section: details, params }));
|
||||
} else {
|
||||
params = getState().queue.details.params;
|
||||
}
|
||||
|
||||
// Ensure there are params before trying to fetch the queue
|
||||
// so we don't make a bad request to the server.
|
||||
// Ensure there are params before trying to fetch the queue
|
||||
// so we don't make a bad request to the server.
|
||||
|
||||
if (params && !_.isEmpty(params)) {
|
||||
fetchQueueDetailsHelper(getState, params, dispatch);
|
||||
}
|
||||
};
|
||||
if (params && !_.isEmpty(params)) {
|
||||
fetchQueueDetailsHelper(getState, params, dispatch);
|
||||
}
|
||||
},
|
||||
|
||||
...createServerSideCollectionHandlers(
|
||||
|
|
|
@ -87,7 +87,7 @@ const config = {
|
|||
slicer,
|
||||
serialize,
|
||||
merge,
|
||||
key: 'sonarr'
|
||||
key: 'lidarr'
|
||||
};
|
||||
|
||||
export default persistState(paths, config);
|
||||
|
|
|
@ -25,7 +25,7 @@ export default function sentryMiddleware() {
|
|||
version,
|
||||
release,
|
||||
isProduction
|
||||
} = window.Sonarr;
|
||||
} = window.Lidarr;
|
||||
|
||||
if (!analytics) {
|
||||
return;
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
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}
|
||||
albumTitle={title}
|
||||
showOpenArtistButton={true}
|
||||
showOpenAlbumButton={true}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
|
|
|
@ -81,6 +81,7 @@ function MissingRow(props) {
|
|||
albumEntity={albumEntities.WANTED_MISSING}
|
||||
albumTitle={title}
|
||||
showOpenArtistButton={true}
|
||||
showOpenAlbumButton={true}
|
||||
/>
|
||||
</TableRowCell>
|
||||
);
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.Sonarr = {
|
||||
window.Lidarr = {
|
||||
apiRoot: 'API_ROOT',
|
||||
apiKey: 'API_KEY',
|
||||
release: 'APP_RELEASE',
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import $ from 'jquery';
|
||||
|
||||
const absUrlRegex = /^(https?:)?\/\//i;
|
||||
const apiRoot = window.Sonarr.apiRoot;
|
||||
const urlBase = window.Sonarr.urlBase;
|
||||
const apiRoot = window.Lidarr.apiRoot;
|
||||
const urlBase = window.Lidarr.urlBase;
|
||||
|
||||
function isRelative(xhr) {
|
||||
return !absUrlRegex.test(xhr.url);
|
||||
|
@ -31,7 +31,7 @@ function addRootUrl(xhr) {
|
|||
|
||||
function addApiKey(xhr) {
|
||||
xhr.headers = xhr.headers || {};
|
||||
xhr.headers['X-Api-Key'] = window.Sonarr.apiKey;
|
||||
xhr.headers['X-Api-Key'] = window.Lidarr.apiKey;
|
||||
}
|
||||
|
||||
export default function() {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint no-undef: 0 */
|
||||
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",
|
||||
"clipboard": "1.7.1",
|
||||
"create-react-class": "^15.6.2",
|
||||
"css-loader": "0.28.7",
|
||||
"css-loader": "0.28.9",
|
||||
"del": "3.0.0",
|
||||
"element-class": "0.2.2",
|
||||
"esformatter": "0.10.0",
|
||||
"eslint": "4.8.0",
|
||||
"eslint": "4.16.0",
|
||||
"eslint-loader": "1.9.0",
|
||||
"eslint-plugin-filenames": "1.2.0",
|
||||
"eslint-plugin-react": "7.4.0",
|
||||
"eslint-plugin-react": "7.5.1",
|
||||
"esprint": "0.4.0",
|
||||
"extract-text-webpack-plugin": "3.0.1",
|
||||
"file-loader": "1.1.5",
|
||||
"filesize": "3.5.10",
|
||||
"extract-text-webpack-plugin": "3.0.2",
|
||||
"file-loader": "1.1.6",
|
||||
"filesize": "3.5.11",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-cached": "1.1.1",
|
||||
"gulp-clean-css": "3.9.0",
|
||||
"gulp-clean-css": "3.9.2",
|
||||
"gulp-concat": "2.6.1",
|
||||
"gulp-declare": "0.3.0",
|
||||
"gulp-livereload": "3.8.1",
|
||||
"gulp-postcss": "7.0.0",
|
||||
"gulp-postcss": "7.0.1",
|
||||
"gulp-print": "2.0.1",
|
||||
"gulp-sourcemaps": "2.6.1",
|
||||
"gulp-sourcemaps": "2.6.3",
|
||||
"gulp-stripbom": "1.0.4",
|
||||
"gulp-util": "3.0.8",
|
||||
"gulp-watch": "4.3.11",
|
||||
"gulp-watch": "5.0.0",
|
||||
"gulp-wrap": "0.13.0",
|
||||
"history": "4.7.2",
|
||||
"jdu": "1.0.0",
|
||||
"jquery": "3.2.1",
|
||||
"jquery": "3.3.1",
|
||||
"loader-utils": "^1.1.0",
|
||||
"lodash": "4.17.4",
|
||||
"mobile-detect": "1.3.7",
|
||||
"moment": "2.18.1",
|
||||
"mobile-detect": "1.4.1",
|
||||
"moment": "2.20.1",
|
||||
"mousetrap": "1.6.1",
|
||||
"normalize.css": "7.0.0",
|
||||
"postcss-loader": "2.0.6",
|
||||
"postcss-mixins": "6.1.1",
|
||||
"postcss-nested": "2.1.2",
|
||||
"postcss-loader": "2.0.10",
|
||||
"postcss-mixins": "6.2.0",
|
||||
"postcss-nested": "3.0.0",
|
||||
"postcss-simple-vars": "4.1.0",
|
||||
"prop-types": "15.6.0",
|
||||
"qs": "6.5.1",
|
||||
"query-string": "5.0.0",
|
||||
"query-string": "5.0.1",
|
||||
"raven-for-redux": "1.0.0",
|
||||
"raven-js": "3.17.0",
|
||||
"react": "15.6.0",
|
||||
"react": "16.2.0",
|
||||
"react-addons-shallow-compare": "15.6.2",
|
||||
"react-async-script": "0.9.1",
|
||||
"react-autosuggest": "9.3.2",
|
||||
"react-custom-scrollbars": "4.1.2",
|
||||
"react-custom-scrollbars": "4.2.1",
|
||||
"react-dnd": "2.5.4",
|
||||
"react-dnd-html5-backend": "2.5.4",
|
||||
"react-document-title": "2.0.3",
|
||||
"react-dom": "15.6.0",
|
||||
"react-google-recaptcha": "0.9.7",
|
||||
"react-lazyload": "2.2.7",
|
||||
"react-dom": "16.2.0",
|
||||
"react-google-recaptcha": "0.9.9",
|
||||
"react-lazyload": "2.3.0",
|
||||
"react-measure": "1.4.7",
|
||||
"react-portal": "3.1.0",
|
||||
"react-redux": "5.0.6",
|
||||
"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-tabs": "2.1.0",
|
||||
"react-tag-autocomplete": "5.4.1",
|
||||
"react-tether": "0.5.7",
|
||||
"react-text-truncate": "0.12.0",
|
||||
"react-virtualized": "9.10.1",
|
||||
"react-tabs": "2.2.1",
|
||||
"react-tag-autocomplete": "5.5.0",
|
||||
"react-tether": "0.6.1",
|
||||
"react-text-truncate": "0.12.1",
|
||||
"react-virtualized": "9.18.0",
|
||||
"redux": "3.7.2",
|
||||
"redux-actions": "2.2.1",
|
||||
"redux-batched-actions": "0.2.0",
|
||||
"redux-batched-actions": "0.2.1",
|
||||
"redux-localstorage": "0.4.1",
|
||||
"redux-thunk": "2.2.0",
|
||||
"require-nocache": "1.0.0",
|
||||
"reselect": "3.0.1",
|
||||
"run-sequence": "2.2.0",
|
||||
"run-sequence": "2.2.1",
|
||||
"signalr": "2.2.2",
|
||||
"streamqueue": "1.1.1",
|
||||
"style-loader": "0.19.0",
|
||||
"stylelint": "8.2.0",
|
||||
"stylelint-order": "0.7.0",
|
||||
"tar.gz": "1.0.5",
|
||||
"streamqueue": "1.1.2",
|
||||
"style-loader": "0.19.1",
|
||||
"stylelint": "8.4.0",
|
||||
"stylelint-order": "0.8.0",
|
||||
"tar.gz": "1.0.7",
|
||||
"url-loader": "0.6.2",
|
||||
"webpack": "3.6.0",
|
||||
"webpack": "3.10.0",
|
||||
"webpack-stream": "^4.0.0"
|
||||
},
|
||||
"main": "index.js"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Nancy.Responses;
|
||||
using NzbDrone.Common.TPL;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.Download.Pending;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
@ -16,12 +17,18 @@ namespace Lidarr.Api.V1.Queue
|
|||
{
|
||||
private readonly IQueueService _queueService;
|
||||
private readonly IPendingReleaseService _pendingReleaseService;
|
||||
private readonly Debouncer _broadcastDebounce;
|
||||
|
||||
|
||||
public QueueStatusModule(IBroadcastSignalRMessage broadcastSignalRMessage, IQueueService queueService, IPendingReleaseService pendingReleaseService)
|
||||
: base(broadcastSignalRMessage, "queue/status")
|
||||
{
|
||||
_queueService = queueService;
|
||||
_pendingReleaseService = pendingReleaseService;
|
||||
|
||||
_broadcastDebounce = new Debouncer(BroadcastChange, TimeSpan.FromSeconds(5));
|
||||
|
||||
|
||||
Get["/"] = x => GetQueueStatusResponse();
|
||||
}
|
||||
|
||||
|
@ -32,25 +39,38 @@ namespace Lidarr.Api.V1.Queue
|
|||
|
||||
private QueueStatusResource GetQueueStatus()
|
||||
{
|
||||
_broadcastDebounce.Pause();
|
||||
|
||||
var queue = _queueService.GetQueue();
|
||||
var pending = _pendingReleaseService.GetPendingQueue();
|
||||
|
||||
return new QueueStatusResource
|
||||
var resource = new QueueStatusResource
|
||||
{
|
||||
Count = queue.Count + pending.Count,
|
||||
Errors = queue.Any(q => q.TrackedDownloadStatus.Equals("Error", 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)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Updated, GetQueueStatus());
|
||||
_broadcastDebounce.Execute();
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
{
|
||||
public class TrackModule : LidarrRestModuleWithSignalR<TrackFileResource, TrackFile>,
|
||||
IHandle<TrackFileAddedEvent>
|
||||
public class TrackFileModule : LidarrRestModuleWithSignalR<TrackFileResource, TrackFile>,
|
||||
IHandle<TrackFileAddedEvent>,
|
||||
IHandle<TrackFileDeletedEvent>
|
||||
{
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IDeleteMediaFiles _mediaFileDeletionService;
|
||||
|
@ -26,7 +27,7 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
private readonly IAlbumService _albumService;
|
||||
private readonly IUpgradableSpecification _upgradableSpecification;
|
||||
|
||||
public TrackModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||
public TrackFileModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||
IMediaFileService mediaFileService,
|
||||
IDeleteMediaFiles mediaFileDeletionService,
|
||||
IArtistService artistService,
|
||||
|
@ -170,5 +171,11 @@ namespace Lidarr.Api.V1.TrackFiles
|
|||
{
|
||||
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.Core.Datastore.Events;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Music.Events;
|
||||
|
@ -15,7 +16,8 @@ namespace Lidarr.Api.V1.Tracks
|
|||
{
|
||||
public abstract class TrackModuleWithSignalR : LidarrRestModuleWithSignalR<TrackResource, Track>,
|
||||
IHandle<TrackInfoRefreshedEvent>,
|
||||
IHandle<TrackImportedEvent>
|
||||
IHandle<TrackImportedEvent>,
|
||||
IHandle<TrackFileDeletedEvent>
|
||||
{
|
||||
protected readonly ITrackService _trackService;
|
||||
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)
|
||||
{
|
||||
var resource = GetResourceById(id);
|
||||
BroadcastResourceChange(action, resource);
|
||||
if (action == ModelAction.Deleted)
|
||||
{
|
||||
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());
|
||||
|
||||
_downloadClientItem = Builder<DownloadClientItem>.CreateNew().Build();
|
||||
|
||||
Mocker.GetMock<IMediaFileService>()
|
||||
.Setup(s => s.GetFilesWithRelativePath(It.IsAny<int>(), It.IsAny<string>()))
|
||||
.Returns(new List<TrackFile>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -198,5 +202,19 @@ namespace NzbDrone.Core.Test.MediaFiles
|
|||
Mocker.GetMock<IUpgradeMediaFiles>()
|
||||
.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;
|
||||
|
||||
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);
|
||||
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"))
|
||||
{
|
||||
_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());
|
||||
}
|
||||
|
||||
|
|
|
@ -248,6 +248,12 @@ namespace NzbDrone.Core.History
|
|||
_logger.Debug("Removing track file from DB as part of cleanup routine, not creating history event.");
|
||||
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)
|
||||
{
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
namespace NzbDrone.Core.MediaFiles
|
||||
namespace NzbDrone.Core.MediaFiles
|
||||
{
|
||||
public enum DeleteMediaFileReason
|
||||
{
|
||||
MissingFromDisk,
|
||||
Manual,
|
||||
Upgrade,
|
||||
NoLinkedEpisodes
|
||||
NoLinkedEpisodes,
|
||||
ManualOverride
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NzbDrone.Core.Datastore;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
|
@ -11,6 +11,7 @@ namespace NzbDrone.Core.MediaFiles
|
|||
List<TrackFile> GetFilesByArtist(int artistId);
|
||||
List<TrackFile> GetFilesByAlbum(int albumId);
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
TrackFile Get(int id);
|
||||
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();
|
||||
}
|
||||
|
||||
public List<TrackFile> GetFilesWithRelativePath(int artistId, string relativePath)
|
||||
{
|
||||
return _mediaFileRepository.GetFilesWithRelativePath(artistId, relativePath);
|
||||
}
|
||||
|
||||
public void HandleAsync(ArtistDeletedEvent message)
|
||||
{
|
||||
var files = GetFilesByArtist(message.Artist.Id);
|
||||
|
|
|
@ -147,7 +147,7 @@ namespace NzbDrone.Core.MediaFiles.MediaInfo
|
|||
}
|
||||
|
||||
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)
|
||||
.Write();
|
||||
|
||||
|
|
|
@ -115,6 +115,15 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
|
|||
else
|
||||
{
|
||||
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);
|
||||
|
|
Loading…
Reference in New Issue