TorrentList: migrate to Functional Component

This commit is contained in:
Jesse Chan
2021-01-06 09:54:53 +08:00
parent 8cc780cfb3
commit b63b06819a
2 changed files with 167 additions and 186 deletions
@@ -1,8 +1,7 @@
import {Component, createRef, FC, ReactNode} from 'react';
import {FormattedMessage, injectIntl, WrappedComponentProps} from 'react-intl';
import {FC, ReactNode, useEffect, useRef} from 'react';
import {FormattedMessage} from 'react-intl';
import {observer} from 'mobx-react';
import {observable, reaction} from 'mobx';
import {useDropzone} from 'react-dropzone';
import {reaction} from 'mobx';
import type {FixedSizeList} from 'react-window';
@@ -18,210 +17,141 @@ import ListViewport from '../general/ListViewport';
import SettingActions from '../../actions/SettingActions';
import SettingStore from '../../stores/SettingStore';
import TableHeading from './TableHeading';
import TorrentActions from '../../actions/TorrentActions';
import TorrentFilterStore from '../../stores/TorrentFilterStore';
import TorrentListDropzone from './TorrentListDropzone';
import TorrentListRow from './TorrentListRow';
import TorrentStore from '../../stores/TorrentStore';
import type {TorrentListColumn} from '../../constants/TorrentListColumns';
const TorrentDropzone: FC<{children: ReactNode}> = ({children}: {children: ReactNode}) => {
const handleFileDrop = (files: Array<File>) => {
const filesData: Array<string> = [];
const TorrentList: FC = observer(() => {
const listHeaderRef = useRef<HTMLDivElement>(null);
const listViewportRef = useRef<FixedSizeList>(null);
const callback = (data: string) => {
filesData.push(data);
if (filesData.length === files.length && filesData[0] != null) {
TorrentActions.addTorrentsByFiles({
files: filesData as [string, ...string[]],
destination:
SettingStore.floodSettings.torrentDestinations?.[''] ?? SettingStore.clientSettings?.directoryDefault ?? '',
isBasePath: false,
start: SettingStore.floodSettings.startTorrentsOnLoad,
});
}
};
files.forEach((file) => {
const reader = new FileReader();
reader.onload = (e) => {
if (e.target?.result != null && typeof e.target.result === 'string') {
callback(e.target.result.split('base64,')[1]);
useEffect(() => {
const dispose = reaction(
() => TorrentFilterStore.filters,
() => {
if (listViewportRef.current != null) {
listViewportRef.current.scrollTo(0);
}
};
reader.readAsDataURL(file);
});
};
const {getRootProps, isDragActive} = useDropzone({
onDrop: handleFileDrop,
noClick: true,
noKeyboard: true,
});
},
);
return (
<div
{...getRootProps({onClick: (evt) => evt.preventDefault()})}
className={`dropzone dropzone--with-overlay torrents ${isDragActive ? 'dropzone--is-dragging' : ''}`}>
{children}
</div>
);
};
return dispose;
}, []);
const getEmptyTorrentListNotification = (): ReactNode => {
let clearFilters = null;
const torrents = TorrentStore.filteredTorrents;
const {torrentListViewSize = 'condensed'} = SettingStore.floodSettings;
if (TorrentFilterStore.isFilterActive) {
clearFilters = (
<div className="torrents__alert__action">
<Button
onClick={() => {
TorrentFilterStore.clearAllFilters();
}}
priority="tertiary">
<FormattedMessage id="torrents.list.clear.filters" />
</Button>
const isCondensed = torrentListViewSize === 'condensed';
const isListEmpty = torrents == null || torrents.length === 0;
let content: ReactNode = null;
let torrentListHeading: ReactNode = null;
if (!ClientStatusStore.isConnected) {
content = (
<div className="torrents__alert__wrapper">
<div className="torrents__alert">
<FormattedMessage id="torrents.list.cannot.connect" />
</div>
</div>
);
}
return (
<div className="torrents__alert__wrapper">
<div className="torrents__alert">
<FormattedMessage id="torrents.list.no.torrents" />
</div>
{clearFilters}
</div>
);
};
@observer
class TorrentList extends Component<WrappedComponentProps> {
listHeaderRef = createRef<HTMLDivElement>();
listViewportRef = createRef<FixedSizeList>();
torrentListViewportSize = observable.object<{
width: number;
height: number;
}>({
width: window.innerWidth,
height: window.innerHeight,
});
constructor(props: WrappedComponentProps) {
super(props);
reaction(() => TorrentFilterStore.filters, this.handleTorrentFilterChange);
}
handleColumnWidthChange = (column: TorrentListColumn, width: number) => {
const {torrentListColumnWidths = defaultFloodSettings.torrentListColumnWidths} = SettingStore.floodSettings;
SettingActions.saveSetting('torrentListColumnWidths', {
...torrentListColumnWidths,
[column]: width,
});
};
handleTorrentFilterChange = () => {
if (this.listViewportRef.current != null) {
this.listViewportRef.current.scrollTo(0);
}
};
handleViewportScroll = (scrollLeft: number) => {
if (this.listHeaderRef.current != null) {
this.listHeaderRef.current.scrollLeft = scrollLeft;
}
};
render() {
const torrents = TorrentStore.filteredTorrents;
const {torrentListViewSize = 'condensed'} = SettingStore.floodSettings;
const isCondensed = torrentListViewSize === 'condensed';
const isListEmpty = torrents == null || torrents.length === 0;
let content: ReactNode = null;
let torrentListHeading: ReactNode = null;
if (!ClientStatusStore.isConnected) {
content = (
<div className="torrents__alert__wrapper">
<div className="torrents__alert">
<FormattedMessage id="torrents.list.cannot.connect" />
</div>
} else if (isListEmpty || torrents == null) {
content = (
<div className="torrents__alert__wrapper">
<div className="torrents__alert">
<FormattedMessage id="torrents.list.no.torrents" />
</div>
);
} else if (isListEmpty || torrents == null) {
content = getEmptyTorrentListNotification();
} else {
if (isCondensed) {
torrentListHeading = (
<TableHeading
onCellClick={(property: TorrentListColumn) => {
const currentSort = SettingStore.floodSettings.sortTorrents;
{TorrentFilterStore.isFilterActive && (
<div className="torrents__alert__action">
<Button
onClick={() => {
TorrentFilterStore.clearAllFilters();
}}
priority="tertiary">
<FormattedMessage id="torrents.list.clear.filters" />
</Button>
</div>
)}
</div>
);
} else {
if (isCondensed) {
torrentListHeading = (
<TableHeading
onCellClick={(property: TorrentListColumn) => {
const currentSort = SettingStore.floodSettings.sortTorrents;
let nextDirection: FloodSettings['sortTorrents']['direction'] = 'asc';
let nextDirection: FloodSettings['sortTorrents']['direction'] = 'asc';
if (currentSort.property === property) {
nextDirection = currentSort.direction === 'asc' ? 'desc' : 'asc';
}
const sortBy = {
property,
direction: nextDirection,
};
SettingActions.saveSetting('sortTorrents', sortBy);
}}
onWidthsChange={this.handleColumnWidthChange}
ref={this.listHeaderRef}
/>
);
}
// itemSize must sync with styles &--is-condensed and &--is-expanded
content = (
<ListViewport
className="torrent__list__viewport"
itemRenderer={({index, style}) => {
const {hash} = TorrentStore.filteredTorrents[index];
return <TorrentListRow key={hash} style={style} hash={hash} />;
}}
itemSize={isCondensed ? 30 : 70}
listLength={torrents.length}
ref={this.listViewportRef}
outerRef={(ref) => {
const viewportDiv = ref;
if (viewportDiv != null && viewportDiv.onscroll == null) {
viewportDiv.onscroll = () => {
this.handleViewportScroll(viewportDiv.scrollLeft);
};
if (currentSort.property === property) {
nextDirection = currentSort.direction === 'asc' ? 'desc' : 'asc';
}
const sortBy = {
property,
direction: nextDirection,
};
SettingActions.saveSetting('sortTorrents', sortBy);
}}
onWidthsChange={(column: TorrentListColumn, width: number) => {
const {torrentListColumnWidths = defaultFloodSettings.torrentListColumnWidths} = SettingStore.floodSettings;
SettingActions.saveSetting('torrentListColumnWidths', {
...torrentListColumnWidths,
[column]: width,
});
}}
ref={listHeaderRef}
/>
);
}
return (
<TorrentDropzone>
<div className="torrent__list__wrapper">
<ContextMenuMountPoint id="torrent-list-item" />
{torrentListHeading}
{content}
</div>
<div className="dropzone__overlay">
<div className="dropzone__copy">
<div className="dropzone__icon">
<Files />
</div>
<FormattedMessage id="torrents.list.drop" />
</div>
</div>
</TorrentDropzone>
// itemSize must sync with styles &--is-condensed and &--is-expanded
content = (
<ListViewport
className="torrent__list__viewport"
itemRenderer={({index, style}) => {
const {hash} = TorrentStore.filteredTorrents[index];
return <TorrentListRow key={hash} style={style} hash={hash} />;
}}
itemSize={isCondensed ? 30 : 70}
listLength={torrents.length}
ref={listViewportRef}
outerRef={(ref) => {
const viewportDiv = ref;
if (viewportDiv != null && viewportDiv.onscroll == null) {
viewportDiv.onscroll = () => {
if (listHeaderRef.current != null) {
listHeaderRef.current.scrollLeft = viewportDiv.scrollLeft;
}
};
}
}}
/>
);
}
}
export default injectIntl(TorrentList);
return (
<TorrentListDropzone>
<div className="torrent__list__wrapper">
<ContextMenuMountPoint id="torrent-list-item" />
{torrentListHeading}
{content}
</div>
<div className="dropzone__overlay">
<div className="dropzone__copy">
<div className="dropzone__icon">
<Files />
</div>
<FormattedMessage id="torrents.list.drop" />
</div>
</div>
</TorrentListDropzone>
);
});
export default TorrentList;
@@ -0,0 +1,51 @@
import {FC, ReactNode} from 'react';
import {useDropzone} from 'react-dropzone';
import SettingStore from '../../stores/SettingStore';
import TorrentActions from '../../actions/TorrentActions';
const handleFileDrop = (files: Array<File>) => {
const filesData: Array<string> = [];
const callback = (data: string) => {
filesData.push(data);
if (filesData.length === files.length && filesData[0] != null) {
TorrentActions.addTorrentsByFiles({
files: filesData as [string, ...string[]],
destination:
SettingStore.floodSettings.torrentDestinations?.[''] ?? SettingStore.clientSettings?.directoryDefault ?? '',
isBasePath: false,
start: SettingStore.floodSettings.startTorrentsOnLoad,
});
}
};
files.forEach((file) => {
const reader = new FileReader();
reader.onload = (e) => {
if (e.target?.result != null && typeof e.target.result === 'string') {
callback(e.target.result.split('base64,')[1]);
}
};
reader.readAsDataURL(file);
});
};
const TorrentListDropzone: FC<{children: ReactNode}> = ({children}: {children: ReactNode}) => {
const {getRootProps, isDragActive} = useDropzone({
onDrop: handleFileDrop,
noClick: true,
noKeyboard: true,
});
return (
<div
{...getRootProps({onClick: (evt) => evt.preventDefault()})}
className={`dropzone dropzone--with-overlay torrents ${isDragActive ? 'dropzone--is-dragging' : ''}`}>
{children}
</div>
);
};
export default TorrentListDropzone;