diff --git a/scss/main.scss b/scss/main.scss index d0b8903..c8ea8e3 100644 --- a/scss/main.scss +++ b/scss/main.scss @@ -9,7 +9,7 @@ .torrent { &.paused { color: $gray-dark; - background: $gray-lighter; + background-color: $gray-lighter; } &.idle { @@ -25,12 +25,16 @@ } } - &.pending { - background-image: linear-gradient(to right, darken($gray-lighter, 10), $gray-lighter); + &.paused { + background-image: linear-gradient(to right, darken($gray-lighter, 20), darken($gray-lighter, 40)); + } + + &.pending, &.idle { + background-image: linear-gradient(to right, lighten($brand-info, 20), $brand-info); } &.seeding { - background-image: linear-gradient(to right, lighten($brand-info, 20), $brand-info); + background-image: linear-gradient(to right, lighten($brand-primary, 20), $brand-primary); } &.leeching { diff --git a/src/actions/filter_subscribe.js b/src/actions/filter_subscribe.js index c99cc40..76fe752 100644 --- a/src/actions/filter_subscribe.js +++ b/src/actions/filter_subscribe.js @@ -5,14 +5,14 @@ export const FILTER_UNSUBSCRIBE = 'FILTER_UNSUBSCRIBE'; export function filter_subscribe(kind='torrent', criteria=[]) { return dispatch => { - const serial = ws_send(FILTER_SUBSCRIBE, { criteria, kind }); - dispatch({ type: FILTER_SUBSCRIBE, serial }); + const serial = ws_send(FILTER_SUBSCRIBE, { kind, criteria }); + dispatch({ type: FILTER_SUBSCRIBE, serial, kind, criteria }); }; } export function filter_unsubscribe(serial) { return dispatch => { - ws_send(FILTER_UNSUBSCRIBE, { serial }); + ws_send(FILTER_UNSUBSCRIBE, { filter_serial: serial }); dispatch({ type: FILTER_UNSUBSCRIBE, serial }); }; } diff --git a/src/reducers/filter_subscribe.js b/src/reducers/filter_subscribe.js index 1d02d1b..dca672e 100644 --- a/src/reducers/filter_subscribe.js +++ b/src/reducers/filter_subscribe.js @@ -6,12 +6,19 @@ import { export default function filter_subscribe(state = [], action) { switch (action.type) { case FILTER_SUBSCRIBE: { - const { serial } = action; - return [...state, serial]; + const { serial, kind, criteria } = action; + return [ + ...state, + { + serial, + kind, + criteria + } + ]; } case FILTER_UNSUBSCRIBE: { - const { serial } = action; - return state.filter(s => s !== serial); + const { _serial } = action; + return state.filter(({ serial }) => serial !== _serial); } } return state; diff --git a/src/socket.js b/src/socket.js index 54725e1..a3b65f3 100644 --- a/src/socket.js +++ b/src/socket.js @@ -10,12 +10,13 @@ export default function ws_send(type, body, callback = null) { if (callback) { transactions[_serial] = callback; } - const msg = JSON.stringify({ + const obj = { type, serial: _serial, ...body - }); - console.log("->", msg); + }; + const msg = JSON.stringify(obj); + console.log("->", obj); ws.send(msg); return _serial; } diff --git a/src/store.js b/src/store.js index 3629363..95fd623 100644 --- a/src/store.js +++ b/src/store.js @@ -19,5 +19,6 @@ const store = createStore( ); export const dispatch = action => store.dispatch(action); +export const getState = () => store.getState(); export default store; diff --git a/src/torrent_state.js b/src/torrent_state.js new file mode 100644 index 0000000..ce30464 --- /dev/null +++ b/src/torrent_state.js @@ -0,0 +1,72 @@ +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(","); + } +} + +// TODO: invoke on page load for /torrents/:id +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); +} diff --git a/src/ui/torrent_details.js b/src/ui/torrent_details.js index f4c73af..7e401d5 100644 --- a/src/ui/torrent_details.js +++ b/src/ui/torrent_details.js @@ -1,6 +1,9 @@ import React from 'react'; import { connect } from 'react-redux'; +// TODO: use component lifecycle functions here to invoke +// torrent_state.updateSubscriptions + function torrent_details(props) { return (
diff --git a/src/ui/torrent_table.js b/src/ui/torrent_table.js index b7febf5..e582ded 100644 --- a/src/ui/torrent_table.js +++ b/src/ui/torrent_table.js @@ -1,6 +1,6 @@ import React, { Component } from 'react'; -import { push } from 'react-router-redux'; import { connect } from 'react-redux'; +import { activeTorrents, selectTorrent, selectop } from '../torrent_state'; function formatBitrate(bitrate) { if (bitrate > 1000000000) { @@ -14,44 +14,10 @@ function formatBitrate(bitrate) { } } -function activeTorrents(router) { - const { pathname } = router.location; - if (pathname.indexOf("/torrents/") !== 0) { - return []; - } else { - return pathname.slice("/torrents/".length).split(","); - } -} - -const EXCLUSIVE = 1, UNION = 2, SUBTRACT = 3; - -function selectTorrent(dispatch, t, router, action = UNION) { - let active = activeTorrents(router); - switch (action) { - case EXCLUSIVE: - active = [t.id]; - break; - case UNION: - if (active.indexOf(t.id) === -1) { - active = [...active, t.id]; - } - break; - case SUBTRACT: - if (active.indexOf(t.id) !== -1) { - active = active.filter(a => a != t.id); - } - break; - } - const url = active.length === 0 ? "/" : `/torrents/${active.join(',')}`; - if (url !== router.location) { - dispatch(push(url)); - } -} - class TorrentTable extends Component { render() { - const { torrents, router, dispatch } = this.props; - const active = activeTorrents(router); + const { torrents } = this.props; + const active = activeTorrents(); return ( @@ -81,8 +47,8 @@ class TorrentTable extends Component { type="checkbox" checked={active.indexOf(t.id) !== -1} onChange={e => - selectTorrent(dispatch, - t, router, e.target.checked ? UNION : SUBTRACT)} + selectTorrent(t, + e.target.checked ? selectop.UNION : selectop.SUBTRACT)} />
@@ -90,7 +56,7 @@ class TorrentTable extends Component { href="#" onClick={e => { e.preventDefault(); - selectTorrent(dispatch, t, router, EXCLUSIVE); + selectTorrent(t, selectop.EXCLUSIVE); }} > {t.name}