2018-01-30 10:33:15 -05:00
|
|
|
const bindAll = require('lodash.bindall');
|
|
|
|
const connect = require('react-redux').connect;
|
|
|
|
const FormattedMessage = require('react-intl').FormattedMessage;
|
|
|
|
const injectIntl = require('react-intl').injectIntl;
|
|
|
|
const intlShape = require('react-intl').intlShape;
|
|
|
|
const PropTypes = require('prop-types');
|
|
|
|
const React = require('react');
|
2016-04-18 16:51:45 -04:00
|
|
|
|
2018-01-30 10:33:15 -05:00
|
|
|
const api = require('../../lib/api');
|
|
|
|
const Button = require('../../components/forms/button.jsx');
|
2018-07-27 12:54:59 -04:00
|
|
|
const Form = require('../../components/forms/form.jsx');
|
2018-01-30 10:33:15 -05:00
|
|
|
const Grid = require('../../components/grid/grid.jsx');
|
|
|
|
const navigationActions = require('../../redux/navigation.js');
|
2018-07-27 13:09:48 -04:00
|
|
|
const Select = require('../../components/forms/select.jsx');
|
2018-01-30 10:33:15 -05:00
|
|
|
const TitleBanner = require('../../components/title-banner/title-banner.jsx');
|
|
|
|
const Tabs = require('../../components/tabs/tabs.jsx');
|
2016-04-18 16:51:45 -04:00
|
|
|
|
2018-01-30 10:33:15 -05:00
|
|
|
const Page = require('../../components/page/www/page.jsx');
|
|
|
|
const render = require('../../lib/render.jsx');
|
2016-04-18 16:51:45 -04:00
|
|
|
|
|
|
|
require('./search.scss');
|
|
|
|
|
2018-01-30 10:33:15 -05:00
|
|
|
class Search extends React.Component {
|
|
|
|
constructor (props) {
|
|
|
|
super(props);
|
|
|
|
bindAll(this, [
|
|
|
|
'getSearchState',
|
2018-07-27 12:54:59 -04:00
|
|
|
'handleChangeSortMode',
|
2018-01-30 10:33:15 -05:00
|
|
|
'handleGetSearchMore',
|
|
|
|
'getTab'
|
|
|
|
]);
|
|
|
|
this.state = this.getSearchState();
|
|
|
|
this.state.loaded = [];
|
|
|
|
this.state.loadNumber = 16;
|
2018-07-27 12:54:59 -04:00
|
|
|
this.state.mode = '';
|
2018-01-30 10:33:15 -05:00
|
|
|
this.state.offset = 0;
|
2018-02-08 10:42:04 -05:00
|
|
|
this.state.loadMore = false;
|
2018-01-30 10:33:15 -05:00
|
|
|
}
|
|
|
|
componentDidMount () {
|
|
|
|
const query = window.location.search;
|
|
|
|
const q = query.lastIndexOf('q=');
|
|
|
|
let term = '';
|
2017-02-10 18:54:20 -05:00
|
|
|
if (q !== -1) {
|
|
|
|
term = query.substring(q + 2, query.length).toLowerCase();
|
|
|
|
}
|
|
|
|
while (term.indexOf('/') > -1) {
|
|
|
|
term = term.substring(0, term.indexOf('/'));
|
|
|
|
}
|
|
|
|
while (term.indexOf('&') > -1) {
|
|
|
|
term = term.substring(0, term.indexOf('&'));
|
|
|
|
}
|
2018-06-03 08:23:52 -04:00
|
|
|
term = decodeURIComponent(term.split('+').join(' '));
|
2017-02-20 11:08:36 -05:00
|
|
|
this.props.dispatch(navigationActions.setSearchTerm(term));
|
2018-07-27 12:54:59 -04:00
|
|
|
|
|
|
|
let mode = '';
|
2018-07-31 17:26:03 -04:00
|
|
|
const m = query.lastIndexOf('mode=');
|
2018-07-27 12:54:59 -04:00
|
|
|
if (m !== -1) {
|
2018-07-31 17:26:03 -04:00
|
|
|
mode = query.substring(m + 5, query.length).toLowerCase();
|
2018-07-27 12:54:59 -04:00
|
|
|
}
|
|
|
|
while (mode.indexOf('/') > -1) {
|
|
|
|
mode = mode.substring(0, term.indexOf('/'));
|
|
|
|
}
|
|
|
|
while (term.indexOf('&') > -1) {
|
|
|
|
mode = mode.substring(0, term.indexOf('&'));
|
|
|
|
}
|
|
|
|
mode = decodeURIComponent(mode.split('+').join(' '));
|
2018-07-27 13:15:53 -04:00
|
|
|
this.props.dispatch(navigationActions.setMode(mode));
|
2018-01-30 10:33:15 -05:00
|
|
|
}
|
|
|
|
componentDidUpdate (prevProps) {
|
|
|
|
if (this.props.searchTerm !== prevProps.searchTerm) {
|
|
|
|
this.handleGetSearchMore();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
getSearchState () {
|
|
|
|
let pathname = window.location.pathname.toLowerCase();
|
|
|
|
if (pathname[pathname.length - 1] === '/') {
|
|
|
|
pathname = pathname.substring(0, pathname.length - 1);
|
|
|
|
}
|
|
|
|
const start = pathname.lastIndexOf('/');
|
|
|
|
const type = pathname.substring(start + 1, pathname.length);
|
2018-08-01 09:16:41 -04:00
|
|
|
const modeOptions = ['trending', 'popular', ''];
|
2018-01-30 10:33:15 -05:00
|
|
|
return {
|
2018-07-27 12:54:59 -04:00
|
|
|
acceptableModes: modeOptions,
|
2018-01-30 10:33:15 -05:00
|
|
|
tab: type,
|
|
|
|
loadNumber: 16
|
|
|
|
};
|
|
|
|
}
|
2018-07-27 12:54:59 -04:00
|
|
|
handleChangeSortMode (name, value) {
|
|
|
|
if (this.state.acceptableModes.indexOf(value) !== -1) {
|
|
|
|
const term = this.props.searchTerm.split(' ').join('+');
|
|
|
|
window.location =
|
2018-07-31 17:26:03 -04:00
|
|
|
`${window.location.origin}/search/${this.state.tab}?q=${term}&mode=${value}`;
|
2018-07-27 12:54:59 -04:00
|
|
|
}
|
|
|
|
}
|
2018-01-30 10:33:15 -05:00
|
|
|
handleGetSearchMore () {
|
|
|
|
let termText = '';
|
2016-06-09 07:32:25 -04:00
|
|
|
if (this.props.searchTerm !== '') {
|
2018-01-30 10:33:15 -05:00
|
|
|
termText = `&q=${encodeURIComponent(this.props.searchTerm.split(' ').join('+'))}`;
|
2016-04-30 16:38:48 -04:00
|
|
|
}
|
2018-01-30 10:33:15 -05:00
|
|
|
const locale = this.props.intl.locale;
|
|
|
|
const loadNumber = this.state.loadNumber;
|
|
|
|
const offset = this.state.offset;
|
2018-07-27 12:54:59 -04:00
|
|
|
const mode = this.state.mode;
|
|
|
|
const queryString = `limit=${loadNumber}&offset=${offset}&language=${locale}&mode=${mode}${termText}`;
|
2018-01-30 10:33:15 -05:00
|
|
|
|
2016-06-14 17:33:54 -04:00
|
|
|
api({
|
2018-01-30 10:33:15 -05:00
|
|
|
uri: `/search/${this.state.tab}?${queryString}`
|
|
|
|
}, (err, body) => {
|
|
|
|
const loadedSoFar = this.state.loaded;
|
2016-06-09 07:32:25 -04:00
|
|
|
Array.prototype.push.apply(loadedSoFar, body);
|
2018-01-30 10:50:05 -05:00
|
|
|
const currentOffset = this.state.offset + this.state.loadNumber;
|
2018-02-12 09:26:39 -05:00
|
|
|
const willLoadMore = body.length === this.state.loadNumber;
|
2018-01-30 10:33:15 -05:00
|
|
|
|
|
|
|
this.setState({
|
|
|
|
loaded: loadedSoFar,
|
2018-02-08 10:42:04 -05:00
|
|
|
offset: currentOffset,
|
|
|
|
loadMore: willLoadMore
|
2018-01-30 10:33:15 -05:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
getTab (type) {
|
|
|
|
const term = this.props.searchTerm.split(' ').join('+');
|
|
|
|
let allTab = (
|
|
|
|
<a href={`/search/${type}?q=${term}/`}>
|
|
|
|
<li>
|
|
|
|
<img
|
|
|
|
className={`tab-icon ${type}`}
|
|
|
|
src={`/svgs/tabs/${type}-inactive.svg`}
|
|
|
|
/>
|
|
|
|
<FormattedMessage id={`general.${type}`} />
|
|
|
|
</li>
|
|
|
|
</a>
|
|
|
|
);
|
2018-01-31 13:00:27 -05:00
|
|
|
if (this.state.tab === type) {
|
2018-01-30 10:33:15 -05:00
|
|
|
allTab = (
|
|
|
|
<a href={`/search/${type}?q=${term}/`}>
|
|
|
|
<li className="active">
|
|
|
|
<img
|
|
|
|
className={`tab-icon ${type}`}
|
|
|
|
src={`/svgs/tabs/${type}-active.svg`}
|
|
|
|
/>
|
|
|
|
<FormattedMessage id={`general.${type}`} />
|
|
|
|
</li>
|
|
|
|
</a>
|
|
|
|
);
|
2016-04-18 16:51:45 -04:00
|
|
|
}
|
|
|
|
return allTab;
|
2018-01-30 10:33:15 -05:00
|
|
|
}
|
2018-02-08 10:42:04 -05:00
|
|
|
getProjectBox () {
|
|
|
|
const results = (
|
|
|
|
<Grid
|
|
|
|
cards
|
|
|
|
showAvatar
|
2018-02-12 09:26:39 -05:00
|
|
|
itemType={this.state.tab}
|
2018-02-08 10:42:04 -05:00
|
|
|
items={this.state.loaded}
|
|
|
|
showFavorites={false}
|
|
|
|
showLoves={false}
|
|
|
|
showViews={false}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
let searchAction = null;
|
2017-07-07 14:10:44 -04:00
|
|
|
if (this.state.loaded.length === 0 && this.state.offset !== 0) {
|
|
|
|
searchAction = <h2 className="search-prompt"><FormattedMessage id="general.searchEmpty" /></h2>;
|
|
|
|
} else if (this.state.loadMore) {
|
2018-02-08 10:42:04 -05:00
|
|
|
searchAction = (
|
2018-02-12 09:26:39 -05:00
|
|
|
<Button
|
|
|
|
onClick={this.handleGetSearchMore}
|
|
|
|
>
|
|
|
|
<FormattedMessage id="general.loadMore" />
|
2018-02-08 10:42:04 -05:00
|
|
|
</Button>
|
|
|
|
);
|
2017-07-07 14:10:44 -04:00
|
|
|
}
|
|
|
|
return (
|
2018-02-08 10:42:04 -05:00
|
|
|
<div
|
|
|
|
id="projectBox"
|
|
|
|
key="projectBox"
|
|
|
|
>
|
|
|
|
{results}
|
|
|
|
{searchAction}
|
|
|
|
</div>
|
2017-07-07 14:10:44 -04:00
|
|
|
);
|
2018-02-08 10:42:04 -05:00
|
|
|
}
|
2018-01-30 10:33:15 -05:00
|
|
|
render () {
|
2016-04-18 16:51:45 -04:00
|
|
|
return (
|
|
|
|
<div>
|
2018-01-30 10:33:15 -05:00
|
|
|
<div className="outer">
|
|
|
|
<TitleBanner className="masthead">
|
|
|
|
<div className="inner">
|
|
|
|
<h1 className="title-banner-h1">
|
|
|
|
<FormattedMessage id="general.search" />
|
|
|
|
</h1>
|
2018-01-30 09:53:25 -05:00
|
|
|
</div>
|
2018-01-30 10:33:15 -05:00
|
|
|
</TitleBanner>
|
|
|
|
<Tabs>
|
|
|
|
{this.getTab('projects')}
|
|
|
|
{this.getTab('studios')}
|
|
|
|
</Tabs>
|
2018-07-27 12:54:59 -04:00
|
|
|
<div className="sort-controls">
|
|
|
|
<Form className="sort-mode">
|
|
|
|
<Select
|
|
|
|
name="sort"
|
|
|
|
options={[
|
|
|
|
{
|
|
|
|
value: 'trending',
|
2018-08-01 09:16:41 -04:00
|
|
|
label: this.props.intl.formatMessage({id: 'search.trending'})
|
2018-07-27 12:54:59 -04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
value: 'popular',
|
2018-08-01 09:16:41 -04:00
|
|
|
label: this.props.intl.formatMessage({id: 'search.popular'})
|
2018-07-27 12:54:59 -04:00
|
|
|
}
|
|
|
|
]}
|
|
|
|
value={this.state.mode}
|
|
|
|
onChange={this.handleChangeSortMode}
|
|
|
|
/>
|
|
|
|
</Form>
|
|
|
|
</div>
|
2018-02-12 09:26:39 -05:00
|
|
|
{this.getProjectBox()}
|
2016-04-18 16:51:45 -04:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2018-01-30 10:33:15 -05:00
|
|
|
}
|
2016-04-18 16:51:45 -04:00
|
|
|
|
2018-01-30 10:33:15 -05:00
|
|
|
Search.propTypes = {
|
|
|
|
dispatch: PropTypes.func,
|
|
|
|
intl: intlShape,
|
2018-01-31 13:00:27 -05:00
|
|
|
searchTerm: PropTypes.string
|
2016-10-31 10:05:08 -04:00
|
|
|
};
|
|
|
|
|
2018-01-30 10:33:15 -05:00
|
|
|
const mapStateToProps = state => ({
|
|
|
|
searchTerm: state.navigation
|
|
|
|
});
|
|
|
|
|
|
|
|
const WrappedSearch = injectIntl(Search);
|
|
|
|
const ConnectedSearch = connect(mapStateToProps)(WrappedSearch);
|
2016-10-31 10:05:08 -04:00
|
|
|
|
2017-08-31 17:05:22 -04:00
|
|
|
render(
|
|
|
|
<Page><ConnectedSearch /></Page>,
|
|
|
|
document.getElementById('app'),
|
|
|
|
{navigation: navigationActions.navigationReducer}
|
|
|
|
);
|