TorrentList: observe torrent properties at cell level

This commit is contained in:
Jesse Chan
2020-10-28 01:47:28 +08:00
parent 482a76081c
commit 9505a62204
8 changed files with 93 additions and 80 deletions
@@ -43,7 +43,7 @@ class TorrentHeading extends React.Component {
return null;
}
const torrentClasses = torrentStatusClasses(torrent, 'torrent-details__header');
const torrentClasses = torrentStatusClasses(torrent.status, 'torrent-details__header');
const torrentStatusIcon = torrentStatusIcons(torrent.status);
this.torrentStatus = getCurrentStatus(torrent.status);
@@ -7,7 +7,6 @@ import React from 'react';
import defaultFloodSettings from '@shared/constants/defaultFloodSettings';
import type {FloodSettings} from '@shared/types/FloodSettings';
import type {TorrentProperties} from '@shared/types/Torrent';
import {Button} from '../../ui';
import ClientStatusStore from '../../stores/ClientStatusStore';
@@ -53,9 +52,9 @@ const getEmptyTorrentListNotification = (): React.ReactNode => {
);
};
const handleClick = (torrent: TorrentProperties, event: React.MouseEvent) =>
UIActions.handleTorrentClick({hash: torrent.hash, event});
const handleDoubleClick = (torrent: TorrentProperties) => TorrentListContextMenu.handleDetailsClick(torrent);
const handleClick = (hash: string, event: React.MouseEvent) => UIActions.handleTorrentClick({hash, event});
const handleDoubleClick = (hash: string) => TorrentListContextMenu.handleDetailsClick(hash);
@observer
class TorrentList extends React.Component<WrappedComponentProps> {
listContainer: HTMLDivElement | null = null;
@@ -82,7 +81,7 @@ class TorrentList extends React.Component<WrappedComponentProps> {
});
};
handleContextMenuClick = (torrent: TorrentProperties, event: React.MouseEvent | React.TouchEvent) => {
handleContextMenuClick = (hash: string, event: React.MouseEvent | React.TouchEvent) => {
if (event.cancelable === true) {
event.preventDefault();
}
@@ -92,11 +91,12 @@ class TorrentList extends React.Component<WrappedComponentProps> {
const touchClientX = ((event as unknown) as TouchEvent).touches?.[0].clientX;
const touchClientY = ((event as unknown) as TouchEvent).touches?.[0].clientY;
if (!TorrentStore.selectedTorrents.includes(torrent.hash)) {
UIActions.handleTorrentClick({hash: torrent.hash, event});
if (!TorrentStore.selectedTorrents.includes(hash)) {
UIActions.handleTorrentClick({hash, event});
}
const {torrentContextMenuActions = defaultFloodSettings.torrentContextMenuActions} = SettingStore.floodSettings;
const torrent = TorrentStore.torrents[hash];
UIActions.displayContextMenu({
id: 'torrent-list-item',
@@ -1,23 +1,32 @@
import classnames from 'classnames';
import {observer} from 'mobx-react';
import React from 'react';
import type {TorrentProperties} from '@shared/types/Torrent';
import DetailNotAvailableIcon from '../icons/DetailNotAvailableIcon';
import {getTorrentListCellContent} from '../../util/torrentListCellContents';
import torrentPropertyIcons from '../../util/torrentPropertyIcons';
import TorrentStore from '../../stores/TorrentStore';
import type {TorrentListColumn} from '../../constants/TorrentListColumns';
interface TorrentListCellProps {
content: React.ReactNode;
hash: string;
column: TorrentListColumn;
content?: (torrent: TorrentProperties, column: TorrentListColumn) => React.ReactNode;
className?: string;
classNameOverride?: boolean;
width?: number;
showIcon?: boolean;
}
const TorrentListCell: React.FC<TorrentListCellProps> = ({
hash,
content,
column,
className,
classNameOverride,
width,
showIcon,
}: TorrentListCellProps) => {
@@ -25,18 +34,20 @@ const TorrentListCell: React.FC<TorrentListCellProps> = ({
return (
<div
className={classnames('torrent__detail', `torrent__detail--${column}`, className)}
className={classNameOverride ? className : classnames('torrent__detail', `torrent__detail--${column}`, className)}
style={{width: `${width}px`}}>
{icon}
{content || <DetailNotAvailableIcon />}
{content?.(TorrentStore.torrents[hash], column) || <DetailNotAvailableIcon />}
</div>
);
};
TorrentListCell.defaultProps = {
className: undefined,
classNameOverride: false,
content: getTorrentListCellContent,
width: undefined,
showIcon: false,
};
export default React.memo(TorrentListCell);
export default observer(TorrentListCell);
@@ -17,10 +17,10 @@ import type {TorrentContextMenuAction} from '../../constants/TorrentContextMenuA
const priorityMeterRef: React.RefObject<PriorityMeterType> = React.createRef();
let prioritySelected = 1;
const handleDetailsClick = (torrent: TorrentProperties): void => {
const handleDetailsClick = (hash: string): void => {
UIActions.displayModal({
id: 'torrent-details',
hash: torrent.hash,
hash,
});
};
@@ -66,10 +66,10 @@ const handleItemClick = (action: TorrentContextMenuAction, event: React.MouseEve
UIActions.displayModal({id: 'move-torrents'});
break;
case 'torrentDetails':
handleDetailsClick(TorrentStore.torrents[selectedTorrents.pop() as string]);
handleDetailsClick(selectedTorrents[selectedTorrents.length - 1]);
break;
case 'torrentDownload':
handleTorrentDownload(TorrentStore.torrents[selectedTorrents.pop() as string], event);
handleTorrentDownload(TorrentStore.torrents[selectedTorrents[selectedTorrents.length - 1]], event);
break;
case 'setPriority':
if (priorityMeterRef.current != null) {
@@ -3,8 +3,6 @@ import {LongPressDetectEvents, useLongPress} from 'use-long-press';
import {observer} from 'mobx-react';
import React from 'react';
import type {TorrentProperties} from '@shared/types/Torrent';
import SettingStore from '../../stores/SettingStore';
import torrentStatusClasses from '../../util/torrentStatusClasses';
import TorrentStore from '../../stores/TorrentStore';
@@ -14,21 +12,20 @@ import TorrentListRowExpanded from './TorrentListRowExpanded';
interface TorrentListRowProps {
hash: string;
handleClick: (torrent: TorrentProperties, event: React.MouseEvent) => void;
handleDoubleClick: (torrent: TorrentProperties, event: React.MouseEvent) => void;
handleRightClick: (torrent: TorrentProperties, event: React.MouseEvent | React.TouchEvent) => void;
handleClick: (hash: string, event: React.MouseEvent) => void;
handleDoubleClick: (hash: string, event: React.MouseEvent) => void;
handleRightClick: (hash: string, event: React.MouseEvent | React.TouchEvent) => void;
}
const TorrentListRow: React.FC<TorrentListRowProps> = (props: TorrentListRowProps) => {
const {hash, handleClick, handleDoubleClick, handleRightClick} = props;
const torrent = TorrentStore.torrents[hash];
const isCondensed = SettingStore.floodSettings.torrentListViewSize === 'condensed';
const torrentClasses = torrentStatusClasses(
torrent,
TorrentStore.torrents?.[hash].status,
classnames({
'torrent--is-selected': TorrentStore.selectedTorrents.includes(torrent.hash),
'torrent--is-selected': TorrentStore.selectedTorrents.includes(hash),
'torrent--is-condensed': isCondensed,
'torrent--is-expanded': !isCondensed,
}),
@@ -38,7 +35,7 @@ const TorrentListRow: React.FC<TorrentListRowProps> = (props: TorrentListRowProp
const longPressBind = useLongPress(
(e) => {
if (e != null) {
handleRightClick(torrent, e);
handleRightClick(hash, e);
}
},
{
@@ -1,21 +1,17 @@
import {observer} from 'mobx-react';
import React from 'react';
import type {TorrentProperties} from '@shared/types/Torrent';
import {getTorrentListCellContent} from '../../util/torrentListCellContents';
import ProgressBar from '../general/ProgressBar';
import SettingStore from '../../stores/SettingStore';
import TorrentListCell from './TorrentListCell';
import torrentStatusIcons from '../../util/torrentStatusIcons';
import TorrentStore from '../../stores/TorrentStore';
interface TorrentListRowCondensedProps {
className: string;
hash: string;
handleClick: (torrent: TorrentProperties, event: React.MouseEvent) => void;
handleDoubleClick: (torrent: TorrentProperties, event: React.MouseEvent) => void;
handleRightClick: (torrent: TorrentProperties, event: React.MouseEvent) => void;
handleClick: (hash: string, event: React.MouseEvent) => void;
handleDoubleClick: (hash: string, event: React.MouseEvent) => void;
handleRightClick: (hash: string, event: React.MouseEvent) => void;
handleTouchStart: (event: React.TouchEvent) => void;
handleTouchEnd: (event: React.TouchEvent) => void;
}
@@ -33,26 +29,35 @@ const TorrentListRowCondensed = React.forwardRef<HTMLLIElement, TorrentListRowCo
}: TorrentListRowCondensedProps,
ref,
) => {
const torrent = TorrentStore.torrents[hash];
const torrentListColumns = SettingStore.floodSettings.torrentListColumns.reduce(
(accumulator: React.ReactNodeArray, {id, visible}) => {
if (!visible) {
return accumulator;
}
const content: React.ReactNode =
id === 'percentComplete' ? (
<ProgressBar percent={torrent.percentComplete} icon={torrentStatusIcons(torrent.status)} />
) : (
getTorrentListCellContent(torrent, id)
if (id === 'percentComplete') {
accumulator.push(
<TorrentListCell
className="table__cell"
key={id}
hash={hash}
column={id}
content={(torrent) => (
<ProgressBar percent={torrent.percentComplete} icon={torrentStatusIcons(torrent.status)} />
)}
width={SettingStore.floodSettings.torrentListColumnWidths[id]}
/>,
);
return accumulator;
}
accumulator.push(
<TorrentListCell
className="table__cell"
key={id}
hash={hash}
column={id}
content={content}
width={SettingStore.floodSettings.torrentListColumnWidths[id]}
/>,
);
@@ -65,9 +70,9 @@ const TorrentListRowCondensed = React.forwardRef<HTMLLIElement, TorrentListRowCo
return (
<li
className={className}
onClick={handleClick.bind(this, torrent)}
onContextMenu={handleRightClick.bind(this, torrent)}
onDoubleClick={handleDoubleClick.bind(this, torrent)}
onClick={handleClick.bind(this, hash)}
onContextMenu={handleRightClick.bind(this, hash)}
onDoubleClick={handleDoubleClick.bind(this, hash)}
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
ref={ref}>
@@ -2,22 +2,18 @@ import {FormattedNumber} from 'react-intl';
import {observer} from 'mobx-react';
import React from 'react';
import type {TorrentProperties} from '@shared/types/Torrent';
import {getTorrentListCellContent} from '../../util/torrentListCellContents';
import ProgressBar from '../general/ProgressBar';
import SettingStore from '../../stores/SettingStore';
import Size from '../general/Size';
import TorrentListCell from './TorrentListCell';
import torrentStatusIcons from '../../util/torrentStatusIcons';
import TorrentStore from '../../stores/TorrentStore';
interface TorrentListRowExpandedProps {
className: string;
hash: string;
handleClick: (torrent: TorrentProperties, event: React.MouseEvent) => void;
handleDoubleClick: (torrent: TorrentProperties, event: React.MouseEvent) => void;
handleRightClick: (torrent: TorrentProperties, event: React.MouseEvent) => void;
handleClick: (hash: string, event: React.MouseEvent) => void;
handleDoubleClick: (hash: string, event: React.MouseEvent) => void;
handleRightClick: (hash: string, event: React.MouseEvent) => void;
handleTouchStart: (event: React.TouchEvent) => void;
handleTouchEnd: (event: React.TouchEvent) => void;
}
@@ -35,42 +31,49 @@ const TorrentListRowExpanded = React.forwardRef<HTMLLIElement, TorrentListRowExp
}: TorrentListRowExpandedProps,
ref,
) => {
const torrent = TorrentStore.torrents[hash];
const columns = SettingStore.floodSettings.torrentListColumns;
const primarySection: React.ReactNodeArray = [
<TorrentListCell
key="name"
hash={hash}
column="name"
className="torrent__details__section torrent__details__section--primary"
content={getTorrentListCellContent(torrent, 'name')}
/>,
];
const secondarySection: React.ReactNodeArray = [
<TorrentListCell key="eta" column="eta" content={getTorrentListCellContent(torrent, 'eta')} showIcon />,
<TorrentListCell
key="downRate"
column="downRate"
content={getTorrentListCellContent(torrent, 'downRate')}
showIcon
/>,
<TorrentListCell key="upRate" column="upRate" content={getTorrentListCellContent(torrent, 'upRate')} showIcon />,
<TorrentListCell key="eta" hash={hash} column="eta" showIcon />,
<TorrentListCell key="downRate" hash={hash} column="downRate" showIcon />,
<TorrentListCell key="upRate" hash={hash} column="upRate" showIcon />,
];
const tertiarySection: React.ReactNodeArray = [
<TorrentListCell
key="percentComplete"
hash={hash}
column="percentComplete"
content={
content={(torrent) => (
<span>
<FormattedNumber value={torrent.percentComplete} />
<em className="unit">%</em>
&nbsp;&mdash;&nbsp;
<Size value={torrent.downTotal} />
</span>
}
)}
showIcon
/>,
];
const quaternarySection: React.ReactNodeArray = [
<TorrentListCell
key="percentBar"
hash={hash}
column="percentComplete"
content={(torrent) => (
<ProgressBar percent={torrent.percentComplete} icon={torrentStatusIcons(torrent.status)} />
)}
className="torrent__details__section torrent__details__section--quaternary"
classNameOverride
/>,
];
// Using a for loop to maximize performance.
for (let index = 0; index < columns.length; index += 1) {
@@ -88,9 +91,7 @@ const TorrentListRowExpanded = React.forwardRef<HTMLLIElement, TorrentListRowExp
case 'percentComplete':
break;
default:
tertiarySection.push(
<TorrentListCell key={id} column={id} content={getTorrentListCellContent(torrent, id)} showIcon />,
);
tertiarySection.push(<TorrentListCell key={id} hash={hash} column={id} showIcon />);
break;
}
}
@@ -99,9 +100,9 @@ const TorrentListRowExpanded = React.forwardRef<HTMLLIElement, TorrentListRowExp
return (
<li
className={className}
onClick={handleClick.bind(this, torrent)}
onContextMenu={handleRightClick.bind(this, torrent)}
onDoubleClick={handleDoubleClick.bind(this, torrent)}
onClick={handleClick.bind(this, hash)}
onContextMenu={handleRightClick.bind(this, hash)}
onDoubleClick={handleDoubleClick.bind(this, hash)}
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
ref={ref}>
@@ -110,9 +111,7 @@ const TorrentListRowExpanded = React.forwardRef<HTMLLIElement, TorrentListRowExp
<div className="torrent__details__section torrent__details__section--secondary">{secondarySection}</div>
</div>
<div className="torrent__details__section torrent__details__section--tertiary">{tertiarySection}</div>
<div className="torrent__details__section torrent__details__section--quaternary">
<ProgressBar percent={torrent.percentComplete} icon={torrentStatusIcons(torrent.status)} />
</div>
{quaternarySection}
</li>
);
},
@@ -1,17 +1,18 @@
import classnames from 'classnames';
import type {TorrentStatus} from '@shared/constants/torrentStatusMap';
function torrentStatusClasses(torrent: {status: Array<TorrentStatus>}, ...classes: Array<string>) {
function torrentStatusClasses(status: Array<TorrentStatus>, ...classes: Array<string>) {
return classnames(classes, {
'torrent--has-error': torrent.status.includes('error'),
'torrent--is-stopped': torrent.status.includes('stopped'),
'torrent--is-downloading': torrent.status.includes('downloading'),
'torrent--is-downloading--actively': torrent.status.includes('activelyDownloading'),
'torrent--is-uploading--actively': torrent.status.includes('activelyUploading'),
'torrent--is-seeding': torrent.status.includes('seeding'),
'torrent--is-completed': torrent.status.includes('complete'),
'torrent--is-checking': torrent.status.includes('checking'),
'torrent--is-inactive': torrent.status.includes('inactive'),
'torrent--has-error': status.includes('error'),
'torrent--is-stopped': status.includes('stopped'),
'torrent--is-downloading': status.includes('downloading'),
'torrent--is-downloading--actively': status.includes('activelyDownloading'),
'torrent--is-uploading--actively': status.includes('activelyUploading'),
'torrent--is-seeding': status.includes('seeding'),
'torrent--is-completed': status.includes('complete'),
'torrent--is-checking': status.includes('checking'),
'torrent--is-inactive': status.includes('inactive'),
});
}