Add search help page

This commit is contained in:
Drew DeVault 2018-02-10 21:18:29 -05:00
parent 85f9b76176
commit ff86be7276
5 changed files with 113 additions and 23 deletions

View File

@ -12,7 +12,7 @@ import scss from '../scss/main.scss';
import { ws_init } from './socket'; import { ws_init } from './socket';
import { filter_subscribe } from './actions/filter_subscribe'; import { filter_subscribe } from './actions/filter_subscribe';
import { socket_uri, socket_update, SOCKET_STATE } from './actions/socket'; import { socket_uri, socket_update, SOCKET_STATE } from './actions/socket';
import search_criteria from './search'; import { search_criteria } from './search';
import Main from './ui/main'; import Main from './ui/main';
import Connection from './ui/connection'; import Connection from './ui/connection';

View File

@ -1,4 +1,7 @@
import numeral from "numeral"; import numeral from "numeral";
import query from 'query-string';
import { filter_subscribe } from './actions/filter_subscribe';
import { push_query } from './actions/routing';
// via https://stackoverflow.com/a/46946490 // via https://stackoverflow.com/a/46946490
const ssplit = str => str.match(/\\?.|^$/g).reduce((p, c) => { const ssplit = str => str.match(/\\?.|^$/g).reduce((p, c) => {
@ -12,7 +15,7 @@ const ssplit = str => str.match(/\\?.|^$/g).reduce((p, c) => {
return p; return p;
}, {a: ['']}).a; }, {a: ['']}).a;
export default function search_criteria(text) { export function search_criteria(text) {
if (!text) { if (!text) {
return []; return [];
} }
@ -41,3 +44,21 @@ export default function search_criteria(text) {
} }
); );
} }
export function search_qs(text) {
const qs = query.stringify({
...query.parse(location.search),
s: text || undefined
});
return `${
location.pathname === "/" ? location.pathname : ""
}${qs && "?" + qs}`;
}
export function update_filter(text, fs, location, dispatch) {
// there will always be one torrent filter
const tfilter = fs.filter(fs => fs.kind === "torrent")[0];
const criteria = search_criteria(text);
dispatch(filter_subscribe("torrent", criteria, tfilter.serial));
dispatch(push_query(search_qs(text)));
}

View File

@ -6,6 +6,7 @@ import TorrentTable from './torrent_table';
import AddTorrent from './add_torrent'; import AddTorrent from './add_torrent';
import TorrentDetails from './torrent_details'; import TorrentDetails from './torrent_details';
import Server from './server'; import Server from './server';
import SearchHelp from './search_help';
import ConnectionOverlay from './connection'; import ConnectionOverlay from './connection';
export default class Main extends Component { export default class Main extends Component {
@ -20,6 +21,7 @@ export default class Main extends Component {
</div> </div>
<div className="col-md-4"> <div className="col-md-4">
<Route exact path="/add-torrent" component={AddTorrent} /> <Route exact path="/add-torrent" component={AddTorrent} />
<Route path="/search-help" component={SearchHelp} />
<Route path="/add-torrent/:magnet" component={AddTorrent} /> <Route path="/add-torrent/:magnet" component={AddTorrent} />
<Route path="/torrents/:ids" component={TorrentDetails} /> <Route path="/torrents/:ids" component={TorrentDetails} />
<Route exact path="/" component={Server} /> <Route exact path="/" component={Server} />

View File

@ -1,28 +1,9 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Link, NavLink } from 'react-router-dom'; import { Link, NavLink } from 'react-router-dom';
import { filter_subscribe } from '../actions/filter_subscribe'; import FontAwesome from 'react-fontawesome';
import { push_query } from '../actions/routing';
import query from 'query-string'; import query from 'query-string';
import search_criteria from '../search'; import { search_criteria, search_qs, update_filter } from '../search';
function search_qs(text) {
const qs = query.stringify({
...query.parse(location.search),
s: text || undefined
});
return `${
location.pathname === "/" ? location.pathname : ""
}${qs && "?" + qs}`;
}
function update_filter(text, fs, location, dispatch) {
// there will always be one torrent filter
const tfilter = fs.filter(fs => fs.kind === "torrent")[0];
const criteria = search_criteria(text);
dispatch(filter_subscribe("torrent", criteria, tfilter.serial));
dispatch(push_query(search_qs(text)));
}
function render(props) { function render(props) {
const { dispatch, router } = props; const { dispatch, router } = props;
@ -65,6 +46,11 @@ function render(props) {
value={qs.s} value={qs.s}
onChange={e => update(e.target.value)} /> onChange={e => update(e.target.value)} />
</form> </form>
<span class="navbar-text">
<Link to="/search-help">
<FontAwesome name="question-circle" />
</Link>
</span>
</ul> </ul>
</div> </div>
</nav>; </nav>;

81
src/ui/search_help.js Normal file
View File

@ -0,0 +1,81 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import query from 'query-string';
import { search_criteria, search_qs, update_filter } from '../search';
function SearchHelp(props) {
const { dispatch, router } = props;
const qs = query.parse(router.location.search);
const update = text => update_filter(
text || "", props.filter_subscribe, router.location, dispatch);
const navto = where => e => {
e.preventDefault();
update(where);
};
const searchLink = target =>
<a href={search_qs(target)} onClick={navto(target)} >{target}</a>;
const searchableProps = {
"name": "torrent name",
"path": "download path",
"status": "paused | pending | leeching | idle | seeding | hashing | error",
"size": "size in bytes",
"progress": "progress from 0 to 1",
"priority": "1-5, low to high",
"availability": "how much is available from the swarm, 0 to 1",
"rate_up": "rate up in bits/sec",
"rate_down": "rate down in bits/sec",
"throttle_up": "throttle up in bits/sec",
"throttle_down": "throttle down in bits/sec",
"transferred_up": "total transfer up in bits/sec",
"transferred_down": "total transfer down in bits/sec",
"peers": "number of peers",
"files": "number of files",
};
return (
<div>
<h3>
Search help
</h3>
<p>
You can search for torrents using the search box at the top of the
page. By default, it will search through torrent titles. You can also
search other properties of torrents with
<code>property<strong>operator</strong>value</code>, where operator is
one of the following:
</p>
<ul>
<li><code>:</code> matches values that contain <code>value</code></li>
<li><code>==</code> matches values that are equal to <code>value</code></li>
<li><code>!=</code> matches values that are not equal to <code>value</code></li>
<li><code>></code> matches values that are greater than <code>value</code></li>
<li><code>>=</code> matches values that are greater than or equal to <code>value</code></li>
<li><code>&lt;</code> matches values that are less than <code>value</code></li>
<li><code>&lt;=</code> matches values that are less than or equal to <code>value</code></li>
</ul>
<p>
The following torrent properties are searchable:
</p>
<ul>
{Object.keys(searchableProps).map(key =>
<li><strong>{key}</strong>: {searchableProps[key]}</li>)}
</ul>
<p>
Here are some example searches:
</p>
<ul>
<li>{searchLink("state:seeding")}</li>
<li>{searchLink("state:leeching")}</li>
<li>{searchLink("rate_up>1048576")}</li>
<li>{searchLink("files>1")}</li>
<li>{searchLink("files==1")}</li>
<li>{searchLink("progress<0.1")}</li>
</ul>
</div>
);
}
export default connect(state => ({
filter_subscribe: state.filter_subscribe,
router: state.router,
}))(SearchHelp);