summaryrefslogtreecommitdiff
path: root/scraper
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2019-03-19 20:45:24 +0100
committerJules Laplace <julescarbon@gmail.com>2019-03-19 20:45:24 +0100
commit7eb3c04ef85fa0311bdf30b24df2aba102757878 (patch)
treebfb1354dcbed9486b43e75c7d2507a725212db24 /scraper
parent46885570e527a2bb8a374e7044afdf0a4c5ba07e (diff)
rebuild site
Diffstat (limited to 'scraper')
-rw-r--r--scraper/client/actions.js10
-rw-r--r--scraper/client/app.js1
-rw-r--r--scraper/client/common/header.component.js1
-rw-r--r--scraper/client/paper/paper.address.js1
-rw-r--r--scraper/client/paper/paper.verify.js205
-rw-r--r--scraper/client/search/browse.component.js77
-rw-r--r--scraper/client/search/index.js17
-rw-r--r--scraper/client/search/panicButton.component.js49
-rw-r--r--scraper/client/search/search.actions.js162
-rw-r--r--scraper/client/search/search.container.js101
-rw-r--r--scraper/client/search/search.css230
-rw-r--r--scraper/client/search/search.menu.js96
-rw-r--r--scraper/client/search/search.meta.js68
-rw-r--r--scraper/client/search/search.query.js227
-rw-r--r--scraper/client/search/search.reducer.js84
-rw-r--r--scraper/client/search/search.results.js49
-rw-r--r--scraper/client/search/search.safety.js17
-rw-r--r--scraper/s2-geocode-server.py42
18 files changed, 258 insertions, 1179 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>
- )
-}
diff --git a/scraper/s2-geocode-server.py b/scraper/s2-geocode-server.py
index edf768aa..62e01b0d 100644
--- a/scraper/s2-geocode-server.py
+++ b/scraper/s2-geocode-server.py
@@ -14,6 +14,7 @@ load_dotenv()
from util import *
locations_worksheet = fetch_worksheet('paper_locations')
+verifications_worksheet = fetch_worksheet('verifications')
paper_lookup = fetch_google_lookup('citation_lookup')
addresses = AddressBook()
@@ -89,6 +90,45 @@ def add_address():
'status': 'ok'
})
+@app.route('/api/verify/<sha256>', methods=['GET'])
+def find_verification(sha256):
+ worksheet = fetch_worksheet('verifications')
+ try:
+ cell = worksheet.find(sha256)
+ except:
+ return jsonify({
+ 'error': 'no_match'
+ })
+ if cell and cell.row:
+ keys = worksheet.row_values(1)
+ values_list = worksheet.row_values(cell.row)
+ lookup = {}
+ for key, value in zip(keys, values_list):
+ lookup[key] = value
+ return jsonify({
+ 'paper': lookup,
+ })
+ else:
+ return jsonify({
+ 'error': 'no_match'
+ })
+
+@app.route('/api/verify/add', methods=['POST'])
+def add_verifications():
+ form = request.get_json()
+ print(form)
+ # id, title, verified, verified_by, notes
+ locations_worksheet.append_row([
+ form['paper_id'],
+ form['title'],
+ form['verified'],
+ form['verified_by'],
+ form['notes'],
+ ])
+ return jsonify({
+ 'status': 'ok'
+ })
+
if __name__=="__main__":
- app.run("0.0.0.0", debug=False)
+ app.run("0.0.0.0", port=7727, debug=False)