summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorjules@lens <julescarbon@gmail.com>2019-04-02 20:36:51 +0200
committerjules@lens <julescarbon@gmail.com>2019-04-02 20:36:51 +0200
commit1d238346b5609e9454a4917c75631a550b5b43d1 (patch)
tree8a936e721e78c7b5948b303e6a1686c96b882d51 /client
parentb4b58f2279fb01fa0240006c460c0b5ec95c1126 (diff)
parentf58d41731fc07d94d594d5582aef203564f990ec (diff)
Merge branch 'master' of asdf.us:megapixels_dev
Diffstat (limited to 'client')
-rw-r--r--client/applet.js7
-rw-r--r--client/chart/constants.js4
-rw-r--r--client/chart/countriesByYear.chart.js11
-rw-r--r--client/chart/pie.charts.js5
-rw-r--r--client/index.js45
-rw-r--r--client/map/index.js81
-rw-r--r--client/modalImage/index.js7
-rw-r--r--client/modalImage/modal.css69
-rw-r--r--client/modalImage/modalImage.container.js85
-rw-r--r--client/table/citations.table.js130
-rw-r--r--client/table/file.table.js82
-rw-r--r--client/table/index.js11
-rw-r--r--client/table/tabulator.css69
-rw-r--r--client/tables.js96
-rw-r--r--client/util/index.js9
15 files changed, 574 insertions, 137 deletions
diff --git a/client/applet.js b/client/applet.js
index 21e1e4fa..db95168a 100644
--- a/client/applet.js
+++ b/client/applet.js
@@ -4,11 +4,12 @@ import { Container as FaceSearchContainer } from './faceSearch'
import { Container as FaceAnalysisContainer } from './faceAnalysis'
import { Container as NameSearchContainer } from './nameSearch'
import { Container as DatasetListContainer } from './datasetList'
+import { CitationsTable, FileTable } from './table'
import { CountriesByYear, PieCharts } from './chart'
export default class Applet extends Component {
render() {
- // console.log(this.props)
+ // console.log(this.props.payload.cmd)
switch (this.props.payload.cmd) {
case 'face_analysis':
return <FaceAnalysisContainer {...this.props} />
@@ -22,6 +23,10 @@ export default class Applet extends Component {
return <CountriesByYear {...this.props} />
case 'piechart':
return <PieCharts {...this.props} />
+ case 'citations':
+ return <CitationsTable {...this.props} />
+ case 'load_file':
+ return <FileTable {...this.props} />
default:
return <pre style={{ color: '#0f0' }}>{'Megapixels'}</pre>
}
diff --git a/client/chart/constants.js b/client/chart/constants.js
index 70375ba3..b916cbd2 100644
--- a/client/chart/constants.js
+++ b/client/chart/constants.js
@@ -59,6 +59,6 @@ export const institutionOrder = {
export const institutionLabels = {
'edu': 'Academic',
'company': 'Commercial',
- 'gov': 'Government / Military',
- 'mil': 'Government / Military',
+ 'gov': 'Military / Government',
+ 'mil': 'Military / Government',
} \ No newline at end of file
diff --git a/client/chart/countriesByYear.chart.js b/client/chart/countriesByYear.chart.js
index 4257748c..df7a4530 100644
--- a/client/chart/countriesByYear.chart.js
+++ b/client/chart/countriesByYear.chart.js
@@ -68,11 +68,13 @@ class CountriesByYearChart extends Component {
)
)
- citationCountsByYear.push(
- [otherCountriesLabel].concat(
- yearList.map(year => otherCountries.reduce((a,b) => (a + years[year][b]), 0))
+ if (otherCountries.length) {
+ citationCountsByYear.push(
+ [otherCountriesLabel].concat(
+ yearList.map(year => otherCountries.reduce((a,b) => (a + years[year][b]), 0))
+ )
)
- )
+ }
let maxCitationsInAYear = 0
let currentSum = 0
@@ -158,6 +160,7 @@ class CountriesByYearChart extends Component {
}
}}
/>
+ <div className='caption'>{paper.name}{' dataset citations per country per year. We verified ' + citations.length + ' papers that used the dataset.'}</div>
</div>
)
}
diff --git a/client/chart/pie.charts.js b/client/chart/pie.charts.js
index 6e579537..84e85c3a 100644
--- a/client/chart/pie.charts.js
+++ b/client/chart/pie.charts.js
@@ -17,6 +17,7 @@ class PieCharts extends Component {
render() {
const { payload } = this.props
const { paper, citations } = payload.data
+ console.log(this.props)
if (!citations.length) return null
const countries = {}
@@ -83,9 +84,10 @@ class PieCharts extends Component {
}
}}
size={{
- height: 336,
+ height: countryRows.length < 4 ? 316 : 336,
}}
/>
+ <span className='chartCaption'>{paper.name}{' dataset citations by country'}</span>
</div>
<div>
<C3Chart
@@ -105,6 +107,7 @@ class PieCharts extends Component {
height: 316,
}}
/>
+ <span className='chartCaption'>{paper.name}{' dataset citations by organization type'}</span>
</div>
</div>
)
diff --git a/client/index.js b/client/index.js
index 5c8bc880..10ed8563 100644
--- a/client/index.js
+++ b/client/index.js
@@ -2,12 +2,13 @@ import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import { Provider } from 'react-redux'
+import 'waypoints/lib/noframework.waypoints.min.js';
import { toArray } from './util'
import Applet from './applet'
import { store } from './store'
-import appendTable from './tables'
import appendMap from './map'
+import { ModalImage } from './modalImage'
function appendReactApplet(el, payload) {
ReactDOM.render(
@@ -19,6 +20,13 @@ function appendReactApplet(el, payload) {
)
}
+function appendModalImage() {
+ const el = document.createElement('div')
+ document.body.appendChild(el)
+ ReactDOM.render(<ModalImage />, el)
+ console.log(el)
+}
+
function fetchDataset(payload) {
if (payload.command === 'face_analysis') return new Promise(resolve => resolve())
if (payload.dataset === 'info') return new Promise(resolve => resolve())
@@ -33,7 +41,8 @@ function appendApplets(applets) {
case 'citations':
case 'load_file':
el.parentNode.classList.add('wide')
- appendTable(el, payload)
+ appendReactApplet(el, payload)
+ el.classList.add('loaded')
break
case 'map':
el.parentNode.classList.add('wide')
@@ -65,14 +74,17 @@ function runApplets() {
let opt = null
payload.cmd = cmd
payload.partz = cmdPartz
+ if (payload.cmd === 'load_file') {
+ payload.url = 'https://nyc3.digitaloceanspaces.com/megapixels/v1' + cmdPartz.shift()
+ return [el, payload]
+ }
+
if (payload.partz.length) {
opt = payload.partz.shift().trim()
if (opt.indexOf('http') === 0) {
dataset = null
url = opt
} else if (opt.indexOf('assets') === 0) {
- let pathname = window.location.pathname.replace('index.html', '')
- url = 'https://nyc3.digitaloceanspaces.com/megapixels/v1' + pathname + opt
dataset = null
// console.log(url)
} else {
@@ -87,14 +99,15 @@ function runApplets() {
if (dataset === 'index.html') {
dataset = path.pop()
}
- console.log('dataset from path:', dataset)
+ // console.log('dataset from path:', dataset)
} else {
- console.log('not on a dataset page')
+ // console.log('not on a dataset page')
return [el, payload]
}
}
payload.dataset = dataset
payload.url = url
+ console.log(payload)
return [el, payload]
}).filter(a => !!a)
const withDataset = applets.map(a => a[1].dataset ? a[1] : null).filter(a => !!a)
@@ -108,6 +121,24 @@ function runApplets() {
}
}
+function buildWaypoints() {
+ const element = document.querySelector('.content section:nth-child(2)')
+ if (element) {
+ var waypoint = new Waypoint({
+ element,
+ handler: function(direction) {
+ if (direction === 'down') {
+ document.body.classList.add('scrolled')
+ } else {
+ document.body.classList.remove('scrolled')
+ }
+ // console.log(direction)
+ // console.log('Scrolled to waypoint!')
+ }
+ })
+ }
+}
+
function main() {
const paras = document.querySelectorAll('section p')
// if (paras.length) {
@@ -119,6 +150,8 @@ function main() {
}
})
runApplets()
+ buildWaypoints()
+ appendModalImage()
}
main()
diff --git a/client/map/index.js b/client/map/index.js
index b35ffddb..475ba3c6 100644
--- a/client/map/index.js
+++ b/client/map/index.js
@@ -37,25 +37,31 @@ const redDot = L.icon({
popupAnchor: [0, -5] // point from which the popup should open relative to the iconAnchor
})
-function addMarker(map, latlng, title, addresses, year, pdf) {
+function addMarker(map, latlng, citations) {
const marker = L.marker(latlng, { icon: redDot }).addTo(map)
- let message = [
- "<b>", title, "</b>",
- ]
- if (pdf && pdf.length) {
- message.unshift("<a href='" + pdf[0] + "' target='_blank'>")
- message.push("</a>")
- }
+ let message = citations.map(citation => {
+ const { title, addresses, year, pdf, doi } = citation
+ let rec = [
+ "<b>", title, "</b>",
+ ]
+ if (pdf && pdf.length) {
+ rec.unshift("<a href='" + pdf[0] + "' target='_blank'>")
+ rec.push("</a>")
+ }
+ else if (doi && doi.length) {
+ rec.unshift("<a href='" + doi[0] + "' target='_blank'>")
+ rec.push("</a>")
+ }
+ if (year) {
+ rec.push(" (" + year + ")")
+ }
+ const addressString = addresses.map(addr => addr.name).join('<br/>')
+ rec.push("<br>")
+ rec.push(addressString)
+ return rec.join("")
+ })
- const addressString = addresses.map(addr => addr.name).join('<br/>')
- message = message.concat([
- "<br>",
- addressString,
- ])
- if (year) {
- message.push(" (" + year + ")")
- }
- marker.bindPopup(message.join(''))
+ marker.bindPopup(message.join('<br><br>'))
return marker
}
@@ -73,7 +79,7 @@ function addArc(map, src, dest, arcStyle) {
export default function append(el, payload) {
const { data } = payload
if (!data) return
- let { paper, addresses, citations } = data
+ let { paper, citations } = data
let source = [0, 0]
let map = L.map(el).setView([25, 0], 2)
@@ -87,30 +93,51 @@ export default function append(el, payload) {
accessToken: 'pk.eyJ1IjoiZmFuc2FsY3kiLCJhIjoiY2pvN3I1czJwMHF5NDNrbWRoMWpteHlrdCJ9.kMpM5syQUhVjKkn1iVx9fg'
}).addTo(map)
- if (addresses && addresses.length) {
- source = [address[0].lat, address[0].lng].map(n => (parseFloat(n) || 0))
+ if (paper.addresses && paper.addresses.length) {
+ source = [paper.addresses[0].lat, paper.addresses[0].lng].map(n => (parseFloat(n) || 0))
} else {
console.error("No address found for root paper")
// console.log(data)
}
// group papers by address
+ let citationsByAddress = {}
citations.forEach(citation => {
- console.log(citation)
if (!citation.addresses) {
- console.log(citation)
+ // console.log(citation)
return
}
- const citationAddress = citation.addresses[0]
+ // console.log(citation)
+ citation.addresses.forEach(address => {
+ if (!(address.name in citationsByAddress)) {
+ citationsByAddress[address.name] = { address, citations: []}
+ }
+ citationsByAddress[address.name].citations.push(citation)
+ })
+ })
+
+ Object.keys(citationsByAddress).map(name => {
+ const { citations: citationList, address: citationAddress } = citationsByAddress[name]
+ // console.log(name, citationsByAddress[name])
+ // console.log(citation)
const latlng = [citationAddress.lat, citationAddress.lng].map(n => parseFloat(n))
if (Number.isNaN(latlng[0]) || Number.isNaN(latlng[1])) return
- addMarker(map, latlng, citation.title, citation.addresses, citation.year, citation.pdf)
- addArc(map, source, latlng, arcStyles[citationAddress.type])
+ addMarker(map, latlng, citationList)
+ const style = { ...arcStyles[citationAddress.type] }
+ let weight = Math.min(citationList.length, 5)
+ let opacity = 0.5 + Math.min(citationList.length / 5, 0.5)
+ if (citationAddress.type !== 'edu') {
+ weight += 1
+ opacity = 1
+ }
+ style.weight = String(weight)
+ style.opacity = opacity
+ addArc(map, source, latlng, style)
})
- console.log(paper)
+ // console.log(paper)
- const rootMarker = addMarker(map, source, paper.title, addresses, paper.year)
+ const rootMarker = addMarker(map, source, [paper])
rootMarker.openPopup()
// a transparent div to cover the map, so normal scroll events will not be eaten by leaflet
diff --git a/client/modalImage/index.js b/client/modalImage/index.js
new file mode 100644
index 00000000..ebb3bb72
--- /dev/null
+++ b/client/modalImage/index.js
@@ -0,0 +1,7 @@
+import ModalImage from './modalImage.container.js'
+
+import './modal.css'
+
+export {
+ ModalImage
+} \ No newline at end of file
diff --git a/client/modalImage/modal.css b/client/modalImage/modal.css
new file mode 100644
index 00000000..d9180125
--- /dev/null
+++ b/client/modalImage/modal.css
@@ -0,0 +1,69 @@
+.modal {
+ position: fixed;
+ top: 0; left: 0; width: 100%; height: 100%;
+ background: rgba(0,0,0,0.8);
+ color: white;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ opacity: 0;
+ pointer-events: none;
+ z-index: -9999999;
+ transition: opacity 0.2s cubic-bezier(0,0,1,1);
+}
+.modal.visible {
+ opacity: 1;
+ pointer-events: auto;
+ z-index: 999999999;
+}
+.modal .inner {
+ position: absolute;
+ top: 0; left: 0;
+ width: 100%; height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+.modal img {
+ max-width: 80vw;
+ max-height: 80vh;
+}
+.modal .caption {
+ display: block;
+ text-align: center;
+}
+.modal .prev {
+ position: absolute;
+ top: 0; left: 0;
+ width: 10%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: white;
+ font-size: 40px;
+ cursor: pointer;
+}
+.modal .next {
+ position: absolute;
+ top: 0; right: 0;
+ width: 10%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: white;
+ font-size: 40px;
+ cursor: pointer;
+}
+.modal .close {
+ position: absolute;
+ top: 0; right: 0;
+ width: 10vw; height: 10vw;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: white;
+ font-size: 40px;
+ cursor: pointer;
+} \ No newline at end of file
diff --git a/client/modalImage/modalImage.container.js b/client/modalImage/modalImage.container.js
new file mode 100644
index 00000000..a637deb6
--- /dev/null
+++ b/client/modalImage/modalImage.container.js
@@ -0,0 +1,85 @@
+import React, { Component } from 'react'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+import { ReactTabulator } from 'react-tabulator'
+
+import { toArray, toTuples, domainFromUrl } from '../util'
+import { Loader } from '../common'
+
+import csv from 'parse-csv'
+
+class ModalImage extends Component {
+ state = {
+ visible: true,
+ images: [],
+ index: 0,
+ }
+
+ componentDidMount() {
+ const images = toArray(document.querySelectorAll('.image img'))
+ // console.log(images)
+ images.forEach((img, i) => {
+ img.addEventListener('click', () => this.loadImage(i))
+ })
+ this.setState({ images })
+ document.body.addEventListener('keydown', e => {
+ if (document.activeElement && document.activeElement !== document.body) {
+ return null
+ }
+ // console.log(e.keyCode)
+ switch (e.keyCode) {
+ case 37: // left
+ this.prev()
+ break
+ case 39: // right
+ this.next()
+ break
+ default:
+ break
+ }
+ })
+ }
+
+ loadImage(index) {
+ this.setState({ visible: true, index })
+ }
+
+ prev() {
+ const { index, images } = this.state
+ this.setState({ index: (images.length + index - 1) % images.length })
+ }
+
+ next() {
+ const { index, images } = this.state
+ this.setState({ index: (index + 1) % images.length })
+ }
+
+ close() {
+ this.setState({ visible: false })
+ }
+
+ render() {
+ const { images, index, visible } = this.state
+ if (!images.length) return null
+ const img = images[index]
+ let caption = null
+ const sib = img.nextSibling
+ if (sib && sib.classList.contains('caption')) {
+ caption = sib.innerText
+ }
+ return (
+ <div className={visible ? 'modal visible' : 'modal'}>
+ <div className='inner'>
+ <div className='centered'>
+ <img src={img.src} />
+ {caption && <div class='caption'>{caption}</div>}
+ </div>
+ </div>
+ <div onClick={() => this.prev()}className='prev'>{'<'}</div>
+ <div onClick={() => this.next()} className='next'>{'>'}</div>
+ <div onClick={() => this.close()} className='close'>{'x'}</div>
+ </div>
+ )
+ }
+}
+export default ModalImage
diff --git a/client/table/citations.table.js b/client/table/citations.table.js
new file mode 100644
index 00000000..178cc65b
--- /dev/null
+++ b/client/table/citations.table.js
@@ -0,0 +1,130 @@
+import React, { Component } from 'react'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+import { ReactTabulator } from 'react-tabulator'
+import { saveAs } from 'file-saver'
+
+import { Loader } from '../common'
+import { toArray, toTuples, domainFromUrl } from '../util'
+
+export const citationsColumns = [
+ { title: 'Title', field: 'title', sorter: 'string' },
+ { title: 'Institution', field: 'institution', sorter: 'string', },
+ { title: 'Country', field: 'country', sorter: 'string', width: 140, },
+ { title: 'Year', field: 'year', sorter: 'number', width: 70 },
+ { title: 'PDF', field: 'pdf_text', formatter: 'link',
+ formatterParams: { target: "_blank", urlField: 'pdf_link', },
+ sorter: 'string', width: 100 },
+]
+
+class CitationsTable extends Component {
+ state = {
+ q: '',
+ formattedCitations: [],
+ filteredCitations: [],
+ }
+
+ componentDidMount(){
+ this.updateCitations()
+ }
+ componentDidUpdate(oldProps){
+ if (this.props.payload.data.citations !== oldProps.payload.data.citations) {
+ this.updateCitations()
+ }
+ }
+ updateCitations(){
+ const { paper, citations } = this.props.payload.data
+ if (!citations.length) this.setState({ formattedCitations: [] })
+ console.log(citations.filter(a => a.title.match('Coarse')))
+ const formattedCitations = citations.sort((a,b) => a.title.localeCompare(b.title)).map(citation => {
+ const pdf_link = (citation.pdf && citation.pdf.length)
+ ? citation.pdf[0]
+ : (citation.doi && citation.doi.length)
+ ? citation.doi[0]
+ : 'https://www.semanticscholar.org/paper/' + citation.id
+ let pdf_text = domainFromUrl(pdf_link)
+ return {
+ title: citation.title,
+ institution: citation.addresses.map(a => a.name).sort().join('; '),
+ country: Array.from(new Set(citation.addresses.map(a => a.country))).sort().join('; '),
+ year: citation.year,
+ pdf_link, pdf_text,
+ }
+ })
+ this.setState({
+ formattedCitations,
+ filteredCitations: formattedCitations,
+ })
+ }
+
+ updateFilter(q) {
+ const { formattedCitations } = this.state
+ if (!q.length) {
+ this.setState({ q, filteredCitations: formattedCitations })
+ } else {
+ let q_re = new RegExp('(' + q.replace(/\s+/g, ' ').trim().replace(' ', '|') + ')', 'gi')
+ let filteredCitations = formattedCitations.filter(citation => (
+ citation.title.match(q_re) ||
+ citation.institution.match(q_re) ||
+ citation.country.match(q_re)
+ ))
+ this.setState({ q, filteredCitations })
+ }
+ }
+
+ download() {
+ const { formattedCitations } = this.state
+ const fn = this.props.payload.data.paper.key + '.csv'
+ const titles = citationsColumns.map(c => c.title)
+ const fields = citationsColumns.map(c => c.formatterParams ? c.formatterParams.urlField : c.field)
+ const rows = formattedCitations.map(citation => {
+ const row = fields.map(field => citation[field]).map(data => {
+ switch (typeof data) {
+ case 'number':
+ return String(data)
+ default:
+ return '\"' + String(data) + '\"'
+ }
+ })
+ return row.join(",")
+ })
+
+ const blob = new Blob([
+ [
+ titles.join(','),
+ ...rows,
+ ].join('\n')
+ ], {type: "text/csv;charset=utf-8"});
+ saveAs(blob, fn);
+ }
+
+ render() {
+ const { formattedCitations, filteredCitations } = this.state
+ if (!formattedCitations.length) return <Loader />
+ return (
+ <div className='citationBrowser'>
+ <div className='citationHeader'>
+ <input
+ type="text"
+ value={this.state.q}
+ onChange={e => this.updateFilter(e.target.value)}
+ className='q'
+ placeholder='Enter text to search citations...'
+ />
+ <span className='download' onClick={() => this.download()}>Download CSV</span>
+ </div>
+ <ReactTabulator
+ columns={citationsColumns}
+ data={filteredCitations}
+ options={{
+ height: Math.max(104, Math.min(37 * formattedCitations.length + 29, 311)),
+ layout: 'fitColumns',
+ placeholder: formattedCitations.length ? '' : 'Nothing matches your query',
+ }}
+ />
+ </div>
+ )
+ }
+}
+
+export default CitationsTable
diff --git a/client/table/file.table.js b/client/table/file.table.js
new file mode 100644
index 00000000..db53243a
--- /dev/null
+++ b/client/table/file.table.js
@@ -0,0 +1,82 @@
+import React, { Component } from 'react'
+import { bindActionCreators } from 'redux'
+import { connect } from 'react-redux'
+import { ReactTabulator } from 'react-tabulator'
+
+import { toArray, toTuples, domainFromUrl } from '../util'
+import { Loader } from '../common'
+
+import csv from 'parse-csv'
+
+class FileTable extends Component {
+ state = {
+ keys: [],
+ data: [],
+ columns: [],
+ }
+
+ componentDidMount() {
+ const { payload } = this.props
+ console.log(payload.url)
+ fetch(payload.url, { mode: 'cors' })
+ .then(r => r.text())
+ .then(text => {
+ try {
+ const keys = text.split('\n')[0].split(',').map(s => s.trim().replace(/\"/,''))
+ const data = csv.toJSON(text, { headers: { included: true } })
+ // console.log(data)
+ const columns = this.getColumns(keys, data, payload.fields)
+ this.setState({ keys, data, columns })
+ } catch (e) {
+ console.error("error making json:", payload.url)
+ console.error(e)
+ }
+ })
+ }
+
+ getColumns(keys, data, fields) {
+ let titles = fields.length ? fields[0].split(', ') : keys
+ let numberFields = []
+ let columns = keys.map((field, i) => {
+ const title = titles[i] || field
+ if (field.match('url')) {
+ let textField = field.replace('url', 'label')
+ data.forEach(el => el[textField] = domainFromUrl(el[field]))
+ return {
+ title,
+ field: textField,
+ formatter: 'link',
+ formatterParams: { target: "_blank", urlField: field, },
+ sorter: 'string'
+ }
+ }
+ switch (field) {
+ case 'images':
+ case 'year':
+ return { title, field: field.toLowerCase(), sorter: 'number' }
+ default:
+ return { title, field: field.toLowerCase(), sorter: 'string' }
+ }
+ })
+ return columns
+ }
+
+ render() {
+ const { payload } = this.props
+ if (!this.state.data.length) {
+ return <Loader />
+ }
+ return (
+ <ReactTabulator
+ columns={this.state.columns}
+ data={this.state.data}
+ options={{
+ height: Math.min(37 * this.state.data.length + 29, 311),
+ layout: 'fitColumns',
+ placeholder: 'No Data Set',
+ }}
+ />
+ )
+ }
+}
+export default FileTable
diff --git a/client/table/index.js b/client/table/index.js
new file mode 100644
index 00000000..c741f33e
--- /dev/null
+++ b/client/table/index.js
@@ -0,0 +1,11 @@
+import 'react-tabulator/lib/styles.css'
+import 'react-tabulator/lib/css/tabulator_midnight.css'
+import './tabulator.css'
+
+import CitationsTable from './citations.table'
+import FileTable from './file.table'
+
+export {
+ CitationsTable,
+ FileTable,
+} \ No newline at end of file
diff --git a/client/table/tabulator.css b/client/table/tabulator.css
new file mode 100644
index 00000000..95768976
--- /dev/null
+++ b/client/table/tabulator.css
@@ -0,0 +1,69 @@
+.tabulator {
+ border-left: 1px solid #333;
+ border-bottom: 1px solid #333;
+}
+.tabulator-row.tabulator-row-odd {
+ background-color: #222;
+}
+.tabulator-row.tabulator-row-even {
+ background-color: #333;
+}
+.desktop .tabulator-row.tabulator-selectable.tabulator-row-even:hover {
+ cursor: arrow;
+ background-color: #333;
+}
+.desktop .tabulator-row.tabulator-selectable.tabulator-row-odd:hover {
+ cursor: arrow;
+ background-color: #222;
+}
+.tabulator-row .tabulator-cell {
+ border-right: 1px solid #444;
+ padding: 8px;
+}
+.tabulator .tabulator-header {
+ border-bottom: 0;
+}
+.tabulator .tabulator-header .tabulator-col {
+ border-right: 1px solid #444;
+}
+.tabulator .tabulator-tableHolder .tabulator-table {
+ background-color: #333;
+}
+.multi-value-formatter-content span {
+ border: 0;
+ padding: 0 5px 0 0;
+}
+
+.citationBrowser {
+}
+.citationBrowser .q {
+ max-width: 400px;
+ margin-bottom: 10px;
+ background-image: url(/assets/img/icon-search.png);
+ background-position: 380px center;
+ background-repeat: no-repeat;
+ box-shadow: 0px 2px 4px rgba(0,0,0,0.2);
+ border: 0;
+}
+
+.citationHeader {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ justify-content: space-between;
+}
+span.download {
+ display: block;
+ font-size: 13px;
+ color: #ddd;
+ cursor: pointer;
+ background: #333;
+ padding: 5px 8px;
+ border-radius: 5px;
+ transition: all 0.2s;
+}
+.desktop span.download:hover {
+ color: #fff;
+ background: #666;
+} \ No newline at end of file
diff --git a/client/tables.js b/client/tables.js
deleted file mode 100644
index 3b53b5db..00000000
--- a/client/tables.js
+++ /dev/null
@@ -1,96 +0,0 @@
-import Tabulator from 'tabulator-tables'
-import csv from 'parse-csv'
-
-const datasetColumns = [
- { title: 'Title', field: 'title', sorter: 'string' },
- { title: 'Images', field: 'images', sorter: 'number' },
- { title: 'People', field: 'people', sorter: 'number' },
- { title: 'Year', field: 'year', sorter: 'number' },
- { title: 'Citations', field: 'citations', sorter: 'number' },
- { title: 'Influenced', field: 'influenced', sorter: 'number' },
- // { title: 'Origin', field: 'origin', sorter: 'string' },
-]
-const citationsColumns = [
- { title: 'Title', field: 'title', sorter: 'string' },
- { title: 'Institution', field: 'institution', sorter: 'string' },
- { title: 'Country', field: 'country', sorter: 'string', width: 140 },
- { title: 'Year', field: 'year', sorter: 'number', width: 70 },
- { title: 'PDF', field: 'pdf', formatter: 'link',
- formatterParams: { target: "_blank", urlField: 'pdf', },
- sorter: 'string', width: 100 },
-]
-
-function getColumns(payload) {
- let { cmd, url, fields } = payload
- if (cmd === 'citations') {
- return citationsColumns
- }
- if (url && url.match('datasets.csv')) {
- return datasetColumns
- }
- return ((fields && fields.length) ? fields[0] : '').split(', ').map(field => {
- switch (field) {
- default:
- return { title: field, field: field.toLowerCase(), sorter: 'string' }
- }
- })
-}
-
-function getCitations(dataset) {
- // console.log(dataset.citations)
- // console.log(dataset.citations.map(d => [d.pdf, d.doi]))
- return dataset.citations.map(citation => ({
- title: citation.title,
- institution: citation.addresses[0].name,
- country: citation.addresses[0].country,
- year: citation.year,
- pdf: (citation.pdf && citation.pdf.length)
- ? citation.pdf[0]
- : (citation.doi && citation.doi.length)
- ? citation.doi[0]
- : "",
- }))
-}
-
-export default function append(el, payload) {
- const columns = getColumns(payload)
- // console.log(columns)
- const table = new Tabulator(el, {
- height: '311px',
- layout: 'fitColumns',
- placeholder: 'No Data Set',
- columns,
- })
- // let path = payload.opt
- // console.log(path, columns)
-
- if (payload.cmd === 'citations') {
- let { data } = payload
- if (!data) return null
- const citations = getCitations(data)
- // console.log(citations)
- table.setData(citations)
- el.classList.add('loaded')
- } else {
- fetch(payload.url, { mode: 'cors' })
- .then(r => r.text())
- .then(text => {
- try {
- // console.log(text)
- const data = csv.toJSON(text, { headers: { included: true } })
- // console.log(data)
- table.setData(data)
- el.classList.add('loaded')
- } catch (e) {
-
- console.error("error making json:", payload.url)
- console.error(e)
- // console.log(text)
- }
- })
- }
-
- // if (fields && fields.length > 1 && fields[1].indexOf('filter')) {
- // const filter = fields[1].split(' ')
- // }
-}
diff --git a/client/util/index.js b/client/util/index.js
index 87d32ebb..e90e5466 100644
--- a/client/util/index.js
+++ b/client/util/index.js
@@ -63,6 +63,15 @@ export const percent = n => (n * 100).toFixed(1) + '%'
export const px = (n, w) => Math.round(n * w) + 'px'
export const clamp = (n, a, b) => n < a ? a : n < b ? n : b
+export const domainFromUrl = url => {
+ const partz = url.split('/')[2].split('.')
+ if (partz.length > 2 && partz[partz.length - 2].length == 2) {
+ return partz.slice(-3).join('.')
+ } else {
+ return partz.slice(-2).join('.')
+ }
+}
+
/* URLs */
export const preloadImage = opt => {