Дополнительные параметры поиска

This commit is contained in:
Alexey Kasyanchuk 2017-11-09 12:30:06 +03:00
parent 3e8cbd07ed
commit b947b670b3
7 changed files with 348 additions and 22 deletions

View File

@ -295,17 +295,41 @@ io.on('connection', function(socket)
let args = [text, index, limit]; let args = [text, index, limit];
const orderBy = navigation.orderBy; const orderBy = navigation.orderBy;
let order = ''; let order = '';
let where = '';
if(orderBy && orderBy.length > 0) if(orderBy && orderBy.length > 0)
{ {
const orderDesc = navigation.orderDesc ? 'DESC' : 'ASC'; const orderDesc = navigation.orderDesc ? 'DESC' : 'ASC';
args.splice(1, 0, orderBy); args.splice(1, 0, orderBy);
order = 'ORDER BY ?? ' + orderDesc; order = 'ORDER BY ?? ' + orderDesc;
} }
if(safeSearch)
{
where += " and contentcategory != 'xxx' ";
}
if(navigation.type && navigation.type.length > 0)
{
where += ' and contenttype = ' + mysqlPool.escape(navigation.type) + ' ';
}
if(navigation.size)
{
if(navigation.size.max > 0)
where += ' and size < ' + mysqlPool.escape(navigation.size.max) + ' ';
if(navigation.size.min > 0)
where += ' and size > ' + mysqlPool.escape(navigation.size.min) + ' ';
}
if(navigation.files)
{
if(navigation.files.max > 0)
where += ' and files < ' + mysqlPool.escape(navigation.files.max) + ' ';
if(navigation.files.min > 0)
where += ' and files > ' + mysqlPool.escape(navigation.files.min) + ' ';
}
console.log(where)
let searchList = []; let searchList = [];
//args.splice(orderBy && orderBy.length > 0 ? 1 : 0, 1); //args.splice(orderBy && orderBy.length > 0 ? 1 : 0, 1);
//mysqlPool.query('SELECT * FROM `torrents` WHERE `name` like \'%' + text + '%\' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) { //mysqlPool.query('SELECT * FROM `torrents` WHERE `name` like \'%' + text + '%\' ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) {
sphinx.query('SELECT * FROM `torrents_index`,`torrents_index_delta` WHERE MATCH(?) ' + (safeSearch ? "and contentcategory != 'xxx'" : '') + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) { sphinx.query('SELECT * FROM `torrents_index`,`torrents_index_delta` WHERE MATCH(?) ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) {
if(!rows) { if(!rows) {
console.log(error) console.log(error)
callback(undefined) callback(undefined)
@ -335,18 +359,42 @@ io.on('connection', function(socket)
let args = [text, index, limit]; let args = [text, index, limit];
const orderBy = navigation.orderBy; const orderBy = navigation.orderBy;
let order = ''; let order = '';
let where = '';
if(orderBy && orderBy.length > 0) if(orderBy && orderBy.length > 0)
{ {
const orderDesc = navigation.orderDesc ? 'DESC' : 'ASC'; const orderDesc = navigation.orderDesc ? 'DESC' : 'ASC';
args.splice(1, 0, orderBy); args.splice(1, 0, orderBy);
order = 'ORDER BY ?? ' + orderDesc; order = 'ORDER BY ?? ' + orderDesc;
} }
if(safeSearch)
{
where += " and contentcategory != 'xxx' ";
}
if(navigation.type && navigation.type.length > 0)
{
where += ' and contenttype = ' + mysqlPool.escape(navigation.type) + ' ';
}
if(navigation.size)
{
if(navigation.size.max > 0)
where += ' and size < ' + mysqlPool.escape(navigation.size.max) + ' ';
if(navigation.size.min > 0)
where += ' and size > ' + mysqlPool.escape(navigation.size.min) + ' ';
}
if(navigation.files)
{
if(navigation.files.max > 0)
where += ' and files < ' + mysqlPool.escape(navigation.files.max) + ' ';
if(navigation.files.min > 0)
where += ' and files > ' + mysqlPool.escape(navigation.files.min) + ' ';
}
let search = {}; let search = {};
let searchList = []; let searchList = [];
//args.splice(orderBy && orderBy.length > 0 ? 1 : 0, 1); //args.splice(orderBy && orderBy.length > 0 ? 1 : 0, 1);
//mysqlPool.query('SELECT * FROM `files` inner join torrents on(torrents.hash = files.hash) WHERE files.path like \'%' + text + '%\' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) { //mysqlPool.query('SELECT * FROM `files` inner join torrents on(torrents.hash = files.hash) WHERE files.path like \'%' + text + '%\' ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) {
sphinx.query('SELECT * FROM `files_index`,`files_index_delta` WHERE MATCH(?) ' + (safeSearch ? "and contentcategory != 'xxx'" : '') + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) { sphinx.query('SELECT * FROM `files_index`,`files_index_delta` WHERE MATCH(?) ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) {
if(!rows) { if(!rows) {
console.log(error) console.log(error)
callback(undefined) callback(undefined)

View File

@ -62,6 +62,7 @@
"phantomjs-prebuilt": "^2.1.15", "phantomjs-prebuilt": "^2.1.15",
"react": "^15.6.2", "react": "^15.6.2",
"react-dom": "^15.6.2", "react-dom": "^15.6.2",
"react-input-range": "^1.2.1",
"react-tap-event-plugin": "^2.0.1", "react-tap-event-plugin": "^2.0.1",
"sitemap": "^1.13.0", "sitemap": "^1.13.0",
"socket.io": "^2.0.4" "socket.io": "^2.0.4"

View File

@ -59,3 +59,28 @@
width: 100% width: 100%
} }
} }
.filter-control-border {
top: -10px;
padding-left: 40px;
}
@media only screen and (max-width: 530px)
{
.filter-control-row {
flex-wrap: wrap;
padding-top: 28px;
}
.filter-row {
padding-top: 30px;
align-items: inherit !important;
flex-direction: column;
}
.filter-control-border {
padding-top: 5px;
top: 6px;
padding-left: 0px;
}
}

72
src/input-files-filter.js Normal file
View File

@ -0,0 +1,72 @@
import React, { Component } from 'react';
import InputRange from 'react-input-range';
import 'react-input-range/lib/css/index.css';
import formatBytes from './format-bytes'
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import Checkbox from 'material-ui/Checkbox';
export default class InputFilesFilter extends Component {
constructor(props)
{
super(props)
this.state = {
files: this.props.value || { min: 0, max: 50 },
enabled: false || this.props.enabled,
filesMax: this.props.filesMax || 100 // 1mb
}
}
setState(val)
{
if(val.filesMax && this.state.files.max > val.filesMax)
val.files = {min: this.state.files.min, max: val.filesMax};
if(val.filesMax && this.state.files.min > val.filesMax)
val.files = {min: 0, max: val.files ? val.files.max || this.state.files.max : this.state.files.max };
super.setState(val, () => {
if(this.props.onChange)
this.props.onChange({
enabled: this.state.enabled,
filesMax: this.state.filesMax,
files: !this.state.enabled ? {min: 0, max: 0} : this.state.files
})
})
}
render() {
return (
<div className='filter-row row inline w100p'>
<Checkbox
label="Files filter"
checked={this.state.enabled}
style={{width: 150}}
onCheck={() => this.setState({enabled: !this.state.enabled})}
/>
<div className='filter-control-row row inline w100p' style={{opacity: this.state.enabled ? 1 : 0.4, transition: '0.5s', paddingLeft: 9}}>
<InputRange
maxValue={this.state.filesMax}
minValue={0}
value={this.state.files}
style={this.props.style}
className={this.props.className}
onChange={files => this.setState({ files })}
/>
<SelectField
floatingLabelText="Size type"
value={this.state.filesMax}
onChange={(event, index, value) => this.setState({filesMax: value})}
className='filter-control-border'
>
<MenuItem value={10} primaryText="10 Files or less" />
<MenuItem value={100} primaryText="100 Files or less" />
<MenuItem value={1000} primaryText="1000 Files or less" />
<MenuItem value={10000} primaryText="10000 Files or less" />
<MenuItem value={100000} primaryText="100000 Files or less" />
<MenuItem value={1000000} primaryText="1000000 Files or less" />
</SelectField>
</div>
</div>
);
}
}

73
src/input-size.js Normal file
View File

@ -0,0 +1,73 @@
import React, { Component } from 'react';
import InputRange from 'react-input-range';
import 'react-input-range/lib/css/index.css';
import formatBytes from './format-bytes'
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import Checkbox from 'material-ui/Checkbox';
export default class InputSize extends Component {
constructor(props)
{
super(props)
this.state = {
size: this.props.value || { min: 0, max: 500 * 1024 },
enabled: false || this.props.enabled,
maxSize: this.props.maxSize || 1024 * 1024 // 1mb
}
}
setState(val)
{
if(val.maxSize && this.state.size.max > val.maxSize)
val.size = {min: this.state.size.min, max: val.maxSize};
if(val.maxSize && this.state.size.min > val.maxSize)
val.size = {min: 0, max: val.size ? val.size.max || this.state.size.max : this.state.size.max };
super.setState(val, () => {
if(this.props.onChange)
this.props.onChange({
enabled: this.state.enabled,
maxSize: this.state.maxSize,
size: !this.state.enabled ? {min: 0, max: 0} : this.state.size
})
})
}
render() {
return (
<div className='filter-row row inline w100p'>
<Checkbox
label="Size filter"
checked={this.state.enabled}
style={{width: 150}}
onCheck={() => this.setState({enabled: !this.state.enabled})}
/>
<div className='filter-control-row row inline w100p' style={{opacity: this.state.enabled ? 1 : 0.4, transition: '0.5s', paddingLeft: 9}}>
<InputRange
maxValue={this.state.maxSize}
minValue={0}
value={this.state.size}
formatLabel={size => formatBytes(size)}
style={this.props.style}
className={this.props.className}
onChange={size => this.setState({ size })}
/>
<SelectField
floatingLabelText="Size type"
value={this.state.maxSize}
onChange={(event, index, value) => this.setState({maxSize: value})}
className='filter-control-border'
>
<MenuItem value={1024} primaryText="KB" />
<MenuItem value={1024 * 1024} primaryText="MB" />
<MenuItem value={1024 * 1024 * 1024} primaryText="GB" />
<MenuItem value={10 * 1024 * 1024 * 1024} primaryText="10 GB" />
<MenuItem value={100 * 1024 * 1024 * 1024} primaryText="100 GB" />
<MenuItem value={1024 * 1024 * 1024 * 1024} primaryText="TB" />
</SelectField>
</div>
</div>
);
}
}

View File

@ -0,0 +1,56 @@
import React, { Component } from 'react';
import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem';
import InputSize from './input-size';
import FilesFilterInput from './input-files-filter';
export default class AdvancedSearchControl extends Component {
constructor(props)
{
super(props)
this.state = {
type: undefined,
size: {min: 0, max: 500 * 1024},
maxSize: 1024 * 1024,
sizeEnabled: false,
filesEnabled: false,
files: {min: 0, max: 5},
filesMax: 100,
}
if(this.props.state)
this.state = Object.assign(this.state, this.props.state)
}
setState(val)
{
super.setState(val, (v) => {
if(this.props.onChange)
this.props.onChange(this.state)
})
}
render() {
return (
<div className='column w100p' style={{maxWidth: 750, overflow: 'hidden', padding: '0px 18px 15px'}}>
<SelectField
floatingLabelText="Filter content type"
value={this.state.type}
onChange={(event, index, value) => this.setState({type: value})}
>
<MenuItem value={undefined} primaryText="" />
<MenuItem value='video' primaryText="Video" />
<MenuItem value='audio' primaryText="Audio" />
<MenuItem value='pictures' primaryText="Pictures" />
<MenuItem value='books' primaryText="Books" />
<MenuItem value='application' primaryText="Applications" />
<MenuItem value='archive' primaryText="Archives" />
<MenuItem value='disc' primaryText="Disk Images" />
</SelectField>
<div className='w100p'>
<InputSize value={this.state.size} enabled={this.state.sizeEnabled} maxSize={this.state.maxSize} onChange={({size, maxSize, enabled}) => this.setState({size, maxSize, sizeEnabled: enabled})} />
</div>
<div className='w100p'>
<FilesFilterInput value={this.state.files} filesMax={this.state.filesMax} enabled={this.state.filesEnabled} onChange={({files, filesMax, enabled}) => this.setState({files, filesMax, filesEnabled: enabled})} />
</div>
</div>
);
}
}

View File

@ -1,6 +1,7 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import SearchResults from './search-results' import SearchResults from './search-results'
import AdvancedSearch from './search-advanced-controls'
import TextField from 'material-ui/TextField'; import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton'; import RaisedButton from 'material-ui/RaisedButton';
import RefreshIndicator from 'material-ui/RefreshIndicator'; import RefreshIndicator from 'material-ui/RefreshIndicator';
@ -8,6 +9,8 @@ import RefreshIndicator from 'material-ui/RefreshIndicator';
import Checkbox from 'material-ui/Checkbox'; import Checkbox from 'material-ui/Checkbox';
import Visibility from 'material-ui/svg-icons/action/visibility'; import Visibility from 'material-ui/svg-icons/action/visibility';
import VisibilityOff from 'material-ui/svg-icons/action/visibility-off'; import VisibilityOff from 'material-ui/svg-icons/action/visibility-off';
import AddIcon from 'material-ui/svg-icons/content/add';
import RemoveIcon from 'material-ui/svg-icons/content/remove';
import SelectField from 'material-ui/SelectField'; import SelectField from 'material-ui/SelectField';
import MenuItem from 'material-ui/MenuItem'; import MenuItem from 'material-ui/MenuItem';
@ -28,8 +31,11 @@ export default class Search extends Component {
moreFilesIndicator: false, moreFilesIndicator: false,
orderBy: null, orderBy: null,
orderDesc: false, orderDesc: false,
advancedSearch: false,
} }
this.searchLimit = 10 this.searchLimit = 10
this.advanced = {}
this.searchError = undefined;
if(session) if(session)
{ {
@ -42,6 +48,9 @@ export default class Search extends Component {
Object.assign(this.state, this.setSafeSearch(session.notSafeSearch)) Object.assign(this.state, this.setSafeSearch(session.notSafeSearch))
this.state.orderBy = session.orderBy; this.state.orderBy = session.orderBy;
this.state.orderDesc = session.orderDesc; this.state.orderDesc = session.orderDesc;
this.state.advancedSearch = session.advancedSearch;
this.advanced = session.advanced;
this.searchError = session.searchError;
} }
} }
@ -55,12 +64,16 @@ export default class Search extends Component {
this.moreSearchFiles = true; this.moreSearchFiles = true;
this.currentSearch = this.searchValue; this.currentSearch = this.searchValue;
let queries = 2; let queries = 2;
window.torrentSocket.emit('searchTorrent', oldSearch ? this.currentSearch : this.searchValue, { let searchTorrentsParams = {
limit: this.searchLimit, limit: this.searchLimit,
safeSearch: !this.notSafeSearch, safeSearch: !this.notSafeSearch,
orderBy: this.state.orderBy, orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc, orderDesc: this.state.orderDesc,
}, window.customLoader((torrents) => { };
if(this.state.advancedSearch && this.advanced)
searchTorrentsParams = Object.assign(searchTorrentsParams, this.advanced);
window.torrentSocket.emit('searchTorrent', oldSearch ? this.currentSearch : this.searchValue, searchTorrentsParams, window.customLoader((torrents) => {
if(torrents) { if(torrents) {
this.searchTorrents = torrents; this.searchTorrents = torrents;
if(torrents.length != this.searchLimit) if(torrents.length != this.searchLimit)
@ -78,12 +91,16 @@ export default class Search extends Component {
this.forceUpdate(); this.forceUpdate();
} }
})); }));
window.torrentSocket.emit('searchFiles', oldSearch ? this.currentSearch : this.searchValue, { let searchFilesParams = {
limit: this.searchLimit, limit: this.searchLimit,
safeSearch: !this.notSafeSearch, safeSearch: !this.notSafeSearch,
orderBy: this.state.orderBy, orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc, orderDesc: this.state.orderDesc,
}, window.customLoader((torrents) => { };
if(this.state.advancedSearch && this.advanced)
searchFilesParams = Object.assign(searchFilesParams, this.advanced);
window.torrentSocket.emit('searchFiles', oldSearch ? this.currentSearch : this.searchValue, searchFilesParams, window.customLoader((torrents) => {
if(torrents) { if(torrents) {
this.searchFiles = torrents; this.searchFiles = torrents;
let files = 0; let files = 0;
@ -181,6 +198,9 @@ export default class Search extends Component {
notSafeSearch: this.notSafeSearch, notSafeSearch: this.notSafeSearch,
orderBy: this.state.orderBy, orderBy: this.state.orderBy,
orderDesc: this.state.orderDesc, orderDesc: this.state.orderDesc,
advancedSearch: this.state.advancedSearch,
advanced: this.advanced,
searchError: this.searchError,
} }
} }
setSafeSearch(ch) { setSafeSearch(ch) {
@ -221,31 +241,62 @@ export default class Search extends Component {
fullWidth={true} fullWidth={true}
ref='searchInput' ref='searchInput'
defaultValue={this.searchValue} defaultValue={this.searchValue}
errorText={this.searchError}
onKeyPress={(e) => { onKeyPress={(e) => {
if (e.key === 'Enter') { if (e.key === 'Enter') {
this.search(); this.search();
} }
}} }}
onChange={e => {this.searchValue = e.target.value}} onChange={e => {
this.searchValue = e.target.value
if(this.searchValue.length < 3 && this.searchValue.length > 0)
this.searchError = 'too short string for search';
else
this.searchError = undefined;
this.forceUpdate()
}}
/> />
<RaisedButton style={{marginTop: '15px', marginLeft: '10px'}} label="Search" primary={true} onClick={() =>{ <RaisedButton style={{marginTop: '15px', marginLeft: '10px'}} label="Search" primary={true} onClick={() =>{
this.search() this.search()
}} /> }} />
</div> </div>
<div className='row'> <div className='row w100p center wrap' style={{padding: '0 8px'}}>
<Checkbox <div style={{padding: '0px 17px'}}>
ref='safeSearch' <Checkbox
checked={this.notSafeSearch ? true : false} ref='safeSearch'
checkedIcon={<Visibility />} checked={this.notSafeSearch ? true : false}
uncheckedIcon={<VisibilityOff />} checkedIcon={<Visibility />}
label={<span className='text-nowrap' style={{fontSize: '0.87em', transition: '0.1s', color: this.state.safeSearchColor}}>{this.state.safeSearchText}</span>} uncheckedIcon={<VisibilityOff />}
iconStyle={{fill: this.state.safeSearchColor}} label={<span className='text-nowrap' style={{fontSize: '0.87em', transition: '0.1s', color: this.state.safeSearchColor}}>{this.state.safeSearchText}</span>}
onCheck={(ev, ch) => { iconStyle={{fill: this.state.safeSearchColor}}
this.setState(this.setSafeSearch(ch)); onCheck={(ev, ch) => {
}} this.setState(this.setSafeSearch(ch));
style={{paddingBottom: '0.8em'}} }}
/> style={{paddingBottom: '0.8em'}}
/>
</div>
<div style={{padding: '0px 17px'}}>
<Checkbox
ref='advancedSearch'
checked={this.state.advancedSearch}
checkedIcon={<RemoveIcon />}
uncheckedIcon={<AddIcon />}
label={<span className='text-nowrap' style={{fontSize: '0.87em', transition: '0.1s', color: 'black'}}>advanced search</span>}
iconStyle={{fill: 'black'}}
onCheck={(ev, ch) => {
this.setState({advancedSearch: ch});
}}
style={{paddingBottom: '0.8em'}}
/>
</div>
</div> </div>
{
this.state.advancedSearch
&&
<AdvancedSearch onChange={(state) => {
this.advanced = state;
}} state={this.advanced} />
}
{ {
this.stats this.stats
? ?