summaryrefslogtreecommitdiff
path: root/client/common
diff options
context:
space:
mode:
Diffstat (limited to 'client/common')
-rw-r--r--client/common/classifier.component.js99
-rw-r--r--client/common/common.css1
-rw-r--r--client/common/detectionBoxes.component.js15
-rw-r--r--client/common/detectionList.component.js16
-rw-r--r--client/common/gate.component.js21
-rw-r--r--client/common/header.component.js1
-rw-r--r--client/common/index.js25
-rw-r--r--client/common/loader.component.js11
-rw-r--r--client/common/sidebar.component.js18
-rw-r--r--client/common/table.component.js121
-rw-r--r--client/common/video.component.js47
11 files changed, 375 insertions, 0 deletions
diff --git a/client/common/classifier.component.js b/client/common/classifier.component.js
new file mode 100644
index 00000000..af6a4934
--- /dev/null
+++ b/client/common/classifier.component.js
@@ -0,0 +1,99 @@
+import React, { Component } from 'react'
+import { courtesyS } from '../util'
+
+import { TableTuples, DetectionList, Keyframe } from '.'
+
+export default class Classifier extends Component {
+ render() {
+ const {
+ tag,
+ sha256,
+ verified,
+ keyframes = {},
+ labels,
+ summary,
+ aspectRatio = 1.777,
+ showAll,
+ } = this.props
+ let totalDetections = 0
+ const keys = Object
+ .keys(keyframes)
+ .map(s => parseInt(s, 10))
+ const validKeyframes = keys
+ .sort((a, b) => a - b)
+ .map(frame => {
+ const detections = keyframes[frame]
+ if (detections.length || showAll) {
+ totalDetections += detections.length
+ return { frame, detections }
+ }
+ return null
+ })
+ .filter(f => !!f)
+ const detectionLookup = validKeyframes
+ .reduce((a, b) => {
+ b.detections.reduce((aa, { idx }) => {
+ if (!(idx in aa)) aa[idx] = [labels[idx], 0]
+ aa[idx][1] += 1
+ return aa
+ }, a)
+ return a
+ }, {})
+ const detectionCounts = Object.keys(detectionLookup)
+ .map(idx => detectionLookup[idx])
+ .sort((a, b) => b[1] - a[1])
+
+ if (summary) {
+ return (
+ <div>
+ <h3>{tag}{' Detections'}</h3>
+ <TableTuples
+ list={detectionCounts}
+ />
+ </div>
+ )
+ }
+ return (
+ <div>
+ <h2>{tag}</h2>
+ <h3>Detections</h3>
+ <TableTuples
+ list={detectionCounts}
+ />
+ <h3>Frames</h3>
+ <ul className='meta'>
+ <li>
+ {'Displaying '}{validKeyframes.length}{' / '}{courtesyS(keys.length, 'frame')}
+ </li>
+ <li>
+ {courtesyS(totalDetections, 'detection')}{' found'}
+ </li>
+ </ul>
+ <div className='thumbnails'>
+ {validKeyframes.map(({ frame, detections }) => (
+ <Keyframe
+ key={frame}
+ sha256={sha256}
+ frame={frame}
+ verified={verified}
+ size='th'
+ showFrame
+ showTimestamp
+ aspectRatio={aspectRatio}
+ detectionList={[
+ { labels, detections }
+ ]}
+ >
+ <DetectionList
+ labels={labels}
+ detections={detections}
+ width={160}
+ height={90}
+ />
+ </Keyframe>
+ ))}
+ </div>
+ </div>
+ )
+ }
+}
diff --git a/client/common/common.css b/client/common/common.css
new file mode 100644
index 00000000..56cf2fe9
--- /dev/null
+++ b/client/common/common.css
@@ -0,0 +1 @@
+* {} \ No newline at end of file
diff --git a/client/common/detectionBoxes.component.js b/client/common/detectionBoxes.component.js
new file mode 100644
index 00000000..c4872ea8
--- /dev/null
+++ b/client/common/detectionBoxes.component.js
@@ -0,0 +1,15 @@
+import React from 'react'
+
+import { px } from '../util'
+
+export default function DetectionBoxes({ detections, width, height }) {
+ return detections.map(({ rect }, i) => (
+ rect &&
+ <div className='rect' key={i} style={{
+ left: px(rect[0], width),
+ top: px(rect[1], height),
+ width: px(rect[2] - rect[0], width),
+ height: px(rect[3] - rect[1], height),
+ }} />
+ ))
+}
diff --git a/client/common/detectionList.component.js b/client/common/detectionList.component.js
new file mode 100644
index 00000000..416e66d8
--- /dev/null
+++ b/client/common/detectionList.component.js
@@ -0,0 +1,16 @@
+import React from 'react'
+
+export default function DetectionList({ detections, labels, tag, showEmpty }) {
+ return (
+ <span className='detectionList'>
+ {tag && <h3>{tag}</h3>}
+ {!detections.length && showEmpty && <label><small>No detections</small></label>}
+ {detections.map(({ idx, score, rect }, i) => (
+ <label key={i}>
+ <small className='title'>{(labels[idx] || 'Unknown').replace(/_/, ' ')}</small>
+ <small>{score.toFixed(2)}</small>
+ </label>
+ ))}
+ </span>
+ )
+}
diff --git a/client/common/gate.component.js b/client/common/gate.component.js
new file mode 100644
index 00000000..9bf9287b
--- /dev/null
+++ b/client/common/gate.component.js
@@ -0,0 +1,21 @@
+import React from 'react'
+import { connect } from 'react-redux'
+
+function Gate(props) {
+ const { app, tag, View } = props
+ const data = app[tag]
+ if (!data) return null
+ if (data === 'loading') {
+ return <div className='tableObject loading'>{tag}{': Loading'}</div>
+ }
+ if (data.err) {
+ return <div className='tableObject error'>{tag}{' Error: '}{data.err}</div>
+ }
+ return <View data={data} {...props} />
+}
+
+const mapStateToProps = state => ({
+ app: state.metadata
+})
+
+export default connect(mapStateToProps)(Gate)
diff --git a/client/common/header.component.js b/client/common/header.component.js
new file mode 100644
index 00000000..84fe306f
--- /dev/null
+++ b/client/common/header.component.js
@@ -0,0 +1 @@
+/* imported from main vcat application */
diff --git a/client/common/index.js b/client/common/index.js
new file mode 100644
index 00000000..cfb34b32
--- /dev/null
+++ b/client/common/index.js
@@ -0,0 +1,25 @@
+import Classifier from './classifier.component'
+import DetectionBoxes from './detectionBoxes.component'
+import DetectionList from './detectionList.component'
+// import Header from './header.component'
+import Loader from './loader.component'
+import Sidebar from './sidebar.component'
+import Gate from './gate.component'
+import Video from './video.component'
+import { TableObject, TableArray, TableTuples, TableRow, TableCell } from './table.component'
+import './common.css'
+
+export {
+ Sidebar,
+ Loader,
+ Gate,
+ TableObject,
+ TableArray,
+ TableTuples,
+ TableRow,
+ TableCell,
+ Classifier,
+ DetectionList,
+ DetectionBoxes,
+ Video,
+}
diff --git a/client/common/loader.component.js b/client/common/loader.component.js
new file mode 100644
index 00000000..5930c63e
--- /dev/null
+++ b/client/common/loader.component.js
@@ -0,0 +1,11 @@
+import React from 'react'
+
+export default function Loader() {
+ return (
+ <div className='loaderWrapper'>
+ <div className='loader'>
+ <img src="/assets/img/loader.gif" />
+ </div>
+ </div>
+ )
+}
diff --git a/client/common/sidebar.component.js b/client/common/sidebar.component.js
new file mode 100644
index 00000000..afbf8c8c
--- /dev/null
+++ b/client/common/sidebar.component.js
@@ -0,0 +1,18 @@
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
+
+class Sidebar extends Component {
+ render() {
+ return (
+ <div className="sidebar">
+ </div>
+ )
+ // <NavLink to={'/metadata/' + hash + '/summary/'}>Summary</NavLink>
+ }
+}
+
+const mapStateToProps = state => ({
+ hash: state.metadata.hash,
+})
+
+export default connect(mapStateToProps)(Sidebar)
diff --git a/client/common/table.component.js b/client/common/table.component.js
new file mode 100644
index 00000000..76a1d57c
--- /dev/null
+++ b/client/common/table.component.js
@@ -0,0 +1,121 @@
+import React from 'react'
+
+import { formatName } from '../util'
+
+const __HR__ = '__HR__'
+
+export function TableObject({ tag, object, order, summary }) {
+ if (!object) return null
+ if (object === 'loading') {
+ return <div className='tableObject loading'>{tag}{': Loading'}</div>
+ }
+ if (object.err) {
+ return <div className='tableObject error'>{tag}{' Error: '}{object.err}</div>
+ }
+ let objects = Object.keys(object)
+ if (order) {
+ const grouped = objects.reduce((a, b) => {
+ const index = order.indexOf(b)
+ if (index !== -1) {
+ a.order.push([index, b])
+ } else {
+ a.alpha.push(b)
+ }
+ return a
+ }, { order: [], alpha: [] })
+ objects = grouped.order
+ .sort((a, b) => a[0] - b[0])
+ .map(([i, s]) => s)
+ if (!summary) {
+ objects = objects
+ // .concat([__HR__])
+ .concat(grouped.alpha.sort())
+ }
+ } else {
+ objects = objects.sort()
+ }
+ return (
+ <div>
+ {tag && <h3>{tag}</h3>}
+ <table className={'tableObject ' + tag}>
+ <tbody>
+ {objects.map((key, i) => (
+ <TableRow key={key + '_' + i} name={key} value={object[key]} />
+ ))}
+ </tbody>
+ </table>
+ </div>
+ )
+}
+
+export function TableArray({ tag, list }) {
+ if (!list) return null
+ return (
+ <div>
+ {tag && <h3>{tag}</h3>}
+ <table className={'tableArray ' + tag}>
+ <tbody>
+ {list.map((value, i) => (
+ <tr key={tag + '_' + i}>
+ <TableCell value={value} />
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ )
+}
+
+export function TableTuples({ tag, list }) {
+ if (!list) return null
+ return (
+ <div>
+ {tag && <h3>{tag}</h3>}
+ <table className={'tableTuples ' + tag}>
+ <tbody>
+ {list.map(([key, ...values], i) => (
+ <tr key={tag + '_' + i}>
+ <th>{formatName(key)}</th>
+ {values.map((value, j) => (
+ <TableCell key={i + '_' + j} value={value} />
+ ))}
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ </div>
+ )
+}
+
+export function TableRow({ name, value }) {
+ if (name === __HR__) {
+ return (
+ <tr>
+ <th className='tr'>
+ <hr />
+ </th>
+ </tr>
+ )
+ }
+ return (
+ <tr>
+ <th>{formatName(name)}</th>
+ <TableCell name={name} value={value} />
+ </tr>
+ )
+}
+
+export function TableCell({ value }) {
+ if (value && typeof value === 'object') {
+ if (value._raw) {
+ value = value.value
+ } else if (value.length) {
+ value = <TableArray nested tag={''} list={value} />
+ } else {
+ value = <TableObject nested tag={''} object={value} />
+ }
+ }
+ return (
+ <td>{value}</td>
+ )
+}
diff --git a/client/common/video.component.js b/client/common/video.component.js
new file mode 100644
index 00000000..e5525bf6
--- /dev/null
+++ b/client/common/video.component.js
@@ -0,0 +1,47 @@
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
+import { imageUrl, widths } from '../util'
+
+import { Gate } from '.'
+
+class Video extends Component {
+ state = {
+ playing: false,
+ }
+
+ render() {
+ const { app, data, size } = this.props
+ const { playing } = this.state
+ const { sugarcube } = data.metadata
+ const url = sugarcube.fp.replace('/var/www/files/', 'https://cube.syrianarchive.org/')
+ const { sha256, verified } = app.mediainfo
+ const { video } = app.mediainfo.metadata.mediainfo
+ const keyframe = app.keyframe.metadata.keyframe.basic[0]
+ return (
+ <div className='video'>
+ {playing
+ ? <video src={url} autoPlay controls muted />
+ : <div
+ className='bg'
+ style={{
+ width: widths[size || 'sm'],
+ height: widths[size || 'sm'] / video.aspect_ratio,
+ backgroundImage: 'url(' + imageUrl(verified, sha256, keyframe, size) + ')',
+ }}
+ onClick={() => this.setState({ playing: true })}
+ >
+ <div className='play'></div>
+ </div>
+ }
+ </div>
+ )
+ }
+}
+
+const mapStateToProps = () => ({
+ tag: 'sugarcube',
+})
+
+export default connect(mapStateToProps)(props => (
+ <Gate View={Video} {...props} />
+))