Merge pull request #33 from Luminarys/master
Improve reducing and rendering performance
This commit is contained in:
commit
d15c787469
|
@ -12,21 +12,17 @@ function hack(old, _new) {
|
||||||
|
|
||||||
export default function resourceReducer(type) {
|
export default function resourceReducer(type) {
|
||||||
return (state = {}, action) => {
|
return (state = {}, action) => {
|
||||||
|
let ns = {...state};
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case UPDATE_RESOURCES:
|
case UPDATE_RESOURCES:
|
||||||
return {
|
action.resources
|
||||||
...state,
|
.filter(r => r.type ===type)
|
||||||
...action.resources
|
.map(r => ns[r.id] = hack(state[r.id], { ...state[r.id], ...r }));
|
||||||
.filter(r => r.type === type)
|
return ns;
|
||||||
.reduce((s, r) => ({
|
|
||||||
...s,
|
|
||||||
[r.id]: hack(state[r.id], { ...state[r.id], ...r })
|
|
||||||
}), {})
|
|
||||||
};
|
|
||||||
case RESOURCES_REMOVED:
|
case RESOURCES_REMOVED:
|
||||||
return Object.values(state)
|
action.ids
|
||||||
.filter(r => action.ids.indexOf(r.id) === -1)
|
.map(id => delete ns[id]);
|
||||||
.reduce((s, r) => ({ ...s, [r.id]: r }), {});
|
return ns;
|
||||||
case SOCKET_UPDATE:
|
case SOCKET_UPDATE:
|
||||||
const _state = action.state;
|
const _state = action.state;
|
||||||
return _state === SOCKET_STATE.CONNECTING ? {} : state;
|
return _state === SOCKET_STATE.CONNECTING ? {} : state;
|
||||||
|
|
|
@ -8,12 +8,12 @@ export default function subscribe(state = [], action) {
|
||||||
return [ ...state, ...ids.map(id => ({ serial, id })) ];
|
return [ ...state, ...ids.map(id => ({ serial, id })) ];
|
||||||
}
|
}
|
||||||
case UNSUBSCRIBE: {
|
case UNSUBSCRIBE: {
|
||||||
const { ids } = action;
|
const ids = new Set(action.ids);
|
||||||
return state.filter(sub => ids.indexOf(sub.id) === -1);
|
return state.filter(sub => !ids.has(sub.id));
|
||||||
}
|
}
|
||||||
case RESOURCES_REMOVED: {
|
case RESOURCES_REMOVED: {
|
||||||
const { ids } = action;
|
const ids = new Set(action.ids);
|
||||||
return state.filter(sub => ids.indexOf(sub.id) === -1);
|
return state.filter(sub => !ids.has(sub.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
|
|
|
@ -35,55 +35,69 @@ function basename(path) {
|
||||||
return parts[parts.length - 1];
|
return parts[parts.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
function File({ dispatch, file }) {
|
class File extends Component {
|
||||||
const { uri } = store.getState().socket;
|
shouldComponentUpdate(nextProps, _) {
|
||||||
const { download_token } = store.getState().server;
|
return nextProps.file !== this.props.file;
|
||||||
return (
|
}
|
||||||
<div className="file">
|
|
||||||
<Progress
|
render() {
|
||||||
value={file.progress * 100}
|
const { dispatch, file } = this.props;
|
||||||
color={file.progress != 1.0 ? "success" : "primary"}
|
const { uri } = store.getState().socket;
|
||||||
>
|
const { download_token } = store.getState().server;
|
||||||
{file.progress === 1.0 ?
|
return (
|
||||||
"done" : `${(file.progress * 100).toFixed(0)}%`}
|
<div className="file">
|
||||||
</Progress>
|
<Progress
|
||||||
<div className="path" title={file.path}>
|
value={file.progress * 100}
|
||||||
{file.progress === 1.0 ?
|
color={file.progress != 1.0 ? "success" : "primary"}
|
||||||
<a href={dlURI(uri, download_token, file.id)} target="_new">
|
|
||||||
{basename(file.path)}
|
|
||||||
</a> : basename(file.path)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Input
|
|
||||||
type="select"
|
|
||||||
id="priority"
|
|
||||||
value={file.priority}
|
|
||||||
onChange={e => dispatch(updateResource({
|
|
||||||
id: file.id,
|
|
||||||
priority: parseInt(e.target.value)
|
|
||||||
}))}
|
|
||||||
>
|
>
|
||||||
<option value="0">Skip</option>
|
{file.progress === 1.0 ?
|
||||||
<option value="1">Lowest</option>
|
"done" : `${(file.progress * 100).toFixed(0)}%`}
|
||||||
<option value="2">Low</option>
|
</Progress>
|
||||||
<option value="3">Normal</option>
|
<div className="path" title={file.path}>
|
||||||
<option value="4">High</option>
|
{file.progress === 1.0 ?
|
||||||
<option value="5">Highest</option>
|
<a href={dlURI(uri, download_token, file.id)} target="_new">
|
||||||
</Input>
|
{basename(file.path)}
|
||||||
|
</a> : basename(file.path)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
type="select"
|
||||||
|
id="priority"
|
||||||
|
value={file.priority}
|
||||||
|
onChange={e => dispatch(updateResource({
|
||||||
|
id: file.id,
|
||||||
|
priority: parseInt(e.target.value)
|
||||||
|
}))}
|
||||||
|
>
|
||||||
|
<option value="0">Skip</option>
|
||||||
|
<option value="1">Lowest</option>
|
||||||
|
<option value="2">Low</option>
|
||||||
|
<option value="3">Normal</option>
|
||||||
|
<option value="4">High</option>
|
||||||
|
<option value="5">Highest</option>
|
||||||
|
</Input>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Peer({ peer }) {
|
class Peer extends Component {
|
||||||
return (
|
shouldComponentUpdate(nextProps, _) {
|
||||||
<div className="peer">
|
return nextProps.peer !== this.props.peer;
|
||||||
<div style={{flexGrow: 5}}>{peer.ip}</div>
|
}
|
||||||
<div style={{flexBasis: "18%"}}>{formatBitrate(peer.rate_up)} up</div>
|
|
||||||
<div style={{flexBasis: "18%"}}>{formatBitrate(peer.rate_down)} down</div>
|
render() {
|
||||||
<div style={{flexBasis: "18%"}}>has {`${(peer.availability * 100).toFixed(0)}%`}</div>
|
const { peer } = this.props;
|
||||||
</div>
|
return (
|
||||||
);
|
<div className="peer">
|
||||||
|
<div style={{flexGrow: 5}}>{peer.ip}</div>
|
||||||
|
<div style={{flexBasis: "18%"}}>{formatBitrate(peer.rate_up)} up</div>
|
||||||
|
<div style={{flexBasis: "18%"}}>{formatBitrate(peer.rate_down)} down</div>
|
||||||
|
<div style={{flexBasis: "18%"}}>has {`${(peer.availability * 100).toFixed(0)}%`}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to separate component
|
// TODO: move to separate component
|
||||||
|
@ -114,6 +128,14 @@ class Torrent extends Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
return nextProps.torrent !== this.props.torrent
|
||||||
|
|| nextState.peersShown !== this.state.peersShown
|
||||||
|
|| nextState.trackersShown !== this.state.trackersShown
|
||||||
|
|| nextState.filesShown !== this.state.filesShown
|
||||||
|
|| nextState.infoShown !== this.state.infoShown;
|
||||||
|
}
|
||||||
|
|
||||||
toggleTorrentState(torrent) {
|
toggleTorrentState(torrent) {
|
||||||
if (torrent.status === "paused") {
|
if (torrent.status === "paused") {
|
||||||
ws_send("RESUME_TORRENT", { id: torrent.id });
|
ws_send("RESUME_TORRENT", { id: torrent.id });
|
||||||
|
@ -222,9 +244,11 @@ class Torrent extends Component {
|
||||||
<Card style={{marginBottom: "1rem"}}>
|
<Card style={{marginBottom: "1rem"}}>
|
||||||
<CardBlock style={{padding: "0"}}>
|
<CardBlock style={{padding: "0"}}>
|
||||||
<div className="files flex-table" style={{marginBottom: "0"}}>
|
<div className="files flex-table" style={{marginBottom: "0"}}>
|
||||||
{files.slice().sort((a, b) =>
|
{this.state.filesShown
|
||||||
a.path.localeCompare(b.path)).map(file =>
|
? files.slice()
|
||||||
<File dispatch={dispatch} file={file} />)}
|
.sort((a, b) => a.path.localeCompare(b.path))
|
||||||
|
.map(file => <File dispatch={dispatch} file={file}/>)
|
||||||
|
: null}
|
||||||
</div>
|
</div>
|
||||||
</CardBlock>
|
</CardBlock>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -264,7 +288,7 @@ class Torrent extends Component {
|
||||||
<Card style={{marginBottom: "1rem"}}>
|
<Card style={{marginBottom: "1rem"}}>
|
||||||
<CardBlock style={{padding: "0"}}>
|
<CardBlock style={{padding: "0"}}>
|
||||||
<div className="peers flex-table" style={{marginBottom: "0"}}>
|
<div className="peers flex-table" style={{marginBottom: "0"}}>
|
||||||
{peers.map(peer => <Peer peer={peer} />)}
|
{this.state.peersShown ? peers.map(peer => <Peer peer={peer} />) : null}
|
||||||
</div>
|
</div>
|
||||||
{peers.length === 0 &&
|
{peers.length === 0 &&
|
||||||
<div style={{padding: "0.5rem 0 0 0.5rem"}}>
|
<div style={{padding: "0.5rem 0 0 0.5rem"}}>
|
||||||
|
@ -361,15 +385,21 @@ class TorrentDetails extends Component {
|
||||||
selection,
|
selection,
|
||||||
dispatch
|
dispatch
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const _files = Object.values(files).reduce((s, f) => ({
|
const index_by_tid = (res) => {
|
||||||
...s, [f.torrent_id]: [...(s[f.torrent_id] || []), f]
|
let _indexed = {};
|
||||||
}), {});
|
Object.values(res).map(r => {
|
||||||
const _trackers = Object.values(trackers).reduce((s, t) => ({
|
if (!(r.torrent_id in _indexed)) {
|
||||||
...s, [t.torrent_id]: [...(s[t.torrent_id] || []), t]
|
_indexed[r.torrent_id] = [];
|
||||||
}), {});
|
}
|
||||||
const _peers = Object.values(peers).reduce((s, p) => ({
|
_indexed[r.torrent_id].push(r);
|
||||||
...s, [p.torrent_id]: [...(s[p.torrent_id] || []), p]
|
});
|
||||||
}), {});
|
return _indexed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _files = index_by_tid(files);
|
||||||
|
const _trackers = index_by_tid(trackers);
|
||||||
|
const _peers = index_by_tid(peers);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{selection.length > 1 ? this.renderHeader.bind(this)() : null}
|
{selection.length > 1 ? this.renderHeader.bind(this)() : null}
|
||||||
|
|
|
@ -12,55 +12,7 @@ const name_style = {
|
||||||
whiteSpace: 'nowrap'
|
whiteSpace: 'nowrap'
|
||||||
};
|
};
|
||||||
|
|
||||||
class TorrentTable extends Component {
|
class _Torrent extends Component {
|
||||||
render() {
|
|
||||||
const { selection, torrents, dispatch } = this.props;
|
|
||||||
return (
|
|
||||||
<table className="table torrents">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th style={{width: "1px"}}>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={selection.length === Object.values(torrents).length}
|
|
||||||
onChange={e => {
|
|
||||||
if (selection.length > 0) {
|
|
||||||
dispatch(selectTorrent([], EXCLUSIVE));
|
|
||||||
} else {
|
|
||||||
dispatch(selectTorrent(Object.keys(torrents), EXCLUSIVE));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</th>
|
|
||||||
<th style={name_style}>name</th>
|
|
||||||
<th style={{ minWidth: '75px' }}>up</th>
|
|
||||||
<th style={{ minWidth: '75px' }}>down</th>
|
|
||||||
<th style={{width: "18rem"}}>
|
|
||||||
<span class="ratio">
|
|
||||||
<span>ratio</span>
|
|
||||||
<span></span>
|
|
||||||
<span></span>
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
<th>progress</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{Object.values(torrents).slice().sort((a, b) => a.name.localeCompare(b.name)).map(t =>
|
|
||||||
<Torrent
|
|
||||||
dispatch={dispatch}
|
|
||||||
torrent={t}
|
|
||||||
selection={selection}
|
|
||||||
key={t.id}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Torrent extends Component {
|
|
||||||
shouldComponentUpdate(nextProps, _) {
|
shouldComponentUpdate(nextProps, _) {
|
||||||
const { selection, torrent } = this.props;
|
const { selection, torrent } = this.props;
|
||||||
const nt = nextProps.torrent;
|
const nt = nextProps.torrent;
|
||||||
|
@ -111,6 +63,60 @@ class Torrent extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Torrent = connect((state, props) => {
|
||||||
|
return {
|
||||||
|
torrent: state.torrents[props.id],
|
||||||
|
selection: state.selection,
|
||||||
|
};
|
||||||
|
})(_Torrent);
|
||||||
|
|
||||||
|
class TorrentTable extends Component {
|
||||||
|
render() {
|
||||||
|
const { selection, torrents, dispatch } = this.props;
|
||||||
|
return (
|
||||||
|
<table className="table torrents">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{width: "1px"}}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selection.length === Object.values(torrents).length}
|
||||||
|
onChange={e => {
|
||||||
|
if (selection.length > 0) {
|
||||||
|
dispatch(selectTorrent([], EXCLUSIVE));
|
||||||
|
} else {
|
||||||
|
dispatch(selectTorrent(Object.keys(torrents), EXCLUSIVE));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
<th style={name_style}>name</th>
|
||||||
|
<th style={{ minWidth: '75px' }}>up</th>
|
||||||
|
<th style={{ minWidth: '75px' }}>down</th>
|
||||||
|
<th style={{width: "18rem"}}>
|
||||||
|
<span class="ratio">
|
||||||
|
<span>ratio</span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</th>
|
||||||
|
<th>progress</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{Object.values(torrents).slice().sort((a, b) => a.name.localeCompare(b.name)).map(t =>
|
||||||
|
<Torrent
|
||||||
|
dispatch={dispatch}
|
||||||
|
id={t.id}
|
||||||
|
key={t.id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default connect(state => ({
|
export default connect(state => ({
|
||||||
torrents: state.torrents,
|
torrents: state.torrents,
|
||||||
selection: state.selection,
|
selection: state.selection,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user