diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2019-03-19 20:45:24 +0100 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2019-03-19 20:45:24 +0100 |
| commit | 7eb3c04ef85fa0311bdf30b24df2aba102757878 (patch) | |
| tree | bfb1354dcbed9486b43e75c7d2507a725212db24 /scraper/client | |
| parent | 46885570e527a2bb8a374e7044afdf0a4c5ba07e (diff) | |
rebuild site
Diffstat (limited to 'scraper/client')
| -rw-r--r-- | scraper/client/actions.js | 10 | ||||
| -rw-r--r-- | scraper/client/app.js | 1 | ||||
| -rw-r--r-- | scraper/client/common/header.component.js | 1 | ||||
| -rw-r--r-- | scraper/client/paper/paper.address.js | 1 | ||||
| -rw-r--r-- | scraper/client/paper/paper.verify.js | 205 | ||||
| -rw-r--r-- | scraper/client/search/browse.component.js | 77 | ||||
| -rw-r--r-- | scraper/client/search/index.js | 17 | ||||
| -rw-r--r-- | scraper/client/search/panicButton.component.js | 49 | ||||
| -rw-r--r-- | scraper/client/search/search.actions.js | 162 | ||||
| -rw-r--r-- | scraper/client/search/search.container.js | 101 | ||||
| -rw-r--r-- | scraper/client/search/search.css | 230 | ||||
| -rw-r--r-- | scraper/client/search/search.menu.js | 96 | ||||
| -rw-r--r-- | scraper/client/search/search.meta.js | 68 | ||||
| -rw-r--r-- | scraper/client/search/search.query.js | 227 | ||||
| -rw-r--r-- | scraper/client/search/search.reducer.js | 84 | ||||
| -rw-r--r-- | scraper/client/search/search.results.js | 49 | ||||
| -rw-r--r-- | scraper/client/search/search.safety.js | 17 |
17 files changed, 217 insertions, 1178 deletions
diff --git a/scraper/client/actions.js b/scraper/client/actions.js index 4e1fd620..8a1f13a6 100644 --- a/scraper/client/actions.js +++ b/scraper/client/actions.js @@ -30,3 +30,13 @@ export const getAddress = sha256 => dispatch => { export const postAddress = data => dispatch => { api(dispatch, post, 'address', '/api/address/add', data) } + +export const getVerification = sha256 => dispatch => { + api(dispatch, get, 'address', '/api/verify/' + sha256, {}) +} + +export const postVerification = data => dispatch => { + api(dispatch, post, 'address', '/api/verify/add', data) +} + + diff --git a/scraper/client/app.js b/scraper/client/app.js index 7600fe52..ab7e2ca7 100644 --- a/scraper/client/app.js +++ b/scraper/client/app.js @@ -21,6 +21,7 @@ export default class App extends Component { <Route exact path="/paper/:key/info/" component={Paper.Info} /> <Route exact path="/paper/:key/random/" component={Paper.Random} /> <Route exact path="/paper/:key/address/:sha256" component={Paper.Address} /> + <Route exact path="/paper/:key/verify/:sha256" component={Paper.Verify} /> </Switch> </div> </div> diff --git a/scraper/client/common/header.component.js b/scraper/client/common/header.component.js index 4da5af8a..2f084979 100644 --- a/scraper/client/common/header.component.js +++ b/scraper/client/common/header.component.js @@ -10,7 +10,6 @@ import * as actions from '../actions' class Header extends Component { componentDidMount() { this.props.actions.getPapers() - this.props.actions.getInstitutions() } pickPaper(e) { diff --git a/scraper/client/paper/paper.address.js b/scraper/client/paper/paper.address.js index 267fe16b..90e28139 100644 --- a/scraper/client/paper/paper.address.js +++ b/scraper/client/paper/paper.address.js @@ -28,6 +28,7 @@ class PaperAddress extends Component { componentDidMount() { const { sha256 } = this.props.match.params + this.props.actions.getInstitutions() this.props.actions.getAddress(sha256) const citationState = this.getCitationState(sha256) console.log('DID MOUNT') diff --git a/scraper/client/paper/paper.verify.js b/scraper/client/paper/paper.verify.js new file mode 100644 index 00000000..cfce8f28 --- /dev/null +++ b/scraper/client/paper/paper.verify.js @@ -0,0 +1,205 @@ +import React, { Component } from 'react' +import { bindActionCreators } from 'redux' +import { connect } from 'react-redux' + +import * as actions from '../actions' + +import { history } from '../store' + +import { Loader, Autocomplete } from '../common' + +const initialState = { + citation: null, + verify: '', + verified_by: '', + notes: '', + pdf_index: 0, +} +class PaperVerify extends Component { + state = { + ...initialState + } + + componentDidMount() { + const { sha256 } = this.props.match.params + this.props.actions.getInstitutions() + this.props.actions.getVerification(sha256) + const citationState = this.getVerificationState(sha256) + console.log('DID MOUNT') + this.setState(citationState) + } + + componentDidUpdate(oldProps) { + const { sha256 } = this.props.match.params + const { address } = this.props.api + const { sha256: oldSha256 } = oldProps.match.params + const { address: oldAddress } = oldProps.api + const oldPaper = oldAddress ? oldAddress.paper : null + const paper = address ? address.paper : null + + if (oldSha256 && sha256 !== oldSha256) { + console.log('update address') + this.props.actions.getVerification(sha256) + const citationState = this.getCitationState(sha256) + this.setState({ + ...initialState, + ...citationState, + }) + } else if (address && !address.loading && address.paper && (!oldPaper || oldPaper !== address.paper)) { + if (paper.error) { + console.log('USING PAPER ADDRESS') + const citationState = this.getCitationState(sha256) + this.setState({ + ...initialState, + ...citationState, + }) + } else { + // console.log(paper) + console.log('GOT CUSTOM ADDRESS') + const citationState = this.getCitationState(sha256) + this.setState({ + ...citationState, + verified: paper.verified, + verified_by: paper.verified_by, + }) + } + } else if (oldProps.api.unknownCitations !== this.props.api.unknownCitations) { + const citationState = this.getCitationState(sha256) + this.setState(citationState) + } + } + + getCitationState(sha256) { + const { paperInfo, unknownCitations } = this.props.api + let citation = (unknownCitations.citations || []).find(f => f.id === sha256) + if (!citation) { + citation = (paperInfo.citations || []).find(f => f.id === sha256) + } + // console.log(sha256, citation) + let state = { citation } + let addresses = citation ? citation.addresses : [] + if (addresses) { + addresses.forEach((address, i) => { + state['institution_' + (i + 1)] = address.address + }) + } + return state + } + + save() { + console.log(this.state) + this.props.actions.postVerification({ + paper_id: this.state.citation.id, + title: this.state.citation.title, + verified: this.state.verified, + verified_by: this.state.verified_by, + notes: this.state.notes, + }) + this.next(false) + } + + next() { + const { key } = this.props.api.paperInfo.dataset + const { unknownCitations } = this.props.api + let citationIndex = (unknownCitations.citations || []) + .findIndex(f => f.id === this.state.citation.id) + if (citationIndex === -1) { + history.push('/paper/' + key + '/info/') + } else { + citationIndex += 1 + if (citationIndex >= unknownCitations.length) { + history.push('/paper/' + key + '/info/') + } else { + let nextId = unknownCitations.citations[citationIndex].id + history.push('/paper/' + key + '/address/' + nextId) + } + } + } + + render() { + let { paperInfo, unknownCitations } = this.props.api + if (paperInfo.loading || unknownCitations.loading) return <Loader /> + console.log(this.state) + const { citation } = this.state + if (!citation) { + return <div>Citation not found in this paper</div> + } + console.log(citation) + return ( + <div className='form'> + <h3>{citation.title}</h3> + <div className='gray'> + {citation.id} + {' | PDFs: '} + {citation.pdf.map((pdf,i) => { + const domain = pdf.replace('www.','').split('/').slice(2,3)[0] || 'unknown' + return ( + <a + key={i} + onClick={() => this.setState({ pdf_index: i })} + className={i === this.state.pdf_index ? 'selected pdfLink' : 'pdfLink'} + > + {'[' + domain + '] '} + </a> + ) + })} + </div> + + <div className='param'> + <input + className='vetting' + type='checkbox' + value={this.state.verified} + onChange={e => this.setState({ + verified: e.target.value, + })} + /> + </div> + + <div className='param'> + <input + type='text' + className='verified_by' + value={this.state.verified_by} + placeholder='Verified by' + onChange={e => this.setState({ notes: e.target.value })} + /> + </div> + + <div className='param'> + <input + type='text' + className='notes' + value={this.state.notes} + placeholder='Notes' + onChange={e => this.setState({ notes: e.target.value })} + /> + </div> + + <div className='param'> + <button + className='btn btn-primary' + onClick={this.save.bind(this)} + ref={ref => this.button = ref} + >Save Verification</button> + + <button + className='btn' + onClick={this.next.bind(this)} + >{'Next >'}</button> + </div> + + <iframe className='pdfViewer' src={citation.pdf[this.state.pdf_index]} /> + </div> + ) + } +} + +const mapStateToProps = state => ({ + api: state.api, +}) +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators({ ...actions }, dispatch), +}) + +export default connect(mapStateToProps, mapDispatchToProps)(PaperVerify) diff --git a/scraper/client/search/browse.component.js b/scraper/client/search/browse.component.js deleted file mode 100644 index e9ddb04e..00000000 --- a/scraper/client/search/browse.component.js +++ /dev/null @@ -1,77 +0,0 @@ -import React, { Component } from 'react' -import { Link, withRouter } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import { Loader, Keyframes, Video } from '../common' -// import { Coco } from '../metadata' -import * as searchActions from './search.actions' -import * as metadataActions from '../metadata/metadata.actions' -import SearchMeta from './search.meta' - -class Browse extends Component { - componentDidMount() { - this.browse() - } - - componentDidUpdate(prevProps) { - if (prevProps.match.params !== this.props.match.params) { - this.browse() - } - } - - browse() { - const { hash } = this.props.match.params - if (hash) { - this.props.searchActions.browse(hash) - } - if (hash) { - this.props.metadataActions.fetchMetadata(hash) - } - } - - render() { - const { browse, options } = this.props - console.log('browse', browse) - - if (!browse || browse.reset || browse.loading) { - return <div className="browseComponent column"><h3>Loading keyframes...</h3><Loader /></div> - } - return ( - <div className="browseComponent column"> - <h3>Video Preview</h3> - <Video size={'md'} /> - <SearchMeta query={browse} sugarcube /> - <div className='row buttons'> - <Link - to={'/metadata/' + browse.hash} - className='btn' - > - View Full Metadata - </Link> - </div> - <h3>Keyframes</h3> - <Keyframes - frames={browse.frames} - showHash - showTimestamp - showSearchButton - showSaveButton - /> - </div> - ) - } -} - -const mapStateToProps = state => ({ - browse: state.search.browse, - options: state.search.options, - metadata: state.metadata, -}) - -const mapDispatchToProps = dispatch => ({ - searchActions: bindActionCreators({ ...searchActions }, dispatch), - metadataActions: bindActionCreators({ ...metadataActions }, dispatch), -}) - -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Browse)) diff --git a/scraper/client/search/index.js b/scraper/client/search/index.js deleted file mode 100644 index 99c2b74b..00000000 --- a/scraper/client/search/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import Menu from './search.menu' -import Container from './search.container' -import Meta from './search.meta' -import Query from './search.query' -import Results from './search.results' -import Browse from './browse.component' - -import './search.css' - -export { - Menu, - Container, - Meta, - Query, - Results, - Browse, -} diff --git a/scraper/client/search/panicButton.component.js b/scraper/client/search/panicButton.component.js deleted file mode 100644 index a12c817b..00000000 --- a/scraper/client/search/panicButton.component.js +++ /dev/null @@ -1,49 +0,0 @@ -import React, { Component } from 'react' -import { withRouter } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import * as actions from './search.actions' - -class PanicButton extends Component { - constructor() { - super() - this.keydown = this.keydown.bind(this) - } - - componentDidMount() { - document.addEventListener('keydown', this.keydown) - } - - componentWillUnmount() { - document.removeEventListener('keydown', this.keydown) - } - - keydown(e) { - if (e.keyCode === 27) { - this.panic() - } - } - - panic() { - this.props.actions.panic() - this.props.history.push('/search/') - } - - render() { - return ( - <button className='btn panic' onClick={() => this.panic()}> - <span>⚠</span> Panic - </button> - ) - } -} - -const mapStateToProps = state => ({ -}) - -const mapDispatchToProps = dispatch => ({ - actions: bindActionCreators({ panic: actions.panic }, dispatch) -}) - -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PanicButton)) diff --git a/scraper/client/search/search.actions.js b/scraper/client/search/search.actions.js deleted file mode 100644 index 95cea433..00000000 --- a/scraper/client/search/search.actions.js +++ /dev/null @@ -1,162 +0,0 @@ -// import fetchJsonp from 'fetch-jsonp' -import * as types from '../types' -// import { hashPath } from '../util' -import { store, history } from '../store' -import { post, pad, verify, preloadImage } from '../util' -import querystring from 'query-string' - -// urls - -const url = { - upload: () => process.env.API_HOST + '/search/api/upload', - search: () => process.env.API_HOST + '/search/api/fetch', - searchByVerifiedFrame: (verified, hash, frame) => process.env.API_HOST + '/search/api/search/' + verified + '/' + hash + '/' + pad(frame, 6), - searchByFrame: (hash, frame) => process.env.API_HOST + '/search/api/search/' + hash + '/' + pad(frame, 6), - browse: hash => process.env.API_HOST + '/search/api/list/' + hash, - random: () => process.env.API_HOST + '/search/api/random', - check: () => process.env.API_HOST + '/api/images/import/search', -} -export const publicUrl = { - browse: hash => '/search/browse/' + hash, - searchByVerifiedFrame: (verified, hash, frame) => '/search/keyframe/' + verify(verified) + '/' + hash + '/' + pad(frame, 6), - searchByFrame: (hash, frame) => '/search/keyframe/' + hash + '/' + pad(frame, 6), - review: () => '/search/review/' -} - -// standard loading events - -const loading = (tag, offset) => ({ - type: types.search.loading, - tag, - offset -}) -const loaded = (tag, data, offset = 0) => ({ - type: types.search.loaded, - tag, - data, - offset -}) -const error = (tag, err) => ({ - type: types.search.error, - tag, - err -}) - -// search UI functions - -export const panic = () => dispatch => { - history.push('/search/') - dispatch({ type: types.search.panic }) -} -export const updateOptions = opt => dispatch => { - dispatch({ type: types.search.update_options, opt }) -} - -// API functions - -export const upload = (file, query) => dispatch => { - const { options } = store.getState().search - const tag = 'query' - const fd = new FormData() - fd.append('query_img', file) - fd.append('limit', options.perPage) - if (!query) { - dispatch(loading(tag)) - } - post(url.upload(), fd) - .then(data => { - if (query) { - const { timing } = data.query - data.query = { - ...query, - timing, - } - let qs = {} - if (data.query.crop) { - let { x, y, w, h } = data.query.crop - qs.crop = [x, y, w, h].map(n => parseInt(n, 10)).join(',') - } - if (query.url && !query.hash) { - qs.url = query.url - } - // history.push(window.location.pathname + '#' + querystring.stringify(qs)) - // window.location.hash = querystring.stringify(qs) - } else if (data.query.url && !window.location.search.match(data.query.url)) { - history.push('/search/?url=' + data.query.url) - } - dispatch(loaded(tag, data)) - }) - .catch(err => dispatch(error(tag, err))) -} -export const searchByVerifiedFrame = (verified, hash, frame, offset = 0) => dispatch => { - const { options } = store.getState().search - const tag = 'query' - dispatch(loading(tag, offset)) - const qs = querystring.stringify({ limit: options.perPage, offset }) - preloadImage({ verified, hash, frame }) - fetch(url.searchByVerifiedFrame(verified, hash, frame) + '?' + qs, { - method: 'GET', - mode: 'cors', - }) - .then(data => data.json()) - .then(data => dispatch(loaded(tag, data, offset))) - .catch(err => dispatch(error(tag, err))) -} -export const searchByFrame = (hash, frame, offset = 0) => dispatch => { - const { options } = store.getState().search - const tag = 'query' - dispatch(loading(tag, offset)) - const qs = querystring.stringify({ limit: options.perPage, offset }) - preloadImage({ verified: false, hash, frame }) - fetch(url.searchByFrame(hash, frame) + '?' + qs, { - method: 'GET', - mode: 'cors', - }) - .then(data => data.json()) - .then(data => dispatch(loaded(tag, data, offset))) - .catch(err => dispatch(error(tag, err))) -} -export const search = (uri, offset = 0) => dispatch => { - const { options } = store.getState().search - const tag = 'query' - dispatch(loading(tag, offset)) - const qs = querystring.stringify({ url: uri, limit: options.perPage, offset }) - if (uri.indexOf('static') === 0) { - preloadImage({ uri }) - } - fetch(url.search(uri) + '?' + qs, { - method: 'GET', - mode: 'cors', - }) - .then(data => data.json()) - .then(data => dispatch(loaded(tag, data, offset))) - .catch(err => dispatch(error(tag, err))) -} -export const browse = hash => dispatch => { - const tag = 'browse' - dispatch(loading(tag)) - fetch(url[tag](hash), { - method: 'GET', - mode: 'cors', - }) - .then(data => data.json()) - .then(data => dispatch(loaded(tag, data))) - .catch(err => dispatch(error(tag, err))) -} -export const random = () => dispatch => { - const { options } = store.getState().search - const qs = querystring.stringify({ limit: options.perPage }) - const tag = 'query' - dispatch(loading(tag)) - fetch(url.random() + '?' + qs, { - method: 'GET', - mode: 'cors', - }) - .then(data => data.json()) - .then(data => { - dispatch(loaded(tag, data)) - history.push(publicUrl.searchByVerifiedFrame(data.query.verified, data.query.hash, data.query.frame)) - // window.history.pushState(null, 'VSearch: Results', publicUrl.searchByVerifiedFrame(data.query.verified, data.query.hash, data.query.frame)) - }) - .catch(err => dispatch(error(tag, err))) -} diff --git a/scraper/client/search/search.container.js b/scraper/client/search/search.container.js deleted file mode 100644 index 965d7c8e..00000000 --- a/scraper/client/search/search.container.js +++ /dev/null @@ -1,101 +0,0 @@ -import React, { Component } from 'react' -import { withRouter } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import * as querystring from 'querystring' - -import * as searchActions from './search.actions' -import * as metadataActions from '../metadata/metadata.actions' - -import SearchQuery from './search.query' -import SearchResults from './search.results' -import SearchSafety from './search.safety' - -class SearchContainer extends Component { - componentDidMount() { - const qs = querystring.parse(this.props.location.search.substr(1)) - if (qs && qs.url) { - this.props.searchActions.search(qs.url) - } else { - this.searchByHash() - } - } - - componentDidUpdate(prevProps) { - if (prevProps.match.params !== this.props.match.params && JSON.stringify(this.props.match.params) !== JSON.stringify(prevProps.match.params)) { - this.searchByHash() - } - // const qsOld = querystring.parse(prevProps.location.search.substr(1)) - // const qsNew = querystring.parse(this.props.location.search.substr(1)) - // if (qsOld && qsNew && qsNew.url && qsNew.url !== qsOld.url) { - // this.props.actions.search(qsNew.url) - // } - } - - searchByHash(offset = 0) { - const { verified, hash, frame } = this.props.match.params - if (verified && hash && frame) { - this.props.searchActions.searchByVerifiedFrame(verified, hash, frame, offset) - } else if (hash && frame) { - this.props.searchActions.searchByFrame(hash, frame, offset) - } - if (hash && !offset) { - this.props.metadataActions.fetchMetadata(hash) - } - } - - searchByOffset() { - const offset = this.props.query.results.length - const qs = querystring.parse(this.props.location.search.substr(1)) - if (qs && qs.url) { - this.props.searchActions.search(qs.url, offset) - } - else { - this.searchByHash(offset) - } - } - - render() { - const { query, results, loadingMore } = this.props.query - const options = this.props.options - // console.log('search container', query, results, loadingMore) - let showLoadMore = true - if (!query || query.reset || query.loading || !results || !results.length) { - showLoadMore = false - } - let isWide = (results && results.length > Math.min(options.perPage, 30)) - let isMoreLoaded = (results && results.length > options.perPage) - return ( - <div className='searchContainer'> - <SearchQuery /> - <SearchResults /> - {showLoadMore - ? !loadingMore - ? <button - onClick={() => this.searchByOffset()} - className={isWide ? 'btn loadMore wide' : 'btn loadMore'} - > - Load more - </button> - : <div className='loadingMore'>{'Loading more results...'}</div> - : <div> - </div> - } - {!isMoreLoaded && <SearchSafety />} - </div> - ) - } -} - -const mapStateToProps = state => ({ - query: state.search.query, - options: state.search.options, - metadata: state.metadata, -}) - -const mapDispatchToProps = dispatch => ({ - searchActions: bindActionCreators({ ...searchActions }, dispatch), - metadataActions: bindActionCreators({ ...metadataActions }, dispatch), -}) - -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(SearchContainer)) diff --git a/scraper/client/search/search.css b/scraper/client/search/search.css deleted file mode 100644 index 280e53fe..00000000 --- a/scraper/client/search/search.css +++ /dev/null @@ -1,230 +0,0 @@ -.btn span { - font-size: large; -} -.row { - display: flex; - flex-direction: row; -} -.column { - display: flex; - flex-direction: column; -} - -.searchContainer h3 { - padding: 0; - margin-top: 0; - margin-bottom: 5px; - margin-left: 3px; -} -.searchContainer h4 { - margin-left: 0; - width: 100%; -} -.searchContainer .subtitle { - display: block; - margin-left: 3px; - margin-bottom: 10px; -} -.searchForm { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - padding: 20px; - background: #eee; -} -.searchForm .row { - align-items: center; -} - -.searchMeta { - display: flex; - flex-direction: column; - font-size: 14px; - line-height: 18px; - padding: 0; -} -.searchMeta span { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: calc(100vw - 23px - 640px - 30px); -} - -.keyframe .thumbnail { - position: relative; - cursor: pointer; -} -.keyframe .searchButtons { - position: absolute; - bottom: 0; left: 0; - padding: 0 5px 15px 5px; - width: 100%; - text-align: center; - opacity: 0; - transition: all 0.2s; -} -.desktop .keyframe .thumbnail:hover .searchButtons, -.mobile .keyframe .searchButtons { - opacity: 1; -} -.keyframe .searchButtons .btn { - margin-right: 0; - height: auto; - padding: 0.15rem 0.3rem; -} -.keyframe a { - text-decoration: none; -} - -.body > div.searchForm { - padding-bottom: 20px; -} -.upload { - position: relative; - cursor: pointer; -} -.upload .btn { - pointer-events: none; - cursor: pointer; -} -.upload input { - position: absolute; - top: 0; left: 0; - width: 100%; height: 100%; - opacity: 0; - cursor: pointer; -} - -.reviewSaved, -.browseComponent, -.searchQuery { - margin: 0px 10px; - padding: 13px; -} -.searchQuery img { - cursor: crosshair; - user-select: none; - max-width: 640px; - max-height: 480px; -} -.searchContainer .searchQuery h3 { - margin-left: 0; - margin-bottom: 10px; -} - -.searchBox { - min-width: 640px; - margin: 0 10px 0 0; - background-color: #eee; - position: relative; -} -.searchBox img { - display: block; -} -.searchBox .box { - position: absolute; - cursor: crosshair; - border: 1px solid #11f; - background-color: rgba(16,16,255,0.1); -} - -.searchResults { - margin: 0 20px 20px 20px; - display: flex; - flex-direction: row; - flex-wrap: wrap; -} -.searchResultsHeading { - width: 100%; -} -.searchOptions .row { - font-size: 12px; - margin-left: 10px; -} -.searchOptions input { - font-size: 12px; - margin-right: 5px; - font-family: Helvetica, sans-serif; -} -.searchOptions input[type=text], -.searchOptions input[type=number] { - width: 30px; - text-align: right; -} -.keyframeGroup { - max-width: 650px; - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-items: flex-start; - align-content: flex-start; - justify-content: flex-start; -} -.keyframeGroup h4 a { - color: #888; - text-decoration: none -} -.keyframeGroup h4 a:hover { - text-decoration: underline -} - -/* load more button that gets bigger */ - -.loadMore { - width: 400px; - margin: 20px; - height: 40px; - transition: all; -} -.loadMore.wide { - width: calc(100% - 40px); - margin: 20px; - height: 100px; -} -.loadingMore { - margin: 20px 20px 200px 20px; -} - -/* health and safety warning */ - -.safety div { - display: inline-block; - margin: 20px 20px; - padding: 10px; - background: #fff8e8; - color: #111; - box-shadow: 0 1px 2px rgba(0,0,0,0.2); - font-size: 13px; - line-height: 1.4; -} -.safety ul { - margin: 0; - padding: 0 21px; -} -.safety li { - padding: 1px 0 0 0; -} -.safety h4 { - margin-top: 5px; -} - -/* browser section */ - -.browseComponent h3 { - margin-bottom: 10px; -} -.browseComponent .buttons { - margin-top: 10px; -} - -/* disable twiddle button on input[type=number] */ - -input::-webkit-outer-spin-button, -input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} -input[type='number'] { - -moz-appearance:textfield; -} diff --git a/scraper/client/search/search.menu.js b/scraper/client/search/search.menu.js deleted file mode 100644 index f5f9423e..00000000 --- a/scraper/client/search/search.menu.js +++ /dev/null @@ -1,96 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' - -import * as actions from './search.actions' -import PanicButton from './panicButton.component' - -class SearchMenu extends Component { - upload(e) { - const files = e.dataTransfer ? e.dataTransfer.files : e.target.files - let i - let file - for (i = 0; i < files.length; i++) { - file = files[i] - if (file && file.type.match('image.*')) break - } - if (!file) return - this.props.actions.upload(file) - } - - random() { - this.props.actions.random() - } - - render() { - const { savedCount, options } = this.props - return ( - <div className="searchForm row"> - <div className='row'> - <div className='upload'> - <button className='btn'><span>⤴</span> Search by Upload</button> - <input - type="file" - name="img" - accept="image/*" - onChange={this.upload.bind(this)} - required - /> - </div> - <button className='btn random' onClick={this.random.bind(this)}><span>♘</span> Random</button> - <PanicButton /> - <Link to={actions.publicUrl.review()}> - <button className='btn btn-primary'><span>⇪</span>{ - ' ' + savedCount + ' Saved Image' + (savedCount === 1 ? '' : 's') - }</button> - </Link> - </div> - - <div className='row searchOptions'> - <select - className='form-select' - onChange={e => this.props.actions.updateOptions({ thumbnailSize: e.target.value })} - value={options.thumbnailSize} - > - <option value='th'>Thumbnail</option> - <option value='sm'>Small</option> - <option value='md'>Medium</option> - <option value='lg'>Large</option> - </select> - <label className='row'> - <input - type='checkbox' - checked={options.groupByHash} - onChange={e => this.props.actions.updateOptions({ groupByHash: e.target.checked })} - /> - {' Group by hash'} - </label> - <label className='row'> - <input - type='number' - value={options.perPage} - className='perPage' - min={1} - max={100} - onChange={e => this.props.actions.updateOptions({ perPage: e.target.value })} - onBlur={() => window.location.reload()} - /> - {' per page'} - </label> - </div> - </div> - ) - } -} - -const mapStateToProps = state => ({ - options: state.search.options, - savedCount: state.review.count, -}) - -const mapDispatchToProps = dispatch => ({ - actions: bindActionCreators({ ...actions }, dispatch) -}) - -export default connect(mapStateToProps, mapDispatchToProps)(SearchMenu) diff --git a/scraper/client/search/search.meta.js b/scraper/client/search/search.meta.js deleted file mode 100644 index b4eaeaad..00000000 --- a/scraper/client/search/search.meta.js +++ /dev/null @@ -1,68 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import { format } from 'date-fns' - -import { timestamp } from '../util' -import * as searchActions from './search.actions' - -class SearchMeta extends Component { - render() { - const { query, metadata, sugarcube } = this.props - if (!query || !metadata || !metadata.mediainfo || metadata.metadata === 'loading') { - return <div className='searchMeta'></div> - } - const sugarcubeId = metadata.mediainfo.sugarcube_id - const { video } = metadata.mediainfo.metadata.mediainfo - const { x, y, w, h } = query.crop || {} - return ( - <div className='searchMeta'> - {'verified' in query && - <span className={query.verified ? 'verified' : 'unverified'}> - {query.verified ? 'verified' : 'unverified'} - </span> - } - {query.hash && - <span> - {'sha256: '} - <Link className="sha256" to={searchActions.publicUrl.browse(query.hash)}>{query.hash}</Link> - </span> - } - {query.frame && - <span> - {'Frame: '} - {timestamp(query.frame, video.frame_rate)} - {' / '} - {timestamp(video.duration / 1000, 1)} - </span> - } - {query.crop && - <span> - {'Crop: '}{parseInt(w, 10) + 'x' + parseInt(h, 10) + ' @ (' + parseInt(x, 10) + ', ' + parseInt(y, 10) + ')'} - </span> - } - {!!(video && video.encoded_date) && - <span> - {'Date: '}{format(new Date(video.encoded_date), "DD-MMM-YYYY")} - </span> - } - {!!(sugarcube && sugarcubeId) && - <span> - sugarcube: {sugarcubeId} - </span> - } - </div> - ) - } -} - -const mapStateToProps = state => ({ - metadata: state.metadata, -}) - -const mapDispatchToProps = dispatch => ({ - actions: bindActionCreators({ ...searchActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(SearchMeta) diff --git a/scraper/client/search/search.query.js b/scraper/client/search/search.query.js deleted file mode 100644 index 276f1943..00000000 --- a/scraper/client/search/search.query.js +++ /dev/null @@ -1,227 +0,0 @@ -import React, { Component } from 'react' -import { Link } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import toBlob from 'data-uri-to-blob' - -import { clamp, px } from '../util' -import { Loader } from '../common' - -import * as searchActions from './search.actions' -import SearchMeta from './search.meta' - -const defaultState = { - dragging: false, - draggingBox: false, - bounds: null, - mouseX: 0, - mouseY: 0, - box: { - x: 0, - y: 0, - w: 0, - h: 0, - } -} - -class SearchQuery extends Component { - state = { - ...defaultState - } - - constructor() { - super() - this.handleMouseDown = this.handleMouseDown.bind(this) - this.handleMouseDownOnBox = this.handleMouseDownOnBox.bind(this) - this.handleMouseMove = this.handleMouseMove.bind(this) - this.handleMouseUp = this.handleMouseUp.bind(this) - } - - componentDidMount() { - document.body.addEventListener('mousemove', this.handleMouseMove) - document.body.addEventListener('mouseup', this.handleMouseUp) - } - - componentDidUpdate(prevProps) { - // console.log(this.props.query.query, !prevProps.query.query) - if (this.state.bounds && (!this.props.query.query || !prevProps.query.query || this.props.query.query.url !== prevProps.query.query.url)) { - this.setState({ ...defaultState }) - } - } - - componentWillUnmount() { - document.body.removeEventListener('mousemove', this.handleMouseMove) - document.body.removeEventListener('mouseup', this.handleMouseUp) - } - - handleMouseDown(e) { - e.preventDefault() - const bounds = this.imgRef.getBoundingClientRect() - const mouseX = e.pageX - const mouseY = e.pageY - const x = mouseX - bounds.left - const y = mouseY - bounds.top - const w = 1 - const h = 1 - this.setState({ - dragging: true, - bounds, - mouseX, - mouseY, - box: { - x, y, w, h, - } - }) - } - - handleMouseDownOnBox(e) { - const bounds = this.imgRef.getBoundingClientRect() - const mouseX = e.pageX - const mouseY = e.pageY - this.setState({ - draggingBox: true, - bounds, - mouseX, - mouseY, - initialBox: { - ...this.state.box - }, - box: { - ...this.state.box - } - }) - } - - handleMouseMove(e) { - const { - dragging, draggingBox, - bounds, mouseX, mouseY, initialBox, box - } = this.state - if (dragging) { - e.preventDefault() - let { x, y } = box - let w = clamp(e.pageX - mouseX, 0, bounds.width - x) - let h = clamp(e.pageY - mouseY, 0, bounds.height - y) - this.setState({ - box: { - x, y, w, h, - } - }) - } else if (draggingBox) { - e.preventDefault() - let { x, y, w, h } = initialBox - let dx = (e.pageX - mouseX) - let dy = (e.pageY - mouseY) - this.setState({ - box: { - x: clamp(x + dx, 0, bounds.width - w), - y: clamp(y + dy, 0, bounds.height - h), - w, - h, - } - }) - } - } - - handleMouseUp(e) { - const { actions } = this.props - const { dragging, draggingBox, bounds, box } = this.state - if (!dragging && !draggingBox) return - e.preventDefault() - const { x, y, w, h } = box - // console.log(x, y, w, h) - const img = this.imgRef - const canvas = document.createElement('canvas') - const ctx = canvas.getContext('2d') - const ratio = img.naturalWidth / bounds.width - canvas.width = w * ratio - canvas.height = h * ratio - if (w < 10 || h < 10) { - this.setState({ dragging: false, draggingBox: false, box: { x: 0, y: 0, w: 0, h: 0 } }) - return - } - this.setState({ dragging: false, draggingBox: false }) - // query_div.appendChild(canvas) - const newImage = new Image() - let loaded = false - newImage.onload = () => { - if (loaded) return - loaded = true - newImage.onload = null - ctx.drawImage( - newImage, - Math.round(x * ratio), - Math.round(y * ratio), - Math.round(w * ratio), - Math.round(h * ratio), - 0, 0, canvas.width, canvas.height - ) - const blob = toBlob(canvas.toDataURL('image/jpeg', 0.9)) - actions.upload(blob, { - ...this.props.query.query, - crop: { - x, y, w, h, - } - }) - } - // console.log(img.src) - newImage.crossOrigin = 'anonymous' - newImage.src = img.src - if (newImage.complete) { - newImage.onload() - } - } - - render() { - const { query } = this.props.query - const { box } = this.state - const { x, y, w, h } = box - if (!query) return null - if (query.loading) { - return <div className="searchQuery column"><h2>Loading results...</h2><Loader /></div> - } - let { url } = query - if (url && url.indexOf('static') === 0) { - url = '/search/' + url - } - return ( - <div className="searchQuery row"> - <div className="searchBox"> - <img - src={url} - ref={ref => this.imgRef = ref} - onMouseDown={this.handleMouseDown} - crossOrigin='anonymous' - /> - {!!w && - <div - className="box" - style={{ - left: x, - top: y, - width: w, - height: h, - }} - onMouseDown={this.handleMouseDownOnBox} - /> - } - </div> - <div> - <h3>Your Query</h3> - <SearchMeta query={query} /> - </div> - </div> - ) - } -} - -const mapStateToProps = state => ({ - query: state.search.query, - options: state.search.options, -}) - -const mapDispatchToProps = dispatch => ({ - actions: bindActionCreators({ ...searchActions }, dispatch), -}) - -export default connect(mapStateToProps, mapDispatchToProps)(SearchQuery) diff --git a/scraper/client/search/search.reducer.js b/scraper/client/search/search.reducer.js deleted file mode 100644 index b9de60bd..00000000 --- a/scraper/client/search/search.reducer.js +++ /dev/null @@ -1,84 +0,0 @@ -import * as types from '../types' -import session from '../session' - -const initialState = () => ({ - query: { reset: true }, - browse: { reset: true }, - options: { - thumbnailSize: session('thumbnailSize') || 'th', - perPage: parseInt(session('perPage'), 10) || 50, - groupByHash: session('groupByHash'), - } -}) - -const loadingState = { - query: { - query: { loading: true }, - results: [] - }, - loading: { - loading: true - } -} - -export default function searchReducer(state = initialState(), action) { - // console.log(action.type, action) - switch (action.type) { - case types.search.loading: - if (action.tag === 'query' && action.offset) { - return { - ...state, - query: { - ...state.query, - loadingMore: true, - } - } - } - return { - ...state, - [action.tag]: loadingState[action.tag] || loadingState.loading, - } - - case types.search.loaded: - if (action.tag === 'query' && action.offset) { - return { - ...state, - query: { - query: action.data.query, - results: [ - ...state.query.results, - ...action.data.results, - ], - loadingMore: false, - } - } - } - return { - ...state, - [action.tag]: action.data, - } - - case types.search.error: - return { - ...state, - [action.tag]: { error: action.err }, - } - - case types.search.panic: - return { - ...initialState(), - } - - case types.search.update_options: - session.setAll(action.opt) - return { - ...state, - options: { - ...action.opt, - } - } - - default: - return state - } -} diff --git a/scraper/client/search/search.results.js b/scraper/client/search/search.results.js deleted file mode 100644 index 8b9e0c5e..00000000 --- a/scraper/client/search/search.results.js +++ /dev/null @@ -1,49 +0,0 @@ -import React, { Component } from 'react' -import { Link, withRouter } from 'react-router-dom' -import { bindActionCreators } from 'redux' -import { connect } from 'react-redux' -import * as querystring from 'querystring' - -import { Keyframes } from '../common' -import * as searchActions from './search.actions' - -function SearchResults({ query, results, options }) { - if (!query || query.reset || query.loading || !results) { - return <div></div> - } - if (!query.loading && !results.length) { - return <div className='searchResults'><h3>No results</h3></div> - } - return ( - <div className="searchResults"> - <div className='searchResultsHeading row'> - <div className='column'> - <h3>Search Results</h3> - <small className="subtitle"> - {'Searched 10,523,176 frames from 576,234 videos (took '}{query.timing.toFixed(2)}{' ms)'} - </small> - </div> - </div> - <Keyframes - frames={results} - showHash - showTimestamp={options.groupByHash} - showSearchButton - showSaveButton - groupByHash={options.groupByHash} - /> - </div> - ) -} - -const mapStateToProps = state => ({ - query: state.search.query.query, - results: state.search.query.results, - options: state.search.options, -}) - -const mapDispatchToProps = dispatch => ({ - searchActions: bindActionCreators({ ...searchActions }, dispatch), -}) - -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(SearchResults)) diff --git a/scraper/client/search/search.safety.js b/scraper/client/search/search.safety.js deleted file mode 100644 index b4f92664..00000000 --- a/scraper/client/search/search.safety.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react' - -export default function SearchWarning() { - return ( - <div className='safety'> - <div> - <h4>Safety Tips</h4> - <ul> - <li> Look away if you see something traumatic </li> - <li> Hit <tt>ESC</tt> to activate panic mode (hides all images) </li> - <li> Use thumbnails to reduce details </li> - <li> Take breaks and refresh yourself with positive imagery </li> - </ul> - </div> - </div> - ) -} |
