mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-14 09:03:49 -07:00
Remove Artists Editor
(cherry picked from commit 67232290e5fb94c96734a8ca59aad0f7de79fa20)
This commit is contained in:
parent
9a4c6a8db8
commit
57206da77b
36 changed files with 9 additions and 2302 deletions
|
@ -7,7 +7,6 @@ import QueueConnector from 'Activity/Queue/QueueConnector';
|
||||||
import AlbumDetailsPageConnector from 'Album/Details/AlbumDetailsPageConnector';
|
import AlbumDetailsPageConnector from 'Album/Details/AlbumDetailsPageConnector';
|
||||||
import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector';
|
import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector';
|
||||||
import ArtistDetailsPageConnector from 'Artist/Details/ArtistDetailsPageConnector';
|
import ArtistDetailsPageConnector from 'Artist/Details/ArtistDetailsPageConnector';
|
||||||
import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector';
|
|
||||||
import ArtistIndex from 'Artist/Index/ArtistIndex';
|
import ArtistIndex from 'Artist/Index/ArtistIndex';
|
||||||
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
|
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
|
||||||
import NotFound from 'Components/NotFound';
|
import NotFound from 'Components/NotFound';
|
||||||
|
@ -78,7 +77,15 @@ function AppRoutes(props) {
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/artisteditor"
|
path="/artisteditor"
|
||||||
component={ArtistEditorConnector}
|
exact={true}
|
||||||
|
render={() => {
|
||||||
|
return (
|
||||||
|
<Redirect
|
||||||
|
to={getPathWithUrlBase('/')}
|
||||||
|
component={app}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
|
|
|
@ -1,281 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import NoArtist from 'Artist/NoArtist';
|
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
|
||||||
import PageContent from 'Components/Page/PageContent';
|
|
||||||
import PageContentBody from 'Components/Page/PageContentBody';
|
|
||||||
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
|
|
||||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
|
||||||
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
|
|
||||||
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
|
|
||||||
import Table from 'Components/Table/Table';
|
|
||||||
import TableBody from 'Components/Table/TableBody';
|
|
||||||
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
|
|
||||||
import { align, icons, sortDirections } from 'Helpers/Props';
|
|
||||||
import getErrorMessage from 'Utilities/Object/getErrorMessage';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
|
||||||
import selectAll from 'Utilities/Table/selectAll';
|
|
||||||
import toggleSelected from 'Utilities/Table/toggleSelected';
|
|
||||||
import ArtistEditorFilterModalConnector from './ArtistEditorFilterModalConnector';
|
|
||||||
import ArtistEditorFooter from './ArtistEditorFooter';
|
|
||||||
import ArtistEditorRowConnector from './ArtistEditorRowConnector';
|
|
||||||
import RetagArtistModal from './AudioTags/RetagArtistModal';
|
|
||||||
import OrganizeArtistModal from './Organize/OrganizeArtistModal';
|
|
||||||
|
|
||||||
class ArtistEditor extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
allSelected: false,
|
|
||||||
allUnselected: false,
|
|
||||||
lastToggled: null,
|
|
||||||
selectedState: {},
|
|
||||||
isOrganizingArtistModalOpen: false,
|
|
||||||
isRetaggingArtistModalOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const {
|
|
||||||
isDeleting,
|
|
||||||
deleteError
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const hasFinishedDeleting = prevProps.isDeleting &&
|
|
||||||
!isDeleting &&
|
|
||||||
!deleteError;
|
|
||||||
|
|
||||||
if (hasFinishedDeleting) {
|
|
||||||
this.onSelectAllChange({ value: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
getSelectedIds = () => {
|
|
||||||
return getSelectedIds(this.state.selectedState);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onSelectAllChange = ({ value }) => {
|
|
||||||
this.setState(selectAll(this.state.selectedState, value));
|
|
||||||
};
|
|
||||||
|
|
||||||
onSelectedChange = ({ id, value, shiftKey = false }) => {
|
|
||||||
this.setState((state) => {
|
|
||||||
return toggleSelected(state, this.props.items, id, value, shiftKey);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onSaveSelected = (changes) => {
|
|
||||||
this.props.onSaveSelected({
|
|
||||||
artistIds: this.getSelectedIds(),
|
|
||||||
...changes
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onOrganizeArtistPress = () => {
|
|
||||||
this.setState({ isOrganizingArtistModalOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onOrganizeArtistModalClose = (organized) => {
|
|
||||||
this.setState({ isOrganizingArtistModalOpen: false });
|
|
||||||
|
|
||||||
if (organized === true) {
|
|
||||||
this.onSelectAllChange({ value: false });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onRetagArtistPress = () => {
|
|
||||||
this.setState({ isRetaggingArtistModalOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onRetagArtistModalClose = (organized) => {
|
|
||||||
this.setState({ isRetaggingArtistModalOpen: false });
|
|
||||||
|
|
||||||
if (organized === true) {
|
|
||||||
this.onSelectAllChange({ value: false });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
totalItems,
|
|
||||||
items,
|
|
||||||
columns,
|
|
||||||
selectedFilterKey,
|
|
||||||
filters,
|
|
||||||
customFilters,
|
|
||||||
sortKey,
|
|
||||||
sortDirection,
|
|
||||||
isSaving,
|
|
||||||
saveError,
|
|
||||||
isDeleting,
|
|
||||||
deleteError,
|
|
||||||
isOrganizingArtist,
|
|
||||||
isRetaggingArtist,
|
|
||||||
onTableOptionChange,
|
|
||||||
onSortPress,
|
|
||||||
onFilterSelect
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
allSelected,
|
|
||||||
allUnselected,
|
|
||||||
selectedState
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const selectedArtistIds = this.getSelectedIds();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageContent title={translate('ArtistEditor')}>
|
|
||||||
<PageToolbar>
|
|
||||||
<PageToolbarSection />
|
|
||||||
<PageToolbarSection alignContent={align.RIGHT}>
|
|
||||||
<TableOptionsModalWrapper
|
|
||||||
columns={columns}
|
|
||||||
onTableOptionChange={onTableOptionChange}
|
|
||||||
>
|
|
||||||
<PageToolbarButton
|
|
||||||
label={translate('Options')}
|
|
||||||
iconName={icons.TABLE}
|
|
||||||
/>
|
|
||||||
</TableOptionsModalWrapper>
|
|
||||||
|
|
||||||
<PageToolbarSeparator />
|
|
||||||
|
|
||||||
<FilterMenu
|
|
||||||
alignMenu={align.RIGHT}
|
|
||||||
selectedFilterKey={selectedFilterKey}
|
|
||||||
filters={filters}
|
|
||||||
customFilters={customFilters}
|
|
||||||
filterModalConnectorComponent={ArtistEditorFilterModalConnector}
|
|
||||||
onFilterSelect={onFilterSelect}
|
|
||||||
/>
|
|
||||||
</PageToolbarSection>
|
|
||||||
</PageToolbar>
|
|
||||||
|
|
||||||
<PageContentBody>
|
|
||||||
{
|
|
||||||
isFetching && !isPopulated &&
|
|
||||||
<LoadingIndicator />
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!isFetching && !!error &&
|
|
||||||
<div>{getErrorMessage(error, 'Failed to load artist from API')}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!error && isPopulated && !!items.length &&
|
|
||||||
<div>
|
|
||||||
<Table
|
|
||||||
columns={columns}
|
|
||||||
sortKey={sortKey}
|
|
||||||
sortDirection={sortDirection}
|
|
||||||
selectAll={true}
|
|
||||||
allSelected={allSelected}
|
|
||||||
allUnselected={allUnselected}
|
|
||||||
onSortPress={onSortPress}
|
|
||||||
onSelectAllChange={this.onSelectAllChange}
|
|
||||||
>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
items.map((item) => {
|
|
||||||
return (
|
|
||||||
<ArtistEditorRowConnector
|
|
||||||
key={item.id}
|
|
||||||
{...item}
|
|
||||||
columns={columns}
|
|
||||||
isSaving={isSaving}
|
|
||||||
isSelected={selectedState[item.id]}
|
|
||||||
onSelectedChange={this.onSelectedChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
!error && isPopulated && !items.length &&
|
|
||||||
<NoArtist totalItems={totalItems} />
|
|
||||||
}
|
|
||||||
</PageContentBody>
|
|
||||||
|
|
||||||
<ArtistEditorFooter
|
|
||||||
artistIds={selectedArtistIds}
|
|
||||||
selectedCount={selectedArtistIds.length}
|
|
||||||
isSaving={isSaving}
|
|
||||||
saveError={saveError}
|
|
||||||
isDeleting={isDeleting}
|
|
||||||
deleteError={deleteError}
|
|
||||||
isOrganizingArtist={isOrganizingArtist}
|
|
||||||
isRetaggingArtist={isRetaggingArtist}
|
|
||||||
columns={columns}
|
|
||||||
showMetadataProfile={columns.find((column) => column.name === 'metadataProfileId').isVisible}
|
|
||||||
onSaveSelected={this.onSaveSelected}
|
|
||||||
onOrganizeArtistPress={this.onOrganizeArtistPress}
|
|
||||||
onRetagArtistPress={this.onRetagArtistPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<OrganizeArtistModal
|
|
||||||
isOpen={this.state.isOrganizingArtistModalOpen}
|
|
||||||
artistIds={selectedArtistIds}
|
|
||||||
onModalClose={this.onOrganizeArtistModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<RetagArtistModal
|
|
||||||
isOpen={this.state.isRetaggingArtistModalOpen}
|
|
||||||
artistIds={selectedArtistIds}
|
|
||||||
onModalClose={this.onRetagArtistModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</PageContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArtistEditor.propTypes = {
|
|
||||||
isFetching: PropTypes.bool.isRequired,
|
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
|
||||||
error: PropTypes.object,
|
|
||||||
totalItems: PropTypes.number.isRequired,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
sortKey: PropTypes.string,
|
|
||||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
|
||||||
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
|
|
||||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
isSaving: PropTypes.bool.isRequired,
|
|
||||||
saveError: PropTypes.object,
|
|
||||||
isDeleting: PropTypes.bool.isRequired,
|
|
||||||
deleteError: PropTypes.object,
|
|
||||||
isOrganizingArtist: PropTypes.bool.isRequired,
|
|
||||||
isRetaggingArtist: PropTypes.bool.isRequired,
|
|
||||||
onTableOptionChange: PropTypes.func.isRequired,
|
|
||||||
onSortPress: PropTypes.func.isRequired,
|
|
||||||
onFilterSelect: PropTypes.func.isRequired,
|
|
||||||
onSaveSelected: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ArtistEditor;
|
|
|
@ -1,97 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import * as commandNames from 'Commands/commandNames';
|
|
||||||
import { saveArtistEditor, setArtistEditorFilter, setArtistEditorSort, setArtistEditorTableOption } from 'Store/Actions/artistEditorActions';
|
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
|
||||||
import { fetchRootFolders } from 'Store/Actions/settingsActions';
|
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
|
||||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
|
||||||
import ArtistEditor from './ArtistEditor';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
createClientSideCollectionSelector('artist', 'artistEditor'),
|
|
||||||
createCommandExecutingSelector(commandNames.RENAME_ARTIST),
|
|
||||||
createCommandExecutingSelector(commandNames.RETAG_ARTIST),
|
|
||||||
(artist, isOrganizingArtist, isRetaggingArtist) => {
|
|
||||||
return {
|
|
||||||
isOrganizingArtist,
|
|
||||||
isRetaggingArtist,
|
|
||||||
...artist
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
dispatchSetArtistEditorSort: setArtistEditorSort,
|
|
||||||
dispatchSetArtistEditorFilter: setArtistEditorFilter,
|
|
||||||
dispatchSetArtistEditorTableOption: setArtistEditorTableOption,
|
|
||||||
dispatchSaveArtistEditor: saveArtistEditor,
|
|
||||||
dispatchFetchRootFolders: fetchRootFolders,
|
|
||||||
dispatchExecuteCommand: executeCommand
|
|
||||||
};
|
|
||||||
|
|
||||||
class ArtistEditorConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.dispatchFetchRootFolders();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onSortPress = (sortKey) => {
|
|
||||||
this.props.dispatchSetArtistEditorSort({ sortKey });
|
|
||||||
};
|
|
||||||
|
|
||||||
onFilterSelect = (selectedFilterKey) => {
|
|
||||||
this.props.dispatchSetArtistEditorFilter({ selectedFilterKey });
|
|
||||||
};
|
|
||||||
|
|
||||||
onTableOptionChange = (payload) => {
|
|
||||||
this.props.dispatchSetArtistEditorTableOption(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
onSaveSelected = (payload) => {
|
|
||||||
this.props.dispatchSaveArtistEditor(payload);
|
|
||||||
};
|
|
||||||
|
|
||||||
onMoveSelected = (payload) => {
|
|
||||||
this.props.dispatchExecuteCommand({
|
|
||||||
name: commandNames.MOVE_ARTIST,
|
|
||||||
...payload
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<ArtistEditor
|
|
||||||
{...this.props}
|
|
||||||
onSortPress={this.onSortPress}
|
|
||||||
onFilterSelect={this.onFilterSelect}
|
|
||||||
onSaveSelected={this.onSaveSelected}
|
|
||||||
onTableOptionChange={this.onTableOptionChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArtistEditorConnector.propTypes = {
|
|
||||||
dispatchSetArtistEditorSort: PropTypes.func.isRequired,
|
|
||||||
dispatchSetArtistEditorFilter: PropTypes.func.isRequired,
|
|
||||||
dispatchSetArtistEditorTableOption: PropTypes.func.isRequired,
|
|
||||||
dispatchSaveArtistEditor: PropTypes.func.isRequired,
|
|
||||||
dispatchFetchRootFolders: PropTypes.func.isRequired,
|
|
||||||
dispatchExecuteCommand: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistEditorConnector);
|
|
|
@ -1,24 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import FilterModal from 'Components/Filter/FilterModal';
|
|
||||||
import { setArtistEditorFilter } from 'Store/Actions/artistEditorActions';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.artist.items,
|
|
||||||
(state) => state.artistEditor.filterBuilderProps,
|
|
||||||
(sectionItems, filterBuilderProps) => {
|
|
||||||
return {
|
|
||||||
sectionItems,
|
|
||||||
filterBuilderProps,
|
|
||||||
customFilterType: 'artistEditor'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
dispatchSetFilter: setArtistEditorFilter
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal);
|
|
|
@ -1,70 +0,0 @@
|
||||||
.inputContainer {
|
|
||||||
margin-right: 20px;
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonContainer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonContainerContent {
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.organizeSelectedButton,
|
|
||||||
.tagsButton {
|
|
||||||
composes: button from '~Components/Link/SpinnerButton.css';
|
|
||||||
|
|
||||||
margin-right: 10px;
|
|
||||||
height: 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deleteSelectedButton {
|
|
||||||
composes: button from '~Components/Link/SpinnerButton.css';
|
|
||||||
|
|
||||||
margin-left: 50px;
|
|
||||||
height: 35px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointExtraLarge) {
|
|
||||||
.deleteSelectedButton {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointLarge) {
|
|
||||||
.buttonContainer {
|
|
||||||
justify-content: flex-start;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointSmall) {
|
|
||||||
.inputContainer {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonContainer {
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonContainerContent {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectedArtistLabel {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
// This file is automatically generated.
|
|
||||||
// Please do not change this file!
|
|
||||||
interface CssExports {
|
|
||||||
'buttonContainer': string;
|
|
||||||
'buttonContainerContent': string;
|
|
||||||
'buttons': string;
|
|
||||||
'deleteSelectedButton': string;
|
|
||||||
'inputContainer': string;
|
|
||||||
'organizeSelectedButton': string;
|
|
||||||
'selectedArtistLabel': string;
|
|
||||||
'tagsButton': string;
|
|
||||||
}
|
|
||||||
export const cssExports: CssExports;
|
|
||||||
export default cssExports;
|
|
|
@ -1,382 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import MoveArtistModal from 'Artist/MoveArtist/MoveArtistModal';
|
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
|
||||||
import MetadataProfileSelectInputConnector from 'Components/Form/MetadataProfileSelectInputConnector';
|
|
||||||
import MonitorNewItemsSelectInput from 'Components/Form/MonitorNewItemsSelectInput';
|
|
||||||
import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSelectInputConnector';
|
|
||||||
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
|
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
|
||||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
|
||||||
import { inputTypes, kinds } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import ArtistEditorFooterLabel from './ArtistEditorFooterLabel';
|
|
||||||
import DeleteArtistModal from './Delete/DeleteArtistModal';
|
|
||||||
import TagsModal from './Tags/TagsModal';
|
|
||||||
import styles from './ArtistEditorFooter.css';
|
|
||||||
|
|
||||||
const NO_CHANGE = 'noChange';
|
|
||||||
|
|
||||||
class ArtistEditorFooter extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
monitored: NO_CHANGE,
|
|
||||||
monitorNewItems: NO_CHANGE,
|
|
||||||
qualityProfileId: NO_CHANGE,
|
|
||||||
metadataProfileId: NO_CHANGE,
|
|
||||||
rootFolderPath: NO_CHANGE,
|
|
||||||
savingTags: false,
|
|
||||||
isDeleteArtistModalOpen: false,
|
|
||||||
isTagsModalOpen: false,
|
|
||||||
isConfirmMoveModalOpen: false,
|
|
||||||
destinationRootFolder: null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const {
|
|
||||||
isSaving,
|
|
||||||
saveError
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
if (prevProps.isSaving && !isSaving && !saveError) {
|
|
||||||
this.setState({
|
|
||||||
monitored: NO_CHANGE,
|
|
||||||
monitorNewItems: NO_CHANGE,
|
|
||||||
qualityProfileId: NO_CHANGE,
|
|
||||||
metadataProfileId: NO_CHANGE,
|
|
||||||
rootFolderPath: NO_CHANGE,
|
|
||||||
savingTags: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onInputChange = ({ name, value }) => {
|
|
||||||
this.setState({ [name]: value });
|
|
||||||
|
|
||||||
if (value === NO_CHANGE) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (name) {
|
|
||||||
case 'rootFolderPath':
|
|
||||||
this.setState({
|
|
||||||
isConfirmMoveModalOpen: true,
|
|
||||||
destinationRootFolder: value
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case 'monitored':
|
|
||||||
this.props.onSaveSelected({ [name]: value === 'monitored' });
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.props.onSaveSelected({ [name]: value });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onApplyTagsPress = (tags, applyTags) => {
|
|
||||||
this.setState({
|
|
||||||
savingTags: true,
|
|
||||||
isTagsModalOpen: false
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onSaveSelected({
|
|
||||||
tags,
|
|
||||||
applyTags
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
onDeleteSelectedPress = () => {
|
|
||||||
this.setState({ isDeleteArtistModalOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onDeleteArtistModalClose = () => {
|
|
||||||
this.setState({ isDeleteArtistModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
onTagsPress = () => {
|
|
||||||
this.setState({ isTagsModalOpen: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
onTagsModalClose = () => {
|
|
||||||
this.setState({ isTagsModalOpen: false });
|
|
||||||
};
|
|
||||||
|
|
||||||
onSaveRootFolderPress = () => {
|
|
||||||
this.setState({
|
|
||||||
isConfirmMoveModalOpen: false,
|
|
||||||
destinationRootFolder: null
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onSaveSelected({ rootFolderPath: this.state.destinationRootFolder });
|
|
||||||
};
|
|
||||||
|
|
||||||
onMoveArtistPress = () => {
|
|
||||||
this.setState({
|
|
||||||
isConfirmMoveModalOpen: false,
|
|
||||||
destinationRootFolder: null
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onSaveSelected({
|
|
||||||
rootFolderPath: this.state.destinationRootFolder,
|
|
||||||
moveFiles: true
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
artistIds,
|
|
||||||
selectedCount,
|
|
||||||
isSaving,
|
|
||||||
isDeleting,
|
|
||||||
isOrganizingArtist,
|
|
||||||
isRetaggingArtist,
|
|
||||||
columns,
|
|
||||||
onOrganizeArtistPress,
|
|
||||||
onRetagArtistPress
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
monitored,
|
|
||||||
monitorNewItems,
|
|
||||||
qualityProfileId,
|
|
||||||
metadataProfileId,
|
|
||||||
rootFolderPath,
|
|
||||||
savingTags,
|
|
||||||
isTagsModalOpen,
|
|
||||||
isDeleteArtistModalOpen,
|
|
||||||
isConfirmMoveModalOpen,
|
|
||||||
destinationRootFolder
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const monitoredOptions = [
|
|
||||||
{ key: NO_CHANGE, value: translate('NoChange'), disabled: true },
|
|
||||||
{ key: 'monitored', value: 'Monitored' },
|
|
||||||
{ key: 'unmonitored', value: 'Unmonitored' }
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageContentFooter>
|
|
||||||
<div className={styles.inputContainer}>
|
|
||||||
<ArtistEditorFooterLabel
|
|
||||||
label={translate('MonitorArtist')}
|
|
||||||
isSaving={isSaving && monitored !== NO_CHANGE}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.SELECT}
|
|
||||||
name="monitored"
|
|
||||||
value={monitored}
|
|
||||||
values={monitoredOptions}
|
|
||||||
includeNoChangeDisabled={false}
|
|
||||||
isDisabled={!selectedCount}
|
|
||||||
onChange={this.onInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.inputContainer}>
|
|
||||||
<ArtistEditorFooterLabel
|
|
||||||
label={translate('MonitorNewItems')}
|
|
||||||
isSaving={isSaving && monitored !== NO_CHANGE}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MonitorNewItemsSelectInput
|
|
||||||
name="monitorNewItems"
|
|
||||||
value={monitorNewItems}
|
|
||||||
includeNoChange={true}
|
|
||||||
includeNoChangeDisabled={false}
|
|
||||||
isDisabled={!selectedCount}
|
|
||||||
onChange={this.onInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{
|
|
||||||
columns.map((column) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
isVisible
|
|
||||||
} = column;
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'qualityProfileId') {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={name}
|
|
||||||
className={styles.inputContainer}
|
|
||||||
>
|
|
||||||
<ArtistEditorFooterLabel
|
|
||||||
label={translate('QualityProfile')}
|
|
||||||
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<QualityProfileSelectInputConnector
|
|
||||||
name="qualityProfileId"
|
|
||||||
value={qualityProfileId}
|
|
||||||
includeNoChange={true}
|
|
||||||
includeNoChangeDisabled={false}
|
|
||||||
isDisabled={!selectedCount}
|
|
||||||
onChange={this.onInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'metadataProfileId') {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={name}
|
|
||||||
className={styles.inputContainer}
|
|
||||||
>
|
|
||||||
<ArtistEditorFooterLabel
|
|
||||||
label={translate('MetadataProfile')}
|
|
||||||
isSaving={isSaving && metadataProfileId !== NO_CHANGE}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MetadataProfileSelectInputConnector
|
|
||||||
name="metadataProfileId"
|
|
||||||
value={metadataProfileId}
|
|
||||||
includeNoChange={true}
|
|
||||||
includeNoChangeDisabled={false}
|
|
||||||
includeNone={true}
|
|
||||||
isDisabled={!selectedCount}
|
|
||||||
onChange={this.onInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'path') {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={name}
|
|
||||||
className={styles.inputContainer}
|
|
||||||
>
|
|
||||||
<ArtistEditorFooterLabel
|
|
||||||
label={translate('RootFolder')}
|
|
||||||
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<RootFolderSelectInputConnector
|
|
||||||
name="rootFolderPath"
|
|
||||||
value={rootFolderPath}
|
|
||||||
includeNoChange={true}
|
|
||||||
isDisabled={!selectedCount}
|
|
||||||
selectedValueOptions={{ includeFreeSpace: false }}
|
|
||||||
onChange={this.onInputChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
<div className={styles.buttonContainer}>
|
|
||||||
<div className={styles.buttonContainerContent}>
|
|
||||||
<ArtistEditorFooterLabel
|
|
||||||
label={translate('SelectedCountArtistsSelectedInterp', { selectedCount })}
|
|
||||||
isSaving={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className={styles.buttons}>
|
|
||||||
<div>
|
|
||||||
<SpinnerButton
|
|
||||||
className={styles.organizeSelectedButton}
|
|
||||||
kind={kinds.WARNING}
|
|
||||||
isSpinning={isOrganizingArtist}
|
|
||||||
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist}
|
|
||||||
onPress={onOrganizeArtistPress}
|
|
||||||
>
|
|
||||||
Rename Files
|
|
||||||
</SpinnerButton>
|
|
||||||
|
|
||||||
<SpinnerButton
|
|
||||||
className={styles.organizeSelectedButton}
|
|
||||||
kind={kinds.WARNING}
|
|
||||||
isSpinning={isRetaggingArtist}
|
|
||||||
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist}
|
|
||||||
onPress={onRetagArtistPress}
|
|
||||||
>
|
|
||||||
Write Metadata Tags
|
|
||||||
</SpinnerButton>
|
|
||||||
|
|
||||||
<SpinnerButton
|
|
||||||
className={styles.tagsButton}
|
|
||||||
isSpinning={isSaving && savingTags}
|
|
||||||
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist}
|
|
||||||
onPress={this.onTagsPress}
|
|
||||||
>
|
|
||||||
Set Lidarr Tags
|
|
||||||
</SpinnerButton>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SpinnerButton
|
|
||||||
className={styles.deleteSelectedButton}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
isSpinning={isDeleting}
|
|
||||||
isDisabled={!selectedCount || isDeleting}
|
|
||||||
onPress={this.onDeleteSelectedPress}
|
|
||||||
>
|
|
||||||
Delete
|
|
||||||
</SpinnerButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<TagsModal
|
|
||||||
isOpen={isTagsModalOpen}
|
|
||||||
artistIds={artistIds}
|
|
||||||
onApplyTagsPress={this.onApplyTagsPress}
|
|
||||||
onModalClose={this.onTagsModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DeleteArtistModal
|
|
||||||
isOpen={isDeleteArtistModalOpen}
|
|
||||||
artistIds={artistIds}
|
|
||||||
onModalClose={this.onDeleteArtistModalClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MoveArtistModal
|
|
||||||
destinationRootFolder={destinationRootFolder}
|
|
||||||
isOpen={isConfirmMoveModalOpen}
|
|
||||||
onSavePress={this.onSaveRootFolderPress}
|
|
||||||
onMoveArtistPress={this.onMoveArtistPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
</PageContentFooter>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArtistEditorFooter.propTypes = {
|
|
||||||
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
|
||||||
selectedCount: PropTypes.number.isRequired,
|
|
||||||
isSaving: PropTypes.bool.isRequired,
|
|
||||||
saveError: PropTypes.object,
|
|
||||||
isDeleting: PropTypes.bool.isRequired,
|
|
||||||
deleteError: PropTypes.object,
|
|
||||||
isOrganizingArtist: PropTypes.bool.isRequired,
|
|
||||||
isRetaggingArtist: PropTypes.bool.isRequired,
|
|
||||||
showMetadataProfile: PropTypes.bool.isRequired,
|
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onSaveSelected: PropTypes.func.isRequired,
|
|
||||||
onOrganizeArtistPress: PropTypes.func.isRequired,
|
|
||||||
onRetagArtistPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ArtistEditorFooter;
|
|
|
@ -1,8 +0,0 @@
|
||||||
.label {
|
|
||||||
margin-bottom: 3px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.savingIcon {
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// This file is automatically generated.
|
|
||||||
// Please do not change this file!
|
|
||||||
interface CssExports {
|
|
||||||
'label': string;
|
|
||||||
'savingIcon': string;
|
|
||||||
}
|
|
||||||
export const cssExports: CssExports;
|
|
||||||
export default cssExports;
|
|
|
@ -1,40 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import SpinnerIcon from 'Components/SpinnerIcon';
|
|
||||||
import { icons } from 'Helpers/Props';
|
|
||||||
import styles from './ArtistEditorFooterLabel.css';
|
|
||||||
|
|
||||||
function ArtistEditorFooterLabel(props) {
|
|
||||||
const {
|
|
||||||
className,
|
|
||||||
label,
|
|
||||||
isSaving
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={className}>
|
|
||||||
{label}
|
|
||||||
|
|
||||||
{
|
|
||||||
isSaving &&
|
|
||||||
<SpinnerIcon
|
|
||||||
className={styles.savingIcon}
|
|
||||||
name={icons.SPINNER}
|
|
||||||
isSpinning={true}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ArtistEditorFooterLabel.propTypes = {
|
|
||||||
className: PropTypes.string.isRequired,
|
|
||||||
label: PropTypes.string.isRequired,
|
|
||||||
isSaving: PropTypes.bool.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
ArtistEditorFooterLabel.defaultProps = {
|
|
||||||
className: styles.label
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ArtistEditorFooterLabel;
|
|
|
@ -1,168 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import ArtistNameLink from 'Artist/ArtistNameLink';
|
|
||||||
import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell';
|
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|
||||||
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
|
||||||
import TableRow from 'Components/Table/TableRow';
|
|
||||||
import TagListConnector from 'Components/TagListConnector';
|
|
||||||
import monitorNewItemsOptions from 'Utilities/Artist/monitorNewItemsOptions';
|
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
|
||||||
|
|
||||||
class ArtistEditorRow extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
status,
|
|
||||||
foreignArtistId,
|
|
||||||
artistName,
|
|
||||||
artistType,
|
|
||||||
monitored,
|
|
||||||
monitorNewItems,
|
|
||||||
metadataProfile,
|
|
||||||
qualityProfile,
|
|
||||||
path,
|
|
||||||
statistics,
|
|
||||||
tags,
|
|
||||||
columns,
|
|
||||||
isSaving,
|
|
||||||
isSelected,
|
|
||||||
onArtistMonitoredPress,
|
|
||||||
onSelectedChange
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const monitorNewItemsName = monitorNewItemsOptions.find((o) => o.key === monitorNewItems)?.value;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow>
|
|
||||||
<TableSelectCell
|
|
||||||
id={id}
|
|
||||||
isSelected={isSelected}
|
|
||||||
onSelectedChange={onSelectedChange}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{
|
|
||||||
columns.map((column) => {
|
|
||||||
const {
|
|
||||||
name,
|
|
||||||
isVisible
|
|
||||||
} = column;
|
|
||||||
|
|
||||||
if (!isVisible) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'status') {
|
|
||||||
return (
|
|
||||||
<ArtistStatusCell
|
|
||||||
key={name}
|
|
||||||
artistType={artistType}
|
|
||||||
monitored={monitored}
|
|
||||||
status={status}
|
|
||||||
isSaving={isSaving}
|
|
||||||
onMonitoredPress={onArtistMonitoredPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'sortName') {
|
|
||||||
return (
|
|
||||||
<TableRowCell
|
|
||||||
key={name}
|
|
||||||
>
|
|
||||||
<ArtistNameLink
|
|
||||||
foreignArtistId={foreignArtistId}
|
|
||||||
artistName={artistName}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'monitorNewItems') {
|
|
||||||
return (
|
|
||||||
<TableRowCell key={name}>
|
|
||||||
{monitorNewItemsName ?? 'Unknown'}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'qualityProfileId') {
|
|
||||||
return (
|
|
||||||
<TableRowCell key={name}>
|
|
||||||
{qualityProfile.name}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'metadataProfileId') {
|
|
||||||
return (
|
|
||||||
<TableRowCell key={name}>
|
|
||||||
{metadataProfile.name}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'path') {
|
|
||||||
return (
|
|
||||||
<TableRowCell key={name}>
|
|
||||||
{path}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'sizeOnDisk') {
|
|
||||||
return (
|
|
||||||
<TableRowCell key={name}>
|
|
||||||
{formatBytes(statistics.sizeOnDisk)}
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'tags') {
|
|
||||||
return (
|
|
||||||
<TableRowCell key={name}>
|
|
||||||
<TagListConnector
|
|
||||||
tags={tags}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArtistEditorRow.propTypes = {
|
|
||||||
id: PropTypes.number.isRequired,
|
|
||||||
status: PropTypes.string.isRequired,
|
|
||||||
foreignArtistId: PropTypes.string.isRequired,
|
|
||||||
artistName: PropTypes.string.isRequired,
|
|
||||||
artistType: PropTypes.string,
|
|
||||||
monitored: PropTypes.bool.isRequired,
|
|
||||||
monitorNewItems: PropTypes.string.isRequired,
|
|
||||||
metadataProfile: PropTypes.object.isRequired,
|
|
||||||
qualityProfile: PropTypes.object.isRequired,
|
|
||||||
path: PropTypes.string.isRequired,
|
|
||||||
statistics: PropTypes.object.isRequired,
|
|
||||||
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
isSaving: PropTypes.bool.isRequired,
|
|
||||||
isSelected: PropTypes.bool,
|
|
||||||
onArtistMonitoredPress: PropTypes.func.isRequired,
|
|
||||||
onSelectedChange: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
ArtistEditorRow.defaultProps = {
|
|
||||||
tags: [],
|
|
||||||
statistics: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ArtistEditorRow;
|
|
|
@ -1,61 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { toggleArtistMonitored } from 'Store/Actions/artistActions';
|
|
||||||
import createMetadataProfileSelector from 'Store/Selectors/createMetadataProfileSelector';
|
|
||||||
import createQualityProfileSelector from 'Store/Selectors/createQualityProfileSelector';
|
|
||||||
import ArtistEditorRow from './ArtistEditorRow';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
createMetadataProfileSelector(),
|
|
||||||
createQualityProfileSelector(),
|
|
||||||
(metadataProfile, qualityProfile) => {
|
|
||||||
return {
|
|
||||||
metadataProfile,
|
|
||||||
qualityProfile
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
toggleArtistMonitored
|
|
||||||
};
|
|
||||||
|
|
||||||
class ArtistEditorRowConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onArtistMonitoredPress = () => {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
monitored
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
this.props.toggleArtistMonitored({
|
|
||||||
artistId: id,
|
|
||||||
monitored: !monitored
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<ArtistEditorRow
|
|
||||||
{...this.props}
|
|
||||||
onArtistMonitoredPress={this.onArtistMonitoredPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ArtistEditorRowConnector.propTypes = {
|
|
||||||
id: PropTypes.number.isRequired,
|
|
||||||
monitored: PropTypes.bool.isRequired,
|
|
||||||
qualityProfileId: PropTypes.number.isRequired,
|
|
||||||
toggleArtistMonitored: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistEditorRowConnector);
|
|
|
@ -1,31 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import RetagArtistModalContentConnector from './RetagArtistModalContentConnector';
|
|
||||||
|
|
||||||
function RetagArtistModal(props) {
|
|
||||||
const {
|
|
||||||
isOpen,
|
|
||||||
onModalClose,
|
|
||||||
...otherProps
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
>
|
|
||||||
<RetagArtistModalContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
RetagArtistModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RetagArtistModal;
|
|
|
@ -1,8 +0,0 @@
|
||||||
.retagIcon {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// This file is automatically generated.
|
|
||||||
// Please do not change this file!
|
|
||||||
interface CssExports {
|
|
||||||
'message': string;
|
|
||||||
'retagIcon': string;
|
|
||||||
}
|
|
||||||
export const cssExports: CssExports;
|
|
||||||
export default cssExports;
|
|
|
@ -1,74 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Alert from 'Components/Alert';
|
|
||||||
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 { icons, kinds } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './RetagArtistModalContent.css';
|
|
||||||
|
|
||||||
function RetagArtistModalContent(props) {
|
|
||||||
const {
|
|
||||||
artistNames,
|
|
||||||
onModalClose,
|
|
||||||
onRetagArtistPress
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onModalClose}>
|
|
||||||
<ModalHeader>
|
|
||||||
Retag Selected Artist
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<Alert>
|
|
||||||
Tip: To preview the tags that will be written... select "Cancel" then click any artist name and use the
|
|
||||||
<Icon
|
|
||||||
className={styles.retagIcon}
|
|
||||||
name={icons.RETAG}
|
|
||||||
/>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<div className={styles.message}>
|
|
||||||
Are you sure you want to re-tag all files in the {artistNames.length} selected artist?
|
|
||||||
</div>
|
|
||||||
<ul>
|
|
||||||
{
|
|
||||||
artistNames.map((artistName) => {
|
|
||||||
return (
|
|
||||||
<li key={artistName}>
|
|
||||||
{artistName}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onPress={onModalClose}>
|
|
||||||
{translate('Cancel')}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
onPress={onRetagArtistPress}
|
|
||||||
>
|
|
||||||
{translate('Retag')}
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
RetagArtistModalContent.propTypes = {
|
|
||||||
artistNames: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired,
|
|
||||||
onRetagArtistPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RetagArtistModalContent;
|
|
|
@ -1,67 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import * as commandNames from 'Commands/commandNames';
|
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
|
||||||
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
|
||||||
import RetagArtistModalContent from './RetagArtistModalContent';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state, { artistIds }) => artistIds,
|
|
||||||
createAllArtistSelector(),
|
|
||||||
(artistIds, allArtists) => {
|
|
||||||
const artist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
|
||||||
return s.id === id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const sortedArtist = _.orderBy(artist, 'sortName');
|
|
||||||
const artistNames = _.map(sortedArtist, 'artistName');
|
|
||||||
|
|
||||||
return {
|
|
||||||
artistNames
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
executeCommand
|
|
||||||
};
|
|
||||||
|
|
||||||
class RetagArtistModalContentConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onRetagArtistPress = () => {
|
|
||||||
this.props.executeCommand({
|
|
||||||
name: commandNames.RETAG_ARTIST,
|
|
||||||
artistIds: this.props.artistIds
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onModalClose(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render(props) {
|
|
||||||
return (
|
|
||||||
<RetagArtistModalContent
|
|
||||||
{...this.props}
|
|
||||||
onRetagArtistPress={this.onRetagArtistPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RetagArtistModalContentConnector.propTypes = {
|
|
||||||
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired,
|
|
||||||
executeCommand: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(RetagArtistModalContentConnector);
|
|
|
@ -1,31 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import DeleteArtistModalContentConnector from './DeleteArtistModalContentConnector';
|
|
||||||
|
|
||||||
function DeleteArtistModal(props) {
|
|
||||||
const {
|
|
||||||
isOpen,
|
|
||||||
onModalClose,
|
|
||||||
...otherProps
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
>
|
|
||||||
<DeleteArtistModalContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteArtistModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DeleteArtistModal;
|
|
|
@ -1,13 +0,0 @@
|
||||||
.message {
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pathContainer {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.path {
|
|
||||||
margin-left: 5px;
|
|
||||||
color: var(--dangerColor);
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
// This file is automatically generated.
|
|
||||||
// Please do not change this file!
|
|
||||||
interface CssExports {
|
|
||||||
'message': string;
|
|
||||||
'path': string;
|
|
||||||
'pathContainer': string;
|
|
||||||
}
|
|
||||||
export const cssExports: CssExports;
|
|
||||||
export default cssExports;
|
|
|
@ -1,124 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
|
||||||
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 { inputTypes, kinds } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './DeleteArtistModalContent.css';
|
|
||||||
|
|
||||||
class DeleteArtistModalContent extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
deleteFiles: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onDeleteFilesChange = ({ value }) => {
|
|
||||||
this.setState({ deleteFiles: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onDeleteArtistConfirmed = () => {
|
|
||||||
const deleteFiles = this.state.deleteFiles;
|
|
||||||
|
|
||||||
this.setState({ deleteFiles: false });
|
|
||||||
this.props.onDeleteSelectedPress(deleteFiles);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
artist,
|
|
||||||
onModalClose
|
|
||||||
} = this.props;
|
|
||||||
const deleteFiles = this.state.deleteFiles;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onModalClose}>
|
|
||||||
<ModalHeader>
|
|
||||||
{translate('DeleteArtist')}
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<div>
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>{`Delete Artist Folder${artist.length > 1 ? 's' : ''}`}</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="deleteFiles"
|
|
||||||
value={deleteFiles}
|
|
||||||
helpText={`Delete Artist Folder${artist.length > 1 ? 's' : ''} and all contents`}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
onChange={this.onDeleteFilesChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.message}>
|
|
||||||
{`Are you sure you want to delete ${artist.length} selected artist${artist.length > 1 ? 's' : ''}${deleteFiles ? ' and all contents' : ''}?`}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{
|
|
||||||
artist.map((s) => {
|
|
||||||
return (
|
|
||||||
<li key={s.artistName}>
|
|
||||||
<span>{s.artistName}</span>
|
|
||||||
|
|
||||||
{
|
|
||||||
deleteFiles &&
|
|
||||||
<span className={styles.pathContainer}>
|
|
||||||
-
|
|
||||||
<span className={styles.path}>
|
|
||||||
{s.path}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onPress={onModalClose}>
|
|
||||||
{translate('Cancel')}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
onPress={this.onDeleteArtistConfirmed}
|
|
||||||
>
|
|
||||||
{translate('Delete')}
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DeleteArtistModalContent.propTypes = {
|
|
||||||
artist: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired,
|
|
||||||
onDeleteSelectedPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DeleteArtistModalContent;
|
|
|
@ -1,45 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { bulkDeleteArtist } from 'Store/Actions/artistEditorActions';
|
|
||||||
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
|
||||||
import DeleteArtistModalContent from './DeleteArtistModalContent';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state, { artistIds }) => artistIds,
|
|
||||||
createAllArtistSelector(),
|
|
||||||
(artistIds, allArtists) => {
|
|
||||||
const selectedArtist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
|
||||||
return s.id === id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const sortedArtist = _.orderBy(selectedArtist, 'sortName');
|
|
||||||
const artist = _.map(sortedArtist, (s) => {
|
|
||||||
return {
|
|
||||||
artistName: s.artistName,
|
|
||||||
path: s.path
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
artist
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
|
||||||
return {
|
|
||||||
onDeleteSelectedPress(deleteFiles) {
|
|
||||||
dispatch(bulkDeleteArtist({
|
|
||||||
artistIds: props.artistIds,
|
|
||||||
deleteFiles
|
|
||||||
}));
|
|
||||||
|
|
||||||
props.onModalClose();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(DeleteArtistModalContent);
|
|
|
@ -1,31 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import OrganizeArtistModalContentConnector from './OrganizeArtistModalContentConnector';
|
|
||||||
|
|
||||||
function OrganizeArtistModal(props) {
|
|
||||||
const {
|
|
||||||
isOpen,
|
|
||||||
onModalClose,
|
|
||||||
...otherProps
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
>
|
|
||||||
<OrganizeArtistModalContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
OrganizeArtistModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OrganizeArtistModal;
|
|
|
@ -1,8 +0,0 @@
|
||||||
.renameIcon {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
// This file is automatically generated.
|
|
||||||
// Please do not change this file!
|
|
||||||
interface CssExports {
|
|
||||||
'message': string;
|
|
||||||
'renameIcon': string;
|
|
||||||
}
|
|
||||||
export const cssExports: CssExports;
|
|
||||||
export default cssExports;
|
|
|
@ -1,75 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Alert from 'Components/Alert';
|
|
||||||
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 { icons, kinds } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './OrganizeArtistModalContent.css';
|
|
||||||
|
|
||||||
function OrganizeArtistModalContent(props) {
|
|
||||||
const {
|
|
||||||
artistNames,
|
|
||||||
onModalClose,
|
|
||||||
onOrganizeArtistPress
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onModalClose}>
|
|
||||||
<ModalHeader>
|
|
||||||
{translate('OrganizeSelectedArtists')}
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<Alert>
|
|
||||||
Tip: To preview a rename, select "Cancel", then select any artist name and use the
|
|
||||||
<Icon
|
|
||||||
className={styles.renameIcon}
|
|
||||||
name={icons.ORGANIZE}
|
|
||||||
/>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<div className={styles.message}>
|
|
||||||
Are you sure you want to organize all files in the {artistNames.length} selected artist?
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
{
|
|
||||||
artistNames.map((artistName) => {
|
|
||||||
return (
|
|
||||||
<li key={artistName}>
|
|
||||||
{artistName}
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onPress={onModalClose}>
|
|
||||||
{translate('Cancel')}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
onPress={onOrganizeArtistPress}
|
|
||||||
>
|
|
||||||
{translate('Organize')}
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
OrganizeArtistModalContent.propTypes = {
|
|
||||||
artistNames: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired,
|
|
||||||
onOrganizeArtistPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OrganizeArtistModalContent;
|
|
|
@ -1,67 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import * as commandNames from 'Commands/commandNames';
|
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
|
||||||
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
|
||||||
import OrganizeArtistModalContent from './OrganizeArtistModalContent';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state, { artistIds }) => artistIds,
|
|
||||||
createAllArtistSelector(),
|
|
||||||
(artistIds, allArtists) => {
|
|
||||||
const artist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
|
||||||
return s.id === id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const sortedArtist = _.orderBy(artist, 'sortName');
|
|
||||||
const artistNames = _.map(sortedArtist, 'artistName');
|
|
||||||
|
|
||||||
return {
|
|
||||||
artistNames
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
executeCommand
|
|
||||||
};
|
|
||||||
|
|
||||||
class OrganizeArtistModalContentConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onOrganizeArtistPress = () => {
|
|
||||||
this.props.executeCommand({
|
|
||||||
name: commandNames.RENAME_ARTIST,
|
|
||||||
artistIds: this.props.artistIds
|
|
||||||
});
|
|
||||||
|
|
||||||
this.props.onModalClose(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render(props) {
|
|
||||||
return (
|
|
||||||
<OrganizeArtistModalContent
|
|
||||||
{...this.props}
|
|
||||||
onOrganizeArtistPress={this.onOrganizeArtistPress}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OrganizeArtistModalContentConnector.propTypes = {
|
|
||||||
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired,
|
|
||||||
executeCommand: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(OrganizeArtistModalContentConnector);
|
|
|
@ -1,31 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import TagsModalContentConnector from './TagsModalContentConnector';
|
|
||||||
|
|
||||||
function TagsModal(props) {
|
|
||||||
const {
|
|
||||||
isOpen,
|
|
||||||
onModalClose,
|
|
||||||
...otherProps
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
>
|
|
||||||
<TagsModalContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TagsModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TagsModal;
|
|
|
@ -1,12 +0,0 @@
|
||||||
.renameIcon {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result {
|
|
||||||
padding-top: 4px;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
// This file is automatically generated.
|
|
||||||
// Please do not change this file!
|
|
||||||
interface CssExports {
|
|
||||||
'message': string;
|
|
||||||
'renameIcon': string;
|
|
||||||
'result': string;
|
|
||||||
}
|
|
||||||
export const cssExports: CssExports;
|
|
||||||
export default cssExports;
|
|
|
@ -1,194 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
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 Label from 'Components/Label';
|
|
||||||
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 { inputTypes, kinds, sizes } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './TagsModalContent.css';
|
|
||||||
|
|
||||||
class TagsModalContent extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
tags: [],
|
|
||||||
applyTags: 'add'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
onInputChange = ({ name, value }) => {
|
|
||||||
this.setState({ [name]: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
onApplyTagsPress = () => {
|
|
||||||
const {
|
|
||||||
tags,
|
|
||||||
applyTags
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
this.props.onApplyTagsPress(tags, applyTags);
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
artistTags,
|
|
||||||
tagList,
|
|
||||||
onModalClose
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
tags,
|
|
||||||
applyTags
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const applyTagsOptions = [
|
|
||||||
{ key: 'add', value: translate('Add') },
|
|
||||||
{ key: 'remove', value: translate('Remove') },
|
|
||||||
{ key: 'replace', value: translate('Replace') }
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onModalClose}>
|
|
||||||
<ModalHeader>
|
|
||||||
Tags
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<Form>
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('Tags')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.TAG}
|
|
||||||
name="tags"
|
|
||||||
value={tags}
|
|
||||||
onChange={this.onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('ApplyTags')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.SELECT}
|
|
||||||
name="applyTags"
|
|
||||||
value={applyTags}
|
|
||||||
values={applyTagsOptions}
|
|
||||||
helpTexts={[
|
|
||||||
translate('ApplyTagsHelpTextHowToApplyArtists'),
|
|
||||||
translate('ApplyTagsHelpTextAdd'),
|
|
||||||
translate('ApplyTagsHelpTextRemove'),
|
|
||||||
translate('ApplyTagsHelpTextReplace')
|
|
||||||
]}
|
|
||||||
onChange={this.onInputChange}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('Result')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<div className={styles.result}>
|
|
||||||
{
|
|
||||||
artistTags.map((t) => {
|
|
||||||
const tag = _.find(tagList, { id: t });
|
|
||||||
|
|
||||||
if (!tag) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeTag = (applyTags === 'remove' && tags.indexOf(t) > -1) ||
|
|
||||||
(applyTags === 'replace' && tags.indexOf(t) === -1);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Label
|
|
||||||
key={tag.id}
|
|
||||||
title={removeTag ? translate('RemoveTagRemovingTag') : translate('RemoveTagExistingTag')}
|
|
||||||
kind={removeTag ? kinds.INVERSE : kinds.INFO}
|
|
||||||
size={sizes.LARGE}
|
|
||||||
>
|
|
||||||
{tag.label}
|
|
||||||
</Label>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
(applyTags === 'add' || applyTags === 'replace') &&
|
|
||||||
tags.map((t) => {
|
|
||||||
const tag = _.find(tagList, { id: t });
|
|
||||||
|
|
||||||
if (!tag) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (artistTags.indexOf(t) > -1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Label
|
|
||||||
key={tag.id}
|
|
||||||
title={translate('AddingTag')}
|
|
||||||
kind={kinds.SUCCESS}
|
|
||||||
size={sizes.LARGE}
|
|
||||||
>
|
|
||||||
{tag.label}
|
|
||||||
</Label>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</FormGroup>
|
|
||||||
</Form>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button onPress={onModalClose}>
|
|
||||||
{translate('Cancel')}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
kind={kinds.PRIMARY}
|
|
||||||
onPress={this.onApplyTagsPress}
|
|
||||||
>
|
|
||||||
{translate('Apply')}
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TagsModalContent.propTypes = {
|
|
||||||
artistTags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
|
||||||
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired,
|
|
||||||
onApplyTagsPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default TagsModalContent;
|
|
|
@ -1,36 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
|
||||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
|
||||||
import TagsModalContent from './TagsModalContent';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state, { artistIds }) => artistIds,
|
|
||||||
createAllArtistSelector(),
|
|
||||||
createTagsSelector(),
|
|
||||||
(artistIds, allArtists, tagList) => {
|
|
||||||
const artist = _.intersectionWith(allArtists, artistIds, (s, id) => {
|
|
||||||
return s.id === id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const artistTags = _.uniq(_.concat(..._.map(artist, 'tags')));
|
|
||||||
|
|
||||||
return {
|
|
||||||
artistTags,
|
|
||||||
tagList
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
|
||||||
return {
|
|
||||||
onAction() {
|
|
||||||
// Do something
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(TagsModalContent);
|
|
|
@ -29,10 +29,6 @@ const links = [
|
||||||
title: () => translate('AddNew'),
|
title: () => translate('AddNew'),
|
||||||
to: '/add/search'
|
to: '/add/search'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: () => translate('MassEditor'),
|
|
||||||
to: '/artisteditor'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: () => translate('AlbumStudio'),
|
title: () => translate('AlbumStudio'),
|
||||||
to: '/albumstudio'
|
to: '/albumstudio'
|
||||||
|
|
|
@ -1,259 +0,0 @@
|
||||||
import { createAction } from 'redux-actions';
|
|
||||||
import { batchActions } from 'redux-batched-actions';
|
|
||||||
import { filterBuilderTypes, filterBuilderValueTypes, sortDirections } from 'Helpers/Props';
|
|
||||||
import { createThunk, handleThunks } from 'Store/thunks';
|
|
||||||
import createAjaxRequest from 'Utilities/createAjaxRequest';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import { filterPredicates, filters, sortPredicates } from './artistActions';
|
|
||||||
import { set, updateItem } from './baseActions';
|
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
|
||||||
import createSetClientSideCollectionFilterReducer from './Creators/Reducers/createSetClientSideCollectionFilterReducer';
|
|
||||||
import createSetClientSideCollectionSortReducer from './Creators/Reducers/createSetClientSideCollectionSortReducer';
|
|
||||||
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Variables
|
|
||||||
|
|
||||||
export const section = 'artistEditor';
|
|
||||||
|
|
||||||
//
|
|
||||||
// State
|
|
||||||
|
|
||||||
export const defaultState = {
|
|
||||||
isSaving: false,
|
|
||||||
saveError: null,
|
|
||||||
isDeleting: false,
|
|
||||||
deleteError: null,
|
|
||||||
sortKey: 'sortName',
|
|
||||||
sortDirection: sortDirections.ASCENDING,
|
|
||||||
secondarySortKey: 'sortName',
|
|
||||||
secondarySortDirection: sortDirections.ASCENDING,
|
|
||||||
selectedFilterKey: 'all',
|
|
||||||
filters,
|
|
||||||
filterPredicates,
|
|
||||||
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
columnLabel: () => translate('Status'),
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true,
|
|
||||||
isModifiable: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sortName',
|
|
||||||
label: () => translate('Name'),
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'monitorNewItems',
|
|
||||||
label: () => translate('MonitorNewItems'),
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'qualityProfileId',
|
|
||||||
label: () => translate('QualityProfile'),
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'metadataProfileId',
|
|
||||||
label: () => translate('MetadataProfile'),
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'path',
|
|
||||||
label: () => translate('Path'),
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sizeOnDisk',
|
|
||||||
label: () => translate('SizeOnDisk'),
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tags',
|
|
||||||
label: () => translate('Tags'),
|
|
||||||
isSortable: true,
|
|
||||||
isVisible: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
filterBuilderProps: [
|
|
||||||
{
|
|
||||||
name: 'monitored',
|
|
||||||
label: () => translate('Monitored'),
|
|
||||||
type: filterBuilderTypes.EXACT,
|
|
||||||
valueType: filterBuilderValueTypes.BOOL
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
label: () => translate('Status'),
|
|
||||||
type: filterBuilderTypes.EXACT,
|
|
||||||
valueType: filterBuilderValueTypes.ARTIST_STATUS
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'qualityProfileId',
|
|
||||||
label: () => translate('QualityProfile'),
|
|
||||||
type: filterBuilderTypes.EXACT,
|
|
||||||
valueType: filterBuilderValueTypes.QUALITY_PROFILE
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'metadataProfileId',
|
|
||||||
label: () => translate('MetadataProfile'),
|
|
||||||
type: filterBuilderTypes.EXACT,
|
|
||||||
valueType: filterBuilderValueTypes.METADATA_PROFILE
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'path',
|
|
||||||
label: () => translate('Path'),
|
|
||||||
type: filterBuilderTypes.STRING
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rootFolderPath',
|
|
||||||
label: () => translate('RootFolderPath'),
|
|
||||||
type: filterBuilderTypes.EXACT
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sizeOnDisk',
|
|
||||||
label: () => translate('SizeOnDisk'),
|
|
||||||
type: filterBuilderTypes.NUMBER,
|
|
||||||
valueType: filterBuilderValueTypes.BYTES
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'tags',
|
|
||||||
label: () => translate('Tags'),
|
|
||||||
type: filterBuilderTypes.ARRAY,
|
|
||||||
valueType: filterBuilderValueTypes.TAG
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
sortPredicates
|
|
||||||
};
|
|
||||||
|
|
||||||
export const persistState = [
|
|
||||||
'artistEditor.sortKey',
|
|
||||||
'artistEditor.sortDirection',
|
|
||||||
'artistEditor.selectedFilterKey',
|
|
||||||
'artistEditor.customFilters',
|
|
||||||
'artistEditor.columns'
|
|
||||||
];
|
|
||||||
|
|
||||||
//
|
|
||||||
// Actions Types
|
|
||||||
|
|
||||||
export const SET_ARTIST_EDITOR_SORT = 'artistEditor/setArtistEditorSort';
|
|
||||||
export const SET_ARTIST_EDITOR_FILTER = 'artistEditor/setArtistEditorFilter';
|
|
||||||
export const SAVE_ARTIST_EDITOR = 'artistEditor/saveArtistEditor';
|
|
||||||
export const BULK_DELETE_ARTIST = 'artistEditor/bulkDeleteArtist';
|
|
||||||
export const SET_ARTIST_EDITOR_TABLE_OPTION = 'artistEditor/setArtistEditorTableOption';
|
|
||||||
|
|
||||||
//
|
|
||||||
// Action Creators
|
|
||||||
|
|
||||||
export const setArtistEditorSort = createAction(SET_ARTIST_EDITOR_SORT);
|
|
||||||
export const setArtistEditorFilter = createAction(SET_ARTIST_EDITOR_FILTER);
|
|
||||||
export const saveArtistEditor = createThunk(SAVE_ARTIST_EDITOR);
|
|
||||||
export const bulkDeleteArtist = createThunk(BULK_DELETE_ARTIST);
|
|
||||||
export const setArtistEditorTableOption = createAction(SET_ARTIST_EDITOR_TABLE_OPTION);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Action Handlers
|
|
||||||
|
|
||||||
export const actionHandlers = handleThunks({
|
|
||||||
[SAVE_ARTIST_EDITOR]: function(getState, payload, dispatch) {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isSaving: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
|
||||||
url: '/artist/editor',
|
|
||||||
method: 'PUT',
|
|
||||||
data: JSON.stringify(payload),
|
|
||||||
dataType: 'json'
|
|
||||||
}).request;
|
|
||||||
|
|
||||||
promise.done((data) => {
|
|
||||||
dispatch(batchActions([
|
|
||||||
...data.map((artist) => {
|
|
||||||
|
|
||||||
const {
|
|
||||||
images,
|
|
||||||
rootFolderPath,
|
|
||||||
statistics,
|
|
||||||
...propsToUpdate
|
|
||||||
} = artist;
|
|
||||||
|
|
||||||
return updateItem({
|
|
||||||
id: artist.id,
|
|
||||||
section: 'artist',
|
|
||||||
...propsToUpdate
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
|
|
||||||
set({
|
|
||||||
section,
|
|
||||||
isSaving: false,
|
|
||||||
saveError: null
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.fail((xhr) => {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isSaving: false,
|
|
||||||
saveError: xhr
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
[BULK_DELETE_ARTIST]: function(getState, payload, dispatch) {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isDeleting: true
|
|
||||||
}));
|
|
||||||
|
|
||||||
const promise = createAjaxRequest({
|
|
||||||
url: '/artist/editor',
|
|
||||||
method: 'DELETE',
|
|
||||||
data: JSON.stringify(payload),
|
|
||||||
dataType: 'json'
|
|
||||||
}).request;
|
|
||||||
|
|
||||||
promise.done(() => {
|
|
||||||
// SignalR will take care of removing the artist from the collection
|
|
||||||
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isDeleting: false,
|
|
||||||
deleteError: null
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.fail((xhr) => {
|
|
||||||
dispatch(set({
|
|
||||||
section,
|
|
||||||
isDeleting: false,
|
|
||||||
deleteError: xhr
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//
|
|
||||||
// Reducers
|
|
||||||
|
|
||||||
export const reducers = createHandleActions({
|
|
||||||
|
|
||||||
[SET_ARTIST_EDITOR_TABLE_OPTION]: createSetTableOptionReducer(section),
|
|
||||||
[SET_ARTIST_EDITOR_SORT]: createSetClientSideCollectionSortReducer(section),
|
|
||||||
[SET_ARTIST_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(section)
|
|
||||||
|
|
||||||
}, defaultState, section);
|
|
|
@ -3,7 +3,6 @@ import * as albumHistory from './albumHistoryActions';
|
||||||
import * as albumStudio from './albumStudioActions';
|
import * as albumStudio from './albumStudioActions';
|
||||||
import * as app from './appActions';
|
import * as app from './appActions';
|
||||||
import * as artist from './artistActions';
|
import * as artist from './artistActions';
|
||||||
import * as artistEditor from './artistEditorActions';
|
|
||||||
import * as artistHistory from './artistHistoryActions';
|
import * as artistHistory from './artistHistoryActions';
|
||||||
import * as artistIndex from './artistIndexActions';
|
import * as artistIndex from './artistIndexActions';
|
||||||
import * as blocklist from './blocklistActions';
|
import * as blocklist from './blocklistActions';
|
||||||
|
@ -49,7 +48,6 @@ export default [
|
||||||
releases,
|
releases,
|
||||||
albumStudio,
|
albumStudio,
|
||||||
artist,
|
artist,
|
||||||
artistEditor,
|
|
||||||
artistHistory,
|
artistHistory,
|
||||||
artistIndex,
|
artistIndex,
|
||||||
search,
|
search,
|
||||||
|
|
|
@ -575,7 +575,6 @@
|
||||||
"MarkAsFailedMessageText": "Are you sure you want to mark '{0}' as failed?",
|
"MarkAsFailedMessageText": "Are you sure you want to mark '{0}' as failed?",
|
||||||
"MassAlbumsCutoffUnmetWarning": "Are you sure you want to search for all '{0}' Cutoff Unmet albums?",
|
"MassAlbumsCutoffUnmetWarning": "Are you sure you want to search for all '{0}' Cutoff Unmet albums?",
|
||||||
"MassAlbumsSearchWarning": "Are you sure you want to search for all '{0}' missing albums?",
|
"MassAlbumsSearchWarning": "Are you sure you want to search for all '{0}' missing albums?",
|
||||||
"MassEditor": "Mass Editor",
|
|
||||||
"MaximumLimits": "Maximum Limits",
|
"MaximumLimits": "Maximum Limits",
|
||||||
"MaximumSize": "Maximum Size",
|
"MaximumSize": "Maximum Size",
|
||||||
"MaximumSizeHelpText": "Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited.",
|
"MaximumSizeHelpText": "Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited.",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue