mirror of
https://github.com/zoriya/flood.git
synced 2026-06-06 20:12:19 +00:00
TorrentList: migrate to Functional Component
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user