diff --git a/client/src/javascript/components/general/TorrentStatusIcon.tsx b/client/src/javascript/components/general/TorrentStatusIcon.tsx new file mode 100644 index 00000000..98afee60 --- /dev/null +++ b/client/src/javascript/components/general/TorrentStatusIcon.tsx @@ -0,0 +1,37 @@ +import {FC, memo} from 'react'; + +import type {TorrentStatus} from '@shared/constants/torrentStatusMap'; + +import {Error, Spinner, Start, Stop} from '@client/ui/icons'; + +const STATUS_ICON_MAP: Partial> = { + error: , + checking: , + stopped: , + downloading: , + seeding: , +} as const; + +interface TorrentStatusIconProps { + statuses: TorrentStatus[]; +} + +const TorrentStatusIcon: FC = ({statuses}: TorrentStatusIconProps) => { + let resultIcon = ; + + Object.keys(STATUS_ICON_MAP).some((key) => { + const status = key as TorrentStatus; + if (statuses.includes(status)) { + const icon = STATUS_ICON_MAP[status]; + if (icon != null) { + resultIcon = icon; + return true; + } + } + return false; + }); + + return resultIcon; +}; + +export default memo(TorrentStatusIcon); diff --git a/client/src/javascript/components/modals/torrent-details-modal/TorrentHeading.tsx b/client/src/javascript/components/modals/torrent-details-modal/TorrentHeading.tsx index 22b34ce1..2921fddb 100644 --- a/client/src/javascript/components/modals/torrent-details-modal/TorrentHeading.tsx +++ b/client/src/javascript/components/modals/torrent-details-modal/TorrentHeading.tsx @@ -6,13 +6,13 @@ import {Trans, useLingui} from '@lingui/react'; import {Clock, DownloadThick, Ratio, Start, Stop, UploadThick} from '@client/ui/icons'; import TorrentActions from '@client/actions/TorrentActions'; import torrentStatusClasses from '@client/util/torrentStatusClasses'; -import torrentStatusIcons from '@client/util/torrentStatusIcons'; import TorrentStore from '@client/stores/TorrentStore'; import UIStore from '@client/stores/UIStore'; import Duration from '../../general/Duration'; import PriorityMeter from '../../general/PriorityMeter'; import ProgressBar from '../../general/ProgressBar'; +import TorrentStatusIcon from '../../general/TorrentStatusIcon'; import Size from '../../general/Size'; const TorrentHeading: FC = observer(() => { @@ -41,7 +41,6 @@ const TorrentHeading: FC = observer(() => { }, 'torrent-details__header', ); - const torrentStatusIcon = torrentStatusIcons(torrent.status); return (
@@ -118,7 +117,10 @@ const TorrentHeading: FC = observer(() => {
- + } + /> ); }); diff --git a/client/src/javascript/components/torrent-list/TorrentListCell.tsx b/client/src/javascript/components/torrent-list/TorrentListCell.tsx index d0501eeb..06701713 100644 --- a/client/src/javascript/components/torrent-list/TorrentListCell.tsx +++ b/client/src/javascript/components/torrent-list/TorrentListCell.tsx @@ -1,20 +1,152 @@ import classnames from 'classnames'; -import {FC, ReactNode} from 'react'; +import {FC, memo} from 'react'; import {observer} from 'mobx-react'; +import {Trans, useLingui} from '@lingui/react'; -import {DetailNotAvailable} from '@client/ui/icons'; -import {getTorrentListCellContent} from '@client/util/torrentListCellContents'; -import torrentPropertyIcons from '@client/util/torrentPropertyIcons'; +import { + CalendarCreated, + Calendar, + CheckmarkThick, + Clock, + DiskFlat, + DetailNotAvailable, + DownloadThick, + FolderClosedSolid, + Hash, + Lock, + TrackerMessage, + Peers, + Ratio, + Seeds, + Radar, + UploadThick, +} from '@client/ui/icons'; +import Duration from '@client/components/general/Duration'; +import Size from '@client/components/general/Size'; import TorrentStore from '@client/stores/TorrentStore'; import type {TorrentListColumn} from '@client/constants/TorrentListColumns'; import type {TorrentProperties} from '@shared/types/Torrent'; +const ICONS: Partial> = { + eta: , + sizeBytes: , + downRate: , + directory: , + hash: , + dateAdded: , + dateCreated: , + isPrivate: , + message: , + percentComplete: , + peers: , + ratio: , + seeds: , + trackerURIs: , + upRate: , + upTotal: , +} as const; + +const BooleanCell: FC<{value: boolean}> = memo(({value}: {value: boolean}) => + value ? : null, +); + +const DateCell: FC<{date: number}> = memo(({date}: {date: number}) => { + const {i18n} = useLingui(); + + return {i18n.date(new Date(date * 1000))}; +}); + +const ETACell: FC<{eta: number}> = memo(({eta}: {eta: number}) => (eta ? : null)); + +const PeerCell: FC<{peersConnected: number; totalPeers: number}> = memo( + ({peersConnected, totalPeers}: {peersConnected: number; totalPeers: number}) => { + const {i18n} = useLingui(); + + return ( + + + + ), + total: i18n.number(totalPeers), + }} + /> + ); + }, +); + +const RatioCell: FC<{ratio: number}> = memo(({ratio}: {ratio: number}) => { + const {i18n} = useLingui(); + + return {i18n.number(ratio, {maximumFractionDigits: 2})}; +}); + +const TagsCell: FC<{tags: string[]}> = memo(({tags}: {tags: string[]}) => ( +
    + {tags.map((tag) => ( +
  • + {tag} +
  • + ))} +
+)); + +const TrackersCell: FC<{trackers: string[]}> = memo(({trackers}: {trackers: string[]}) => ( + {trackers.join(', ')} +)); + +interface TorrentListCellContentProps { + torrent: TorrentProperties; + column: TorrentListColumn; +} + +const DefaultTorrentListCellContent: FC = observer( + ({torrent, column}: TorrentListCellContentProps) => { + switch (column) { + case 'dateAdded': + return ; + case 'dateCreated': + return ; + case 'downRate': + return ; + case 'upRate': + return ; + case 'downTotal': + return ; + case 'upTotal': + return ; + case 'sizeBytes': + return ; + case 'ratio': + return ; + case 'isPrivate': + return ; + case 'tags': + return ; + case 'trackerURIs': + return ; + case 'eta': + return ; + case 'seeds': + return ; + case 'peers': + return ; + default: + return {torrent[column]}; + } + }, +); + interface TorrentListCellProps { hash: string; column: TorrentListColumn; - content?: (torrent: TorrentProperties, column: TorrentListColumn) => ReactNode; + content?: FC; className?: string; classNameOverride?: boolean; width?: number; @@ -22,8 +154,16 @@ interface TorrentListCellProps { } const TorrentListCell: FC = observer( - ({hash, content, column, className, classNameOverride, width, showIcon}: TorrentListCellProps) => { - const icon = showIcon ? torrentPropertyIcons[column] : null; + ({ + hash, + content: TorrentListCellContent, + column, + className, + classNameOverride, + width, + showIcon, + }: TorrentListCellProps) => { + const icon = showIcon ? ICONS[column] : null; return (
= observer( role="cell" style={{width: `${width}px`}}> {icon} - {content?.(TorrentStore.torrents[hash], column) || } + {TorrentListCellContent ? ( + + ) : ( + + )}
); }, @@ -42,7 +186,7 @@ const TorrentListCell: FC = observer( TorrentListCell.defaultProps = { className: undefined, classNameOverride: false, - content: getTorrentListCellContent, + content: DefaultTorrentListCellContent, width: undefined, showIcon: false, }; diff --git a/client/src/javascript/components/torrent-list/TorrentListRowCondensed.tsx b/client/src/javascript/components/torrent-list/TorrentListRowCondensed.tsx index 751a8f06..71323ceb 100644 --- a/client/src/javascript/components/torrent-list/TorrentListRowCondensed.tsx +++ b/client/src/javascript/components/torrent-list/TorrentListRowCondensed.tsx @@ -5,7 +5,7 @@ import ProgressBar from '../general/ProgressBar'; import SettingStore from '../../stores/SettingStore'; import TorrentListCell from './TorrentListCell'; import TorrentListColumns from '../../constants/TorrentListColumns'; -import torrentStatusIcons from '../../util/torrentStatusIcons'; +import TorrentStatusIcon from '../general/TorrentStatusIcon'; interface TorrentListRowCondensedProps { className: string; @@ -52,8 +52,11 @@ const TorrentListRowCondensed = observer( key={id} hash={hash} column={id} - content={(torrent) => ( - + content={({torrent}) => ( + } + /> )} width={SettingStore.floodSettings.torrentListColumnWidths[id]} />, diff --git a/client/src/javascript/components/torrent-list/TorrentListRowExpanded.tsx b/client/src/javascript/components/torrent-list/TorrentListRowExpanded.tsx index 586c0a84..f8e5cdcd 100644 --- a/client/src/javascript/components/torrent-list/TorrentListRowExpanded.tsx +++ b/client/src/javascript/components/torrent-list/TorrentListRowExpanded.tsx @@ -4,11 +4,11 @@ import {useLingui} from '@lingui/react'; import SettingStore from '@client/stores/SettingStore'; import TorrentListColumns from '@client/constants/TorrentListColumns'; -import torrentStatusIcons from '@client/util/torrentStatusIcons'; import ProgressBar from '../general/ProgressBar'; import Size from '../general/Size'; import TorrentListCell from './TorrentListCell'; +import TorrentStatusIcon from '../general/TorrentStatusIcon'; interface TorrentListRowExpandedProps { className: string; @@ -59,7 +59,7 @@ const TorrentListRowExpanded = observer( key="percentComplete" hash={hash} column="percentComplete" - content={(torrent) => ( + content={({torrent}) => ( {i18n.number(torrent.percentComplete, {maximumFractionDigits: 1})} % @@ -75,8 +75,11 @@ const TorrentListRowExpanded = observer( key="percentBar" hash={hash} column="percentComplete" - content={(torrent) => ( - + content={({torrent}) => ( + } + /> )} className="torrent__details__section torrent__details__section--quaternary" classNameOverride diff --git a/client/src/javascript/util/torrentListCellContents.tsx b/client/src/javascript/util/torrentListCellContents.tsx deleted file mode 100644 index 1bcc924f..00000000 --- a/client/src/javascript/util/torrentListCellContents.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import {i18n} from '@lingui/core'; -import {Trans} from '@lingui/react'; - -import type {ReactNode} from 'react'; - -import {CheckmarkThick} from '@client/ui/icons'; -import Duration from '@client/components/general/Duration'; -import Size from '@client/components/general/Size'; - -import type {TorrentListColumn} from '@client/constants/TorrentListColumns'; - -import type {TorrentProperties} from '@shared/types/Torrent'; - -const booleanTransformer = (value: boolean): ReactNode => - value ? : null; -const dateTransformer = (date: number): ReactNode => i18n.date(new Date(date * 1000)); -const peersTransformer = (peersConnected: number, totalPeers: number): ReactNode => ( - - - - ), - total: i18n.number(totalPeers), - }} - /> -); -const speedTransformer = (value: number): ReactNode => ; -const sizeTransformer = (value: number): ReactNode => ; - -export const torrentListCellTransformers = { - dateAdded: dateTransformer, - dateCreated: dateTransformer, - downRate: speedTransformer, - downTotal: sizeTransformer, - isPrivate: booleanTransformer, - peers: peersTransformer, - seeds: peersTransformer, - tags: (tags: TorrentProperties['tags']): ReactNode => ( -
    - {tags.map((tag) => ( -
  • - {tag} -
  • - ))} -
- ), - ratio: (ratio: TorrentProperties['ratio']): ReactNode => i18n.number(ratio, {maximumFractionDigits: 2}), - sizeBytes: sizeTransformer, - trackerURIs: (trackers: TorrentProperties['trackerURIs']): ReactNode => trackers.join(', '), - upRate: speedTransformer, - upTotal: sizeTransformer, - eta: (eta: TorrentProperties['eta']): ReactNode => { - if (!eta) { - return null; - } - - return ; - }, -}; - -export const getTorrentListCellContent = (torrent: TorrentProperties, column: TorrentListColumn): ReactNode => { - switch (column) { - case 'dateAdded': - case 'dateCreated': - case 'downRate': - case 'upRate': - case 'downTotal': - case 'upTotal': - case 'sizeBytes': - case 'ratio': - return torrentListCellTransformers[column](torrent[column]); - case 'isPrivate': - return torrentListCellTransformers[column](torrent[column]); - case 'tags': - case 'trackerURIs': - return torrentListCellTransformers[column](torrent[column]); - case 'eta': - return torrentListCellTransformers[column](torrent[column]); - case 'seeds': - return torrentListCellTransformers[column](torrent.seedsConnected, torrent.seedsTotal); - case 'peers': - return torrentListCellTransformers[column](torrent.peersConnected, torrent.peersTotal); - default: - return torrent[column]; - } -}; diff --git a/client/src/javascript/util/torrentPropertyIcons.tsx b/client/src/javascript/util/torrentPropertyIcons.tsx deleted file mode 100644 index 1f4d4acf..00000000 --- a/client/src/javascript/util/torrentPropertyIcons.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { - CalendarCreated, - Calendar, - Clock, - DiskFlat, - DownloadThick, - FolderClosedSolid, - Hash, - Lock, - TrackerMessage, - Peers, - Ratio, - Seeds, - Radar, - UploadThick, -} from '@client/ui/icons'; - -import type {TorrentListColumn} from '@client/constants/TorrentListColumns'; - -const ICONS: Partial> = { - eta: , - sizeBytes: , - downRate: , - directory: , - hash: , - dateAdded: , - dateCreated: , - isPrivate: , - message: , - percentComplete: , - peers: , - ratio: , - seeds: , - trackerURIs: , - upRate: , - upTotal: , -} as const; - -export default ICONS; diff --git a/client/src/javascript/util/torrentStatusIcons.tsx b/client/src/javascript/util/torrentStatusIcons.tsx deleted file mode 100644 index c82854c5..00000000 --- a/client/src/javascript/util/torrentStatusIcons.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import type {ReactNode} from 'react'; - -import type {TorrentStatus} from '@shared/constants/torrentStatusMap'; - -import {Error, Spinner, Start, Stop} from '@client/ui/icons'; - -const STATUS_ICON_MAP: Partial> = { - error: , - checking: , - stopped: , - downloading: , - seeding: , -} as const; - -function torrentStatusIcons(statuses: Array): ReactNode { - let resultIcon: ReactNode = ; - Object.entries(STATUS_ICON_MAP).some(([status, icon]) => { - if (statuses.includes(status as TorrentStatus) && icon != null) { - resultIcon = icon; - return true; - } - return false; - }); - return resultIcon; -} - -export default torrentStatusIcons;