mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-06 04:52:21 -07:00
New: Use natural sorting for lists of items in the UI
(cherry picked from commit 1a1c8e6c08a6db5fcd2b5d17e65fa1f943d2e746) Closes #4912 Closes #4921
This commit is contained in:
parent
a9dd947eed
commit
7f73a2e23a
29 changed files with 94 additions and 63 deletions
|
@ -2,6 +2,7 @@ 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 createTagsSelector from 'Store/Selectors/createTagsSelector';
|
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||||
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import ArtistTags from './ArtistTags';
|
import ArtistTags from './ArtistTags';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
|
@ -12,8 +13,8 @@ function createMapStateToProps() {
|
||||||
const tags = artist.tags
|
const tags = artist.tags
|
||||||
.map((tagId) => tagList.find((tag) => tag.id === tagId))
|
.map((tagId) => tagList.find((tag) => tag.id === tagId))
|
||||||
.filter((tag) => !!tag)
|
.filter((tag) => !!tag)
|
||||||
.map((tag) => tag.label)
|
.sort(sortByProp('label'))
|
||||||
.sort((a, b) => a.localeCompare(b));
|
.map((tag) => tag.label);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tags
|
tags
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import Artist from 'Artist/Artist';
|
import Artist from 'Artist/Artist';
|
||||||
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
|
import FilterBuilderRowValueProps from './FilterBuilderRowValueProps';
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ function ArtistFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
|
||||||
|
|
||||||
const tagList = allArtists
|
const tagList = allArtists
|
||||||
.map((artist) => ({ id: artist.id, name: artist.artistName }))
|
.map((artist) => ({ id: artist.id, name: artist.artistName }))
|
||||||
.sort(sortByName);
|
.sort(sortByProp('name'));
|
||||||
|
|
||||||
return <FilterBuilderRowValue {...props} tagList={tagList} />;
|
return <FilterBuilderRowValue {...props} tagList={tagList} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import React, { Component } from 'react';
|
||||||
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 { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Props';
|
||||||
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import ArtistFilterBuilderRowValue from './ArtistFilterBuilderRowValue';
|
import ArtistFilterBuilderRowValue from './ArtistFilterBuilderRowValue';
|
||||||
import ArtistStatusFilterBuilderRowValue from './ArtistStatusFilterBuilderRowValue';
|
import ArtistStatusFilterBuilderRowValue from './ArtistStatusFilterBuilderRowValue';
|
||||||
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
|
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
|
||||||
|
@ -224,7 +225,7 @@ class FilterBuilderRow extends Component {
|
||||||
key: name,
|
key: name,
|
||||||
value: typeof label === 'function' ? label() : label
|
value: typeof label === 'function' ? label() : label
|
||||||
};
|
};
|
||||||
}).sort((a, b) => a.value.localeCompare(b.value));
|
}).sort(sortByProp('value'));
|
||||||
|
|
||||||
const ValueComponent = getRowValueConnector(selectedFilterBuilderProp);
|
const ValueComponent = getRowValueConnector(selectedFilterBuilderProp);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { filterBuilderTypes } from 'Helpers/Props';
|
import { filterBuilderTypes } from 'Helpers/Props';
|
||||||
import * as filterTypes from 'Helpers/Props/filterTypes';
|
import * as filterTypes from 'Helpers/Props/filterTypes';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
import FilterBuilderRowValue from './FilterBuilderRowValue';
|
||||||
|
|
||||||
function createTagListSelector() {
|
function createTagListSelector() {
|
||||||
|
@ -38,7 +38,7 @@ function createTagListSelector() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, []).sort(sortByName);
|
}, []).sort(sortByProp('name'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return _.uniqBy(items, 'id');
|
return _.uniqBy(items, 'id');
|
||||||
|
|
|
@ -5,6 +5,7 @@ import ModalBody from 'Components/Modal/ModalBody';
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
import ModalContent from 'Components/Modal/ModalContent';
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||||
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import CustomFilter from './CustomFilter';
|
import CustomFilter from './CustomFilter';
|
||||||
import styles from './CustomFiltersModalContent.css';
|
import styles from './CustomFiltersModalContent.css';
|
||||||
|
@ -31,7 +32,7 @@ function CustomFiltersModalContent(props) {
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{
|
{
|
||||||
customFilters
|
customFilters
|
||||||
.sort((a, b) => a.label.localeCompare(b.label))
|
.sort((a, b) => sortByProp(a, b, 'label'))
|
||||||
.map((customFilter) => {
|
.map((customFilter) => {
|
||||||
return (
|
return (
|
||||||
<CustomFilter
|
<CustomFilter
|
||||||
|
|
|
@ -4,7 +4,8 @@ import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
|
import { fetchDownloadClients } from 'Store/Actions/settingsActions';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
|
@ -22,7 +23,7 @@ function createMapStateToProps() {
|
||||||
|
|
||||||
const filteredItems = items.filter((item) => item.protocol === protocolFilter);
|
const filteredItems = items.filter((item) => item.protocol === protocolFilter);
|
||||||
|
|
||||||
const values = _.map(filteredItems.sort(sortByName), (downloadClient) => {
|
const values = _.map(filteredItems.sort(sortByProp('name')), (downloadClient) => {
|
||||||
return {
|
return {
|
||||||
key: downloadClient.id,
|
key: downloadClient.id,
|
||||||
value: downloadClient.name,
|
value: downloadClient.name,
|
||||||
|
@ -33,7 +34,7 @@ function createMapStateToProps() {
|
||||||
if (includeAny) {
|
if (includeAny) {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 0,
|
key: 0,
|
||||||
value: '(Any)'
|
value: `(${translate('Any')})`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { fetchIndexers } from 'Store/Actions/settingsActions';
|
import { fetchIndexers } from 'Store/Actions/settingsActions';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
|
@ -19,7 +20,7 @@ function createMapStateToProps() {
|
||||||
items
|
items
|
||||||
} = indexers;
|
} = indexers;
|
||||||
|
|
||||||
const values = _.map(items.sort(sortByName), (indexer) => {
|
const values = _.map(items.sort(sortByProp('name')), (indexer) => {
|
||||||
return {
|
return {
|
||||||
key: indexer.id,
|
key: indexer.id,
|
||||||
value: indexer.name
|
value: indexer.name
|
||||||
|
@ -29,7 +30,7 @@ function createMapStateToProps() {
|
||||||
if (includeAny) {
|
if (includeAny) {
|
||||||
values.unshift({
|
values.unshift({
|
||||||
key: 0,
|
key: 0,
|
||||||
value: '(Any)'
|
value: `(${translate('Any')})`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,13 @@ import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { metadataProfileNames } from 'Helpers/Props';
|
import { metadataProfileNames } from 'Helpers/Props';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.metadataProfiles', sortByName),
|
createSortedSectionSelector('settings.metadataProfiles', sortByProp('name')),
|
||||||
(state, { includeNoChange }) => includeNoChange,
|
(state, { includeNoChange }) => includeNoChange,
|
||||||
(state, { includeNoChangeDisabled }) => includeNoChangeDisabled,
|
(state, { includeNoChangeDisabled }) => includeNoChangeDisabled,
|
||||||
(state, { includeMixed }) => includeMixed,
|
(state, { includeMixed }) => includeMixed,
|
||||||
|
|
|
@ -4,13 +4,13 @@ import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import EnhancedSelectInput from './EnhancedSelectInput';
|
import EnhancedSelectInput from './EnhancedSelectInput';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.qualityProfiles', sortByName),
|
createSortedSectionSelector('settings.qualityProfiles', sortByProp('name')),
|
||||||
(state, { includeNoChange }) => includeNoChange,
|
(state, { includeNoChange }) => includeNoChange,
|
||||||
(state, { includeNoChangeDisabled }) => includeNoChangeDisabled,
|
(state, { includeNoChangeDisabled }) => includeNoChangeDisabled,
|
||||||
(state, { includeMixed }) => includeMixed,
|
(state, { includeMixed }) => includeMixed,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import FilterMenuItem from './FilterMenuItem';
|
import FilterMenuItem from './FilterMenuItem';
|
||||||
import MenuContent from './MenuContent';
|
import MenuContent from './MenuContent';
|
||||||
|
@ -47,7 +48,7 @@ class FilterMenuContent extends Component {
|
||||||
|
|
||||||
{
|
{
|
||||||
customFilters
|
customFilters
|
||||||
.sort((a, b) => a.label.localeCompare(b.label))
|
.sort(sortByProp('label'))
|
||||||
.map((filter) => {
|
.map((filter) => {
|
||||||
return (
|
return (
|
||||||
<FilterMenuItem
|
<FilterMenuItem
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
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 sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import Label from './Label';
|
import Label from './Label';
|
||||||
import styles from './TagList.css';
|
import styles from './TagList.css';
|
||||||
|
|
||||||
|
@ -8,7 +9,7 @@ function TagList({ tags, tagList }) {
|
||||||
const sortedTags = tags
|
const sortedTags = tags
|
||||||
.map((tagId) => tagList.find((tag) => tag.id === tagId))
|
.map((tagId) => tagList.find((tag) => tag.id === tagId))
|
||||||
.filter((tag) => !!tag)
|
.filter((tag) => !!tag)
|
||||||
.sort((a, b) => a.label.localeCompare(b.label));
|
.sort(sortByProp('label'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.tags}>
|
<div className={styles.tags}>
|
||||||
|
|
|
@ -4,12 +4,12 @@ import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { cloneCustomFormat, deleteCustomFormat, fetchCustomFormats } from 'Store/Actions/settingsActions';
|
import { cloneCustomFormat, deleteCustomFormat, fetchCustomFormats } from 'Store/Actions/settingsActions';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import CustomFormats from './CustomFormats';
|
import CustomFormats from './CustomFormats';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.customFormats', sortByName),
|
createSortedSectionSelector('settings.customFormats', sortByProp('name')),
|
||||||
(customFormats) => customFormats
|
(customFormats) => customFormats
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,12 @@ import { createSelector } from 'reselect';
|
||||||
import { deleteDownloadClient, fetchDownloadClients } from 'Store/Actions/settingsActions';
|
import { deleteDownloadClient, fetchDownloadClients } from 'Store/Actions/settingsActions';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import DownloadClients from './DownloadClients';
|
import DownloadClients from './DownloadClients';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.downloadClients', sortByName),
|
createSortedSectionSelector('settings.downloadClients', sortByProp('name')),
|
||||||
createTagsSelector(),
|
createTagsSelector(),
|
||||||
(downloadClients, tagList) => {
|
(downloadClients, tagList) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,12 +4,12 @@ import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { deleteImportList, fetchImportLists, fetchRootFolders } from 'Store/Actions/settingsActions';
|
import { deleteImportList, fetchImportLists, fetchRootFolders } from 'Store/Actions/settingsActions';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import ImportLists from './ImportLists';
|
import ImportLists from './ImportLists';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.importLists', sortByName),
|
createSortedSectionSelector('settings.importLists', sortByProp('name')),
|
||||||
(importLists) => importLists
|
(importLists) => importLists
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,12 @@ import { createSelector } from 'reselect';
|
||||||
import { cloneIndexer, deleteIndexer, fetchIndexers } from 'Store/Actions/settingsActions';
|
import { cloneIndexer, deleteIndexer, fetchIndexers } from 'Store/Actions/settingsActions';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import Indexers from './Indexers';
|
import Indexers from './Indexers';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.indexers', sortByName),
|
createSortedSectionSelector('settings.indexers', sortByProp('name')),
|
||||||
createTagsSelector(),
|
createTagsSelector(),
|
||||||
(indexers, tagList) => {
|
(indexers, tagList) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,12 +4,12 @@ import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { fetchMetadata } from 'Store/Actions/settingsActions';
|
import { fetchMetadata } from 'Store/Actions/settingsActions';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import Metadatas from './Metadatas';
|
import Metadatas from './Metadatas';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.metadata', sortByName),
|
createSortedSectionSelector('settings.metadata', sortByProp('name')),
|
||||||
(metadata) => metadata
|
(metadata) => metadata
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,12 +5,12 @@ import { createSelector } from 'reselect';
|
||||||
import { deleteNotification, fetchNotifications } from 'Store/Actions/settingsActions';
|
import { deleteNotification, fetchNotifications } from 'Store/Actions/settingsActions';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import Notifications from './Notifications';
|
import Notifications from './Notifications';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.notifications', sortByName),
|
createSortedSectionSelector('settings.notifications', sortByProp('name')),
|
||||||
createTagsSelector(),
|
createTagsSelector(),
|
||||||
(notifications, tagList) => {
|
(notifications, tagList) => {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -5,7 +5,7 @@ import FieldSet from 'Components/FieldSet';
|
||||||
import Icon from 'Components/Icon';
|
import Icon from 'Components/Icon';
|
||||||
import PageSectionContent from 'Components/Page/PageSectionContent';
|
import PageSectionContent from 'Components/Page/PageSectionContent';
|
||||||
import { icons, metadataProfileNames } from 'Helpers/Props';
|
import { icons, metadataProfileNames } from 'Helpers/Props';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import EditMetadataProfileModalConnector from './EditMetadataProfileModalConnector';
|
import EditMetadataProfileModalConnector from './EditMetadataProfileModalConnector';
|
||||||
import MetadataProfile from './MetadataProfile';
|
import MetadataProfile from './MetadataProfile';
|
||||||
|
@ -59,17 +59,20 @@ class MetadataProfiles extends Component {
|
||||||
>
|
>
|
||||||
<div className={styles.metadataProfiles}>
|
<div className={styles.metadataProfiles}>
|
||||||
{
|
{
|
||||||
items.filter((item) => item.name !== metadataProfileNames.NONE).sort(sortByName).map((item) => {
|
items
|
||||||
return (
|
.filter((item) => item.name !== metadataProfileNames.NONE)
|
||||||
<MetadataProfile
|
.sort(sortByProp('name'))
|
||||||
key={item.id}
|
.map((item) => {
|
||||||
{...item}
|
return (
|
||||||
isDeleting={isDeleting}
|
<MetadataProfile
|
||||||
onConfirmDeleteMetadataProfile={onConfirmDeleteMetadataProfile}
|
key={item.id}
|
||||||
onCloneMetadataProfilePress={this.onCloneMetadataProfilePress}
|
{...item}
|
||||||
/>
|
isDeleting={isDeleting}
|
||||||
);
|
onConfirmDeleteMetadataProfile={onConfirmDeleteMetadataProfile}
|
||||||
})
|
onCloneMetadataProfilePress={this.onCloneMetadataProfilePress}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
|
|
|
@ -19,7 +19,8 @@ function calcOrder(profileFormatItems) {
|
||||||
if (b.score !== a.score) {
|
if (b.score !== a.score) {
|
||||||
return b.score - a.score;
|
return b.score - a.score;
|
||||||
}
|
}
|
||||||
return a.name > b.name ? 1 : -1;
|
|
||||||
|
return a.name.localeCompare(b.name, undefined, { numeric: true });
|
||||||
}).map((x) => items[x.format]);
|
}).map((x) => items[x.format]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,12 @@ import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { cloneQualityProfile, deleteQualityProfile, fetchQualityProfiles } from 'Store/Actions/settingsActions';
|
import { cloneQualityProfile, deleteQualityProfile, fetchQualityProfiles } from 'Store/Actions/settingsActions';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import QualityProfiles from './QualityProfiles';
|
import QualityProfiles from './QualityProfiles';
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.qualityProfiles', sortByName),
|
createSortedSectionSelector('settings.qualityProfiles', sortByProp('name')),
|
||||||
(qualityProfiles) => qualityProfiles
|
(qualityProfiles) => qualityProfiles
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import {
|
||||||
} from 'Store/Actions/settingsActions';
|
} from 'Store/Actions/settingsActions';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import AutoTagging from './AutoTagging';
|
import AutoTagging from './AutoTagging';
|
||||||
import EditAutoTaggingModal from './EditAutoTaggingModal';
|
import EditAutoTaggingModal from './EditAutoTaggingModal';
|
||||||
|
@ -27,7 +27,7 @@ export default function AutoTaggings() {
|
||||||
isFetching,
|
isFetching,
|
||||||
isPopulated
|
isPopulated
|
||||||
} = useSelector(
|
} = useSelector(
|
||||||
createSortedSectionSelector('settings.autoTaggings', sortByName)
|
createSortedSectionSelector('settings.autoTaggings', sortByProp('name'))
|
||||||
);
|
);
|
||||||
|
|
||||||
const tagList = useSelector(createTagsSelector());
|
const tagList = useSelector(createTagsSelector());
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
|
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, sortDirections } from 'Helpers/Props';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import { filterPredicates, filters, sortPredicates } from './artistActions';
|
import { filterPredicates, filters, sortPredicates } from './artistActions';
|
||||||
import createHandleActions from './Creators/createHandleActions';
|
import createHandleActions from './Creators/createHandleActions';
|
||||||
|
@ -334,7 +334,7 @@ export const defaultState = {
|
||||||
return acc;
|
return acc;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return tagList.sort(sortByName);
|
return tagList.sort(sortByProp('name'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { RootFolderAppState } from 'App/State/SettingsAppState';
|
import { RootFolderAppState } from 'App/State/SettingsAppState';
|
||||||
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
import sortByName from 'Utilities/Array/sortByName';
|
import RootFolder from 'typings/RootFolder';
|
||||||
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
|
|
||||||
export default function createRootFoldersSelector() {
|
export default function createRootFoldersSelector() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createSortedSectionSelector('settings.rootFolders', sortByName),
|
createSortedSectionSelector<RootFolder>('rootFolders', sortByProp('name')),
|
||||||
(rootFolders: RootFolderAppState) => rootFolders
|
(rootFolders: RootFolderAppState) => rootFolders
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import getSectionState from 'Utilities/State/getSectionState';
|
import getSectionState from 'Utilities/State/getSectionState';
|
||||||
|
|
||||||
function createSortedSectionSelector(section, comparer) {
|
function createSortedSectionSelector<T>(
|
||||||
|
section: string,
|
||||||
|
comparer: (a: T, b: T) => number
|
||||||
|
) {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
(state) => state,
|
(state) => state,
|
||||||
(state) => {
|
(state) => {
|
||||||
const sectionState = getSectionState(state, section, true);
|
const sectionState = getSectionState(state, section, true);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...sectionState,
|
...sectionState,
|
||||||
items: [...sectionState.items].sort(comparer)
|
items: [...sectionState.items].sort(comparer),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
|
@ -3,6 +3,7 @@ import { useSelector } from 'react-redux';
|
||||||
import { CommandBody } from 'Commands/Command';
|
import { CommandBody } from 'Commands/Command';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import createMultiArtistsSelector from 'Store/Selectors/createMultiArtistsSelector';
|
import createMultiArtistsSelector from 'Store/Selectors/createMultiArtistsSelector';
|
||||||
|
import sortByProp from 'Utilities/Array/sortByProp';
|
||||||
import translate from 'Utilities/String/translate';
|
import translate from 'Utilities/String/translate';
|
||||||
import styles from './QueuedTaskRowNameCell.css';
|
import styles from './QueuedTaskRowNameCell.css';
|
||||||
|
|
||||||
|
@ -39,9 +40,7 @@ export default function QueuedTaskRowNameCell(
|
||||||
}
|
}
|
||||||
|
|
||||||
const artists = useSelector(createMultiArtistsSelector(movieIds));
|
const artists = useSelector(createMultiArtistsSelector(movieIds));
|
||||||
const sortedArtists = artists.sort((a, b) =>
|
const sortedArtists = artists.sort(sortByProp('sortName'));
|
||||||
a.sortName.localeCompare(b.sortName)
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRowCell>
|
<TableRowCell>
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
function sortByName(a, b) {
|
|
||||||
return a.name.localeCompare(b.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default sortByName;
|
|
13
frontend/src/Utilities/Array/sortByProp.ts
Normal file
13
frontend/src/Utilities/Array/sortByProp.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { StringKey } from 'typings/Helpers/KeysMatching';
|
||||||
|
|
||||||
|
export function sortByProp<
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
T extends Record<K, string>,
|
||||||
|
K extends StringKey<T>
|
||||||
|
>(sortKey: K) {
|
||||||
|
return (a: T, b: T) => {
|
||||||
|
return a[sortKey].localeCompare(b[sortKey], undefined, { numeric: true });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default sortByProp;
|
7
frontend/src/typings/Helpers/KeysMatching.ts
Normal file
7
frontend/src/typings/Helpers/KeysMatching.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
type KeysMatching<T, V> = {
|
||||||
|
[K in keyof T]-?: T[K] extends V ? K : never;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
export type StringKey<T> = KeysMatching<T, string>;
|
||||||
|
|
||||||
|
export default KeysMatching;
|
|
@ -89,6 +89,7 @@
|
||||||
"AnalyticsEnabledHelpText": "Send anonymous usage and error information to {appName}'s servers. This includes information on your browser, which {appName} WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes.",
|
"AnalyticsEnabledHelpText": "Send anonymous usage and error information to {appName}'s servers. This includes information on your browser, which {appName} WebUI pages you use, error reporting as well as OS and runtime version. We will use this information to prioritize features and bug fixes.",
|
||||||
"AnalyticsEnabledHelpTextWarning": "Requires restart to take effect",
|
"AnalyticsEnabledHelpTextWarning": "Requires restart to take effect",
|
||||||
"AnchorTooltip": "This file is already in your library for a release you are currently importing",
|
"AnchorTooltip": "This file is already in your library for a release you are currently importing",
|
||||||
|
"Any": "Any",
|
||||||
"AnyReleaseOkHelpText": "{appName} will automatically switch to the release best matching downloaded tracks",
|
"AnyReleaseOkHelpText": "{appName} will automatically switch to the release best matching downloaded tracks",
|
||||||
"ApiKeyHelpTextWarning": "Requires restart to take effect",
|
"ApiKeyHelpTextWarning": "Requires restart to take effect",
|
||||||
"ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file",
|
"ApiKeyValidationHealthCheckMessage": "Please update your API key to be at least {0} characters long. You can do this via settings or the config file",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue