diff --git a/index.js b/index.js index e4a9fa9..dbcf304 100644 --- a/index.js +++ b/index.js @@ -295,17 +295,41 @@ io.on('connection', function(socket) let args = [text, index, limit]; const orderBy = navigation.orderBy; let order = ''; + let where = ''; if(orderBy && orderBy.length > 0) { const orderDesc = navigation.orderDesc ? 'DESC' : 'ASC'; args.splice(1, 0, orderBy); 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 = []; //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) { - sphinx.query('SELECT * FROM `torrents_index`,`torrents_index_delta` WHERE MATCH(?) ' + (safeSearch ? "and contentcategory != 'xxx'" : '') + ' ' + 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(?) ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) { if(!rows) { console.log(error) callback(undefined) @@ -335,18 +359,42 @@ io.on('connection', function(socket) let args = [text, index, limit]; const orderBy = navigation.orderBy; let order = ''; + let where = ''; + if(orderBy && orderBy.length > 0) { const orderDesc = navigation.orderDesc ? 'DESC' : 'ASC'; args.splice(1, 0, orderBy); 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 searchList = []; //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) { - sphinx.query('SELECT * FROM `files_index`,`files_index_delta` WHERE MATCH(?) ' + (safeSearch ? "and contentcategory != 'xxx'" : '') + ' ' + 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(?) ' + where + ' ' + order + ' LIMIT ?,?', args, function (error, rows, fields) { if(!rows) { console.log(error) callback(undefined) diff --git a/package.json b/package.json index 230fc60..936503a 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "phantomjs-prebuilt": "^2.1.15", "react": "^15.6.2", "react-dom": "^15.6.2", + "react-input-range": "^1.2.1", "react-tap-event-plugin": "^2.0.1", "sitemap": "^1.13.0", "socket.io": "^2.0.4" diff --git a/src/css/izi/components.css b/src/css/izi/components.css index e452495..77b69a8 100644 --- a/src/css/izi/components.css +++ b/src/css/izi/components.css @@ -59,3 +59,28 @@ 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; + } +} \ No newline at end of file diff --git a/src/input-files-filter.js b/src/input-files-filter.js new file mode 100644 index 0000000..c1080b1 --- /dev/null +++ b/src/input-files-filter.js @@ -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 ( +
+ this.setState({enabled: !this.state.enabled})} + /> +
+ this.setState({ files })} + /> + this.setState({filesMax: value})} + className='filter-control-border' + > + + + + + + + +
+
+ ); + } +} diff --git a/src/input-size.js b/src/input-size.js new file mode 100644 index 0000000..2aecc9a --- /dev/null +++ b/src/input-size.js @@ -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 ( +
+ this.setState({enabled: !this.state.enabled})} + /> +
+ formatBytes(size)} + style={this.props.style} + className={this.props.className} + onChange={size => this.setState({ size })} + /> + this.setState({maxSize: value})} + className='filter-control-border' + > + + + + + + + +
+
+ ); + } +} diff --git a/src/search-advanced-controls.js b/src/search-advanced-controls.js new file mode 100644 index 0000000..101185f --- /dev/null +++ b/src/search-advanced-controls.js @@ -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 ( +
+ this.setState({type: value})} + > + + + + + + + + + +
+ this.setState({size, maxSize, sizeEnabled: enabled})} /> +
+
+ this.setState({files, filesMax, filesEnabled: enabled})} /> +
+
+ ); + } +} diff --git a/src/search.js b/src/search.js index 193056b..aa37f79 100644 --- a/src/search.js +++ b/src/search.js @@ -1,6 +1,7 @@ import React, { Component } from 'react'; import SearchResults from './search-results' +import AdvancedSearch from './search-advanced-controls' import TextField from 'material-ui/TextField'; import RaisedButton from 'material-ui/RaisedButton'; import RefreshIndicator from 'material-ui/RefreshIndicator'; @@ -8,6 +9,8 @@ import RefreshIndicator from 'material-ui/RefreshIndicator'; import Checkbox from 'material-ui/Checkbox'; import Visibility from 'material-ui/svg-icons/action/visibility'; 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 MenuItem from 'material-ui/MenuItem'; @@ -28,8 +31,11 @@ export default class Search extends Component { moreFilesIndicator: false, orderBy: null, orderDesc: false, + advancedSearch: false, } this.searchLimit = 10 + this.advanced = {} + this.searchError = undefined; if(session) { @@ -42,6 +48,9 @@ export default class Search extends Component { Object.assign(this.state, this.setSafeSearch(session.notSafeSearch)) this.state.orderBy = session.orderBy; 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.currentSearch = this.searchValue; let queries = 2; - window.torrentSocket.emit('searchTorrent', oldSearch ? this.currentSearch : this.searchValue, { + let searchTorrentsParams = { limit: this.searchLimit, safeSearch: !this.notSafeSearch, orderBy: this.state.orderBy, 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) { this.searchTorrents = torrents; if(torrents.length != this.searchLimit) @@ -78,12 +91,16 @@ export default class Search extends Component { this.forceUpdate(); } })); - window.torrentSocket.emit('searchFiles', oldSearch ? this.currentSearch : this.searchValue, { + let searchFilesParams = { limit: this.searchLimit, safeSearch: !this.notSafeSearch, orderBy: this.state.orderBy, 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) { this.searchFiles = torrents; let files = 0; @@ -181,6 +198,9 @@ export default class Search extends Component { notSafeSearch: this.notSafeSearch, orderBy: this.state.orderBy, orderDesc: this.state.orderDesc, + advancedSearch: this.state.advancedSearch, + advanced: this.advanced, + searchError: this.searchError, } } setSafeSearch(ch) { @@ -221,31 +241,62 @@ export default class Search extends Component { fullWidth={true} ref='searchInput' defaultValue={this.searchValue} + errorText={this.searchError} onKeyPress={(e) => { if (e.key === 'Enter') { 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() + }} /> { this.search() }} /> -
- } - uncheckedIcon={} - label={{this.state.safeSearchText}} - iconStyle={{fill: this.state.safeSearchColor}} - onCheck={(ev, ch) => { - this.setState(this.setSafeSearch(ch)); - }} - style={{paddingBottom: '0.8em'}} - /> +
+
+ } + uncheckedIcon={} + label={{this.state.safeSearchText}} + iconStyle={{fill: this.state.safeSearchColor}} + onCheck={(ev, ch) => { + this.setState(this.setSafeSearch(ch)); + }} + style={{paddingBottom: '0.8em'}} + /> +
+
+ } + uncheckedIcon={} + label={advanced search} + iconStyle={{fill: 'black'}} + onCheck={(ev, ch) => { + this.setState({advancedSearch: ch}); + }} + style={{paddingBottom: '0.8em'}} + /> +
+ { + this.state.advancedSearch + && + { + this.advanced = state; + }} state={this.advanced} /> + } { this.stats ?