Refactor torrent selection state management
Now reduxified and more consistent
This commit is contained in:
parent
ce3653c294
commit
6983703672
74
src/actions/selection.js
Normal file
74
src/actions/selection.js
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import { filter_subscribe, filter_unsubscribe } from './filter_subscribe';
|
||||||
|
import { unsubscribe } from './subscribe';
|
||||||
|
import { push } from 'react-router-redux';
|
||||||
|
|
||||||
|
export const UNION = 'UNION';
|
||||||
|
export const SUBTRACT = 'SUBTRACT';
|
||||||
|
export const EXCLUSIVE = 'EXCLUSIVE';
|
||||||
|
|
||||||
|
Set.prototype.difference = function(set) {
|
||||||
|
var diff = new Set(this);
|
||||||
|
for (var v of set) {
|
||||||
|
diff.delete(v);
|
||||||
|
}
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function selectTorrent(id, action) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const previous = new Set(getState().selection);
|
||||||
|
dispatch({ type: action, id });
|
||||||
|
|
||||||
|
const state = getState();
|
||||||
|
const next = new Set(state.selection);
|
||||||
|
const filter_subscriptions = state.filter_subscribe;
|
||||||
|
const subscriptions = state.subscribe;
|
||||||
|
const { peers, files, pieces, trackers } = state;
|
||||||
|
|
||||||
|
const added = next.difference(previous);
|
||||||
|
const removed = previous.difference(next);
|
||||||
|
|
||||||
|
added.forEach(t => {
|
||||||
|
const criteria = [
|
||||||
|
{ field: "torrent_id", op: "==", value: id }
|
||||||
|
];
|
||||||
|
dispatch(filter_subscribe("peer", criteria));
|
||||||
|
dispatch(filter_subscribe("file", criteria));
|
||||||
|
dispatch(filter_subscribe("piece", criteria));
|
||||||
|
dispatch(filter_subscribe("tracker", criteria));
|
||||||
|
});
|
||||||
|
|
||||||
|
removed.forEach(t => {
|
||||||
|
/* Remove filter subscriptions */
|
||||||
|
const serials = filter_subscriptions
|
||||||
|
.filter(sub => sub.criteria[0] && sub.criteria[0].value === t)
|
||||||
|
.map(sub => sub.serial);
|
||||||
|
serials.forEach(serial => dispatch(filter_unsubscribe(serial)));
|
||||||
|
/* Remove resource subscriptions */
|
||||||
|
const ids = [
|
||||||
|
...Object.values(files)
|
||||||
|
.filter(file => file.torrent_id === t)
|
||||||
|
.map(file => file.id)
|
||||||
|
//...Object.values(peers)
|
||||||
|
// .filter(peer => peer.torrent_id === t)
|
||||||
|
// .map(peer => peer.id),
|
||||||
|
//...Object.values(trackers)
|
||||||
|
// .filter(tracker => tracker.torrent_id === t)
|
||||||
|
// .map(tracker => tracker.id),
|
||||||
|
//...Object.values(pieces)
|
||||||
|
// .filter(piece => piece.torrent_id === t)
|
||||||
|
// .map(piece => piece.id),
|
||||||
|
];
|
||||||
|
if (ids.length > 0) {
|
||||||
|
dispatch(unsubscribe(...ids));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const url_torrents = state.selection.slice(0, 3);
|
||||||
|
if (url_torrents.length > 0) {
|
||||||
|
dispatch(push(`/torrents/${url_torrents}`));
|
||||||
|
} else {
|
||||||
|
dispatch(push("/"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -17,8 +17,8 @@ export default function filter_subscribe(state = [], action) {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
case FILTER_UNSUBSCRIBE: {
|
case FILTER_UNSUBSCRIBE: {
|
||||||
const { _serial } = action;
|
const { serial } = action;
|
||||||
return state.filter(({ serial }) => serial !== _serial);
|
return state.filter(filter => filter.serial !== serial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return state;
|
return state;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { combineReducers } from 'redux';
|
import { combineReducers } from 'redux';
|
||||||
import { routerReducer } from 'react-router-redux'
|
import { routerReducer } from 'react-router-redux'
|
||||||
|
import selection from './selection';
|
||||||
import subscribe from './subscribe';
|
import subscribe from './subscribe';
|
||||||
import filter_subscribe from './filter_subscribe';
|
import filter_subscribe from './filter_subscribe';
|
||||||
import server from './server';
|
import server from './server';
|
||||||
|
@ -7,6 +8,7 @@ import torrents from './torrents';
|
||||||
import files from './files';
|
import files from './files';
|
||||||
|
|
||||||
const root = combineReducers({
|
const root = combineReducers({
|
||||||
|
selection,
|
||||||
subscribe,
|
subscribe,
|
||||||
filter_subscribe,
|
filter_subscribe,
|
||||||
server,
|
server,
|
||||||
|
|
14
src/reducers/selection.js
Normal file
14
src/reducers/selection.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { UNION, SUBTRACT, EXCLUSIVE } from '../actions/selection';
|
||||||
|
|
||||||
|
export default function selection(state = [], action) {
|
||||||
|
const { id } = action;
|
||||||
|
switch (action.type) {
|
||||||
|
case UNION:
|
||||||
|
return [id, ...state.filter(t => t !== id)];
|
||||||
|
case SUBTRACT:
|
||||||
|
return state.filter(t => t !== id);
|
||||||
|
case EXCLUSIVE:
|
||||||
|
return [id];
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
|
@ -1,71 +0,0 @@
|
||||||
import { filter_subscribe, filter_unsubscribe } from './actions/filter_subscribe';
|
|
||||||
import { push } from 'react-router-redux';
|
|
||||||
/* Listen pal, we're just gonna pretend this import isn't happening */
|
|
||||||
import { dispatch, getState } from './store';
|
|
||||||
|
|
||||||
export function activeTorrents() {
|
|
||||||
const { pathname } = getState().router.location;
|
|
||||||
if (pathname.indexOf("/torrents/") !== 0) {
|
|
||||||
return [];
|
|
||||||
} else {
|
|
||||||
return pathname.slice("/torrents/".length).split(",");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateSubscriptions(added, removed) {
|
|
||||||
if (added.length > 0) {
|
|
||||||
added.forEach(t => {
|
|
||||||
const criteria = [
|
|
||||||
{ field: "torrent_id", op: "==", value: t }
|
|
||||||
];
|
|
||||||
dispatch(filter_subscribe("peer", criteria));
|
|
||||||
dispatch(filter_subscribe("file", criteria));
|
|
||||||
dispatch(filter_subscribe("piece", criteria));
|
|
||||||
dispatch(filter_subscribe("tracker", criteria));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (removed.length > 0) {
|
|
||||||
const subscriptions = getState().filter_subscribe;
|
|
||||||
removed.forEach(t => {
|
|
||||||
const serials = subscriptions
|
|
||||||
.filter(sub => sub.criteria[0] && sub.criteria[0].value == t)
|
|
||||||
.map(sub => sub.serial);
|
|
||||||
serials.forEach(serial => dispatch(filter_unsubscribe(serial)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const selectop = {
|
|
||||||
EXCLUSIVE: 1,
|
|
||||||
UNION: 2,
|
|
||||||
SUBTRACT: 3
|
|
||||||
};
|
|
||||||
|
|
||||||
export function selectTorrent(t, action = UNION) {
|
|
||||||
let active = activeTorrents(getState().router);
|
|
||||||
let removed = [], added = [];
|
|
||||||
switch (action) {
|
|
||||||
case selectop.EXCLUSIVE:
|
|
||||||
removed = active.slice();
|
|
||||||
active = [t.id];
|
|
||||||
added = [t.id];
|
|
||||||
break;
|
|
||||||
case selectop.UNION:
|
|
||||||
if (active.indexOf(t.id) === -1) {
|
|
||||||
active = [...active, t.id];
|
|
||||||
added = [t.id];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case selectop.SUBTRACT:
|
|
||||||
if (active.indexOf(t.id) !== -1) {
|
|
||||||
removed = [t.id];
|
|
||||||
active = active.filter(a => a != t.id);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const url = active.length === 0 ? "/" : `/torrents/${active.join(',')}`;
|
|
||||||
if (url !== getState().router.location) {
|
|
||||||
dispatch(push(url));
|
|
||||||
}
|
|
||||||
updateSubscriptions(added, removed);
|
|
||||||
}
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { activeTorrents } from '../torrent_state';
|
|
||||||
import FontAwesome from 'react-fontawesome';
|
import FontAwesome from 'react-fontawesome';
|
||||||
import {
|
import {
|
||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
|
@ -153,10 +152,10 @@ class TorrentDetails extends Component {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
renderHeader(active) {
|
renderHeader(selection) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3>{active.length} torrents</h3>
|
<h3>{selection.length} torrents</h3>
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
<button className="btn btn-default btn-sm">Pause all</button>
|
<button className="btn btn-default btn-sm">Pause all</button>
|
||||||
<button className="btn btn-default btn-sm">Resume all</button>
|
<button className="btn btn-default btn-sm">Resume all</button>
|
||||||
|
@ -178,16 +177,14 @@ class TorrentDetails extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const active = activeTorrents();
|
const { torrents, files, selection } = this.props;
|
||||||
const { torrents } = this.props;
|
|
||||||
const { files } = this.props;
|
|
||||||
const _files = Object.values(files).reduce((s, f) => ({
|
const _files = Object.values(files).reduce((s, f) => ({
|
||||||
...s, [f.torrent_id]: [...(s[f.torrent_id] || []), f]
|
...s, [f.torrent_id]: [...(s[f.torrent_id] || []), f]
|
||||||
}), {});
|
}), {});
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{active.length > 1 ? this.renderHeader(active) : null}
|
{selection.length > 1 ? this.renderHeader(selection) : null}
|
||||||
{active.map(id => <Torrent
|
{selection.map(id => <Torrent
|
||||||
torrent={torrents[id]}
|
torrent={torrents[id]}
|
||||||
files={_files[id] || []}
|
files={_files[id] || []}
|
||||||
/>)}
|
/>)}
|
||||||
|
@ -199,5 +196,6 @@ class TorrentDetails extends Component {
|
||||||
export default connect(state => ({
|
export default connect(state => ({
|
||||||
router: state.router,
|
router: state.router,
|
||||||
torrents: state.torrents,
|
torrents: state.torrents,
|
||||||
files: state.files
|
files: state.files,
|
||||||
|
selection: state.selection
|
||||||
}))(TorrentDetails);
|
}))(TorrentDetails);
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { activeTorrents, selectTorrent, selectop } from '../torrent_state';
|
import selectTorrent, { UNION, SUBTRACT, EXCLUSIVE } from '../actions/selection';
|
||||||
import { formatBitrate } from '../bitrate';
|
import { formatBitrate } from '../bitrate';
|
||||||
|
|
||||||
class TorrentTable extends Component {
|
class TorrentTable extends Component {
|
||||||
render() {
|
render() {
|
||||||
const { torrents } = this.props;
|
const { selection, torrents, dispatch } = this.props;
|
||||||
const active = activeTorrents();
|
|
||||||
return (
|
return (
|
||||||
<table className="table">
|
<table className="table">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -25,7 +24,7 @@ class TorrentTable extends Component {
|
||||||
className={`torrent progress-row ${
|
className={`torrent progress-row ${
|
||||||
t.status
|
t.status
|
||||||
} ${
|
} ${
|
||||||
active.indexOf(t.id) !== -1 ? "selected" : ""
|
selection.indexOf(t.id) !== -1 ? "selected" : ""
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
backgroundSize: `${t.progress * 100}% 3px`
|
backgroundSize: `${t.progress * 100}% 3px`
|
||||||
|
@ -34,22 +33,20 @@ class TorrentTable extends Component {
|
||||||
<td>
|
<td>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={active.indexOf(t.id) !== -1}
|
checked={selection.indexOf(t.id) !== -1}
|
||||||
onChange={e =>
|
onChange={e =>
|
||||||
selectTorrent(t,
|
dispatch(selectTorrent(t.id, e.target.checked ? UNION : SUBTRACT))
|
||||||
e.target.checked ? selectop.UNION : selectop.SUBTRACT)}
|
}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href={`/torrents/${t.id}`}
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
selectTorrent(t, selectop.EXCLUSIVE);
|
dispatch(selectTorrent(t.id, EXCLUSIVE));
|
||||||
}}
|
}}
|
||||||
>
|
>{t.name}</a>
|
||||||
{t.name}
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td>{formatBitrate(t.rate_up)}</td>
|
<td>{formatBitrate(t.rate_up)}</td>
|
||||||
<td>{formatBitrate(t.rate_down)}</td>
|
<td>{formatBitrate(t.rate_down)}</td>
|
||||||
|
@ -64,5 +61,6 @@ class TorrentTable extends Component {
|
||||||
|
|
||||||
export default connect(state => ({
|
export default connect(state => ({
|
||||||
torrents: state.torrents,
|
torrents: state.torrents,
|
||||||
|
selection: state.selection,
|
||||||
router: state.router
|
router: state.router
|
||||||
}))(TorrentTable);
|
}))(TorrentTable);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user