diff --git a/client/src/javascript/stores/TorrentStore.ts b/client/src/javascript/stores/TorrentStore.ts index a5113a10..09258f31 100644 --- a/client/src/javascript/stores/TorrentStore.ts +++ b/client/src/javascript/stores/TorrentStore.ts @@ -220,7 +220,7 @@ class TorrentStoreClass extends BaseStore { switch (diff.action) { case 'TORRENT_LIST_ACTION_TORRENT_ADDED': - this.torrents[torrentHash] = diff.data as TorrentProperties; + this.torrents[torrentHash] = diff.data; break; case 'TORRENT_LIST_ACTION_TORRENT_DELETED': if (this.selectedTorrents.includes(torrentHash)) { @@ -266,7 +266,7 @@ class TorrentStoreClass extends BaseStore { sortTorrents() { // Convert torrents hash to array and sort it. - this.sortedTorrents = sortTorrents(this.torrents, this.getTorrentsSort()); + this.sortedTorrents = sortTorrents(Object.values(this.torrents), this.getTorrentsSort()); } startPollingTorrentDetails() { diff --git a/client/src/javascript/util/sortTorrents.ts b/client/src/javascript/util/sortTorrents.ts index aa6b4623..45d5db15 100644 --- a/client/src/javascript/util/sortTorrents.ts +++ b/client/src/javascript/util/sortTorrents.ts @@ -1,83 +1,62 @@ -import type {Duration, TorrentProperties, Torrents} from '@shared/types/Torrent'; +import sort from 'fast-sort'; + +import type {TorrentProperties} from '@shared/types/Torrent'; import type {FloodSettings} from '../stores/SettingsStore'; -const stringProps = ['basePath', 'comment', 'hash', 'message', 'name']; +type SortRule = { + [direction in FloodSettings['sortTorrents']['direction']]: + | keyof TorrentProperties + | ((p: TorrentProperties) => unknown); +}; -// TODO: Split up this garbage. -function sortTorrents(torrentsHash: Torrents, sortBy: FloodSettings['sortTorrents']) { - const torrents = Object.keys(torrentsHash).map((hash) => ({...torrentsHash[hash]})); +function sortTorrents(torrents: Array, sortBy: Readonly) { + const {property} = sortBy; + const sortRules: Array = []; - if (torrents.length) { - const {direction, property} = sortBy; - - torrents.sort((a, b) => { - let valA = a[property as keyof TorrentProperties]; - let valB = b[property as keyof TorrentProperties]; - - if (property === 'peers' || property === 'seeds') { - valA = a[`${property}Connected` as keyof TorrentProperties]; - valB = b[`${property}Connected` as keyof TorrentProperties]; - - if (valA === valB) { - valA = a[`${property}Total` as keyof TorrentProperties]; - valB = b[`${property}Total` as keyof TorrentProperties]; - } - } else if (property === 'eta') { - // Keep Infinity and null values at bottom of array. - if ((valA === 'Infinity' && valB !== 'Infinity') || (valA == null && valB != null)) { - return 1; - } - if ((valA !== 'Infinity' && valB === 'Infinity') || (valA != null && valB == null)) { - return -1; - } - if (valA == null && valB == null) { - return 0; - } - - // If it's not infinity, compare the cumulative seconds as regular numbers. - if (valA !== 'Infinity') { - valA = Number((valA as Duration).cumSeconds); - } - - if (valB !== 'Infinity') { - valB = Number((valB as Duration).cumSeconds); - } - } else if (property === 'tags') { - // TODO: Find a better way to sort tags. - valA = (valA as TorrentProperties['tags']).join(',').toLowerCase(); - valB = (valB as TorrentProperties['tags']).join(',').toLowerCase(); - } else if (stringProps.includes(property)) { - valA = (valA as string).toLowerCase(); - valB = (valB as string).toLowerCase(); - } else { - valA = Number(valA); - valB = Number(valB); - } - - // TODO: Use locale compare for sorting strings. - if (direction === 'asc') { - if (valA > valB) { - return 1; - } - if (valA < valB) { - return -1; - } - } else { - if (valA > valB) { - return -1; - } - if (valA < valB) { - return 1; - } - } - - return 0; - }); - - return torrents; + switch (property) { + case 'peers': + case 'seeds': + sortRules.push( + {[sortBy.direction]: `${property}Connected`} as SortRule, + {[sortBy.direction]: `${property}Total`} as SortRule, + ); + break; + case 'eta': + sortRules.push({ + [sortBy.direction]: (p: TorrentProperties) => { + if (p.eta === 'Infinity') { + return -1; + } + return p.eta.cumSeconds; + }, + } as SortRule); + break; + case 'tags': + sortRules.push({ + [sortBy.direction]: (p: TorrentProperties) => { + return p[property].join(',').toLowerCase(); + }, + } as SortRule); + break; + case 'basePath': + case 'comment': + case 'hash': + case 'message': + case 'name': + // Those fields are strings. We want case-insensitive sorting. + sortRules.push({ + [sortBy.direction]: (p: TorrentProperties) => { + return p[property].toLowerCase(); + }, + } as SortRule); + break; + default: + sortRules.push({[sortBy.direction]: property} as SortRule); + break; } - return torrents; + + return sort(torrents).by(sortRules); } export default sortTorrents; diff --git a/package-lock.json b/package-lock.json index 0f5a152f..1debc34b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7695,6 +7695,12 @@ "integrity": "sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw==", "dev": true }, + "fast-sort": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/fast-sort/-/fast-sort-2.2.0.tgz", + "integrity": "sha512-W7zqnn2zsYoQA87FKmYtgOsbJohOrh7XrtZrCVHN5XZKqTBTv5UG+rSS3+iWbg/nepRQUOu+wnas8BwtK8kiCg==", + "dev": true + }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", diff --git a/package.json b/package.json index 1901e44f..ecd3b912 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,7 @@ "eslint-plugin-react": "^7.21.2", "eslint-plugin-react-hooks": "^4.1.2", "express": "^4.17.1", + "fast-sort": "^2.2.0", "feedsub": "^0.7.1", "file-loader": "^6.1.0", "flux": "^3.1.3",