mirror of https://github.com/lidarr/Lidarr
New: UI Updates, Tag manager, More custom filters (#437)
* New: UI Updates, Tag manager, More custom filters * fixup! Fix ScanFixture Unit Tests * Fixed: Sentry Errors from UI don't have release, branch, environment * Changed: Bump Mobile Detect for New Device Detection * Fixed: Build on changes to package.json * fixup! Add MetadataProfile filter option * fixup! Tag Note, Blacklist, Manual Import * fixup: Remove connectSection * fixup: root folder comment
This commit is contained in:
parent
afa78b1d20
commit
6581b3a2c5
|
@ -56,4 +56,5 @@ only_commits:
|
||||||
- appveyor.yml
|
- appveyor.yml
|
||||||
- build.sh
|
- build.sh
|
||||||
- test.sh
|
- test.sh
|
||||||
|
- package.json
|
||||||
- appveyor-package.sh
|
- appveyor-package.sh
|
||||||
|
|
|
@ -5,6 +5,7 @@ const path = require('path');
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
const errorHandler = require('./helpers/errorHandler');
|
const errorHandler = require('./helpers/errorHandler');
|
||||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||||
|
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
|
||||||
|
|
||||||
const uiFolder = 'UI';
|
const uiFolder = 'UI';
|
||||||
const root = path.join(__dirname, '..', 'src');
|
const root = path.join(__dirname, '..', 'src');
|
||||||
|
@ -27,19 +28,49 @@ const extractCSSPlugin = new ExtractTextPlugin({
|
||||||
ignoreOrder: true
|
ignoreOrder: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
extractCSSPlugin,
|
||||||
|
|
||||||
|
new webpack.optimize.CommonsChunkPlugin({
|
||||||
|
name: 'vendor'
|
||||||
|
}),
|
||||||
|
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
__DEV__: !isProduction,
|
||||||
|
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isProduction) {
|
||||||
|
plugins.push(new UglifyJSPlugin({
|
||||||
|
sourceMap: true,
|
||||||
|
uglifyOptions: {
|
||||||
|
mangle: false,
|
||||||
|
output: {
|
||||||
|
comments: false,
|
||||||
|
beautify: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
devtool: '#source-map',
|
devtool: '#source-map',
|
||||||
|
|
||||||
stats: {
|
stats: {
|
||||||
children: false
|
children: false
|
||||||
},
|
},
|
||||||
|
|
||||||
watchOptions: {
|
watchOptions: {
|
||||||
ignored: /node_modules/
|
ignored: /node_modules/
|
||||||
},
|
},
|
||||||
|
|
||||||
entry: {
|
entry: {
|
||||||
preload: 'preload.js',
|
preload: 'preload.js',
|
||||||
vendor: 'vendor.js',
|
vendor: 'vendor.js',
|
||||||
index: 'index.js'
|
index: 'index.js'
|
||||||
},
|
},
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: [
|
modules: [
|
||||||
root,
|
root,
|
||||||
|
@ -50,35 +81,21 @@ const config = {
|
||||||
jquery: 'jquery/src/jquery'
|
jquery: 'jquery/src/jquery'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
output: {
|
output: {
|
||||||
filename: path.join('_output', uiFolder, '[name].js'),
|
filename: path.join('_output', uiFolder, '[name].js'),
|
||||||
sourceMapFilename: '[file].map'
|
sourceMapFilename: '[file].map'
|
||||||
},
|
},
|
||||||
plugins: [
|
|
||||||
extractCSSPlugin,
|
|
||||||
new webpack.optimize.CommonsChunkPlugin({
|
|
||||||
name: 'vendor'
|
|
||||||
}),
|
|
||||||
|
|
||||||
new webpack.DefinePlugin({
|
plugins,
|
||||||
__DEV__: !isProduction,
|
|
||||||
'process.env': {
|
|
||||||
NODE_ENV: isProduction ? JSON.stringify('production') : JSON.stringify('development')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
],
|
|
||||||
resolveLoader: {
|
resolveLoader: {
|
||||||
modules: [
|
modules: [
|
||||||
'node_modules',
|
'node_modules',
|
||||||
'frontend/gulp/webpack/'
|
'frontend/gulp/webpack/'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// TODO: Do we need this loader?
|
|
||||||
// eslint: {
|
|
||||||
// formatter: function(results) {
|
|
||||||
// return JSON.stringify(results);
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
width: 80px;
|
width: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details {
|
.actions {
|
||||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
width: 30px;
|
width: 70px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { icons } from 'Helpers/Props';
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
|
@ -48,7 +48,8 @@ class BlacklistRow extends Component {
|
||||||
protocol,
|
protocol,
|
||||||
indexer,
|
indexer,
|
||||||
message,
|
message,
|
||||||
columns
|
columns,
|
||||||
|
onRemovePress
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -129,16 +130,21 @@ class BlacklistRow extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'details') {
|
if (name === 'actions') {
|
||||||
return (
|
return (
|
||||||
<TableRowCell
|
<TableRowCell
|
||||||
key={name}
|
key={name}
|
||||||
className={styles.details}
|
className={styles.actions}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
name={icons.INFO}
|
name={icons.INFO}
|
||||||
onPress={this.onDetailsPress}
|
onPress={this.onDetailsPress}
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
name={icons.REMOVE}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
onPress={onRemovePress}
|
||||||
|
/>
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -171,7 +177,8 @@ BlacklistRow.propTypes = {
|
||||||
protocol: PropTypes.string.isRequired,
|
protocol: PropTypes.string.isRequired,
|
||||||
indexer: PropTypes.string,
|
indexer: PropTypes.string,
|
||||||
message: PropTypes.string,
|
message: PropTypes.string,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
onRemovePress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BlacklistRow;
|
export default BlacklistRow;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||||
|
import { removeFromBlacklist } from 'Store/Actions/blacklistActions';
|
||||||
import BlacklistRow from './BlacklistRow';
|
import BlacklistRow from './BlacklistRow';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
|
@ -14,4 +15,12 @@ function createMapStateToProps() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(createMapStateToProps)(BlacklistRow);
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
|
return {
|
||||||
|
onRemovePress() {
|
||||||
|
dispatch(removeFromBlacklist({ id: props.id }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, createMapDispatchToProps)(BlacklistRow);
|
||||||
|
|
|
@ -106,9 +106,7 @@ class ImportArtistSelectFolder extends Component {
|
||||||
{
|
{
|
||||||
items.length > 0 ?
|
items.length > 0 ?
|
||||||
<div className={styles.recentFolders}>
|
<div className={styles.recentFolders}>
|
||||||
<FieldSet
|
<FieldSet legend="Recent Folders">
|
||||||
legend="Recent Folders"
|
|
||||||
>
|
|
||||||
<Table
|
<Table
|
||||||
columns={rootFolderColumns}
|
columns={rootFolderColumns}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
.descriptionList {
|
||||||
|
composes: descriptionList from 'Components/DescriptionList/DescriptionList.css';
|
||||||
|
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
composes: title from 'Components/DescriptionList/DescriptionListItemTitle.css';
|
composes: title from 'Components/DescriptionList/DescriptionListItemTitle.css';
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ function SceneInfo(props) {
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DescriptionList>
|
<DescriptionList className={styles.descriptionList}>
|
||||||
{
|
{
|
||||||
sceneSeasonNumber !== undefined &&
|
sceneSeasonNumber !== undefined &&
|
||||||
<DescriptionListItem
|
<DescriptionListItem
|
||||||
|
|
|
@ -117,9 +117,9 @@ class InteractiveAlbumSearchModalContent extends Component {
|
||||||
selectedFilterKey={selectedFilterKey}
|
selectedFilterKey={selectedFilterKey}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
customFilters={customFilters}
|
customFilters={customFilters}
|
||||||
onFilterSelect={onFilterSelect}
|
|
||||||
buttonComponent={PageMenuButton}
|
buttonComponent={PageMenuButton}
|
||||||
filterModalConnectorComponent={InteractiveSearchFilterModalConnector}
|
filterModalConnectorComponent={InteractiveSearchFilterModalConnector}
|
||||||
|
onFilterSelect={onFilterSelect}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import * as releaseActions from 'Store/Actions/releaseActions';
|
import * as releaseActions from 'Store/Actions/releaseActions';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
|
@ -10,7 +10,7 @@ import InteractiveAlbumSearchModalContent from './InteractiveAlbumSearchModalCon
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.releases.items.length,
|
(state) => state.releases.items.length,
|
||||||
createClientSideCollectionSelector(),
|
createClientSideCollectionSelector('releases'),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
(totalReleasesCount, releases, uiSettings) => {
|
(totalReleasesCount, releases, uiSettings) => {
|
||||||
return {
|
return {
|
||||||
|
@ -95,10 +95,4 @@ InteractiveAlbumSearchModalContentConnector.propTypes = {
|
||||||
dispatchCancelFetchReleases: PropTypes.func.isRequired
|
dispatchCancelFetchReleases: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveAlbumSearchModalContentConnector);
|
||||||
createMapStateToProps,
|
|
||||||
createMapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'releases' }
|
|
||||||
)(InteractiveAlbumSearchModalContentConnector);
|
|
||||||
|
|
|
@ -18,8 +18,8 @@ function createMapStateToProps() {
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
return {
|
return {
|
||||||
onRemoveCustomFilterPress(index) {
|
onRemoveCustomFilterPress(payload) {
|
||||||
dispatch(releaseActions.removeReleasesCustomFilter({ index }));
|
dispatch(releaseActions.removeReleasesCustomFilter(payload));
|
||||||
},
|
},
|
||||||
|
|
||||||
onSaveCustomFilterPress(payload) {
|
onSaveCustomFilterPress(payload) {
|
||||||
|
|
|
@ -101,6 +101,7 @@ class AlbumStudio extends Component {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
error,
|
error,
|
||||||
|
totalItems,
|
||||||
items,
|
items,
|
||||||
selectedFilterKey,
|
selectedFilterKey,
|
||||||
filters,
|
filters,
|
||||||
|
@ -178,7 +179,7 @@ class AlbumStudio extends Component {
|
||||||
|
|
||||||
{
|
{
|
||||||
!error && isPopulated && !items.length &&
|
!error && isPopulated && !items.length &&
|
||||||
<NoArtist />
|
<NoArtist totalItems={totalItems} />
|
||||||
}
|
}
|
||||||
</PageContentBodyConnector>
|
</PageContentBodyConnector>
|
||||||
|
|
||||||
|
@ -197,6 +198,7 @@ AlbumStudio.propTypes = {
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
|
totalItems: PropTypes.number.isRequired,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||||
|
|
|
@ -26,7 +26,7 @@ class AlbumStudioAlbum extends Component {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
monitored,
|
monitored,
|
||||||
statistics,
|
statistics = {},
|
||||||
isSaving
|
isSaving
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import { setAlbumStudioSort, setAlbumStudioFilter, saveAlbumStudio } from 'Store/Actions/albumStudioActions';
|
import { setAlbumStudioSort, setAlbumStudioFilter, saveAlbumStudio } from 'Store/Actions/albumStudioActions';
|
||||||
import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions';
|
import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions';
|
||||||
|
@ -9,7 +9,7 @@ import AlbumStudio from './AlbumStudio';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createClientSideCollectionSelector(),
|
createClientSideCollectionSelector('artist', 'albumStudio'),
|
||||||
(artist) => {
|
(artist) => {
|
||||||
return {
|
return {
|
||||||
...artist
|
...artist
|
||||||
|
@ -88,10 +88,4 @@ AlbumStudioConnector.propTypes = {
|
||||||
saveAlbumStudio: PropTypes.func.isRequired
|
saveAlbumStudio: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(AlbumStudioConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'artist', uiSection: 'albumStudio' }
|
|
||||||
)(AlbumStudioConnector);
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import ImportListSettings from 'Settings/ImportLists/ImportListSettings';
|
||||||
import DownloadClientSettings from 'Settings/DownloadClients/DownloadClientSettings';
|
import DownloadClientSettings from 'Settings/DownloadClients/DownloadClientSettings';
|
||||||
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
|
import NotificationSettings from 'Settings/Notifications/NotificationSettings';
|
||||||
import MetadataSettings from 'Settings/Metadata/MetadataSettings';
|
import MetadataSettings from 'Settings/Metadata/MetadataSettings';
|
||||||
|
import TagSettings from 'Settings/Tags/TagSettings';
|
||||||
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
import GeneralSettingsConnector from 'Settings/General/GeneralSettingsConnector';
|
||||||
import UISettingsConnector from 'Settings/UI/UISettingsConnector';
|
import UISettingsConnector from 'Settings/UI/UISettingsConnector';
|
||||||
import Status from 'System/Status/Status';
|
import Status from 'System/Status/Status';
|
||||||
|
@ -191,6 +192,11 @@ function AppRoutes(props) {
|
||||||
component={MetadataSettings}
|
component={MetadataSettings}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/settings/tags"
|
||||||
|
component={TagSettings}
|
||||||
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/settings/general"
|
path="/settings/general"
|
||||||
component={GeneralSettingsConnector}
|
component={GeneralSettingsConnector}
|
||||||
|
|
|
@ -29,9 +29,8 @@
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.episodeCountContainer {
|
.episodeCountTooltip {
|
||||||
margin-left: 10px;
|
display: flex;
|
||||||
vertical-align: text-bottom;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.expandButton {
|
.expandButton {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import { findCommand } from 'Utilities/Command';
|
import { findCommand } from 'Utilities/Command';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
import createArtistSelector from 'Store/Selectors/createArtistSelector';
|
||||||
|
@ -18,7 +17,7 @@ import ArtistDetailsSeason from './ArtistDetailsSeason';
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { label }) => label,
|
(state, { label }) => label,
|
||||||
createClientSideCollectionSelector(),
|
createClientSideCollectionSelector('albums'),
|
||||||
createArtistSelector(),
|
createArtistSelector(),
|
||||||
createCommandsSelector(),
|
createCommandsSelector(),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
|
@ -96,10 +95,4 @@ ArtistDetailsSeasonConnector.propTypes = {
|
||||||
executeCommand: PropTypes.func.isRequired
|
executeCommand: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistDetailsSeasonConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'albums' }
|
|
||||||
)(ArtistDetailsSeasonConnector);
|
|
||||||
|
|
|
@ -13,9 +13,10 @@ import FilterMenu from 'Components/Menu/FilterMenu';
|
||||||
import Table from 'Components/Table/Table';
|
import Table from 'Components/Table/Table';
|
||||||
import TableBody from 'Components/Table/TableBody';
|
import TableBody from 'Components/Table/TableBody';
|
||||||
import NoArtist from 'Artist/NoArtist';
|
import NoArtist from 'Artist/NoArtist';
|
||||||
|
import OrganizeArtistModal from './Organize/OrganizeArtistModal';
|
||||||
import ArtistEditorRowConnector from './ArtistEditorRowConnector';
|
import ArtistEditorRowConnector from './ArtistEditorRowConnector';
|
||||||
import ArtistEditorFooter from './ArtistEditorFooter';
|
import ArtistEditorFooter from './ArtistEditorFooter';
|
||||||
import OrganizeArtistModal from './Organize/OrganizeArtistModal';
|
import ArtistEditorFilterModalConnector from './ArtistEditorFilterModalConnector';
|
||||||
|
|
||||||
function getColumns(showLanguageProfile, showMetadataProfile) {
|
function getColumns(showLanguageProfile, showMetadataProfile) {
|
||||||
return [
|
return [
|
||||||
|
@ -148,6 +149,7 @@ class ArtistEditor extends Component {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
error,
|
error,
|
||||||
|
totalItems,
|
||||||
items,
|
items,
|
||||||
selectedFilterKey,
|
selectedFilterKey,
|
||||||
filters,
|
filters,
|
||||||
|
@ -184,6 +186,7 @@ class ArtistEditor extends Component {
|
||||||
selectedFilterKey={selectedFilterKey}
|
selectedFilterKey={selectedFilterKey}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
customFilters={customFilters}
|
customFilters={customFilters}
|
||||||
|
filterModalConnectorComponent={ArtistEditorFilterModalConnector}
|
||||||
onFilterSelect={onFilterSelect}
|
onFilterSelect={onFilterSelect}
|
||||||
/>
|
/>
|
||||||
</PageToolbarSection>
|
</PageToolbarSection>
|
||||||
|
@ -234,7 +237,7 @@ class ArtistEditor extends Component {
|
||||||
|
|
||||||
{
|
{
|
||||||
!error && isPopulated && !items.length &&
|
!error && isPopulated && !items.length &&
|
||||||
<NoArtist />
|
<NoArtist totalItems={totalItems} />
|
||||||
}
|
}
|
||||||
</PageContentBodyConnector>
|
</PageContentBodyConnector>
|
||||||
|
|
||||||
|
@ -266,6 +269,7 @@ ArtistEditor.propTypes = {
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
|
totalItems: PropTypes.number.isRequired,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
sortKey: PropTypes.string,
|
sortKey: PropTypes.string,
|
||||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createCommandSelector from 'Store/Selectors/createCommandSelector';
|
import createCommandSelector from 'Store/Selectors/createCommandSelector';
|
||||||
import { setArtistEditorSort, setArtistEditorFilter, saveArtistEditor } from 'Store/Actions/artistEditorActions';
|
import { setArtistEditorSort, setArtistEditorFilter, saveArtistEditor } from 'Store/Actions/artistEditorActions';
|
||||||
|
@ -14,7 +14,7 @@ function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.languageProfiles,
|
(state) => state.settings.languageProfiles,
|
||||||
(state) => state.settings.metadataProfiles,
|
(state) => state.settings.metadataProfiles,
|
||||||
createClientSideCollectionSelector(),
|
createClientSideCollectionSelector('artist', 'artistEditor'),
|
||||||
createCommandSelector(commandNames.RENAME_ARTIST),
|
createCommandSelector(commandNames.RENAME_ARTIST),
|
||||||
(languageProfiles, metadataProfiles, artist, isOrganizingArtist) => {
|
(languageProfiles, metadataProfiles, artist, isOrganizingArtist) => {
|
||||||
return {
|
return {
|
||||||
|
@ -89,10 +89,4 @@ ArtistEditorConnector.propTypes = {
|
||||||
dispatchExecuteCommand: PropTypes.func.isRequired
|
dispatchExecuteCommand: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistEditorConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'artist', uiSection: 'artistEditor' }
|
|
||||||
)(ArtistEditorConnector);
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import * as artistEditorActions from 'Store/Actions/artistEditorActions';
|
||||||
|
import FilterModal from 'Components/Filter/FilterModal';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.artist.items,
|
||||||
|
(state) => state.artistEditor.filterBuilderProps,
|
||||||
|
(sectionItems, filterBuilderProps) => {
|
||||||
|
return {
|
||||||
|
sectionItems,
|
||||||
|
filterBuilderProps
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
|
return {
|
||||||
|
onRemoveCustomFilterPress(payload) {
|
||||||
|
dispatch(artistEditorActions.removeArtistEditorCustomFilter(payload));
|
||||||
|
},
|
||||||
|
|
||||||
|
onSaveCustomFilterPress(payload) {
|
||||||
|
dispatch(artistEditorActions.saveArtistEditorCustomFilter(payload));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, createMapDispatchToProps)(FilterModal);
|
|
@ -185,6 +185,7 @@ class ArtistIndex extends Component {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
error,
|
error,
|
||||||
|
totalItems,
|
||||||
items,
|
items,
|
||||||
selectedFilterKey,
|
selectedFilterKey,
|
||||||
filters,
|
filters,
|
||||||
|
@ -341,7 +342,7 @@ class ArtistIndex extends Component {
|
||||||
|
|
||||||
{
|
{
|
||||||
!error && isPopulated && !items.length &&
|
!error && isPopulated && !items.length &&
|
||||||
<NoArtist />
|
<NoArtist totalItems={totalItems} />
|
||||||
}
|
}
|
||||||
</PageContentBodyConnector>
|
</PageContentBodyConnector>
|
||||||
|
|
||||||
|
@ -379,6 +380,7 @@ ArtistIndex.propTypes = {
|
||||||
isFetching: PropTypes.bool.isRequired,
|
isFetching: PropTypes.bool.isRequired,
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
isPopulated: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.object,
|
error: PropTypes.object,
|
||||||
|
totalItems: PropTypes.number.isRequired,
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
selectedFilterKey: PropTypes.string.isRequired,
|
selectedFilterKey: PropTypes.string.isRequired,
|
||||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
import dimensions from 'Styles/Variables/dimensions';
|
||||||
import createCommandSelector from 'Store/Selectors/createCommandSelector';
|
import createCommandSelector from 'Store/Selectors/createCommandSelector';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
@ -45,18 +46,21 @@ function getScrollTop(view, scrollTop, isSmallScreen) {
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.artist,
|
createClientSideCollectionSelector('artist', 'artistIndex'),
|
||||||
(state) => state.artistIndex,
|
|
||||||
createCommandSelector(commandNames.REFRESH_ARTIST),
|
createCommandSelector(commandNames.REFRESH_ARTIST),
|
||||||
createCommandSelector(commandNames.RSS_SYNC),
|
createCommandSelector(commandNames.RSS_SYNC),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(artist, artistIndex, isRefreshingArtist, isRssSyncExecuting, dimensionsState) => {
|
(
|
||||||
|
artist,
|
||||||
|
isRefreshingArtist,
|
||||||
|
isRssSyncExecuting,
|
||||||
|
dimensionsState
|
||||||
|
) => {
|
||||||
return {
|
return {
|
||||||
|
...artist,
|
||||||
isRefreshingArtist,
|
isRefreshingArtist,
|
||||||
isRssSyncExecuting,
|
isRssSyncExecuting,
|
||||||
isSmallScreen: dimensionsState.isSmallScreen,
|
isSmallScreen: dimensionsState.isSmallScreen
|
||||||
...artist,
|
|
||||||
...artistIndex
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import * as artistIndexActions from 'Store/Actions/artistIndexActions';
|
||||||
|
import FilterModal from 'Components/Filter/FilterModal';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.artist.items,
|
||||||
|
(state) => state.artistIndex.filterBuilderProps,
|
||||||
|
(sectionItems, filterBuilderProps) => {
|
||||||
|
return {
|
||||||
|
sectionItems,
|
||||||
|
filterBuilderProps
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMapDispatchToProps(dispatch, props) {
|
||||||
|
return {
|
||||||
|
onRemoveCustomFilterPress(payload) {
|
||||||
|
dispatch(artistIndexActions.removeArtistCustomFilter(payload));
|
||||||
|
},
|
||||||
|
|
||||||
|
onSaveCustomFilterPress(payload) {
|
||||||
|
dispatch(artistIndexActions.saveArtistCustomFilter(payload));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps, createMapDispatchToProps)(FilterModal);
|
|
@ -15,8 +15,16 @@ function ArtistIndexFooter({ artist }) {
|
||||||
let totalFileSize = 0;
|
let totalFileSize = 0;
|
||||||
|
|
||||||
artist.forEach((s) => {
|
artist.forEach((s) => {
|
||||||
tracks += s.statistics.trackCount || 0;
|
const { statistics = {} } = s;
|
||||||
trackFiles += s.statistics.trackFileCount || 0;
|
|
||||||
|
const {
|
||||||
|
trackCount = 0,
|
||||||
|
trackFileCount = 0,
|
||||||
|
sizeOnDisk = 0
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
|
tracks += trackCount;
|
||||||
|
trackFiles += trackFileCount;
|
||||||
|
|
||||||
if (s.status === 'ended') {
|
if (s.status === 'ended') {
|
||||||
ended++;
|
ended++;
|
||||||
|
@ -28,7 +36,7 @@ function ArtistIndexFooter({ artist }) {
|
||||||
monitored++;
|
monitored++;
|
||||||
}
|
}
|
||||||
|
|
||||||
totalFileSize += s.statistics.sizeOnDisk || 0;
|
totalFileSize += sizeOnDisk;
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
@ -8,7 +8,7 @@ import ArtistIndexBanners from './ArtistIndexBanners';
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.artistIndex.bannerOptions,
|
(state) => state.artistIndex.bannerOptions,
|
||||||
createClientSideCollectionSelector(),
|
createClientSideCollectionSelector('artist', 'artistIndex'),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(bannerOptions, artist, uiSettings, dimensions) => {
|
(bannerOptions, artist, uiSettings, dimensions) => {
|
||||||
|
@ -24,10 +24,4 @@ function createMapStateToProps() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps)(ArtistIndexBanners);
|
||||||
createMapStateToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'artist', uiSection: 'artistIndex' }
|
|
||||||
)(ArtistIndexBanners);
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { align } from 'Helpers/Props';
|
import { align } from 'Helpers/Props';
|
||||||
import FilterMenu from 'Components/Menu/FilterMenu';
|
import FilterMenu from 'Components/Menu/FilterMenu';
|
||||||
|
import ArtistIndexFilterModalConnector from 'Artist/Index/ArtistIndexFilterModalConnector';
|
||||||
|
|
||||||
function ArtistIndexFilterMenu(props) {
|
function ArtistIndexFilterMenu(props) {
|
||||||
const {
|
const {
|
||||||
|
@ -19,6 +20,7 @@ function ArtistIndexFilterMenu(props) {
|
||||||
selectedFilterKey={selectedFilterKey}
|
selectedFilterKey={selectedFilterKey}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
customFilters={customFilters}
|
customFilters={customFilters}
|
||||||
|
filterModalConnectorComponent={ArtistIndexFilterModalConnector}
|
||||||
onFilterSelect={onFilterSelect}
|
onFilterSelect={onFilterSelect}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
@ -8,7 +8,7 @@ import ArtistIndexOverviews from './ArtistIndexOverviews';
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.artistIndex.overviewOptions,
|
(state) => state.artistIndex.overviewOptions,
|
||||||
createClientSideCollectionSelector(),
|
createClientSideCollectionSelector('artist', 'artistIndex'),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(overviewOptions, artist, uiSettings, dimensions) => {
|
(overviewOptions, artist, uiSettings, dimensions) => {
|
||||||
|
@ -24,10 +24,4 @@ function createMapStateToProps() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps)(ArtistIndexOverviews);
|
||||||
createMapStateToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'artist', uiSection: 'artistIndex' }
|
|
||||||
)(ArtistIndexOverviews);
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||||
|
@ -8,7 +8,7 @@ import ArtistIndexPosters from './ArtistIndexPosters';
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.artistIndex.posterOptions,
|
(state) => state.artistIndex.posterOptions,
|
||||||
createClientSideCollectionSelector(),
|
createClientSideCollectionSelector('artist', 'artistIndex'),
|
||||||
createUISettingsSelector(),
|
createUISettingsSelector(),
|
||||||
createDimensionsSelector(),
|
createDimensionsSelector(),
|
||||||
(posterOptions, artist, uiSettings, dimensions) => {
|
(posterOptions, artist, uiSettings, dimensions) => {
|
||||||
|
@ -24,10 +24,4 @@ function createMapStateToProps() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps)(ArtistIndexPosters);
|
||||||
createMapStateToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'artist', uiSection: 'artistIndex' }
|
|
||||||
)(ArtistIndexPosters);
|
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
|
|
||||||
.nextAlbum,
|
.nextAlbum,
|
||||||
.lastAlbum,
|
.lastAlbum,
|
||||||
.added {
|
.added,
|
||||||
|
.genres {
|
||||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
flex: 0 0 180px;
|
flex: 0 0 180px;
|
||||||
|
@ -58,6 +59,12 @@
|
||||||
flex: 0 0 120px;
|
flex: 0 0 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ratings {
|
||||||
|
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
|
flex: 0 0 80px;
|
||||||
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,8 @@
|
||||||
|
|
||||||
.nextAlbum,
|
.nextAlbum,
|
||||||
.lastAlbum,
|
.lastAlbum,
|
||||||
.added {
|
.added,
|
||||||
|
.genres {
|
||||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
||||||
|
|
||||||
flex: 0 0 180px;
|
flex: 0 0 180px;
|
||||||
|
@ -60,6 +61,12 @@
|
||||||
flex: 0 0 120px;
|
flex: 0 0 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ratings {
|
||||||
|
composes: headerCell from 'Components/Table/VirtualTableHeaderCell.css';
|
||||||
|
|
||||||
|
flex: 0 0 80px;
|
||||||
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
composes: cell from 'Components/Table/Cells/VirtualTableRowCell.css';
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
||||||
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 { icons } from 'Helpers/Props';
|
import { icons } from 'Helpers/Props';
|
||||||
|
import HeartRating from 'Components/HeartRating';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||||
import ProgressBar from 'Components/ProgressBar';
|
import ProgressBar from 'Components/ProgressBar';
|
||||||
|
@ -75,6 +76,8 @@ class ArtistIndexRow extends Component {
|
||||||
lastAlbum,
|
lastAlbum,
|
||||||
added,
|
added,
|
||||||
statistics,
|
statistics,
|
||||||
|
genres,
|
||||||
|
ratings,
|
||||||
path,
|
path,
|
||||||
tags,
|
tags,
|
||||||
columns,
|
columns,
|
||||||
|
@ -303,6 +306,34 @@ class ArtistIndexRow extends Component {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (name === 'genres') {
|
||||||
|
const joinedGenres = genres.join(', ');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VirtualTableRowCell
|
||||||
|
key={name}
|
||||||
|
className={styles[name]}
|
||||||
|
>
|
||||||
|
<span title={joinedGenres}>
|
||||||
|
{joinedGenres}
|
||||||
|
</span>
|
||||||
|
</VirtualTableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === 'ratings') {
|
||||||
|
return (
|
||||||
|
<VirtualTableRowCell
|
||||||
|
key={name}
|
||||||
|
className={styles[name]}
|
||||||
|
>
|
||||||
|
<HeartRating
|
||||||
|
rating={ratings.value}
|
||||||
|
/>
|
||||||
|
</VirtualTableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (name === 'tags') {
|
if (name === 'tags') {
|
||||||
return (
|
return (
|
||||||
<VirtualTableRowCell
|
<VirtualTableRowCell
|
||||||
|
@ -376,6 +407,8 @@ ArtistIndexRow.propTypes = {
|
||||||
statistics: PropTypes.object.isRequired,
|
statistics: PropTypes.object.isRequired,
|
||||||
latestAlbum: PropTypes.object,
|
latestAlbum: PropTypes.object,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
|
genres: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
ratings: PropTypes.object.isRequired,
|
||||||
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
isRefreshingArtist: PropTypes.bool.isRequired,
|
isRefreshingArtist: PropTypes.bool.isRequired,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import { setArtistSort } from 'Store/Actions/artistIndexActions';
|
import { setArtistSort } from 'Store/Actions/artistIndexActions';
|
||||||
import ArtistIndexTable from './ArtistIndexTable';
|
import ArtistIndexTable from './ArtistIndexTable';
|
||||||
|
@ -7,7 +7,7 @@ import ArtistIndexTable from './ArtistIndexTable';
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.app.dimensions,
|
(state) => state.app.dimensions,
|
||||||
createClientSideCollectionSelector(),
|
createClientSideCollectionSelector('artist', 'artistIndex'),
|
||||||
(dimensions, artist) => {
|
(dimensions, artist) => {
|
||||||
return {
|
return {
|
||||||
isSmallScreen: dimensions.isSmallScreen,
|
isSmallScreen: dimensions.isSmallScreen,
|
||||||
|
@ -25,10 +25,4 @@ function createMapDispatchToProps(dispatch, props) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, createMapDispatchToProps)(ArtistIndexTable);
|
||||||
createMapStateToProps,
|
|
||||||
createMapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'artist', uiSection: 'artistIndex' }
|
|
||||||
)(ArtistIndexTable);
|
|
||||||
|
|
|
@ -1,9 +1,22 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
import styles from './NoArtist.css';
|
import styles from './NoArtist.css';
|
||||||
|
|
||||||
function NoArtist() {
|
function NoArtist(props) {
|
||||||
|
const { totalItems } = props;
|
||||||
|
|
||||||
|
if (totalItems > 0) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className={styles.message}>
|
||||||
|
All artists are hidden due to the applied filter.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.message}>
|
<div className={styles.message}>
|
||||||
|
@ -31,4 +44,8 @@ function NoArtist() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NoArtist.propTypes = {
|
||||||
|
totalItems: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
export default NoArtist;
|
export default NoArtist;
|
||||||
|
|
|
@ -2,14 +2,15 @@ import _ from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import CalendarDay from './CalendarDay';
|
import CalendarDay from './CalendarDay';
|
||||||
|
|
||||||
function createCalendarEventsConnector() {
|
function createCalendarEventsConnector() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { date }) => date,
|
(state, { date }) => date,
|
||||||
(state) => state.calendar,
|
createClientSideCollectionSelector('calendar'),
|
||||||
(date, calendar) => {
|
(date, calendar) => {
|
||||||
const filtered = _.filter(calendar.items, (item) => {
|
const filtered = _.filter(calendar.items, (item) => {
|
||||||
return moment(date).isSame(moment(item.releaseDate), 'day');
|
return moment(date).isSame(moment(item.releaseDate), 'day');
|
||||||
|
@ -52,10 +53,4 @@ CalendarDayConnector.propTypes = {
|
||||||
date: PropTypes.string.isRequired
|
date: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps)(CalendarDayConnector);
|
||||||
createMapStateToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'calendar' }
|
|
||||||
)(CalendarDayConnector);
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.descriptionList {
|
.descriptionList {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,12 @@ class DescriptionList extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
className,
|
||||||
children
|
children
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<dl className={styles.descriptionList}>
|
<dl className={className}>
|
||||||
{children}
|
{children}
|
||||||
</dl>
|
</dl>
|
||||||
);
|
);
|
||||||
|
@ -21,7 +22,12 @@ class DescriptionList extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
DescriptionList.propTypes = {
|
DescriptionList.propTypes = {
|
||||||
|
className: PropTypes.string.isRequired,
|
||||||
children: PropTypes.node
|
children: PropTypes.node
|
||||||
};
|
};
|
||||||
|
|
||||||
|
DescriptionList.defaultProps = {
|
||||||
|
className: styles.descriptionList
|
||||||
|
};
|
||||||
|
|
||||||
export default DescriptionList;
|
export default DescriptionList;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react';
|
||||||
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
|
const protocols = [
|
||||||
|
{ id: 'continuing', name: 'Continuing' },
|
||||||
|
{ id: 'ended', name: 'Ended' }
|
||||||
|
];
|
||||||
|
|
||||||
|
function ArtistStatusFilterBuilderRowValue(props) {
|
||||||
|
return (
|
||||||
|
<FilterBuilderRowValue
|
||||||
|
tagList={protocols}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ArtistStatusFilterBuilderRowValue;
|
|
@ -0,0 +1,18 @@
|
||||||
|
import React from 'react';
|
||||||
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
|
const protocols = [
|
||||||
|
{ id: true, name: 'true' },
|
||||||
|
{ id: false, name: 'false' }
|
||||||
|
];
|
||||||
|
|
||||||
|
function BoolFilterBuilderRowValue(props) {
|
||||||
|
return (
|
||||||
|
<FilterBuilderRowValue
|
||||||
|
tagList={protocols}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BoolFilterBuilderRowValue;
|
|
@ -0,0 +1,15 @@
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.numberInput {
|
||||||
|
composes: text from 'Components/Form/TextInput.css';
|
||||||
|
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectInput {
|
||||||
|
composes: select from 'Components/Form/SelectInput.css';
|
||||||
|
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
import isString from 'Utilities/String/isString';
|
||||||
|
import { IN_LAST, IN_NEXT } from 'Helpers/Props/filterTypes';
|
||||||
|
import NumberInput from 'Components/Form/NumberInput';
|
||||||
|
import SelectInput from 'Components/Form/SelectInput';
|
||||||
|
import TextInput from 'Components/Form/TextInput';
|
||||||
|
import { NAME } from './FilterBuilderRowValue';
|
||||||
|
import styles from './DateFilterBuilderRowValue.css';
|
||||||
|
|
||||||
|
const timeOptions = [
|
||||||
|
{ key: 'seconds', value: 'seconds' },
|
||||||
|
{ key: 'minutes', value: 'minutes' },
|
||||||
|
{ key: 'hours', value: 'hours' },
|
||||||
|
{ key: 'days', value: 'days' },
|
||||||
|
{ key: 'weeks', value: 'weeks' },
|
||||||
|
{ key: 'months', value: 'months' }
|
||||||
|
];
|
||||||
|
|
||||||
|
function isInFilter(filterType) {
|
||||||
|
return filterType === IN_LAST || filterType === IN_NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DateFilterBuilderRowValue extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const {
|
||||||
|
filterType,
|
||||||
|
filterValue,
|
||||||
|
onChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (isInFilter(filterType) && isString(filterValue)) {
|
||||||
|
onChange({
|
||||||
|
name: NAME,
|
||||||
|
value: {
|
||||||
|
time: timeOptions[0].key,
|
||||||
|
value: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const {
|
||||||
|
filterType,
|
||||||
|
filterValue,
|
||||||
|
onChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (prevProps.filterType === filterType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInFilter(filterType) && isString(filterValue)) {
|
||||||
|
onChange({
|
||||||
|
name: NAME,
|
||||||
|
value: {
|
||||||
|
time: timeOptions[0].key,
|
||||||
|
value: null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isInFilter(filterType) && !isString(filterValue)) {
|
||||||
|
onChange({
|
||||||
|
name: NAME,
|
||||||
|
value: ''
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onValueChange = ({ value }) => {
|
||||||
|
const {
|
||||||
|
filterValue,
|
||||||
|
onChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
let newValue = value;
|
||||||
|
|
||||||
|
if (!isString(value)) {
|
||||||
|
newValue = {
|
||||||
|
time: filterValue.time,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange({
|
||||||
|
name: NAME,
|
||||||
|
value: newValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onTimeChange = ({ value }) => {
|
||||||
|
const {
|
||||||
|
filterValue,
|
||||||
|
onChange
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onChange({
|
||||||
|
name: NAME,
|
||||||
|
value: {
|
||||||
|
time: value,
|
||||||
|
value: filterValue.value
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Render
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
filterType,
|
||||||
|
filterValue
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(isInFilter(filterType) && isString(filterValue)) ||
|
||||||
|
(!isInFilter(filterType) && !isString(filterValue))
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInFilter(filterType)) {
|
||||||
|
return (
|
||||||
|
<div className={styles.container}>
|
||||||
|
<NumberInput
|
||||||
|
className={styles.numberInput}
|
||||||
|
name={NAME}
|
||||||
|
value={filterValue.value}
|
||||||
|
onChange={this.onValueChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectInput
|
||||||
|
className={styles.selectInput}
|
||||||
|
name={NAME}
|
||||||
|
value={filterValue.time}
|
||||||
|
values={timeOptions}
|
||||||
|
onChange={this.onTimeChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TextInput
|
||||||
|
name={NAME}
|
||||||
|
value={filterValue}
|
||||||
|
placeholder="yyyy-mm-dd"
|
||||||
|
onChange={this.onValueChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DateFilterBuilderRowValue.propTypes = {
|
||||||
|
filterType: PropTypes.string,
|
||||||
|
filterValue: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DateFilterBuilderRowValue;
|
|
@ -3,10 +3,17 @@ import React, { Component } from 'react';
|
||||||
import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Props';
|
||||||
import SelectInput from 'Components/Form/SelectInput';
|
import SelectInput from 'Components/Form/SelectInput';
|
||||||
import IconButton from 'Components/Link/IconButton';
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
|
||||||
|
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
|
||||||
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
|
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
|
||||||
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
|
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
|
||||||
|
import LanguageProfileFilterBuilderRowValueConnector from './LanguageProfileFilterBuilderRowValueConnector';
|
||||||
|
import MetadataProfileFilterBuilderRowValueConnector from './MetadataProfileFilterBuilderRowValueConnector';
|
||||||
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
|
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
|
||||||
import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector';
|
import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector';
|
||||||
|
import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector';
|
||||||
|
import ArtistStatusFilterBuilderRowValue from './ArtistStatusFilterBuilderRowValue';
|
||||||
|
import TagFilterBuilderRowValueConnector from './TagFilterBuilderRowValueConnector';
|
||||||
import styles from './FilterBuilderRow.css';
|
import styles from './FilterBuilderRow.css';
|
||||||
|
|
||||||
function getselectedFilterBuilderProp(filterBuilderProps, name) {
|
function getselectedFilterBuilderProp(filterBuilderProps, name) {
|
||||||
|
@ -29,6 +36,14 @@ function getDefaultFilterType(selectedFilterBuilderProp) {
|
||||||
return filterBuilderTypes.possibleFilterTypes[selectedFilterBuilderProp.type][0].key;
|
return filterBuilderTypes.possibleFilterTypes[selectedFilterBuilderProp.type][0].key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getDefaultFilterValue(selectedFilterBuilderProp) {
|
||||||
|
if (selectedFilterBuilderProp.type === filterBuilderTypes.DATE) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
function getRowValueConnector(selectedFilterBuilderProp) {
|
function getRowValueConnector(selectedFilterBuilderProp) {
|
||||||
if (!selectedFilterBuilderProp) {
|
if (!selectedFilterBuilderProp) {
|
||||||
return FilterBuilderRowValueConnector;
|
return FilterBuilderRowValueConnector;
|
||||||
|
@ -37,15 +52,36 @@ function getRowValueConnector(selectedFilterBuilderProp) {
|
||||||
const valueType = selectedFilterBuilderProp.valueType;
|
const valueType = selectedFilterBuilderProp.valueType;
|
||||||
|
|
||||||
switch (valueType) {
|
switch (valueType) {
|
||||||
|
case filterBuilderValueTypes.BOOL:
|
||||||
|
return BoolFilterBuilderRowValue;
|
||||||
|
|
||||||
|
case filterBuilderValueTypes.DATE:
|
||||||
|
return DateFilterBuilderRowValue;
|
||||||
|
|
||||||
case filterBuilderValueTypes.INDEXER:
|
case filterBuilderValueTypes.INDEXER:
|
||||||
return IndexerFilterBuilderRowValueConnector;
|
return IndexerFilterBuilderRowValueConnector;
|
||||||
|
|
||||||
|
case filterBuilderValueTypes.LANGUAGE_PROFILE:
|
||||||
|
return LanguageProfileFilterBuilderRowValueConnector;
|
||||||
|
|
||||||
|
case filterBuilderValueTypes.METADATA_PROFILE:
|
||||||
|
return MetadataProfileFilterBuilderRowValueConnector;
|
||||||
|
|
||||||
case filterBuilderValueTypes.PROTOCOL:
|
case filterBuilderValueTypes.PROTOCOL:
|
||||||
return ProtocolFilterBuilderRowValue;
|
return ProtocolFilterBuilderRowValue;
|
||||||
|
|
||||||
case filterBuilderValueTypes.QUALITY:
|
case filterBuilderValueTypes.QUALITY:
|
||||||
return QualityFilterBuilderRowValueConnector;
|
return QualityFilterBuilderRowValueConnector;
|
||||||
|
|
||||||
|
case filterBuilderValueTypes.QUALITY_PROFILE:
|
||||||
|
return QualityProfileFilterBuilderRowValueConnector;
|
||||||
|
|
||||||
|
case filterBuilderValueTypes.ARTIST_STATUS:
|
||||||
|
return ArtistStatusFilterBuilderRowValue;
|
||||||
|
|
||||||
|
case filterBuilderValueTypes.TAG:
|
||||||
|
return TagFilterBuilderRowValueConnector;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return FilterBuilderRowValueConnector;
|
return FilterBuilderRowValueConnector;
|
||||||
}
|
}
|
||||||
|
@ -59,9 +95,15 @@ class FilterBuilderRow extends Component {
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
const {
|
||||||
selectedFilterBuilderProp: null
|
filterKey,
|
||||||
};
|
filterBuilderProps
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
if (filterKey) {
|
||||||
|
const selectedFilterBuilderProp = filterBuilderProps.find((a) => a.name === filterKey);
|
||||||
|
this.selectedFilterBuilderProp = selectedFilterBuilderProp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -74,7 +116,7 @@ class FilterBuilderRow extends Component {
|
||||||
|
|
||||||
if (filterKey) {
|
if (filterKey) {
|
||||||
const selectedFilterBuilderProp = filterBuilderProps.find((a) => a.name === filterKey);
|
const selectedFilterBuilderProp = filterBuilderProps.find((a) => a.name === filterKey);
|
||||||
this.setState({ selectedFilterBuilderProp });
|
this.selectedFilterBuilderProp = selectedFilterBuilderProp;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -83,13 +125,12 @@ class FilterBuilderRow extends Component {
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
key: selectedFilterBuilderProp.name,
|
key: selectedFilterBuilderProp.name,
|
||||||
value: [],
|
value: getDefaultFilterValue(selectedFilterBuilderProp),
|
||||||
type: getDefaultFilterType(selectedFilterBuilderProp)
|
type: getDefaultFilterType(selectedFilterBuilderProp)
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState({ selectedFilterBuilderProp }, () => {
|
this.selectedFilterBuilderProp = selectedFilterBuilderProp;
|
||||||
onFilterChange(index, filter);
|
onFilterChange(index, filter);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -107,13 +148,12 @@ class FilterBuilderRow extends Component {
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
key,
|
key,
|
||||||
value: [],
|
value: getDefaultFilterValue(selectedFilterBuilderProp),
|
||||||
type
|
type
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState({ selectedFilterBuilderProp }, () => {
|
this.selectedFilterBuilderProp = selectedFilterBuilderProp;
|
||||||
onFilterChange(index, filter);
|
onFilterChange(index, filter);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onFilterChange = ({ name, value }) => {
|
onFilterChange = ({ name, value }) => {
|
||||||
|
@ -163,12 +203,11 @@ class FilterBuilderRow extends Component {
|
||||||
filterType,
|
filterType,
|
||||||
filterValue,
|
filterValue,
|
||||||
filterCount,
|
filterCount,
|
||||||
filterBuilderProps
|
filterBuilderProps,
|
||||||
|
sectionItems
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
|
||||||
selectedFilterBuilderProp
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
|
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
|
||||||
return {
|
return {
|
||||||
|
@ -209,8 +248,10 @@ class FilterBuilderRow extends Component {
|
||||||
{
|
{
|
||||||
filterValue != null && !!selectedFilterBuilderProp &&
|
filterValue != null && !!selectedFilterBuilderProp &&
|
||||||
<ValueComponent
|
<ValueComponent
|
||||||
|
filterType={filterType}
|
||||||
filterValue={filterValue}
|
filterValue={filterValue}
|
||||||
selectedFilterBuilderProp={selectedFilterBuilderProp}
|
selectedFilterBuilderProp={selectedFilterBuilderProp}
|
||||||
|
sectionItems={sectionItems}
|
||||||
onChange={this.onFilterChange}
|
onChange={this.onFilterChange}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -236,10 +277,11 @@ class FilterBuilderRow extends Component {
|
||||||
FilterBuilderRow.propTypes = {
|
FilterBuilderRow.propTypes = {
|
||||||
index: PropTypes.number.isRequired,
|
index: PropTypes.number.isRequired,
|
||||||
filterKey: PropTypes.string,
|
filterKey: PropTypes.string,
|
||||||
filterValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array]),
|
filterValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object]),
|
||||||
filterType: PropTypes.string,
|
filterType: PropTypes.string,
|
||||||
filterCount: PropTypes.number.isRequired,
|
filterCount: PropTypes.number.isRequired,
|
||||||
filterBuilderProps: PropTypes.arrayOf(PropTypes.object).isRequired,
|
filterBuilderProps: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
|
sectionItems: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onFilterChange: PropTypes.func.isRequired,
|
onFilterChange: PropTypes.func.isRequired,
|
||||||
onAddPress: PropTypes.func.isRequired,
|
onAddPress: PropTypes.func.isRequired,
|
||||||
onRemovePress: PropTypes.func.isRequired
|
onRemovePress: PropTypes.func.isRequired
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { kinds, filterBuilderTypes } from 'Helpers/Props';
|
||||||
import TagInput, { tagShape } from 'Components/Form/TagInput';
|
import TagInput, { tagShape } from 'Components/Form/TagInput';
|
||||||
import FilterBuilderRowValueTag from './FilterBuilderRowValueTag';
|
import FilterBuilderRowValueTag from './FilterBuilderRowValueTag';
|
||||||
|
|
||||||
const NAME = 'value';
|
export const NAME = 'value';
|
||||||
|
|
||||||
class FilterBuilderRowValue extends Component {
|
class FilterBuilderRowValue extends Component {
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ class FilterBuilderRowValue extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
FilterBuilderRowValue.propTypes = {
|
FilterBuilderRowValue.propTypes = {
|
||||||
filterValue: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])).isRequired,
|
filterValue: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number])).isRequired,
|
||||||
selectedFilterBuilderProp: PropTypes.object.isRequired,
|
selectedFilterBuilderProp: PropTypes.object.isRequired,
|
||||||
tagList: PropTypes.arrayOf(PropTypes.shape(tagShape)).isRequired,
|
tagList: PropTypes.arrayOf(PropTypes.shape(tagShape)).isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
onChange: PropTypes.func.isRequired
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
import sortByName from 'Utilities/Array/sortByName';
|
||||||
import { filterBuilderTypes } from 'Helpers/Props';
|
import { filterBuilderTypes } from 'Helpers/Props';
|
||||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
function createTagListSelector() {
|
function createTagListSelector() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state, { sectionItems }) => _.get(state, sectionItems),
|
(state, { sectionItems }) => sectionItems,
|
||||||
(state, { selectedFilterBuilderProp }) => selectedFilterBuilderProp,
|
(state, { selectedFilterBuilderProp }) => selectedFilterBuilderProp,
|
||||||
(sectionItems, selectedFilterBuilderProp) => {
|
(sectionItems, selectedFilterBuilderProp) => {
|
||||||
if (
|
if (
|
||||||
|
@ -19,16 +20,20 @@ function createTagListSelector() {
|
||||||
let items = [];
|
let items = [];
|
||||||
|
|
||||||
if (selectedFilterBuilderProp.optionsSelector) {
|
if (selectedFilterBuilderProp.optionsSelector) {
|
||||||
items = sectionItems.map(selectedFilterBuilderProp.optionsSelector);
|
items = selectedFilterBuilderProp.optionsSelector(sectionItems);
|
||||||
} else {
|
} else {
|
||||||
items = sectionItems.map((item) => {
|
items = sectionItems.reduce((acc, item) => {
|
||||||
const name = item[selectedFilterBuilderProp.name];
|
const name = item[selectedFilterBuilderProp.name];
|
||||||
|
|
||||||
return {
|
if (name) {
|
||||||
id: name,
|
acc.push({
|
||||||
name
|
id: name,
|
||||||
};
|
name
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, []).sort(sortByName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _.uniqBy(items, 'id');
|
return _.uniqBy(items, 'id');
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.languageProfiles,
|
||||||
|
(languageProfiles) => {
|
||||||
|
const tagList = languageProfiles.items.map((languageProfile) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
} = languageProfile;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
tagList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps)(FilterBuilderRowValue);
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.metadataProfiles,
|
||||||
|
(metadataProfiles) => {
|
||||||
|
const tagList = metadataProfiles.items.map((metadataProfile) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
} = metadataProfile;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
tagList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps)(FilterBuilderRowValue);
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
(state) => state.settings.qualityProfiles,
|
||||||
|
(qualityProfiles) => {
|
||||||
|
const tagList = qualityProfiles.items.map((qualityProfile) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
} = qualityProfile;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
tagList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps)(FilterBuilderRowValue);
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||||
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
|
function createMapStateToProps() {
|
||||||
|
return createSelector(
|
||||||
|
createTagsSelector(),
|
||||||
|
(tagList) => {
|
||||||
|
return {
|
||||||
|
tagList: tagList.map((tag) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
label: name
|
||||||
|
} = tag;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
name
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(createMapStateToProps)(FilterBuilderRowValue);
|
|
@ -9,9 +9,28 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputContainer {
|
.inputContainer {
|
||||||
|
position: relative;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inputUnit {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 20px;
|
||||||
|
margin-top: 7px;
|
||||||
|
width: 75px;
|
||||||
|
color: #c6c6c6;
|
||||||
|
text-align: right;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputUnitNumber {
|
||||||
|
composes: inputUnit;
|
||||||
|
|
||||||
|
right: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
.pendingChangesContainer {
|
.pendingChangesContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
|
@ -83,6 +83,7 @@ function FormInputGroup(props) {
|
||||||
containerClassName,
|
containerClassName,
|
||||||
inputClassName,
|
inputClassName,
|
||||||
type,
|
type,
|
||||||
|
unit,
|
||||||
buttons,
|
buttons,
|
||||||
helpText,
|
helpText,
|
||||||
helpTexts,
|
helpTexts,
|
||||||
|
@ -115,6 +116,19 @@ function FormInputGroup(props) {
|
||||||
hasButton={hasButton}
|
hasButton={hasButton}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{
|
||||||
|
unit &&
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
type === inputTypes.NUMBER ?
|
||||||
|
styles.inputUnitNumber :
|
||||||
|
styles.inputUnit
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{unit}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -219,6 +233,7 @@ FormInputGroup.propTypes = {
|
||||||
containerClassName: PropTypes.string.isRequired,
|
containerClassName: PropTypes.string.isRequired,
|
||||||
inputClassName: PropTypes.string,
|
inputClassName: PropTypes.string,
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
|
unit: PropTypes.string,
|
||||||
buttons: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
buttons: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
|
||||||
helpText: PropTypes.string,
|
helpText: PropTypes.string,
|
||||||
helpTexts: PropTypes.arrayOf(PropTypes.string),
|
helpTexts: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
|
|
@ -48,12 +48,14 @@ class NumberInput extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
value,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<TextInput
|
||||||
type="number"
|
type="number"
|
||||||
|
value={value == null ? '' : value}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
|
|
|
@ -1,30 +1,39 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
|
||||||
|
|
||||||
function OAuthInput(props) {
|
function OAuthInput(props) {
|
||||||
const {
|
const {
|
||||||
|
label,
|
||||||
authorizing,
|
authorizing,
|
||||||
|
error,
|
||||||
onPress
|
onPress
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SpinnerButton
|
<SpinnerErrorButton
|
||||||
kind={kinds.PRIMARY}
|
kind={kinds.PRIMARY}
|
||||||
isSpinning={authorizing}
|
isSpinning={authorizing}
|
||||||
|
error={error}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
>
|
>
|
||||||
Start OAuth
|
{label}
|
||||||
</SpinnerButton>
|
</SpinnerErrorButton>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuthInput.propTypes = {
|
OAuthInput.propTypes = {
|
||||||
|
label: PropTypes.string.isRequired,
|
||||||
authorizing: PropTypes.bool.isRequired,
|
authorizing: PropTypes.bool.isRequired,
|
||||||
|
error: PropTypes.object,
|
||||||
onPress: PropTypes.func.isRequired
|
onPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
OAuthInput.defaultProps = {
|
||||||
|
label: 'Start OAuth'
|
||||||
|
};
|
||||||
|
|
||||||
export default OAuthInput;
|
export default OAuthInput;
|
||||||
|
|
|
@ -26,18 +26,17 @@ class OAuthInputConnector extends Component {
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const {
|
const {
|
||||||
accessToken,
|
result,
|
||||||
accessTokenSecret,
|
|
||||||
onChange
|
onChange
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (accessToken &&
|
if (!result || result === prevProps.result) {
|
||||||
accessToken !== prevProps.accessToken &&
|
return;
|
||||||
accessTokenSecret &&
|
|
||||||
accessTokenSecret !== prevProps.accessTokenSecret) {
|
|
||||||
onChange({ name: 'AccessToken', value: accessToken });
|
|
||||||
onChange({ name: 'AccessTokenSecret', value: accessTokenSecret });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.keys(result).forEach((key) => {
|
||||||
|
onChange({ name: key, value: result[key] });
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount = () => {
|
componentWillUnmount = () => {
|
||||||
|
@ -70,8 +69,7 @@ class OAuthInputConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuthInputConnector.propTypes = {
|
OAuthInputConnector.propTypes = {
|
||||||
accessToken: PropTypes.string,
|
result: PropTypes.object,
|
||||||
accessTokenSecret: PropTypes.string,
|
|
||||||
provider: PropTypes.string.isRequired,
|
provider: PropTypes.string.isRequired,
|
||||||
providerData: PropTypes.object.isRequired,
|
providerData: PropTypes.object.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -7,19 +7,6 @@ import FormLabel from 'Components/Form/FormLabel';
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||||
|
|
||||||
function getType(type) {
|
function getType(type) {
|
||||||
// Textbox,
|
|
||||||
// Password,
|
|
||||||
// Checkbox,
|
|
||||||
// Select,
|
|
||||||
// Path,
|
|
||||||
// FilePath,
|
|
||||||
// Hidden,
|
|
||||||
// Tag,
|
|
||||||
// Action,
|
|
||||||
// Url,
|
|
||||||
// Captcha
|
|
||||||
// OAuth
|
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'captcha':
|
case 'captcha':
|
||||||
return inputTypes.CAPTCHA;
|
return inputTypes.CAPTCHA;
|
||||||
|
@ -27,6 +14,8 @@ function getType(type) {
|
||||||
return inputTypes.CHECK;
|
return inputTypes.CHECK;
|
||||||
case 'password':
|
case 'password':
|
||||||
return inputTypes.PASSWORD;
|
return inputTypes.PASSWORD;
|
||||||
|
case 'number':
|
||||||
|
return inputTypes.NUMBER;
|
||||||
case 'path':
|
case 'path':
|
||||||
return inputTypes.PATH;
|
return inputTypes.PATH;
|
||||||
case 'select':
|
case 'select':
|
||||||
|
@ -83,6 +72,7 @@ function ProviderFieldFormGroup(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={getType(type)}
|
type={getType(type)}
|
||||||
name={name}
|
name={name}
|
||||||
|
label={label}
|
||||||
helpText={helpText}
|
helpText={helpText}
|
||||||
helpLink={helpLink}
|
helpLink={helpLink}
|
||||||
value={value}
|
value={value}
|
||||||
|
|
|
@ -32,7 +32,7 @@ function RootFolderSelectInputSelectedValue(props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
RootFolderSelectInputSelectedValue.propTypes = {
|
RootFolderSelectInputSelectedValue.propTypes = {
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string,
|
||||||
freeSpace: PropTypes.number,
|
freeSpace: PropTypes.number,
|
||||||
includeFreeSpace: PropTypes.bool.isRequired
|
includeFreeSpace: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
min-width: 20%;
|
min-width: 20%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
width: 0%;
|
width: 0%;
|
||||||
|
height: 21px;
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,20 +58,20 @@ class TagInput extends Component {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
addTag = _.debounce((tag) => {
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onInputContainerPress = () => {
|
|
||||||
this._autosuggestRef.input.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
onTagAdd(tag) {
|
|
||||||
this.props.onTagAdd(tag);
|
this.props.onTagAdd(tag);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
value: '',
|
value: '',
|
||||||
suggestions: []
|
suggestions: []
|
||||||
});
|
});
|
||||||
|
}, 250, { leading: true, trailing: false })
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onInputContainerPress = () => {
|
||||||
|
this._autosuggestRef.input.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
onInputChange = (event, { newValue, method }) => {
|
onInputChange = (event, { newValue, method }) => {
|
||||||
|
@ -116,10 +116,9 @@ class TagInput extends Component {
|
||||||
const tag = getTag(value, selectedIndex, suggestions, allowNew);
|
const tag = getTag(value, selectedIndex, suggestions, allowNew);
|
||||||
|
|
||||||
if (tag) {
|
if (tag) {
|
||||||
this.onTagAdd(tag);
|
this.addTag(tag);
|
||||||
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +146,7 @@ class TagInput extends Component {
|
||||||
const tag = getTag(value, selectedIndex, suggestions, allowNew);
|
const tag = getTag(value, selectedIndex, suggestions, allowNew);
|
||||||
|
|
||||||
if (tag) {
|
if (tag) {
|
||||||
this.onTagAdd(tag);
|
this.addTag(tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +173,7 @@ class TagInput extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onSuggestionSelected = (event, { suggestion }) => {
|
onSuggestionSelected = (event, { suggestion }) => {
|
||||||
this.onTagAdd(suggestion);
|
this.addTag(suggestion);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -262,7 +261,7 @@ class TagInput extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tagShape = {
|
export const tagShape = {
|
||||||
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
|
id: PropTypes.oneOfType([PropTypes.bool, PropTypes.number, PropTypes.string]).isRequired,
|
||||||
name: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired
|
name: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
color: $white;
|
color: $white;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
@ -92,6 +91,7 @@
|
||||||
|
|
||||||
.large {
|
.large {
|
||||||
padding: 3px 7px;
|
padding: 3px 7px;
|
||||||
|
font-weight: bold;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,10 @@ class Menu extends Component {
|
||||||
this.setMaxHeight();
|
this.setMaxHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this._removeListener();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Control
|
// Control
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
.extraLarge {
|
.extraLarge {
|
||||||
composes: modal;
|
composes: modal;
|
||||||
|
|
||||||
width: 1440px;
|
width: 1280px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointExtraLarge) {
|
@media only screen and (max-width: $breakpointExtraLarge) {
|
||||||
|
|
|
@ -123,6 +123,10 @@ const links = [
|
||||||
title: 'Metadata',
|
title: 'Metadata',
|
||||||
to: '/settings/metadata'
|
to: '/settings/metadata'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Tags',
|
||||||
|
to: '/settings/tags'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'General',
|
title: 'General',
|
||||||
to: '/settings/general'
|
to: '/settings/general'
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { repopulatePage } from 'Utilities/pagePopulator';
|
import { repopulatePage } from 'Utilities/pagePopulator';
|
||||||
|
import titleCase from 'Utilities/String/titleCase';
|
||||||
import { updateCommand, finishCommand } from 'Store/Actions/commandActions';
|
import { updateCommand, finishCommand } from 'Store/Actions/commandActions';
|
||||||
import { setAppValue, setVersion } from 'Store/Actions/appActions';
|
import { setAppValue, setVersion } from 'Store/Actions/appActions';
|
||||||
import { update, updateItem, removeItem } from 'Store/Actions/baseActions';
|
import { update, updateItem, removeItem } from 'Store/Actions/baseActions';
|
||||||
|
@ -34,6 +35,13 @@ function isAppDisconnected(disconnectedTime) {
|
||||||
return Math.floor(new Date().getTime() / 1000) - disconnectedTime > 180;
|
return Math.floor(new Date().getTime() / 1000) - disconnectedTime > 180;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getHandlerName(name) {
|
||||||
|
name = titleCase(name);
|
||||||
|
name = name.replace('/', '');
|
||||||
|
|
||||||
|
return `handle${name}`;
|
||||||
|
}
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.app.isReconnecting,
|
(state) => state.app.isReconnecting,
|
||||||
|
@ -91,6 +99,10 @@ class SignalRConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
if (this.retryTimeoutId) {
|
||||||
|
this.retryTimeoutId = clearTimeout(this.retryTimeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
this.signalRconnection.stop();
|
this.signalRconnection.stop();
|
||||||
this.signalRconnection = null;
|
this.signalRconnection = null;
|
||||||
}
|
}
|
||||||
|
@ -106,6 +118,11 @@ class SignalRConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.retryTimeoutId = setTimeout(() => {
|
this.retryTimeoutId = setTimeout(() => {
|
||||||
|
if (!this.signalRconnection) {
|
||||||
|
console.error('signalR: Connection was disposed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.signalRconnection.start(this.signalRconnectionOptions);
|
this.signalRconnection.start(this.signalRconnectionOptions);
|
||||||
this.retryInterval = Math.min(this.retryInterval + 1, 10);
|
this.retryInterval = Math.min(this.retryInterval + 1, 10);
|
||||||
}, this.retryInterval * 1000);
|
}, this.retryInterval * 1000);
|
||||||
|
@ -117,70 +134,14 @@ class SignalRConnector extends Component {
|
||||||
body
|
body
|
||||||
} = message;
|
} = message;
|
||||||
|
|
||||||
if (name === 'calendar') {
|
const handler = this[getHandlerName(name)];
|
||||||
this.handleCalendar(body);
|
|
||||||
|
if (handler) {
|
||||||
|
handler(body);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name === 'command') {
|
console.error(`signalR: Unable to find handler for ${name}`);
|
||||||
this.handleCommand(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'album') {
|
|
||||||
this.handleAlbum(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'track') {
|
|
||||||
this.handleTrack(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'trackfile') {
|
|
||||||
this.handleTrackFile(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'health') {
|
|
||||||
this.handleHealth(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'artist') {
|
|
||||||
this.handleArtist(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'queue') {
|
|
||||||
this.handleQueue(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'queue/details') {
|
|
||||||
this.handleQueueDetails(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'queue/status') {
|
|
||||||
this.handleQueueStatus(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'version') {
|
|
||||||
this.handleVersion(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'wanted/cutoff') {
|
|
||||||
this.handleWantedCutoff(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name === 'wanted/missing') {
|
|
||||||
this.handleWantedMissing(body);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleCalendar = (body) => {
|
handleCalendar = (body) => {
|
||||||
|
@ -237,7 +198,7 @@ class SignalRConnector extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHealth = (body) => {
|
handleHealth = () => {
|
||||||
this.props.fetchHealth();
|
this.props.fetchHealth();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,13 +213,13 @@ class SignalRConnector extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQueue = (body) => {
|
handleQueue = () => {
|
||||||
if (this.props.isQueuePopulated) {
|
if (this.props.isQueuePopulated) {
|
||||||
this.props.fetchQueue();
|
this.props.fetchQueue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQueueDetails = (body) => {
|
handleQueueDetails = () => {
|
||||||
this.props.fetchQueueDetails();
|
this.props.fetchQueueDetails();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,12 +253,16 @@ class SignalRConnector extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleSystemTask = () => {
|
||||||
|
// No-op for now, we may want this later
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Listeners
|
// Listeners
|
||||||
|
|
||||||
onStateChanged = (change) => {
|
onStateChanged = (change) => {
|
||||||
const state = getState(change.newState);
|
const state = getState(change.newState);
|
||||||
console.log(`SignalR: ${state}`);
|
console.log(`signalR: ${state}`);
|
||||||
|
|
||||||
if (state === 'connected') {
|
if (state === 'connected') {
|
||||||
// Clear disconnected time
|
// Clear disconnected time
|
||||||
|
@ -326,7 +291,7 @@ class SignalRConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
onReceived = (message) => {
|
onReceived = (message) => {
|
||||||
console.debug('SignalR: received', message.name, message.body);
|
console.debug('signalR: received', message.name, message.body);
|
||||||
|
|
||||||
this.handleMessage(message);
|
this.handleMessage(message);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ function TableRow(props) {
|
||||||
const {
|
const {
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
|
overlayContent,
|
||||||
...otherProps
|
...otherProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
@ -21,7 +22,8 @@ function TableRow(props) {
|
||||||
|
|
||||||
TableRow.propTypes = {
|
TableRow.propTypes = {
|
||||||
className: PropTypes.string.isRequired,
|
className: PropTypes.string.isRequired,
|
||||||
children: PropTypes.node
|
children: PropTypes.node,
|
||||||
|
overlayContent: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
TableRow.defaultProps = {
|
TableRow.defaultProps = {
|
||||||
|
|
|
@ -100,5 +100,5 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
padding: 20px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ class Popover extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
className,
|
||||||
anchor,
|
anchor,
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
|
@ -103,6 +104,7 @@ class Popover extends Component {
|
||||||
{...tetherOptions[position]}
|
{...tetherOptions[position]}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
|
className={className}
|
||||||
// onClick={this.onClick}
|
// onClick={this.onClick}
|
||||||
onMouseEnter={this.onMouseEnter}
|
onMouseEnter={this.onMouseEnter}
|
||||||
onMouseLeave={this.onMouseLeave}
|
onMouseLeave={this.onMouseLeave}
|
||||||
|
@ -141,6 +143,7 @@ class Popover extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
Popover.propTypes = {
|
Popover.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
anchor: PropTypes.node.isRequired,
|
anchor: PropTypes.node.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
body: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
|
body: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
|
||||||
|
|
|
@ -1,33 +1,50 @@
|
||||||
import * as filterTypes from './filterTypes';
|
import * as filterTypes from './filterTypes';
|
||||||
|
|
||||||
|
export const ARRAY = 'array';
|
||||||
|
export const DATE = 'date';
|
||||||
export const EXACT = 'exact';
|
export const EXACT = 'exact';
|
||||||
export const NUMBER = 'number';
|
export const NUMBER = 'number';
|
||||||
export const STRING = 'string';
|
export const STRING = 'string';
|
||||||
|
|
||||||
export const all = [
|
export const all = [
|
||||||
|
ARRAY,
|
||||||
|
DATE,
|
||||||
EXACT,
|
EXACT,
|
||||||
NUMBER,
|
NUMBER,
|
||||||
STRING
|
STRING
|
||||||
];
|
];
|
||||||
|
|
||||||
export const possibleFilterTypes = {
|
export const possibleFilterTypes = {
|
||||||
|
[ARRAY]: [
|
||||||
|
{ key: filterTypes.CONTAINS, value: 'contains' },
|
||||||
|
{ key: filterTypes.NOT_CONTAINS, value: 'does not contain' }
|
||||||
|
],
|
||||||
|
|
||||||
|
[DATE]: [
|
||||||
|
{ key: filterTypes.LESS_THAN, value: 'is before' },
|
||||||
|
{ key: filterTypes.GREATER_THAN, value: 'is after' },
|
||||||
|
{ key: filterTypes.IN_LAST, value: 'in the last' },
|
||||||
|
{ key: filterTypes.IN_NEXT, value: 'in the next' }
|
||||||
|
],
|
||||||
|
|
||||||
[EXACT]: [
|
[EXACT]: [
|
||||||
{ key: filterTypes.EQUAL, value: 'Is' },
|
{ key: filterTypes.EQUAL, value: 'is' },
|
||||||
{ key: filterTypes.NOT_EQUAL, value: 'Is Not' }
|
{ key: filterTypes.NOT_EQUAL, value: 'is not' }
|
||||||
],
|
],
|
||||||
|
|
||||||
[NUMBER]: [
|
[NUMBER]: [
|
||||||
{ key: filterTypes.EQUAL, value: 'Equal' },
|
{ key: filterTypes.EQUAL, value: 'equal' },
|
||||||
{ key: filterTypes.GREATER_THAN, value: 'Greater Than' },
|
{ key: filterTypes.GREATER_THAN, value: 'greater than' },
|
||||||
{ key: filterTypes.GREATER_THAN_OR_EQUAL, value: 'Greater Than or Equal' },
|
{ key: filterTypes.GREATER_THAN_OR_EQUAL, value: 'greater than or equal' },
|
||||||
{ key: filterTypes.LESS_THAN, value: 'Less Than' },
|
{ key: filterTypes.LESS_THAN, value: 'less than' },
|
||||||
{ key: filterTypes.LESS_THAN_OR_EQUAL, value: 'Less Than or Equal' },
|
{ key: filterTypes.LESS_THAN_OR_EQUAL, value: 'less than or equal' },
|
||||||
{ key: filterTypes.NOT_EQUAL, value: 'Not Equal' }
|
{ key: filterTypes.NOT_EQUAL, value: 'not equal' }
|
||||||
],
|
],
|
||||||
|
|
||||||
[STRING]: [
|
[STRING]: [
|
||||||
{ key: filterTypes.CONTAINS, value: 'Contains' },
|
{ key: filterTypes.CONTAINS, value: 'contains' },
|
||||||
{ key: filterTypes.EQUAL, value: 'Equal' },
|
{ key: filterTypes.NOT_CONTAINS, value: 'does not contain' },
|
||||||
{ key: filterTypes.NOT_EQUAL, value: 'Not Equal' }
|
{ key: filterTypes.EQUAL, value: 'equal' },
|
||||||
|
{ key: filterTypes.NOT_EQUAL, value: 'not equal' }
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
|
export const BOOL = 'bool';
|
||||||
|
export const DATE = 'date';
|
||||||
export const DEFAULT = 'default';
|
export const DEFAULT = 'default';
|
||||||
export const INDEXER = 'indexer';
|
export const INDEXER = 'indexer';
|
||||||
|
export const LANGUAGE_PROFILE = 'languageProfile';
|
||||||
|
export const METADATA_PROFILE = 'metadataProfile';
|
||||||
export const PROTOCOL = 'protocol';
|
export const PROTOCOL = 'protocol';
|
||||||
export const QUALITY = 'quality';
|
export const QUALITY = 'quality';
|
||||||
|
export const QUALITY_PROFILE = 'qualityProfile';
|
||||||
|
export const ARTIST_STATUS = 'artistStatus';
|
||||||
|
export const TAG = 'tag';
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import * as filterTypes from './filterTypes';
|
||||||
|
|
||||||
|
const filterTypePredicates = {
|
||||||
|
[filterTypes.CONTAINS]: function(itemValue, filterValue) {
|
||||||
|
if (Array.isArray(itemValue)) {
|
||||||
|
return itemValue.some((v) => v === filterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemValue.toLowerCase().contains(filterValue.toLowerCase());
|
||||||
|
},
|
||||||
|
|
||||||
|
[filterTypes.EQUAL]: function(itemValue, filterValue) {
|
||||||
|
return itemValue === filterValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
[filterTypes.GREATER_THAN]: function(itemValue, filterValue) {
|
||||||
|
return itemValue > filterValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
[filterTypes.GREATER_THAN_OR_EQUAL]: function(itemValue, filterValue) {
|
||||||
|
return itemValue >= filterValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
[filterTypes.LESS_THAN]: function(itemValue, filterValue) {
|
||||||
|
return itemValue < filterValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
[filterTypes.LESS_THAN_OR_EQUAL]: function(itemValue, filterValue) {
|
||||||
|
return itemValue <= filterValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
[filterTypes.NOT_CONTAINS]: function(itemValue, filterValue) {
|
||||||
|
if (Array.isArray(itemValue)) {
|
||||||
|
return !itemValue.some((v) => v === filterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return !itemValue.toLowerCase().contains(filterValue.toLowerCase());
|
||||||
|
},
|
||||||
|
|
||||||
|
[filterTypes.NOT_EQUAL]: function(itemValue, filterValue) {
|
||||||
|
return itemValue !== filterValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default filterTypePredicates;
|
|
@ -2,8 +2,11 @@ export const CONTAINS = 'contains';
|
||||||
export const EQUAL = 'equal';
|
export const EQUAL = 'equal';
|
||||||
export const GREATER_THAN = 'greaterThan';
|
export const GREATER_THAN = 'greaterThan';
|
||||||
export const GREATER_THAN_OR_EQUAL = 'greaterThanOrEqual';
|
export const GREATER_THAN_OR_EQUAL = 'greaterThanOrEqual';
|
||||||
|
export const IN_LAST = 'inLast';
|
||||||
|
export const IN_NEXT = 'inNext';
|
||||||
export const LESS_THAN = 'lessThan';
|
export const LESS_THAN = 'lessThan';
|
||||||
export const LESS_THAN_OR_EQUAL = 'lessThanOrEqual';
|
export const LESS_THAN_OR_EQUAL = 'lessThanOrEqual';
|
||||||
|
export const NOT_CONTAINS = 'notContains';
|
||||||
export const NOT_EQUAL = 'notEqual';
|
export const NOT_EQUAL = 'notEqual';
|
||||||
|
|
||||||
export const all = [
|
export const all = [
|
||||||
|
@ -13,5 +16,6 @@ export const all = [
|
||||||
GREATER_THAN_OR_EQUAL,
|
GREATER_THAN_OR_EQUAL,
|
||||||
LESS_THAN,
|
LESS_THAN,
|
||||||
LESS_THAN_OR_EQUAL,
|
LESS_THAN_OR_EQUAL,
|
||||||
|
NOT_CONTAINS,
|
||||||
NOT_EQUAL
|
NOT_EQUAL
|
||||||
];
|
];
|
||||||
|
|
|
@ -2,6 +2,7 @@ import * as align from './align';
|
||||||
import * as inputTypes from './inputTypes';
|
import * as inputTypes from './inputTypes';
|
||||||
import * as filterBuilderTypes from './filterBuilderTypes';
|
import * as filterBuilderTypes from './filterBuilderTypes';
|
||||||
import * as filterBuilderValueTypes from './filterBuilderValueTypes';
|
import * as filterBuilderValueTypes from './filterBuilderValueTypes';
|
||||||
|
import filterTypePredicates from './filterTypePredicates';
|
||||||
import * as filterTypes from './filterTypes';
|
import * as filterTypes from './filterTypes';
|
||||||
import * as icons from './icons';
|
import * as icons from './icons';
|
||||||
import * as kinds from './kinds';
|
import * as kinds from './kinds';
|
||||||
|
@ -16,6 +17,7 @@ export {
|
||||||
inputTypes,
|
inputTypes,
|
||||||
filterBuilderTypes,
|
filterBuilderTypes,
|
||||||
filterBuilderValueTypes,
|
filterBuilderValueTypes,
|
||||||
|
filterTypePredicates,
|
||||||
filterTypes,
|
filterTypes,
|
||||||
icons,
|
icons,
|
||||||
kinds,
|
kinds,
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
.modalBody {
|
||||||
|
composes: modalBody from 'Components/Modal/ModalBody.css';
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filterInput {
|
||||||
|
composes: text from 'Components/Form/TextInput.css';
|
||||||
|
|
||||||
|
flex: 0 0 auto;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroller {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
|
@ -1,15 +1,62 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import Button from 'Components/Link/Button';
|
import Button from 'Components/Link/Button';
|
||||||
|
import { scrollDirections } from 'Helpers/Props';
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
|
import Table from 'Components/Table/Table';
|
||||||
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import Scroller from 'Components/Scroller/Scroller';
|
||||||
|
import TextInput from 'Components/Form/TextInput';
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import SelectAlbumRow from './SelectAlbumRow';
|
import SelectAlbumRow from './SelectAlbumRow';
|
||||||
|
import styles from './SelectAlbumModalContent.css';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
name: 'title',
|
||||||
|
label: 'Album Title',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'albumType',
|
||||||
|
label: 'Album Type',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'releaseDate',
|
||||||
|
label: 'Release Date',
|
||||||
|
isVisible: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'status',
|
||||||
|
label: 'Album Status',
|
||||||
|
isVisible: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
class SelectAlbumModalContent extends Component {
|
class SelectAlbumModalContent extends Component {
|
||||||
|
|
||||||
|
//
|
||||||
|
// Lifecycle
|
||||||
|
|
||||||
|
constructor(props, context) {
|
||||||
|
super(props, context);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
filter: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Listeners
|
||||||
|
|
||||||
|
onFilterChange = ({ value }) => {
|
||||||
|
this.setState({ filter: value.toLowerCase() });
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
|
@ -18,33 +65,60 @@ class SelectAlbumModalContent extends Component {
|
||||||
items,
|
items,
|
||||||
onAlbumSelect,
|
onAlbumSelect,
|
||||||
onModalClose,
|
onModalClose,
|
||||||
isFetching
|
isFetching,
|
||||||
|
...otherProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const filter = this.state.filter;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ModalContent onModalClose={onModalClose}>
|
<ModalContent onModalClose={onModalClose}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
Manual Import - Select Album
|
Manual Import - Select Album
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
<ModalBody>
|
<ModalBody
|
||||||
|
className={styles.modalBody}
|
||||||
|
scrollDirection={scrollDirections.NONE}
|
||||||
|
>
|
||||||
{
|
{
|
||||||
isFetching &&
|
isFetching &&
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
}
|
}
|
||||||
{
|
<TextInput
|
||||||
items.map((item) => {
|
className={styles.filterInput}
|
||||||
return (
|
placeholder="Filter album"
|
||||||
<SelectAlbumRow
|
name="filter"
|
||||||
key={item.id}
|
value={filter}
|
||||||
id={item.id}
|
autoFocus={true}
|
||||||
title={item.title}
|
onChange={this.onFilterChange}
|
||||||
albumType={item.albumType}
|
/>
|
||||||
onAlbumSelect={onAlbumSelect}
|
|
||||||
/>
|
<Scroller className={styles.scroller}>
|
||||||
);
|
{
|
||||||
})
|
<Table
|
||||||
}
|
columns={columns}
|
||||||
|
{...otherProps}
|
||||||
|
>
|
||||||
|
<TableBody>
|
||||||
|
{
|
||||||
|
items.map((item) => {
|
||||||
|
return item.title.toLowerCase().includes(filter) ?
|
||||||
|
(
|
||||||
|
<SelectAlbumRow
|
||||||
|
key={item.id}
|
||||||
|
columns={columns}
|
||||||
|
onAlbumSelect={onAlbumSelect}
|
||||||
|
{...item}
|
||||||
|
/>
|
||||||
|
) :
|
||||||
|
null;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
}
|
||||||
|
</Scroller>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import {
|
import {
|
||||||
updateInteractiveImportItem,
|
updateInteractiveImportItem,
|
||||||
fetchInteractiveImportAlbums,
|
fetchInteractiveImportAlbums,
|
||||||
|
@ -14,7 +14,7 @@ import SelectAlbumModalContent from './SelectAlbumModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createClientSideCollectionSelector(),
|
createClientSideCollectionSelector('interactiveImport.albums'),
|
||||||
(albums) => {
|
(albums) => {
|
||||||
return albums;
|
return albums;
|
||||||
}
|
}
|
||||||
|
@ -92,10 +92,4 @@ SelectAlbumModalContentConnector.propTypes = {
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(SelectAlbumModalContentConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'interactiveImport.albums' }
|
|
||||||
)(SelectAlbumModalContentConnector);
|
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
.season {
|
|
||||||
padding: 8px;
|
|
||||||
border-bottom: 1px solid $borderColor;
|
|
||||||
}
|
|
|
@ -1,7 +1,23 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { kinds, sizes } from 'Helpers/Props';
|
||||||
|
import TableRow from 'Components/Table/TableRow';
|
||||||
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
|
import Label from 'Components/Label';
|
||||||
import Link from 'Components/Link/Link';
|
import Link from 'Components/Link/Link';
|
||||||
import styles from './SelectAlbumRow.css';
|
|
||||||
|
function getTrackCountKind(monitored, trackFileCount, trackCount) {
|
||||||
|
if (trackFileCount === trackCount && trackCount > 0) {
|
||||||
|
return kinds.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!monitored) {
|
||||||
|
return kinds.WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kinds.DANGER;
|
||||||
|
}
|
||||||
|
|
||||||
class SelectAlbumRow extends Component {
|
class SelectAlbumRow extends Component {
|
||||||
|
|
||||||
|
@ -16,14 +32,87 @@ class SelectAlbumRow extends Component {
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {
|
||||||
|
title,
|
||||||
|
albumType,
|
||||||
|
releaseDate,
|
||||||
|
statistics,
|
||||||
|
monitored,
|
||||||
|
columns
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
trackCount,
|
||||||
|
trackFileCount,
|
||||||
|
totalTrackCount
|
||||||
|
} = statistics;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
|
||||||
className={styles.season}
|
<TableRow>
|
||||||
component="div"
|
{
|
||||||
onPress={this.onPress}
|
columns.map((column) => {
|
||||||
>
|
const {
|
||||||
{this.props.title} ({this.props.albumType})
|
name,
|
||||||
</Link>
|
isVisible
|
||||||
|
} = column;
|
||||||
|
|
||||||
|
if (!isVisible) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === 'title') {
|
||||||
|
return (
|
||||||
|
<TableRowCell key={name}>
|
||||||
|
<Link
|
||||||
|
onPress={this.onPress}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Link>
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === 'albumType') {
|
||||||
|
return (
|
||||||
|
<TableRowCell key={name}>
|
||||||
|
{albumType}
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === 'releaseDate') {
|
||||||
|
return (
|
||||||
|
<RelativeDateCellConnector
|
||||||
|
key={name}
|
||||||
|
date={releaseDate}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name === 'status') {
|
||||||
|
return (
|
||||||
|
<TableRowCell
|
||||||
|
key={name}
|
||||||
|
>
|
||||||
|
<Label
|
||||||
|
title={`${totalTrackCount} tracks total. ${trackFileCount} tracks with files.`}
|
||||||
|
kind={getTrackCountKind(monitored, trackFileCount, trackCount)}
|
||||||
|
size={sizes.MEDIUM}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
<span>{trackFileCount} / {trackCount}</span>
|
||||||
|
}
|
||||||
|
</Label>
|
||||||
|
</TableRowCell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +121,18 @@ SelectAlbumRow.propTypes = {
|
||||||
id: PropTypes.number.isRequired,
|
id: PropTypes.number.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
albumType: PropTypes.string.isRequired,
|
albumType: PropTypes.string.isRequired,
|
||||||
onAlbumSelect: PropTypes.func.isRequired
|
releaseDate: PropTypes.string.isRequired,
|
||||||
|
onAlbumSelect: PropTypes.func.isRequired,
|
||||||
|
statistics: PropTypes.object.isRequired,
|
||||||
|
monitored: PropTypes.bool.isRequired,
|
||||||
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
SelectAlbumRow.defaultProps = {
|
||||||
|
statistics: {
|
||||||
|
trackCount: 0,
|
||||||
|
trackFileCount: 0
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SelectAlbumRow;
|
export default SelectAlbumRow;
|
||||||
|
|
|
@ -21,6 +21,10 @@ const recentFoldersColumns = [
|
||||||
{
|
{
|
||||||
name: 'lastUsed',
|
name: 'lastUsed',
|
||||||
label: 'Last Used'
|
label: 'Last Used'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'actions',
|
||||||
|
label: ''
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -62,6 +66,7 @@ class InteractiveImportSelectFolderModalContent extends Component {
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
recentFolders,
|
recentFolders,
|
||||||
|
onRemoveRecentFolderPress,
|
||||||
onModalClose
|
onModalClose
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -95,6 +100,7 @@ class InteractiveImportSelectFolderModalContent extends Component {
|
||||||
folder={recentFolder.folder}
|
folder={recentFolder.folder}
|
||||||
lastUsed={recentFolder.lastUsed}
|
lastUsed={recentFolder.lastUsed}
|
||||||
onPress={this.onRecentPathPress}
|
onPress={this.onRecentPathPress}
|
||||||
|
onRemoveRecentFolderPress={onRemoveRecentFolderPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
@ -155,6 +161,7 @@ InteractiveImportSelectFolderModalContent.propTypes = {
|
||||||
recentFolders: PropTypes.arrayOf(PropTypes.object).isRequired,
|
recentFolders: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onQuickImportPress: PropTypes.func.isRequired,
|
onQuickImportPress: PropTypes.func.isRequired,
|
||||||
onInteractiveImportPress: PropTypes.func.isRequired,
|
onInteractiveImportPress: PropTypes.func.isRequired,
|
||||||
|
onRemoveRecentFolderPress: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { addRecentFolder } from 'Store/Actions/interactiveImportActions';
|
import { addRecentFolder, removeRecentFolder } from 'Store/Actions/interactiveImportActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import InteractiveImportSelectFolderModalContent from './InteractiveImportSelectFolderModalContent';
|
import InteractiveImportSelectFolderModalContent from './InteractiveImportSelectFolderModalContent';
|
||||||
|
@ -20,6 +20,7 @@ function createMapStateToProps() {
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
addRecentFolder,
|
addRecentFolder,
|
||||||
|
removeRecentFolder,
|
||||||
executeCommand
|
executeCommand
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,6 +45,10 @@ class InteractiveImportSelectFolderModalContentConnector extends Component {
|
||||||
this.props.onFolderSelect(folder);
|
this.props.onFolderSelect(folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRemoveRecentFolderPress = (folder) => {
|
||||||
|
this.props.removeRecentFolder({ folder });
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
|
@ -57,6 +62,7 @@ class InteractiveImportSelectFolderModalContentConnector extends Component {
|
||||||
{...this.props}
|
{...this.props}
|
||||||
onQuickImportPress={this.onQuickImportPress}
|
onQuickImportPress={this.onQuickImportPress}
|
||||||
onInteractiveImportPress={this.onInteractiveImportPress}
|
onInteractiveImportPress={this.onInteractiveImportPress}
|
||||||
|
onRemoveRecentFolderPress={this.onRemoveRecentFolderPress}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -67,6 +73,7 @@ InteractiveImportSelectFolderModalContentConnector.propTypes = {
|
||||||
onFolderSelect: PropTypes.func.isRequired,
|
onFolderSelect: PropTypes.func.isRequired,
|
||||||
onModalClose: PropTypes.func.isRequired,
|
onModalClose: PropTypes.func.isRequired,
|
||||||
addRecentFolder: PropTypes.func.isRequired,
|
addRecentFolder: PropTypes.func.isRequired,
|
||||||
|
removeRecentFolder: PropTypes.func.isRequired,
|
||||||
executeCommand: PropTypes.func.isRequired
|
executeCommand: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
.actions {
|
||||||
|
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||||
|
|
||||||
|
width: 40px;
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { icons } from 'Helpers/Props';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
import TableRowButton from 'Components/Table/TableRowButton';
|
import TableRowButton from 'Components/Table/TableRowButton';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
|
import styles from './RecentFolderRow.css';
|
||||||
|
|
||||||
class RecentFolderRow extends Component {
|
class RecentFolderRow extends Component {
|
||||||
|
|
||||||
|
@ -13,6 +16,17 @@ class RecentFolderRow extends Component {
|
||||||
this.props.onPress(this.props.folder);
|
this.props.onPress(this.props.folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRemovePress = (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const {
|
||||||
|
folder,
|
||||||
|
onRemoveRecentFolderPress
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
onRemoveRecentFolderPress(folder);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Render
|
// Render
|
||||||
|
|
||||||
|
@ -27,6 +41,14 @@ class RecentFolderRow extends Component {
|
||||||
<TableRowCell>{folder}</TableRowCell>
|
<TableRowCell>{folder}</TableRowCell>
|
||||||
|
|
||||||
<RelativeDateCellConnector date={lastUsed} />
|
<RelativeDateCellConnector date={lastUsed} />
|
||||||
|
|
||||||
|
<TableRowCell className={styles.actions}>
|
||||||
|
<IconButton
|
||||||
|
name={icons.REMOVE}
|
||||||
|
title="Remove"
|
||||||
|
onPress={this.onRemovePress}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
</TableRowButton>
|
</TableRowButton>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -35,7 +57,8 @@ class RecentFolderRow extends Component {
|
||||||
RecentFolderRow.propTypes = {
|
RecentFolderRow.propTypes = {
|
||||||
folder: PropTypes.string.isRequired,
|
folder: PropTypes.string.isRequired,
|
||||||
lastUsed: PropTypes.string.isRequired,
|
lastUsed: PropTypes.string.isRequired,
|
||||||
onPress: PropTypes.func.isRequired
|
onPress: PropTypes.func.isRequired,
|
||||||
|
onRemoveRecentFolderPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RecentFolderRow;
|
export default RecentFolderRow;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import { fetchInteractiveImportItems, setInteractiveImportSort, clearInteractiveImport, setInteractiveImportMode } from 'Store/Actions/interactiveImportActions';
|
import { fetchInteractiveImportItems, setInteractiveImportSort, clearInteractiveImport, setInteractiveImportMode } from 'Store/Actions/interactiveImportActions';
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
|
@ -11,7 +11,7 @@ import InteractiveImportModalContent from './InteractiveImportModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createClientSideCollectionSelector(),
|
createClientSideCollectionSelector('interactiveImport'),
|
||||||
(interactiveImport) => {
|
(interactiveImport) => {
|
||||||
return interactiveImport;
|
return interactiveImport;
|
||||||
}
|
}
|
||||||
|
@ -125,8 +125,19 @@ class InteractiveImportModalContentConnector extends Component {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!quality) {
|
||||||
|
this.setState({ interactiveImportErrorMessage: 'Quality must be chosen for each selected file' });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!language) {
|
||||||
|
this.setState({ interactiveImportErrorMessage: 'Language must be chosen for each selected file' });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
files.push({
|
files.push({
|
||||||
path: item.path,
|
path: item.path,
|
||||||
|
folderName: item.folderName,
|
||||||
artistId: artist.id,
|
artistId: artist.id,
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
trackIds: _.map(tracks, 'id'),
|
trackIds: _.map(tracks, 'id'),
|
||||||
|
@ -190,10 +201,4 @@ InteractiveImportModalContentConnector.defaultProps = {
|
||||||
filterExistingFiles: true
|
filterExistingFiles: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(InteractiveImportModalContentConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'interactiveImport' }
|
|
||||||
)(InteractiveImportModalContentConnector);
|
|
||||||
|
|
|
@ -191,6 +191,8 @@ class InteractiveImportRow extends Component {
|
||||||
const showArtistPlaceholder = isSelected && !artist;
|
const showArtistPlaceholder = isSelected && !artist;
|
||||||
const showAlbumNumberPlaceholder = isSelected && !!artist && !album;
|
const showAlbumNumberPlaceholder = isSelected && !!artist && !album;
|
||||||
const showTrackNumbersPlaceholder = isSelected && !!album && !tracks.length;
|
const showTrackNumbersPlaceholder = isSelected && !!album && !tracks.length;
|
||||||
|
const showQualityPlaceholder = isSelected && !quality;
|
||||||
|
const showLanguagePlaceholder = isSelected && !language;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
@ -237,20 +239,36 @@ class InteractiveImportRow extends Component {
|
||||||
className={styles.quality}
|
className={styles.quality}
|
||||||
onPress={this.onSelectQualityPress}
|
onPress={this.onSelectQualityPress}
|
||||||
>
|
>
|
||||||
<EpisodeQuality
|
{
|
||||||
className={styles.label}
|
showQualityPlaceholder &&
|
||||||
quality={quality}
|
<InteractiveImportRowCellPlaceholder />
|
||||||
/>
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!showQualityPlaceholder && !!quality &&
|
||||||
|
<EpisodeQuality
|
||||||
|
className={styles.label}
|
||||||
|
quality={quality}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</TableRowCellButton>
|
</TableRowCellButton>
|
||||||
|
|
||||||
<TableRowCellButton
|
<TableRowCellButton
|
||||||
className={styles.language}
|
className={styles.language}
|
||||||
onPress={this.onSelectLanguagePress}
|
onPress={this.onSelectLanguagePress}
|
||||||
>
|
>
|
||||||
<EpisodeLanguage
|
{
|
||||||
className={styles.label}
|
showLanguagePlaceholder &&
|
||||||
language={language}
|
<InteractiveImportRowCellPlaceholder />
|
||||||
/>
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
!showLanguagePlaceholder && !!language &&
|
||||||
|
<EpisodeLanguage
|
||||||
|
className={styles.label}
|
||||||
|
language={language}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</TableRowCellButton>
|
</TableRowCellButton>
|
||||||
|
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
|
@ -310,16 +328,16 @@ class InteractiveImportRow extends Component {
|
||||||
<SelectQualityModal
|
<SelectQualityModal
|
||||||
isOpen={isSelectQualityModalOpen}
|
isOpen={isSelectQualityModalOpen}
|
||||||
id={id}
|
id={id}
|
||||||
qualityId={quality.quality.id}
|
qualityId={quality ? quality.quality.id : 0}
|
||||||
proper={quality.revision.version > 1}
|
proper={quality ? quality.revision.version > 1 : false}
|
||||||
real={quality.revision.real > 0}
|
real={quality ? quality.revision.real > 0 : false}
|
||||||
onModalClose={this.onSelectQualityModalClose}
|
onModalClose={this.onSelectQualityModalClose}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectLanguageModal
|
<SelectLanguageModal
|
||||||
isOpen={isSelectLanguageModalOpen}
|
isOpen={isSelectLanguageModalOpen}
|
||||||
id={id}
|
id={id}
|
||||||
languageId={language.id}
|
languageId={language ? language.id : 0}
|
||||||
onModalClose={this.onSelectLanguageModalClose}
|
onModalClose={this.onSelectLanguageModalClose}
|
||||||
/>
|
/>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
|
@ -22,7 +22,7 @@ function createMapStateToProps() {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated,
|
isPopulated,
|
||||||
error,
|
error,
|
||||||
items: schema.languages || []
|
items: schema.languages ? [...schema.languages].reverse() : []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import { fetchTracks, setTracksSort, clearTracks } from 'Store/Actions/trackActions';
|
import { fetchTracks, setTracksSort, clearTracks } from 'Store/Actions/trackActions';
|
||||||
import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
|
import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
|
||||||
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
|
||||||
|
@ -10,7 +10,7 @@ import SelectTrackModalContent from './SelectTrackModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createClientSideCollectionSelector(),
|
createClientSideCollectionSelector('tracks'),
|
||||||
(tracks) => {
|
(tracks) => {
|
||||||
return tracks;
|
return tracks;
|
||||||
}
|
}
|
||||||
|
@ -94,10 +94,4 @@ SelectTrackModalContentConnector.propTypes = {
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(SelectTrackModalContentConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'tracks' }
|
|
||||||
)(SelectTrackModalContentConnector);
|
|
||||||
|
|
|
@ -59,9 +59,7 @@ class DownloadClients extends Component {
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldSet
|
<FieldSet legend="Download Clients">
|
||||||
legend="Download Clients"
|
|
||||||
>
|
|
||||||
<PageSectionContent
|
<PageSectionContent
|
||||||
errorMessage="Unable to load download clients"
|
errorMessage="Unable to load download clients"
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
import EditDownloadClientModalContentConnector from './EditDownloadClientModalContentConnector';
|
import EditDownloadClientModalContentConnector from './EditDownloadClientModalContentConnector';
|
||||||
|
|
||||||
function EditDownloadClientModal({ isOpen, onModalClose, ...otherProps }) {
|
function EditDownloadClientModal({ isOpen, onModalClose, ...otherProps }) {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
size={sizes.MEDIUM}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||||
import { setDownloadClientValue, setDownloadClientFieldValue, saveDownloadClient, testDownloadClient } from 'Store/Actions/settingsActions';
|
import { setDownloadClientValue, setDownloadClientFieldValue, saveDownloadClient, testDownloadClient } from 'Store/Actions/settingsActions';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
|
import EditDownloadClientModalContent from './EditDownloadClientModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.advancedSettings,
|
(state) => state.settings.advancedSettings,
|
||||||
createProviderSettingsSelector(),
|
createProviderSettingsSelector('downloadClients'),
|
||||||
(advancedSettings, downloadClient) => {
|
(advancedSettings, downloadClient) => {
|
||||||
return {
|
return {
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
|
@ -85,10 +85,4 @@ EditDownloadClientModalContentConnector.propTypes = {
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(EditDownloadClientModalContentConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'downloadClients' }
|
|
||||||
)(EditDownloadClientModalContentConnector);
|
|
||||||
|
|
|
@ -33,9 +33,7 @@ function DownloadClientOptions(props) {
|
||||||
{
|
{
|
||||||
hasSettings && !isFetching && !error &&
|
hasSettings && !isFetching && !error &&
|
||||||
<div>
|
<div>
|
||||||
<FieldSet
|
<FieldSet legend="Completed Download Handling">
|
||||||
legend="Completed Download Handling"
|
|
||||||
>
|
|
||||||
<Form>
|
<Form>
|
||||||
<FormGroup size={sizes.MEDIUM}>
|
<FormGroup size={sizes.MEDIUM}>
|
||||||
<FormLabel>Enable</FormLabel>
|
<FormLabel>Enable</FormLabel>
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||||
import { fetchDownloadClientOptions, setDownloadClientOptionsValue, saveDownloadClientOptions } from 'Store/Actions/settingsActions';
|
import { fetchDownloadClientOptions, setDownloadClientOptionsValue, saveDownloadClientOptions } from 'Store/Actions/settingsActions';
|
||||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import DownloadClientOptions from './DownloadClientOptions';
|
import DownloadClientOptions from './DownloadClientOptions';
|
||||||
|
|
||||||
|
const SECTION = 'downloadClientOptions';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.advancedSettings,
|
(state) => state.settings.advancedSettings,
|
||||||
createSettingsSectionSelector(),
|
createSettingsSectionSelector(SECTION),
|
||||||
(advancedSettings, sectionSettings) => {
|
(advancedSettings, sectionSettings) => {
|
||||||
return {
|
return {
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
|
@ -62,7 +64,7 @@ class DownloadClientOptionsConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.dispatchClearPendingChanges({ section: this.props.section });
|
this.props.dispatchClearPendingChanges({ section: SECTION });
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -86,7 +88,6 @@ class DownloadClientOptionsConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadClientOptionsConnector.propTypes = {
|
DownloadClientOptionsConnector.propTypes = {
|
||||||
section: PropTypes.string.isRequired,
|
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
hasPendingChanges: PropTypes.bool.isRequired,
|
hasPendingChanges: PropTypes.bool.isRequired,
|
||||||
dispatchFetchDownloadClientOptions: PropTypes.func.isRequired,
|
dispatchFetchDownloadClientOptions: PropTypes.func.isRequired,
|
||||||
|
@ -97,10 +98,4 @@ DownloadClientOptionsConnector.propTypes = {
|
||||||
onChildStateChange: PropTypes.func.isRequired
|
onChildStateChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(DownloadClientOptionsConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'settings.downloadClientOptions' }
|
|
||||||
)(DownloadClientOptionsConnector);
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
import EditRemotePathMappingModalContentConnector from './EditRemotePathMappingModalContentConnector';
|
import EditRemotePathMappingModalContentConnector from './EditRemotePathMappingModalContentConnector';
|
||||||
|
|
||||||
function EditRemotePathMappingModal({ isOpen, onModalClose, ...otherProps }) {
|
function EditRemotePathMappingModal({ isOpen, onModalClose, ...otherProps }) {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
size={sizes.MEDIUM}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
|
|
|
@ -44,9 +44,7 @@ class RemotePathMappings extends Component {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldSet
|
<FieldSet legend="Remote Path Mappings">
|
||||||
legend="Remote Path Mappings"
|
|
||||||
>
|
|
||||||
<PageSectionContent
|
<PageSectionContent
|
||||||
errorMessage="Unable to load Remote Path Mappings"
|
errorMessage="Unable to load Remote Path Mappings"
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
|
|
@ -49,7 +49,8 @@ function BackupSettings(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="backupInterval"
|
name="backupInterval"
|
||||||
helpText="Interval in days"
|
unit="days"
|
||||||
|
helpText="Interval to backup the Lidarr DB and settings"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...backupInterval}
|
{...backupInterval}
|
||||||
/>
|
/>
|
||||||
|
@ -64,7 +65,8 @@ function BackupSettings(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="backupRetention"
|
name="backupRetention"
|
||||||
helpText="Retention in days. Automatic backups older the retention will be cleaned up automatically"
|
unit="days"
|
||||||
|
helpText="Automatic backups older than the retention will be cleaned up automatically"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...backupRetention}
|
{...backupRetention}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||||
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
|
||||||
|
@ -9,14 +10,15 @@ import { setGeneralSettingsValue, saveGeneralSettings, fetchGeneralSettings } fr
|
||||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
import { executeCommand } from 'Store/Actions/commandActions';
|
import { executeCommand } from 'Store/Actions/commandActions';
|
||||||
import { restart } from 'Store/Actions/systemActions';
|
import { restart } from 'Store/Actions/systemActions';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import * as commandNames from 'Commands/commandNames';
|
import * as commandNames from 'Commands/commandNames';
|
||||||
import GeneralSettings from './GeneralSettings';
|
import GeneralSettings from './GeneralSettings';
|
||||||
|
|
||||||
|
const SECTION = 'general';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.advancedSettings,
|
(state) => state.settings.advancedSettings,
|
||||||
createSettingsSectionSelector(),
|
createSettingsSectionSelector(SECTION),
|
||||||
createCommandsSelector(),
|
createCommandsSelector(),
|
||||||
createSystemStatusSelector(),
|
createSystemStatusSelector(),
|
||||||
(advancedSettings, sectionSettings, commands, systemStatus) => {
|
(advancedSettings, sectionSettings, commands, systemStatus) => {
|
||||||
|
@ -59,7 +61,7 @@ class GeneralSettingsConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.clearPendingChanges({ section: this.props.section });
|
this.props.clearPendingChanges({ section: SECTION });
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -98,7 +100,6 @@ class GeneralSettingsConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
GeneralSettingsConnector.propTypes = {
|
GeneralSettingsConnector.propTypes = {
|
||||||
section: PropTypes.string.isRequired,
|
|
||||||
isResettingApiKey: PropTypes.bool.isRequired,
|
isResettingApiKey: PropTypes.bool.isRequired,
|
||||||
setGeneralSettingsValue: PropTypes.func.isRequired,
|
setGeneralSettingsValue: PropTypes.func.isRequired,
|
||||||
saveGeneralSettings: PropTypes.func.isRequired,
|
saveGeneralSettings: PropTypes.func.isRequired,
|
||||||
|
@ -108,10 +109,4 @@ GeneralSettingsConnector.propTypes = {
|
||||||
clearPendingChanges: PropTypes.func.isRequired
|
clearPendingChanges: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(GeneralSettingsConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'settings.general' }
|
|
||||||
)(GeneralSettingsConnector);
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||||
import { setImportListValue, setImportListFieldValue, saveImportList, testImportList } from 'Store/Actions/settingsActions';
|
import { setImportListValue, setImportListFieldValue, saveImportList, testImportList } from 'Store/Actions/settingsActions';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import EditImportListModalContent from './EditImportListModalContent';
|
import EditImportListModalContent from './EditImportListModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
|
@ -11,7 +11,7 @@ function createMapStateToProps() {
|
||||||
(state) => state.settings.advancedSettings,
|
(state) => state.settings.advancedSettings,
|
||||||
(state) => state.settings.languageProfiles,
|
(state) => state.settings.languageProfiles,
|
||||||
(state) => state.settings.metadataProfiles,
|
(state) => state.settings.metadataProfiles,
|
||||||
createProviderSettingsSelector(),
|
createProviderSettingsSelector('importLists'),
|
||||||
(advancedSettings, languageProfiles, metadataProfiles, importList) => {
|
(advancedSettings, languageProfiles, metadataProfiles, importList) => {
|
||||||
return {
|
return {
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
|
@ -89,10 +89,4 @@ EditImportListModalContentConnector.propTypes = {
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(EditImportListModalContentConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'importLists' }
|
|
||||||
)(EditImportListModalContentConnector);
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
import EditIndexerModalContentConnector from './EditIndexerModalContentConnector';
|
import EditIndexerModalContentConnector from './EditIndexerModalContentConnector';
|
||||||
|
|
||||||
function EditIndexerModal({ isOpen, onModalClose, ...otherProps }) {
|
function EditIndexerModal({ isOpen, onModalClose, ...otherProps }) {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
size={sizes.MEDIUM}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
|
|
|
@ -96,7 +96,7 @@ function EditIndexerModalContent(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="enableAutomaticSearch"
|
name="enableAutomaticSearch"
|
||||||
helpText={supportsSearch.value && 'Will be used when automatic searches are performed via the UI or by Lidarr'}
|
helpText={supportsSearch.value ? 'Will be used when automatic searches are performed via the UI or by Lidarr' : undefined}
|
||||||
helpTextWarning={supportsSearch.value ? undefined : 'Search is not supported with this indexer'}
|
helpTextWarning={supportsSearch.value ? undefined : 'Search is not supported with this indexer'}
|
||||||
isDisabled={!supportsSearch.value}
|
isDisabled={!supportsSearch.value}
|
||||||
{...enableAutomaticSearch}
|
{...enableAutomaticSearch}
|
||||||
|
@ -110,7 +110,7 @@ function EditIndexerModalContent(props) {
|
||||||
<FormInputGroup
|
<FormInputGroup
|
||||||
type={inputTypes.CHECK}
|
type={inputTypes.CHECK}
|
||||||
name="enableInteractiveSearch"
|
name="enableInteractiveSearch"
|
||||||
helpText={supportsSearch.value && 'Will be used when interactive search is used'}
|
helpText={supportsSearch.value ? 'Will be used when interactive search is used' : undefined}
|
||||||
helpTextWarning={supportsSearch.value ? undefined : 'Search is not supported with this indexer'}
|
helpTextWarning={supportsSearch.value ? undefined : 'Search is not supported with this indexer'}
|
||||||
isDisabled={!supportsSearch.value}
|
isDisabled={!supportsSearch.value}
|
||||||
{...enableInteractiveSearch}
|
{...enableInteractiveSearch}
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
import createProviderSettingsSelector from 'Store/Selectors/createProviderSettingsSelector';
|
||||||
import { setIndexerValue, setIndexerFieldValue, saveIndexer, testIndexer } from 'Store/Actions/settingsActions';
|
import { setIndexerValue, setIndexerFieldValue, saveIndexer, testIndexer } from 'Store/Actions/settingsActions';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import EditIndexerModalContent from './EditIndexerModalContent';
|
import EditIndexerModalContent from './EditIndexerModalContent';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.advancedSettings,
|
(state) => state.settings.advancedSettings,
|
||||||
createProviderSettingsSelector(),
|
createProviderSettingsSelector('indexers'),
|
||||||
(advancedSettings, indexer) => {
|
(advancedSettings, indexer) => {
|
||||||
return {
|
return {
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
|
@ -85,10 +85,4 @@ EditIndexerModalContentConnector.propTypes = {
|
||||||
onModalClose: PropTypes.func.isRequired
|
onModalClose: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(EditIndexerModalContentConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'indexers' }
|
|
||||||
)(EditIndexerModalContentConnector);
|
|
||||||
|
|
|
@ -59,9 +59,7 @@ class Indexers extends Component {
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldSet
|
<FieldSet legend="Indexers">
|
||||||
legend="Indexers"
|
|
||||||
>
|
|
||||||
<PageSectionContent
|
<PageSectionContent
|
||||||
errorMessage="Unable to load Indexers"
|
errorMessage="Unable to load Indexers"
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
|
|
@ -19,9 +19,7 @@ function IndexerOptions(props) {
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldSet
|
<FieldSet legend="Options">
|
||||||
legend="Options"
|
|
||||||
>
|
|
||||||
{
|
{
|
||||||
isFetching &&
|
isFetching &&
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
|
@ -42,6 +40,7 @@ function IndexerOptions(props) {
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="minimumAge"
|
name="minimumAge"
|
||||||
min={0}
|
min={0}
|
||||||
|
unit="minutes"
|
||||||
helpText="Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider."
|
helpText="Usenet only: Minimum age in minutes of NZBs before they are grabbed. Use this to give new releases time to propagate to your usenet provider."
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...settings.minimumAge}
|
{...settings.minimumAge}
|
||||||
|
@ -55,6 +54,7 @@ function IndexerOptions(props) {
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="maximumSize"
|
name="maximumSize"
|
||||||
min={0}
|
min={0}
|
||||||
|
unit="MB"
|
||||||
helpText="Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited."
|
helpText="Maximum size for a release to be grabbed in MB. Set to zero to set to unlimited."
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...settings.maximumSize}
|
{...settings.maximumSize}
|
||||||
|
@ -68,6 +68,7 @@ function IndexerOptions(props) {
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="retention"
|
name="retention"
|
||||||
min={0}
|
min={0}
|
||||||
|
unit="days"
|
||||||
helpText="Usenet only: Set to zero to set for unlimited retention"
|
helpText="Usenet only: Set to zero to set for unlimited retention"
|
||||||
onChange={onInputChange}
|
onChange={onInputChange}
|
||||||
{...settings.retention}
|
{...settings.retention}
|
||||||
|
@ -84,6 +85,7 @@ function IndexerOptions(props) {
|
||||||
type={inputTypes.NUMBER}
|
type={inputTypes.NUMBER}
|
||||||
name="rssSyncInterval"
|
name="rssSyncInterval"
|
||||||
min={0}
|
min={0}
|
||||||
|
unit="minutes"
|
||||||
helpText="Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)"
|
helpText="Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)"
|
||||||
helpTextWarning="This will apply to all indexers, please follow the rules set forth by them"
|
helpTextWarning="This will apply to all indexers, please follow the rules set forth by them"
|
||||||
helpLink="https://github.com/Lidarr/Lidarr/wiki/RSS-Sync"
|
helpLink="https://github.com/Lidarr/Lidarr/wiki/RSS-Sync"
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||||
import { fetchIndexerOptions, setIndexerOptionsValue, saveIndexerOptions } from 'Store/Actions/settingsActions';
|
import { fetchIndexerOptions, setIndexerOptionsValue, saveIndexerOptions } from 'Store/Actions/settingsActions';
|
||||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||||
import connectSection from 'Store/connectSection';
|
|
||||||
import IndexerOptions from './IndexerOptions';
|
import IndexerOptions from './IndexerOptions';
|
||||||
|
|
||||||
|
const SECTION = 'indexerOptions';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state.settings.advancedSettings,
|
(state) => state.settings.advancedSettings,
|
||||||
createSettingsSectionSelector(),
|
createSettingsSectionSelector(SECTION),
|
||||||
(advancedSettings, sectionSettings) => {
|
(advancedSettings, sectionSettings) => {
|
||||||
return {
|
return {
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
|
@ -62,7 +64,7 @@ class IndexerOptionsConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.props.dispatchClearPendingChanges({ section: this.props.section });
|
this.props.dispatchClearPendingChanges({ section: SECTION });
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -86,7 +88,6 @@ class IndexerOptionsConnector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
IndexerOptionsConnector.propTypes = {
|
IndexerOptionsConnector.propTypes = {
|
||||||
section: PropTypes.string.isRequired,
|
|
||||||
isSaving: PropTypes.bool.isRequired,
|
isSaving: PropTypes.bool.isRequired,
|
||||||
hasPendingChanges: PropTypes.bool.isRequired,
|
hasPendingChanges: PropTypes.bool.isRequired,
|
||||||
dispatchFetchIndexerOptions: PropTypes.func.isRequired,
|
dispatchFetchIndexerOptions: PropTypes.func.isRequired,
|
||||||
|
@ -97,10 +98,4 @@ IndexerOptionsConnector.propTypes = {
|
||||||
onChildStateChange: PropTypes.func.isRequired
|
onChildStateChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connectSection(
|
export default connect(createMapStateToProps, mapDispatchToProps)(IndexerOptionsConnector);
|
||||||
createMapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ section: 'settings.indexerOptions' }
|
|
||||||
)(IndexerOptionsConnector);
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { sizes } from 'Helpers/Props';
|
||||||
import Modal from 'Components/Modal/Modal';
|
import Modal from 'Components/Modal/Modal';
|
||||||
import EditRestrictionModalContentConnector from './EditRestrictionModalContentConnector';
|
import EditRestrictionModalContentConnector from './EditRestrictionModalContentConnector';
|
||||||
|
|
||||||
function EditRestrictionModal({ isOpen, onModalClose, ...otherProps }) {
|
function EditRestrictionModal({ isOpen, onModalClose, ...otherProps }) {
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
size={sizes.MEDIUM}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
>
|
>
|
||||||
|
|
|
@ -45,9 +45,7 @@ class Restrictions extends Component {
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FieldSet
|
<FieldSet legend="Restrictions">
|
||||||
legend="Restrictions"
|
|
||||||
>
|
|
||||||
<PageSectionContent
|
<PageSectionContent
|
||||||
errorMessage="Unable to load Restrictions"
|
errorMessage="Unable to load Restrictions"
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue