client: convert elements in util/ to FC

This commit is contained in:
Jesse Chan
2021-03-24 14:42:09 +08:00
parent de0a498a0d
commit a18b071ef7
8 changed files with 208 additions and 175 deletions
@@ -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<Record<TorrentStatus, JSX.Element>> = {
error: <Error />,
checking: <Spinner />,
stopped: <Stop />,
downloading: <Start />,
seeding: <Start />,
} as const;
interface TorrentStatusIconProps {
statuses: TorrentStatus[];
}
const TorrentStatusIcon: FC<TorrentStatusIconProps> = ({statuses}: TorrentStatusIconProps) => {
let resultIcon = <Stop />;
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);
@@ -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 (
<div className={torrentClasses}>
@@ -118,7 +117,10 @@ const TorrentHeading: FC = observer(() => {
</li>
</ul>
</div>
<ProgressBar percent={Math.ceil(torrent.percentComplete)} icon={torrentStatusIcon} />
<ProgressBar
percent={Math.ceil(torrent.percentComplete)}
icon={<TorrentStatusIcon statuses={torrent.status} />}
/>
</div>
);
});
@@ -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<Record<TorrentListColumn, JSX.Element>> = {
eta: <Clock />,
sizeBytes: <DiskFlat />,
downRate: <DownloadThick />,
directory: <FolderClosedSolid />,
hash: <Hash />,
dateAdded: <Calendar />,
dateCreated: <CalendarCreated />,
isPrivate: <Lock />,
message: <TrackerMessage />,
percentComplete: <DownloadThick />,
peers: <Peers />,
ratio: <Ratio />,
seeds: <Seeds />,
trackerURIs: <Radar />,
upRate: <UploadThick />,
upTotal: <UploadThick />,
} as const;
const BooleanCell: FC<{value: boolean}> = memo(({value}: {value: boolean}) =>
value ? <CheckmarkThick className="torrent__detail__icon torrent__detail__icon--checkmark" /> : null,
);
const DateCell: FC<{date: number}> = memo(({date}: {date: number}) => {
const {i18n} = useLingui();
return <span>{i18n.date(new Date(date * 1000))}</span>;
});
const ETACell: FC<{eta: number}> = memo(({eta}: {eta: number}) => (eta ? <Duration value={eta} /> : null));
const PeerCell: FC<{peersConnected: number; totalPeers: number}> = memo(
({peersConnected, totalPeers}: {peersConnected: number; totalPeers: number}) => {
const {i18n} = useLingui();
return (
<Trans
id="torrent.list.peers"
values={{
connected: i18n.number(peersConnected),
of: (
<em className="unit">
<Trans id="torrent.list.peers.of" />
</em>
),
total: i18n.number(totalPeers),
}}
/>
);
},
);
const RatioCell: FC<{ratio: number}> = memo(({ratio}: {ratio: number}) => {
const {i18n} = useLingui();
return <span>{i18n.number(ratio, {maximumFractionDigits: 2})}</span>;
});
const TagsCell: FC<{tags: string[]}> = memo(({tags}: {tags: string[]}) => (
<ul className="torrent__tags tag">
{tags.map((tag) => (
<li className="torrent__tag" key={tag}>
{tag}
</li>
))}
</ul>
));
const TrackersCell: FC<{trackers: string[]}> = memo(({trackers}: {trackers: string[]}) => (
<span>{trackers.join(', ')}</span>
));
interface TorrentListCellContentProps {
torrent: TorrentProperties;
column: TorrentListColumn;
}
const DefaultTorrentListCellContent: FC<TorrentListCellContentProps> = observer(
({torrent, column}: TorrentListCellContentProps) => {
switch (column) {
case 'dateAdded':
return <DateCell date={torrent[column]} />;
case 'dateCreated':
return <DateCell date={torrent[column]} />;
case 'downRate':
return <Size value={torrent[column]} isSpeed />;
case 'upRate':
return <Size value={torrent[column]} isSpeed />;
case 'downTotal':
return <Size value={torrent[column]} />;
case 'upTotal':
return <Size value={torrent[column]} />;
case 'sizeBytes':
return <Size value={torrent[column]} />;
case 'ratio':
return <RatioCell ratio={torrent[column]} />;
case 'isPrivate':
return <BooleanCell value={torrent[column]} />;
case 'tags':
return <TagsCell tags={torrent[column]} />;
case 'trackerURIs':
return <TrackersCell trackers={torrent[column]} />;
case 'eta':
return <ETACell eta={torrent[column]} />;
case 'seeds':
return <PeerCell peersConnected={torrent.seedsConnected} totalPeers={torrent.seedsTotal} />;
case 'peers':
return <PeerCell peersConnected={torrent.peersConnected} totalPeers={torrent.peersTotal} />;
default:
return <span>{torrent[column]}</span>;
}
},
);
interface TorrentListCellProps {
hash: string;
column: TorrentListColumn;
content?: (torrent: TorrentProperties, column: TorrentListColumn) => ReactNode;
content?: FC<TorrentListCellContentProps>;
className?: string;
classNameOverride?: boolean;
width?: number;
@@ -22,8 +154,16 @@ interface TorrentListCellProps {
}
const TorrentListCell: FC<TorrentListCellProps> = 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 (
<div
@@ -33,7 +173,11 @@ const TorrentListCell: FC<TorrentListCellProps> = observer(
role="cell"
style={{width: `${width}px`}}>
{icon}
{content?.(TorrentStore.torrents[hash], column) || <DetailNotAvailable />}
{TorrentListCellContent ? (
<TorrentListCellContent torrent={TorrentStore.torrents[hash]} column={column} />
) : (
<DetailNotAvailable />
)}
</div>
);
},
@@ -42,7 +186,7 @@ const TorrentListCell: FC<TorrentListCellProps> = observer(
TorrentListCell.defaultProps = {
className: undefined,
classNameOverride: false,
content: getTorrentListCellContent,
content: DefaultTorrentListCellContent,
width: undefined,
showIcon: false,
};
@@ -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) => (
<ProgressBar percent={Math.ceil(torrent.percentComplete)} icon={torrentStatusIcons(torrent.status)} />
content={({torrent}) => (
<ProgressBar
percent={Math.ceil(torrent.percentComplete)}
icon={<TorrentStatusIcon statuses={torrent.status} />}
/>
)}
width={SettingStore.floodSettings.torrentListColumnWidths[id]}
/>,
@@ -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}) => (
<span>
{i18n.number(torrent.percentComplete, {maximumFractionDigits: 1})}
<em className="unit">%</em>
@@ -75,8 +75,11 @@ const TorrentListRowExpanded = observer(
key="percentBar"
hash={hash}
column="percentComplete"
content={(torrent) => (
<ProgressBar percent={Math.ceil(torrent.percentComplete)} icon={torrentStatusIcons(torrent.status)} />
content={({torrent}) => (
<ProgressBar
percent={Math.ceil(torrent.percentComplete)}
icon={<TorrentStatusIcon statuses={torrent.status} />}
/>
)}
className="torrent__details__section torrent__details__section--quaternary"
classNameOverride
@@ -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 ? <CheckmarkThick className="torrent__detail__icon torrent__detail__icon--checkmark" /> : null;
const dateTransformer = (date: number): ReactNode => i18n.date(new Date(date * 1000));
const peersTransformer = (peersConnected: number, totalPeers: number): ReactNode => (
<Trans
id="torrent.list.peers"
values={{
connected: i18n.number(peersConnected),
of: (
<em className="unit">
<Trans id="torrent.list.peers.of" />
</em>
),
total: i18n.number(totalPeers),
}}
/>
);
const speedTransformer = (value: number): ReactNode => <Size value={value} isSpeed />;
const sizeTransformer = (value: number): ReactNode => <Size value={value} />;
export const torrentListCellTransformers = {
dateAdded: dateTransformer,
dateCreated: dateTransformer,
downRate: speedTransformer,
downTotal: sizeTransformer,
isPrivate: booleanTransformer,
peers: peersTransformer,
seeds: peersTransformer,
tags: (tags: TorrentProperties['tags']): ReactNode => (
<ul className="torrent__tags tag">
{tags.map((tag) => (
<li className="torrent__tag" key={tag}>
{tag}
</li>
))}
</ul>
),
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 <Duration value={eta} />;
},
};
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];
}
};
@@ -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<Record<TorrentListColumn, JSX.Element>> = {
eta: <Clock />,
sizeBytes: <DiskFlat />,
downRate: <DownloadThick />,
directory: <FolderClosedSolid />,
hash: <Hash />,
dateAdded: <Calendar />,
dateCreated: <CalendarCreated />,
isPrivate: <Lock />,
message: <TrackerMessage />,
percentComplete: <DownloadThick />,
peers: <Peers />,
ratio: <Ratio />,
seeds: <Seeds />,
trackerURIs: <Radar />,
upRate: <UploadThick />,
upTotal: <UploadThick />,
} as const;
export default ICONS;
@@ -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<Record<TorrentStatus, ReactNode>> = {
error: <Error />,
checking: <Spinner />,
stopped: <Stop />,
downloading: <Start />,
seeding: <Start />,
} as const;
function torrentStatusIcons(statuses: Array<TorrentStatus>): ReactNode {
let resultIcon: ReactNode = <Stop />;
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;