summaryrefslogtreecommitdiff
path: root/client/table
diff options
context:
space:
mode:
Diffstat (limited to 'client/table')
-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
4 files changed, 292 insertions, 0 deletions
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