mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-30 03:38:26 -07:00
New: Album Studio is now part of artists list
(cherry picked from commit bdcfef80d627e777d7932c54cda04cbe7c656ffc) Load albums for album details on mouse hover
This commit is contained in:
parent
de6c9589d0
commit
72267d3cb4
25 changed files with 591 additions and 18 deletions
|
@ -1,4 +1,5 @@
|
||||||
import ModelBase from 'App/ModelBase';
|
import ModelBase from 'App/ModelBase';
|
||||||
|
import Artist from 'Artist/Artist';
|
||||||
|
|
||||||
export interface Statistics {
|
export interface Statistics {
|
||||||
trackCount: number;
|
trackCount: number;
|
||||||
|
@ -9,13 +10,16 @@ export interface Statistics {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Album extends ModelBase {
|
interface Album extends ModelBase {
|
||||||
|
artist: Artist;
|
||||||
foreignAlbumId: string;
|
foreignAlbumId: string;
|
||||||
title: string;
|
title: string;
|
||||||
overview: string;
|
overview: string;
|
||||||
disambiguation?: string;
|
disambiguation?: string;
|
||||||
|
albumType: string;
|
||||||
monitored: boolean;
|
monitored: boolean;
|
||||||
releaseDate: string;
|
releaseDate: string;
|
||||||
statistics: Statistics;
|
statistics: Statistics;
|
||||||
|
isSaving?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Album;
|
export default Album;
|
||||||
|
|
8
frontend/src/App/State/AlbumAppState.ts
Normal file
8
frontend/src/App/State/AlbumAppState.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import Album from 'Album/Album';
|
||||||
|
import AppSectionState, {
|
||||||
|
AppSectionDeleteState,
|
||||||
|
} from 'App/State/AppSectionState';
|
||||||
|
|
||||||
|
interface AlbumAppState extends AppSectionState<Album>, AppSectionDeleteState {}
|
||||||
|
|
||||||
|
export default AlbumAppState;
|
|
@ -1,3 +1,4 @@
|
||||||
|
import AlbumAppState from './AlbumAppState';
|
||||||
import ArtistAppState, { ArtistIndexAppState } from './ArtistAppState';
|
import ArtistAppState, { ArtistIndexAppState } from './ArtistAppState';
|
||||||
import SettingsAppState from './SettingsAppState';
|
import SettingsAppState from './SettingsAppState';
|
||||||
import TagsAppState from './TagsAppState';
|
import TagsAppState from './TagsAppState';
|
||||||
|
@ -35,6 +36,7 @@ export interface CustomFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
|
albums: AlbumAppState;
|
||||||
artist: ArtistAppState;
|
artist: ArtistAppState;
|
||||||
artistIndex: ArtistIndexAppState;
|
artistIndex: ArtistIndexAppState;
|
||||||
settings: SettingsAppState;
|
settings: SettingsAppState;
|
||||||
|
|
|
@ -23,6 +23,7 @@ export interface Ratings {
|
||||||
|
|
||||||
interface Artist extends ModelBase {
|
interface Artist extends ModelBase {
|
||||||
added: string;
|
added: string;
|
||||||
|
artistMetadataId: string;
|
||||||
foreignArtistId: string;
|
foreignArtistId: string;
|
||||||
cleanName: string;
|
cleanName: string;
|
||||||
ended: boolean;
|
ended: boolean;
|
||||||
|
@ -37,7 +38,6 @@ interface Artist extends ModelBase {
|
||||||
metadataProfileId: number;
|
metadataProfileId: number;
|
||||||
ratings: Ratings;
|
ratings: Ratings;
|
||||||
rootFolderPath: string;
|
rootFolderPath: string;
|
||||||
albums: Album[];
|
|
||||||
sortName: string;
|
sortName: string;
|
||||||
statistics: Statistics;
|
statistics: Statistics;
|
||||||
status: string;
|
status: string;
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
.albums {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.truncated {
|
||||||
|
align-self: center;
|
||||||
|
flex: 0 0 100%;
|
||||||
|
padding: 4px 6px;
|
||||||
|
}
|
8
frontend/src/Artist/Index/Select/AlbumStudio/AlbumDetails.css.d.ts
vendored
Normal file
8
frontend/src/Artist/Index/Select/AlbumStudio/AlbumDetails.css.d.ts
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'albums': string;
|
||||||
|
'truncated': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
|
@ -0,0 +1,88 @@
|
||||||
|
import _ from 'lodash';
|
||||||
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import { clearAlbums, fetchAlbums } from 'Store/Actions/albumActions';
|
||||||
|
import createArtistAlbumsSelector from 'Store/Selectors/createArtistAlbumsSelector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import AlbumStudioAlbum from './AlbumStudioAlbum';
|
||||||
|
import styles from './AlbumDetails.css';
|
||||||
|
|
||||||
|
interface AlbumDetailsProps {
|
||||||
|
artistId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlbumDetails(props: AlbumDetailsProps) {
|
||||||
|
const { artistId } = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items: albums,
|
||||||
|
} = useSelector(createArtistAlbumsSelector(artistId));
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchAlbums({ artistId }));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dispatch(clearAlbums());
|
||||||
|
};
|
||||||
|
}, [dispatch, artistId]);
|
||||||
|
|
||||||
|
const latestAlbums = useMemo(() => {
|
||||||
|
const sortedAlbums = _.orderBy(albums, 'releaseDate', 'desc');
|
||||||
|
|
||||||
|
return sortedAlbums.slice(0, 20);
|
||||||
|
}, [albums]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.albums}>
|
||||||
|
{isFetching ? <LoadingIndicator /> : null}
|
||||||
|
|
||||||
|
{!isFetching && error ? (
|
||||||
|
<Alert kind={kinds.DANGER}>{translate('AlbumsLoadError')}</Alert>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{isPopulated && !error
|
||||||
|
? latestAlbums.map((album) => {
|
||||||
|
const {
|
||||||
|
id: albumId,
|
||||||
|
title,
|
||||||
|
disambiguation,
|
||||||
|
albumType,
|
||||||
|
monitored,
|
||||||
|
statistics,
|
||||||
|
isSaving,
|
||||||
|
} = album;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlbumStudioAlbum
|
||||||
|
key={albumId}
|
||||||
|
artistId={artistId}
|
||||||
|
albumId={albumId}
|
||||||
|
title={title}
|
||||||
|
disambiguation={disambiguation}
|
||||||
|
albumType={albumType}
|
||||||
|
monitored={monitored}
|
||||||
|
statistics={statistics}
|
||||||
|
isSaving={isSaving}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
|
||||||
|
{latestAlbums.length < albums.length ? (
|
||||||
|
<div className={styles.truncated}>
|
||||||
|
{translate('AlbumStudioTruncated')}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AlbumDetails;
|
|
@ -0,0 +1,39 @@
|
||||||
|
.album {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 2px 4px;
|
||||||
|
border: 1px solid var(--borderColor);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: var(--albumBackgroundColor);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.albumType {
|
||||||
|
padding: 0 4px;
|
||||||
|
border-width: 0 1px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--borderColor);
|
||||||
|
background-color: var(--albumBackgroundColor);
|
||||||
|
color: var(--defaultColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tracks {
|
||||||
|
padding: 0 4px;
|
||||||
|
background-color: var(--trackBackgroundColor);
|
||||||
|
color: var(--defaultColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
.allTracks {
|
||||||
|
background-color: color(#27c24c saturation(-25%));
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.missingWanted {
|
||||||
|
background-color: color(#f05050 saturation(-20%));
|
||||||
|
color: var(--white);
|
||||||
|
}
|
12
frontend/src/Artist/Index/Select/AlbumStudio/AlbumStudioAlbum.css.d.ts
vendored
Normal file
12
frontend/src/Artist/Index/Select/AlbumStudio/AlbumStudioAlbum.css.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'album': string;
|
||||||
|
'albumType': string;
|
||||||
|
'allTracks': string;
|
||||||
|
'info': string;
|
||||||
|
'missingWanted': string;
|
||||||
|
'tracks': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
|
@ -0,0 +1,83 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { Statistics } from 'Album/Album';
|
||||||
|
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||||
|
import { toggleAlbumsMonitored } from 'Store/Actions/albumActions';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './AlbumStudioAlbum.css';
|
||||||
|
|
||||||
|
interface AlbumStudioAlbumProps {
|
||||||
|
artistId: number;
|
||||||
|
albumId: number;
|
||||||
|
title: string;
|
||||||
|
disambiguation: string;
|
||||||
|
albumType: string;
|
||||||
|
monitored: boolean;
|
||||||
|
statistics: Statistics;
|
||||||
|
isSaving: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlbumStudioAlbum(props: AlbumStudioAlbumProps) {
|
||||||
|
const {
|
||||||
|
albumId,
|
||||||
|
title,
|
||||||
|
disambiguation,
|
||||||
|
albumType,
|
||||||
|
monitored,
|
||||||
|
statistics = {
|
||||||
|
trackFileCount: 0,
|
||||||
|
totalTrackCount: 0,
|
||||||
|
percentOfTracks: 0,
|
||||||
|
},
|
||||||
|
isSaving = false,
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const { trackFileCount, totalTrackCount, percentOfTracks } = statistics;
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const onAlbumMonitoredPress = useCallback(() => {
|
||||||
|
dispatch(
|
||||||
|
toggleAlbumsMonitored({
|
||||||
|
albumIds: [albumId],
|
||||||
|
monitored: !monitored,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, [albumId, monitored, dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.album}>
|
||||||
|
<div className={styles.info}>
|
||||||
|
<MonitorToggleButton
|
||||||
|
monitored={monitored}
|
||||||
|
isSaving={isSaving}
|
||||||
|
onPress={onAlbumMonitoredPress}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{disambiguation ? `${title} (${disambiguation})` : `${title}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.albumType}>
|
||||||
|
<span>{albumType}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
styles.tracks,
|
||||||
|
percentOfTracks < 100 && monitored && styles.missingWanted,
|
||||||
|
percentOfTracks === 100 && styles.allTracks
|
||||||
|
)}
|
||||||
|
title={translate('AlbumStudioTracksDownloaded', {
|
||||||
|
trackFileCount,
|
||||||
|
totalTrackCount,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{totalTrackCount === 0 ? '0/0' : `${trackFileCount}/${totalTrackCount}`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AlbumStudioAlbum;
|
|
@ -0,0 +1,26 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Modal from 'Components/Modal/Modal';
|
||||||
|
import ChangeMonitoringModalContent from './ChangeMonitoringModalContent';
|
||||||
|
|
||||||
|
interface ChangeMonitoringModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
artistIds: number[];
|
||||||
|
onSavePress(monitor: string): void;
|
||||||
|
onModalClose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChangeMonitoringModal(props: ChangeMonitoringModalProps) {
|
||||||
|
const { isOpen, artistIds, onSavePress, onModalClose } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||||
|
<ChangeMonitoringModalContent
|
||||||
|
artistIds={artistIds}
|
||||||
|
onSavePress={onSavePress}
|
||||||
|
onModalClose={onModalClose}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChangeMonitoringModal;
|
|
@ -0,0 +1,26 @@
|
||||||
|
.labelIcon {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
composes: alert from '~Components/Alert.css';
|
||||||
|
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modalFooter {
|
||||||
|
composes: modalFooter from '~Components/Modal/ModalFooter.css';
|
||||||
|
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: $breakpointExtraSmall) {
|
||||||
|
.modalFooter {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
10
frontend/src/Artist/Index/Select/AlbumStudio/ChangeMonitoringModalContent.css.d.ts
vendored
Normal file
10
frontend/src/Artist/Index/Select/AlbumStudio/ChangeMonitoringModalContent.css.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'labelIcon': string;
|
||||||
|
'message': string;
|
||||||
|
'modalFooter': string;
|
||||||
|
'selected': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
|
@ -0,0 +1,96 @@
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import ArtistMonitoringOptionsPopoverContent from 'AddArtist/ArtistMonitoringOptionsPopoverContent';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
import Form from 'Components/Form/Form';
|
||||||
|
import FormGroup from 'Components/Form/FormGroup';
|
||||||
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
import FormLabel from 'Components/Form/FormLabel';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
|
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './ChangeMonitoringModalContent.css';
|
||||||
|
|
||||||
|
const NO_CHANGE = 'noChange';
|
||||||
|
|
||||||
|
interface ChangeMonitoringModalContentProps {
|
||||||
|
artistIds: number[];
|
||||||
|
saveError?: object;
|
||||||
|
onSavePress(monitor: string): void;
|
||||||
|
onModalClose(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChangeMonitoringModalContent(
|
||||||
|
props: ChangeMonitoringModalContentProps
|
||||||
|
) {
|
||||||
|
const { artistIds, onSavePress, onModalClose, ...otherProps } = props;
|
||||||
|
|
||||||
|
const [monitor, setMonitor] = useState(NO_CHANGE);
|
||||||
|
|
||||||
|
const onInputChange = useCallback(
|
||||||
|
({ value }) => {
|
||||||
|
setMonitor(value);
|
||||||
|
},
|
||||||
|
[setMonitor]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSavePressWrapper = useCallback(() => {
|
||||||
|
onSavePress(monitor);
|
||||||
|
}, [monitor, onSavePress]);
|
||||||
|
|
||||||
|
const selectedCount = artistIds.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalContent onModalClose={onModalClose}>
|
||||||
|
<ModalHeader>{translate('MonitorArtists')}</ModalHeader>
|
||||||
|
|
||||||
|
<ModalBody>
|
||||||
|
<Alert kind={kinds.INFO} className={styles.message}>
|
||||||
|
{translate('MonitorAlbumExistingOnlyWarning')}
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<Form {...otherProps}>
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>
|
||||||
|
{translate('MonitorExistingAlbums')}
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
anchor={<Icon className={styles.labelIcon} name={icons.INFO} />}
|
||||||
|
title={translate('MonitoringOptions')}
|
||||||
|
body={<ArtistMonitoringOptionsPopoverContent />}
|
||||||
|
position={tooltipPositions.RIGHT}
|
||||||
|
/>
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.MONITOR_ALBUMS_SELECT}
|
||||||
|
name="monitor"
|
||||||
|
value={monitor}
|
||||||
|
includeNoChange={true}
|
||||||
|
onChange={onInputChange}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</Form>
|
||||||
|
</ModalBody>
|
||||||
|
|
||||||
|
<ModalFooter className={styles.modalFooter}>
|
||||||
|
<div className={styles.selected}>
|
||||||
|
{translate('CountArtistsSelected', { count: selectedCount })}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
||||||
|
|
||||||
|
<Button onPress={onSavePressWrapper}>{translate('Save')}</Button>
|
||||||
|
</div>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChangeMonitoringModalContent;
|
|
@ -51,6 +51,10 @@
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.actionButtons {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
.actionButtons,
|
.actionButtons,
|
||||||
.deleteButtons {
|
.deleteButtons {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -2,15 +2,20 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
import { RENAME_ARTIST, RETAG_ARTIST } from 'Commands/commandNames';
|
import { RENAME_ARTIST, RETAG_ARTIST } from 'Commands/commandNames';
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import { saveArtistEditor } from 'Store/Actions/artistActions';
|
import {
|
||||||
|
saveArtistEditor,
|
||||||
|
updateArtistsMonitor,
|
||||||
|
} from 'Store/Actions/artistActions';
|
||||||
import { fetchRootFolders } from 'Store/Actions/settingsActions';
|
import { fetchRootFolders } from 'Store/Actions/settingsActions';
|
||||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||||
|
import ChangeMonitoringModal from './AlbumStudio/ChangeMonitoringModal';
|
||||||
import RetagArtistModal from './AudioTags/RetagArtistModal';
|
import RetagArtistModal from './AudioTags/RetagArtistModal';
|
||||||
import DeleteArtistModal from './Delete/DeleteArtistModal';
|
import DeleteArtistModal from './Delete/DeleteArtistModal';
|
||||||
import EditArtistModal from './Edit/EditArtistModal';
|
import EditArtistModal from './Edit/EditArtistModal';
|
||||||
|
@ -19,7 +24,7 @@ import TagsModal from './Tags/TagsModal';
|
||||||
import styles from './ArtistIndexSelectFooter.css';
|
import styles from './ArtistIndexSelectFooter.css';
|
||||||
|
|
||||||
const artistEditorSelector = createSelector(
|
const artistEditorSelector = createSelector(
|
||||||
(state) => state.artist,
|
(state: AppState) => state.artist,
|
||||||
(artist) => {
|
(artist) => {
|
||||||
const { isSaving, isDeleting, deleteError } = artist;
|
const { isSaving, isDeleting, deleteError } = artist;
|
||||||
|
|
||||||
|
@ -48,9 +53,11 @@ function ArtistIndexSelectFooter() {
|
||||||
const [isOrganizeModalOpen, setIsOrganizeModalOpen] = useState(false);
|
const [isOrganizeModalOpen, setIsOrganizeModalOpen] = useState(false);
|
||||||
const [isRetaggingModalOpen, setIsRetaggingModalOpen] = useState(false);
|
const [isRetaggingModalOpen, setIsRetaggingModalOpen] = useState(false);
|
||||||
const [isTagsModalOpen, setIsTagsModalOpen] = useState(false);
|
const [isTagsModalOpen, setIsTagsModalOpen] = useState(false);
|
||||||
|
const [isMonitoringModalOpen, setIsMonitoringModalOpen] = useState(false);
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
const [isSavingArtist, setIsSavingArtist] = useState(false);
|
const [isSavingArtist, setIsSavingArtist] = useState(false);
|
||||||
const [isSavingTags, setIsSavingTags] = useState(false);
|
const [isSavingTags, setIsSavingTags] = useState(false);
|
||||||
|
const [isSavingMonitoring, setIsSavingMonitoring] = useState(false);
|
||||||
|
|
||||||
const [selectState, selectDispatch] = useSelect();
|
const [selectState, selectDispatch] = useSelect();
|
||||||
const { selectedState } = selectState;
|
const { selectedState } = selectState;
|
||||||
|
@ -124,6 +131,29 @@ function ArtistIndexSelectFooter() {
|
||||||
[artistIds, dispatch]
|
[artistIds, dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onMonitoringPress = useCallback(() => {
|
||||||
|
setIsMonitoringModalOpen(true);
|
||||||
|
}, [setIsMonitoringModalOpen]);
|
||||||
|
|
||||||
|
const onMonitoringClose = useCallback(() => {
|
||||||
|
setIsMonitoringModalOpen(false);
|
||||||
|
}, [setIsMonitoringModalOpen]);
|
||||||
|
|
||||||
|
const onMonitoringSavePress = useCallback(
|
||||||
|
(monitor: string) => {
|
||||||
|
setIsSavingMonitoring(true);
|
||||||
|
setIsMonitoringModalOpen(false);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
updateArtistsMonitor({
|
||||||
|
artistIds,
|
||||||
|
monitor,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[artistIds, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
const onDeletePress = useCallback(() => {
|
const onDeletePress = useCallback(() => {
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
}, [setIsDeleteModalOpen]);
|
}, [setIsDeleteModalOpen]);
|
||||||
|
@ -136,6 +166,7 @@ function ArtistIndexSelectFooter() {
|
||||||
if (!isSaving) {
|
if (!isSaving) {
|
||||||
setIsSavingArtist(false);
|
setIsSavingArtist(false);
|
||||||
setIsSavingTags(false);
|
setIsSavingTags(false);
|
||||||
|
setIsSavingMonitoring(false);
|
||||||
}
|
}
|
||||||
}, [isSaving]);
|
}, [isSaving]);
|
||||||
|
|
||||||
|
@ -188,6 +219,14 @@ function ArtistIndexSelectFooter() {
|
||||||
>
|
>
|
||||||
{translate('SetAppTags')}
|
{translate('SetAppTags')}
|
||||||
</SpinnerButton>
|
</SpinnerButton>
|
||||||
|
|
||||||
|
<SpinnerButton
|
||||||
|
isSpinning={isSaving && isSavingMonitoring}
|
||||||
|
isDisabled={!anySelected || isOrganizingArtist || isRetaggingArtist}
|
||||||
|
onPress={onMonitoringPress}
|
||||||
|
>
|
||||||
|
{translate('UpdateMonitoring')}
|
||||||
|
</SpinnerButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.deleteButtons}>
|
<div className={styles.deleteButtons}>
|
||||||
|
@ -220,6 +259,13 @@ function ArtistIndexSelectFooter() {
|
||||||
onModalClose={onTagsModalClose}
|
onModalClose={onTagsModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ChangeMonitoringModal
|
||||||
|
isOpen={isMonitoringModalOpen}
|
||||||
|
artistIds={artistIds}
|
||||||
|
onSavePress={onMonitoringSavePress}
|
||||||
|
onModalClose={onMonitoringClose}
|
||||||
|
/>
|
||||||
|
|
||||||
<OrganizeArtistModal
|
<OrganizeArtistModal
|
||||||
isOpen={isOrganizeModalOpen}
|
isOpen={isOrganizeModalOpen}
|
||||||
artistIds={artistIds}
|
artistIds={artistIds}
|
||||||
|
|
4
frontend/src/Artist/Index/Table/AlbumsCell.css
Normal file
4
frontend/src/Artist/Index/Table/AlbumsCell.css
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
.albumCount {
|
||||||
|
width: 100%;
|
||||||
|
cursor: default;
|
||||||
|
}
|
7
frontend/src/Artist/Index/Table/AlbumsCell.css.d.ts
vendored
Normal file
7
frontend/src/Artist/Index/Table/AlbumsCell.css.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// This file is automatically generated.
|
||||||
|
// Please do not change this file!
|
||||||
|
interface CssExports {
|
||||||
|
'albumCount': string;
|
||||||
|
}
|
||||||
|
export const cssExports: CssExports;
|
||||||
|
export default cssExports;
|
38
frontend/src/Artist/Index/Table/AlbumsCell.tsx
Normal file
38
frontend/src/Artist/Index/Table/AlbumsCell.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import React from 'react';
|
||||||
|
import AlbumDetails from 'Artist/Index/Select/AlbumStudio/AlbumDetails';
|
||||||
|
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||||
|
import Popover from 'Components/Tooltip/Popover';
|
||||||
|
import TooltipPosition from 'Helpers/Props/TooltipPosition';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './AlbumsCell.css';
|
||||||
|
|
||||||
|
interface SeriesStatusCellProps {
|
||||||
|
className: string;
|
||||||
|
artistId: number;
|
||||||
|
albumCount: number;
|
||||||
|
isSelectMode: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function AlbumsCell(props: SeriesStatusCellProps) {
|
||||||
|
const { className, artistId, albumCount, isSelectMode, ...otherProps } =
|
||||||
|
props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VirtualTableRowCell className={className} {...otherProps}>
|
||||||
|
{isSelectMode && albumCount > 0 ? (
|
||||||
|
<Popover
|
||||||
|
className={styles.albumCount}
|
||||||
|
anchor={albumCount}
|
||||||
|
title={translate('AlbumDetails')}
|
||||||
|
body={<AlbumDetails artistId={artistId} />}
|
||||||
|
position={TooltipPosition.Left}
|
||||||
|
canFlip={true}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
albumCount
|
||||||
|
)}
|
||||||
|
</VirtualTableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AlbumsCell;
|
|
@ -26,6 +26,7 @@ import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import getProgressBarKind from 'Utilities/Artist/getProgressBarKind';
|
import getProgressBarKind from 'Utilities/Artist/getProgressBarKind';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
|
import AlbumsCell from './AlbumsCell';
|
||||||
import hasGrowableColumns from './hasGrowableColumns';
|
import hasGrowableColumns from './hasGrowableColumns';
|
||||||
import selectTableOptions from './selectTableOptions';
|
import selectTableOptions from './selectTableOptions';
|
||||||
import styles from './ArtistIndexRow.css';
|
import styles from './ArtistIndexRow.css';
|
||||||
|
@ -164,6 +165,7 @@ function ArtistIndexRow(props: ArtistIndexRowProps) {
|
||||||
artistType={artistType}
|
artistType={artistType}
|
||||||
monitored={monitored}
|
monitored={monitored}
|
||||||
status={status}
|
status={status}
|
||||||
|
isSelectMode={isSelectMode}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
component={VirtualTableRowCell}
|
component={VirtualTableRowCell}
|
||||||
/>
|
/>
|
||||||
|
@ -288,9 +290,13 @@ function ArtistIndexRow(props: ArtistIndexRowProps) {
|
||||||
|
|
||||||
if (name === 'albumCount') {
|
if (name === 'albumCount') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableRowCell key={name} className={styles[name]}>
|
<AlbumsCell
|
||||||
{albumCount}
|
key={name}
|
||||||
</VirtualTableRowCell>
|
className={styles[name]}
|
||||||
|
artistId={artistId}
|
||||||
|
albumCount={albumCount}
|
||||||
|
isSelectMode={isSelectMode}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ interface ArtistStatusCellProps {
|
||||||
artistType?: string;
|
artistType?: string;
|
||||||
monitored: boolean;
|
monitored: boolean;
|
||||||
status: string;
|
status: string;
|
||||||
|
isSelectMode: boolean;
|
||||||
isSaving: boolean;
|
isSaving: boolean;
|
||||||
component?: React.ElementType;
|
component?: React.ElementType;
|
||||||
}
|
}
|
||||||
|
@ -25,12 +26,14 @@ function ArtistStatusCell(props: ArtistStatusCellProps) {
|
||||||
artistType,
|
artistType,
|
||||||
monitored,
|
monitored,
|
||||||
status,
|
status,
|
||||||
|
isSelectMode,
|
||||||
isSaving,
|
isSaving,
|
||||||
component: Component = VirtualTableRowCell,
|
component: Component = VirtualTableRowCell,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const endedString = artistType === 'Person' ? 'Deceased' : 'Inactive';
|
const endedString =
|
||||||
|
artistType === 'Person' ? translate('Deceased') : translate('Inactive');
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const onMonitoredPress = useCallback(() => {
|
const onMonitoredPress = useCallback(() => {
|
||||||
|
@ -39,13 +42,24 @@ function ArtistStatusCell(props: ArtistStatusCellProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Component className={className} {...otherProps}>
|
<Component className={className} {...otherProps}>
|
||||||
<MonitorToggleButton
|
{isSelectMode ? (
|
||||||
className={styles.monitorToggle}
|
<MonitorToggleButton
|
||||||
monitored={monitored}
|
className={styles.statusIcon}
|
||||||
size={14}
|
monitored={monitored}
|
||||||
isSaving={isSaving}
|
isSaving={isSaving}
|
||||||
onPress={onMonitoredPress}
|
onPress={onMonitoredPress}
|
||||||
/>
|
/>
|
||||||
|
) : (
|
||||||
|
<Icon
|
||||||
|
className={styles.statusIcon}
|
||||||
|
name={monitored ? icons.MONITORED : icons.UNMONITORED}
|
||||||
|
title={
|
||||||
|
monitored
|
||||||
|
? translate('ArtistIsMonitored')
|
||||||
|
: translate('ArtistIsUnmonitored')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Icon
|
<Icon
|
||||||
className={styles.statusIcon}
|
className={styles.statusIcon}
|
||||||
|
|
|
@ -47,7 +47,8 @@ class MonitoringOptionsModalContentConnector extends Component {
|
||||||
onSavePress = ({ monitor }) => {
|
onSavePress = ({ monitor }) => {
|
||||||
this.props.dispatchUpdateMonitoringOptions({
|
this.props.dispatchUpdateMonitoringOptions({
|
||||||
artistIds: [this.props.artistId],
|
artistIds: [this.props.artistId],
|
||||||
monitor
|
monitor,
|
||||||
|
shouldFetchAlbumsAfterUpdate: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -358,7 +358,8 @@ export const actionHandlers = handleThunks({
|
||||||
artistIds,
|
artistIds,
|
||||||
monitor,
|
monitor,
|
||||||
monitored,
|
monitored,
|
||||||
monitorNewItems
|
monitorNewItems,
|
||||||
|
shouldFetchAlbumsAfterUpdate = false
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const artists = [];
|
const artists = [];
|
||||||
|
@ -390,7 +391,9 @@ export const actionHandlers = handleThunks({
|
||||||
}).request;
|
}).request;
|
||||||
|
|
||||||
promise.done((data) => {
|
promise.done((data) => {
|
||||||
dispatch(fetchAlbums({ artistId: artistIds[0] }));
|
if (shouldFetchAlbumsAfterUpdate) {
|
||||||
|
dispatch(fetchAlbums({ artistId: artistIds[0] }));
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(set({
|
dispatch(set({
|
||||||
section,
|
section,
|
||||||
|
|
27
frontend/src/Store/Selectors/createArtistAlbumsSelector.ts
Normal file
27
frontend/src/Store/Selectors/createArtistAlbumsSelector.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import AppState from 'App/State/AppState';
|
||||||
|
import Artist from 'Artist/Artist';
|
||||||
|
import { createArtistSelectorForHook } from './createArtistSelector';
|
||||||
|
|
||||||
|
function createArtistAlbumsSelector(artistId: number) {
|
||||||
|
return createSelector(
|
||||||
|
(state: AppState) => state.albums,
|
||||||
|
createArtistSelectorForHook(artistId),
|
||||||
|
(albums, artist = {} as Artist) => {
|
||||||
|
const { isFetching, isPopulated, error, items } = albums;
|
||||||
|
|
||||||
|
const filteredAlbums = items.filter(
|
||||||
|
(album) => album.artist.artistMetadataId === artist.artistMetadataId
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isFetching,
|
||||||
|
isPopulated,
|
||||||
|
error,
|
||||||
|
items: filteredAlbums,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createArtistAlbumsSelector;
|
|
@ -41,6 +41,7 @@
|
||||||
"AgeWhenGrabbed": "Age (when grabbed)",
|
"AgeWhenGrabbed": "Age (when grabbed)",
|
||||||
"Album": "Album",
|
"Album": "Album",
|
||||||
"AlbumCount": "Album Count",
|
"AlbumCount": "Album Count",
|
||||||
|
"AlbumDetails": "Album Details",
|
||||||
"AlbumHasNotAired": "Album has not aired",
|
"AlbumHasNotAired": "Album has not aired",
|
||||||
"AlbumIsDownloading": "Album is downloading",
|
"AlbumIsDownloading": "Album is downloading",
|
||||||
"AlbumIsDownloadingInterp": "Album is downloading - {0}% {1}",
|
"AlbumIsDownloadingInterp": "Album is downloading - {0}% {1}",
|
||||||
|
@ -49,9 +50,12 @@
|
||||||
"AlbumReleaseDate": "Album Release Date",
|
"AlbumReleaseDate": "Album Release Date",
|
||||||
"AlbumStatus": "Album Status",
|
"AlbumStatus": "Album Status",
|
||||||
"AlbumStudio": "Album Studio",
|
"AlbumStudio": "Album Studio",
|
||||||
|
"AlbumStudioTracksDownloaded": "{trackFileCount}/{totalTrackCount} tracks downloaded",
|
||||||
|
"AlbumStudioTruncated": "Only latest 20 albums are shown, go to details to see all albums",
|
||||||
"AlbumTitle": "Album Title",
|
"AlbumTitle": "Album Title",
|
||||||
"AlbumType": "Album Type",
|
"AlbumType": "Album Type",
|
||||||
"Albums": "Albums",
|
"Albums": "Albums",
|
||||||
|
"AlbumsLoadError": "Unable to load albums",
|
||||||
"All": "All",
|
"All": "All",
|
||||||
"AllAlbums": "All Albums",
|
"AllAlbums": "All Albums",
|
||||||
"AllAlbumsData": "Monitor all albums except specials",
|
"AllAlbumsData": "Monitor all albums except specials",
|
||||||
|
@ -99,6 +103,8 @@
|
||||||
"ArtistClickToChangeAlbum": "Click to change album",
|
"ArtistClickToChangeAlbum": "Click to change album",
|
||||||
"ArtistEditor": "Artist Editor",
|
"ArtistEditor": "Artist Editor",
|
||||||
"ArtistFolderFormat": "Artist Folder Format",
|
"ArtistFolderFormat": "Artist Folder Format",
|
||||||
|
"ArtistIsMonitored": "Artist is monitored",
|
||||||
|
"ArtistIsUnmonitored": "Artist is unmonitored",
|
||||||
"ArtistMonitoring": "Artist Monitoring",
|
"ArtistMonitoring": "Artist Monitoring",
|
||||||
"ArtistName": "Artist Name",
|
"ArtistName": "Artist Name",
|
||||||
"ArtistNameHelpText": "The name of the artist/album to exclude (can be anything meaningful)",
|
"ArtistNameHelpText": "The name of the artist/album to exclude (can be anything meaningful)",
|
||||||
|
@ -193,7 +199,7 @@
|
||||||
"ContinuingNoAdditionalAlbumsAreExpected": "No additional albums are expected",
|
"ContinuingNoAdditionalAlbumsAreExpected": "No additional albums are expected",
|
||||||
"ContinuingOnly": "Continuing Only",
|
"ContinuingOnly": "Continuing Only",
|
||||||
"CopyToClipboard": "Copy to clipboard",
|
"CopyToClipboard": "Copy to clipboard",
|
||||||
"CopyUsingHardlinksHelpText": "Hardlinks allow Lidarr to import seeding torrents to the the series folder without taking extra disk space or copying the entire contents of the file. Hardlinks will only work if the source and destination are on the same volume",
|
"CopyUsingHardlinksHelpText": "Hardlinks allow Lidarr to import seeding torrents to the the artist folder without taking extra disk space or copying the entire contents of the file. Hardlinks will only work if the source and destination are on the same volume",
|
||||||
"CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Lidarr's rename function as a work around.",
|
"CopyUsingHardlinksHelpTextWarning": "Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Lidarr's rename function as a work around.",
|
||||||
"CouldntFindAnyResultsForTerm": "Couldn't find any results for '{0}'",
|
"CouldntFindAnyResultsForTerm": "Couldn't find any results for '{0}'",
|
||||||
"CountAlbums": "{albumCount} albums",
|
"CountAlbums": "{albumCount} albums",
|
||||||
|
@ -222,6 +228,7 @@
|
||||||
"Date": "Date",
|
"Date": "Date",
|
||||||
"DateAdded": "Date Added",
|
"DateAdded": "Date Added",
|
||||||
"Dates": "Dates",
|
"Dates": "Dates",
|
||||||
|
"Deceased": "Deceased",
|
||||||
"DefaultDelayProfileHelpText": "This is the default profile. It applies to all artist that don't have an explicit profile.",
|
"DefaultDelayProfileHelpText": "This is the default profile. It applies to all artist that don't have an explicit profile.",
|
||||||
"DefaultLidarrTags": "Default Lidarr Tags",
|
"DefaultLidarrTags": "Default Lidarr Tags",
|
||||||
"DefaultMetadataProfileIdHelpText": "Default Metadata Profile for artists detected in this folder",
|
"DefaultMetadataProfileIdHelpText": "Default Metadata Profile for artists detected in this folder",
|
||||||
|
@ -523,6 +530,7 @@
|
||||||
"ItsEasyToAddANewArtistJustStartTypingTheNameOfTheArtistYouWantToAdd": "It's easy to add a new artist, just start typing the name of the artist you want to add.",
|
"ItsEasyToAddANewArtistJustStartTypingTheNameOfTheArtistYouWantToAdd": "It's easy to add a new artist, just start typing the name of the artist you want to add.",
|
||||||
"Label": "Label",
|
"Label": "Label",
|
||||||
"Language": "Language",
|
"Language": "Language",
|
||||||
|
"Large": "Large",
|
||||||
"LastAlbum": "Last Album",
|
"LastAlbum": "Last Album",
|
||||||
"LastDuration": "Last Duration",
|
"LastDuration": "Last Duration",
|
||||||
"LastExecution": "Last Execution",
|
"LastExecution": "Last Execution",
|
||||||
|
@ -604,6 +612,7 @@
|
||||||
"MonitorAlbum": "Monitor Album",
|
"MonitorAlbum": "Monitor Album",
|
||||||
"MonitorAlbumExistingOnlyWarning": "This is a one off adjustment of the monitored setting for each album. Use the option under Artist/Edit to control what happens for newly added albums",
|
"MonitorAlbumExistingOnlyWarning": "This is a one off adjustment of the monitored setting for each album. Use the option under Artist/Edit to control what happens for newly added albums",
|
||||||
"MonitorArtist": "Monitor Artist",
|
"MonitorArtist": "Monitor Artist",
|
||||||
|
"MonitorArtists": "Monitor Artists",
|
||||||
"MonitorExistingAlbums": "Monitor Existing Albums",
|
"MonitorExistingAlbums": "Monitor Existing Albums",
|
||||||
"MonitorNewAlbums": "Monitor New Albums",
|
"MonitorNewAlbums": "Monitor New Albums",
|
||||||
"MonitorNewItems": "Monitor New Albums",
|
"MonitorNewItems": "Monitor New Albums",
|
||||||
|
@ -954,6 +963,7 @@
|
||||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Use when Lidarr is unable to detect free space of your root folder during file import",
|
"SkipFreeSpaceCheckWhenImportingHelpText": "Use when Lidarr is unable to detect free space of your root folder during file import",
|
||||||
"SkipRedownload": "Skip Redownload",
|
"SkipRedownload": "Skip Redownload",
|
||||||
"SkipRedownloadHelpText": "Prevents Lidarr from trying download alternative releases for the removed items",
|
"SkipRedownloadHelpText": "Prevents Lidarr from trying download alternative releases for the removed items",
|
||||||
|
"Small": "Small",
|
||||||
"SmartReplace": "Smart Replace",
|
"SmartReplace": "Smart Replace",
|
||||||
"SomeResultsAreHiddenByTheAppliedFilter": "Some results are hidden by the applied filter",
|
"SomeResultsAreHiddenByTheAppliedFilter": "Some results are hidden by the applied filter",
|
||||||
"SorryThatAlbumCannotBeFound": "Sorry, that album cannot be found.",
|
"SorryThatAlbumCannotBeFound": "Sorry, that album cannot be found.",
|
||||||
|
@ -1087,6 +1097,7 @@
|
||||||
"UpdateCheckStartupTranslocationMessage": "Cannot install update because startup folder '{0}' is in an App Translocation folder.",
|
"UpdateCheckStartupTranslocationMessage": "Cannot install update because startup folder '{0}' is in an App Translocation folder.",
|
||||||
"UpdateCheckUINotWritableMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.",
|
"UpdateCheckUINotWritableMessage": "Cannot install update because UI folder '{0}' is not writable by the user '{1}'.",
|
||||||
"UpdateMechanismHelpText": "Use Lidarr's built-in updater or a script",
|
"UpdateMechanismHelpText": "Use Lidarr's built-in updater or a script",
|
||||||
|
"UpdateMonitoring": "Update Monitoring",
|
||||||
"UpdateScriptPathHelpText": "Path to a custom script that takes an extracted update package and handle the remainder of the update process",
|
"UpdateScriptPathHelpText": "Path to a custom script that takes an extracted update package and handle the remainder of the update process",
|
||||||
"UpdateSelected": "Update Selected",
|
"UpdateSelected": "Update Selected",
|
||||||
"Updates": "Updates",
|
"Updates": "Updates",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue