New: Use natural sorting for lists of items in the UI

Closes #6955
This commit is contained in:
Mark McDowall 2024-07-14 14:33:08 -07:00
parent e97e5bfe8f
commit 8faef23f76
28 changed files with 81 additions and 56 deletions

View File

@ -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 BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue'; import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue'; import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector'; import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
@ -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);

View File

@ -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');

View File

@ -2,7 +2,7 @@ import React from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import Series from 'Series/Series'; import Series from 'Series/Series';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
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 SeriesFilterBuilderRowValue(props: FilterBuilderRowValueProps) {
const tagList = allSeries const tagList = allSeries
.map((series) => ({ id: series.id, name: series.title })) .map((series) => ({ id: series.id, name: series.title }))
.sort(sortByName); .sort(sortByProp('name'));
return <FilterBuilderRowValue {...props} tagList={tagList} />; return <FilterBuilderRowValue {...props} tagList={tagList} />;
} }

View File

@ -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

View File

@ -4,7 +4,7 @@ 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 translate from 'Utilities/String/translate';
import EnhancedSelectInput from './EnhancedSelectInput'; import EnhancedSelectInput from './EnhancedSelectInput';
@ -23,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,

View File

@ -4,7 +4,7 @@ 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 translate from 'Utilities/String/translate';
import EnhancedSelectInput from './EnhancedSelectInput'; import EnhancedSelectInput from './EnhancedSelectInput';
@ -20,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

View File

@ -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,

View File

@ -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

View File

@ -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}>

View File

@ -21,6 +21,7 @@ import { scrollDirections } from 'Helpers/Props';
import Series from 'Series/Series'; import Series from 'Series/Series';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import dimensions from 'Styles/Variables/dimensions'; import dimensions from 'Styles/Variables/dimensions';
import sortByProp from 'Utilities/Array/sortByProp';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import SelectSeriesModalTableHeader from './SelectSeriesModalTableHeader'; import SelectSeriesModalTableHeader from './SelectSeriesModalTableHeader';
import SelectSeriesRow from './SelectSeriesRow'; import SelectSeriesRow from './SelectSeriesRow';
@ -163,9 +164,7 @@ function SelectSeriesModalContent(props: SelectSeriesModalContentProps) {
); );
const items = useMemo(() => { const items = useMemo(() => {
const sorted = [...allSeries].sort((a, b) => const sorted = [...allSeries].sort(sortByProp('sortTitle'));
a.sortTitle.localeCompare(b.sortTitle)
);
return sorted.filter( return sorted.filter(
(item) => (item) =>

View File

@ -2,6 +2,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createSeriesSelector from 'Store/Selectors/createSeriesSelector'; import createSeriesSelector from 'Store/Selectors/createSeriesSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector'; import createTagsSelector from 'Store/Selectors/createTagsSelector';
import sortByProp from 'Utilities/Array/sortByProp';
import SeriesTags from './SeriesTags'; import SeriesTags from './SeriesTags';
function createMapStateToProps() { function createMapStateToProps() {
@ -12,8 +13,8 @@ function createMapStateToProps() {
const tags = series.tags const tags = series.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

View File

@ -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
); );
} }

View File

@ -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 {

View File

@ -5,12 +5,12 @@ import { createSelector } from 'reselect';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { deleteImportList, fetchImportLists } from 'Store/Actions/settingsActions'; import { deleteImportList, fetchImportLists } 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
); );
} }

View File

@ -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 {

View File

@ -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
); );
} }

View File

@ -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 {

View File

@ -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
); );
} }

View File

@ -9,7 +9,7 @@ import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import { cloneAutoTagging, deleteAutoTagging, fetchAutoTaggings } from 'Store/Actions/settingsActions'; import { cloneAutoTagging, deleteAutoTagging, fetchAutoTaggings } 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';
@ -23,7 +23,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());

View File

@ -1,7 +1,7 @@
import { createAction } from 'redux-actions'; import { createAction } from 'redux-actions';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props'; import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks'; import { createThunk, handleThunks } from 'Store/thunks';
import sortByName from 'Utilities/Array/sortByName'; import sortByProp from 'Utilities/Array/sortByProp';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import createFetchHandler from './Creators/createFetchHandler'; import createFetchHandler from './Creators/createFetchHandler';
@ -232,7 +232,7 @@ export const defaultState = {
return acc; return acc;
}, []); }, []);
return genreList.sort(sortByName); return genreList.sort(sortByProp('name'));
} }
}, },
{ {

View File

@ -3,7 +3,7 @@ import { createAction } from 'redux-actions';
import { batchActions } from 'redux-batched-actions'; import { batchActions } from 'redux-batched-actions';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props'; import { filterBuilderTypes, filterBuilderValueTypes, filterTypePredicates, filterTypes, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks'; import { createThunk, handleThunks } from 'Store/thunks';
import sortByName from 'Utilities/Array/sortByName'; import sortByProp from 'Utilities/Array/sortByProp';
import createAjaxRequest from 'Utilities/createAjaxRequest'; import createAjaxRequest from 'Utilities/createAjaxRequest';
import dateFilterPredicate from 'Utilities/Date/dateFilterPredicate'; import dateFilterPredicate from 'Utilities/Date/dateFilterPredicate';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
@ -246,7 +246,7 @@ export const filterBuilderProps = [
return acc; return acc;
}, []); }, []);
return tagList.sort(sortByName); return tagList.sort(sortByProp('name'));
} }
}, },
{ {
@ -315,7 +315,7 @@ export const filterBuilderProps = [
return acc; return acc;
}, []); }, []);
return tagList.sort(sortByName); return tagList.sort(sortByProp('name'));
} }
}, },
{ {
@ -334,7 +334,7 @@ export const filterBuilderProps = [
return acc; return acc;
}, []); }, []);
return languageList.sort(sortByName); return languageList.sort(sortByProp('name'));
} }
}, },
{ {

View File

@ -2,13 +2,17 @@ import { createSelector } from 'reselect';
import { DownloadClientAppState } from 'App/State/SettingsAppState'; import { DownloadClientAppState } from 'App/State/SettingsAppState';
import DownloadProtocol from 'DownloadClient/DownloadProtocol'; import DownloadProtocol from 'DownloadClient/DownloadProtocol';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import sortByName from 'Utilities/Array/sortByName'; import DownloadClient from 'typings/DownloadClient';
import sortByProp from 'Utilities/Array/sortByProp';
export default function createEnabledDownloadClientsSelector( export default function createEnabledDownloadClientsSelector(
protocol: DownloadProtocol protocol: DownloadProtocol
) { ) {
return createSelector( return createSelector(
createSortedSectionSelector('settings.downloadClients', sortByName), createSortedSectionSelector<DownloadClient>(
'settings.downloadClients',
sortByProp('name')
),
(downloadClients: DownloadClientAppState) => { (downloadClients: DownloadClientAppState) => {
const { isFetching, isPopulated, error, items } = downloadClients; const { isFetching, isPopulated, error, items } = downloadClients;

View File

@ -2,12 +2,11 @@ import { createSelector } from 'reselect';
import RootFolderAppState from 'App/State/RootFolderAppState'; import RootFolderAppState from 'App/State/RootFolderAppState';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import RootFolder from 'typings/RootFolder'; import RootFolder from 'typings/RootFolder';
import sortByProp from 'Utilities/Array/sortByProp';
export default function createRootFoldersSelector() { export default function createRootFoldersSelector() {
return createSelector( return createSelector(
createSortedSectionSelector('rootFolders', (a: RootFolder, b: RootFolder) => createSortedSectionSelector<RootFolder>('rootFolders', sortByProp('path')),
a.path.localeCompare(b.path)
),
(rootFolders: RootFolderAppState) => rootFolders (rootFolders: RootFolderAppState) => rootFolders
); );
} }

View File

@ -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),
}; };
} }
); );

View File

@ -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 createMultiSeriesSelector from 'Store/Selectors/createMultiSeriesSelector'; import createMultiSeriesSelector from 'Store/Selectors/createMultiSeriesSelector';
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 series = useSelector(createMultiSeriesSelector(seriesIds)); const series = useSelector(createMultiSeriesSelector(seriesIds));
const sortedSeries = series.sort((a, b) => const sortedSeries = series.sort(sortByProp('sortTitle'));
a.sortTitle.localeCompare(b.sortTitle)
);
return ( return (
<TableRowCell> <TableRowCell>

View File

@ -1,5 +0,0 @@
function sortByName(a, b) {
return a.name.localeCompare(b.name);
}
export default sortByName;

View 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;

View 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;