Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import React from "react"
import { Card, Container } from "react-bootstrap"
import { BrowserRouter } from "react-router-dom"
import "./App.css"
import { StudySet } from './calc/jsonTypes'
import About from "./components/About/About"
import Algorithms, { AlgorithmEntry } from "./components/Algorithms/Algorithms"
// import ConfigPanelWrapper from "./components/ConfigPanelWrapper"
import ExpandingHeatmapTable from "./components/ExpandingHeatmapTable"
import Footer from './components/Footer/Footer'
import Header from './components/Header/Header'
import Recordings from './components/Recordings/RecordingsPage'
import Preloader from "./components/Shared/Preloader"
// import MetricsDescription from "./components/MetricsDescription/MetricsDescription"
import "./index.css"
Expand All @@ -16,13 +18,43 @@ import {
expandingHeatmapTableSampleHeader as header,
expandingHeatmapTableSampleRows as tableRows
} from "./sampleData/expandingHeatmapTableTestData"
import studysetsjson from './sampleData/studysets-from-api.json'


// import { basicConfig } from "./sampleData/HeatmapConfigTestData"

function App() {
// const columnConfig = {...basicConfig, useColumnFormat: true}
// const cpuConfig = {...basicConfig, format: 'cpu' as FormatType, showCPU: true }

// NOTE: this is for testing, the data will actually need to be pulled from an API
const studysets: StudySet[] = studysetsjson.map((s) => { return {
_id: s._id,
name: s.name,
description: s.description,
studies: s.studies.map((study) => {
return {
recordings: study.recordings.map((recording) => {return {
_id: recording._id,
name: recording.name,
studyName: recording.studyName,
studySetName: recording.studySetName,
directory: recording.directory,
firingsTrue: recording.firingsTrue,
sampleRateHz: recording.sampleRateHz,
numChannels: recording.numChannels,
durationSec: recording.durationSec,
numTrueUnits: recording.numTrueUnits,
spikeSign: recording.spikeSign > 0 ? 1 : -1
}}),
_id: study._id,
name: study.name,
studySetName: study.studySetName
}
})
}
})

return (
<BrowserRouter>
<div className="wrapper">
Expand All @@ -41,6 +73,8 @@ function App() {
</div>
</div>
<div className="wrapper">
<hr />
<Recordings Sets={studysets} />
{/* <div>
<hr />
<ConfigPanelWrapper />
Expand Down
2 changes: 1 addition & 1 deletion src/calc/jsonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export interface StudySet {
name: string,
description: string,
studies: Study[],
__v: number
__v?: number
}

export type Nullable<T> = T extends null | undefined ? never: T
Expand Down
24 changes: 24 additions & 0 deletions src/components/Recordings/RecordingPageContent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Recordings

## Recording Organization

Recordings are grouped into "studies." Each study contains a set of real or synthesized
recordings sharing a common source (probe and brain region, or simulation code
settings). It is appropriate to aggregate the statistics from all recordings within a
particular study.

In turn, studies are grouped into "study sets." Each study set is a collection of studies
which have different parameters but share some common features.

## Recording Types

Our hosted recordings include many popular probe geometries and types, and fall
into three cartegories:

1. **Paired (*in vivo* or *in vitro*)** Recordings from various laboratories
where an independent intra- or juxta-cellular probe providers reliable ground
truth firing events, usually for one neuron per recording.
1. **Simulated (*in silico*)** Recordings from neural simulator codes with
various degrees of fidelity, with known firing events for large numbers of
neurons.
1. **Human-curated** We also host a small set of expert human-curated sorting results.
129 changes: 129 additions & 0 deletions src/components/Recordings/RecordingsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { Fragment, FunctionComponent, useEffect, useState } from "react"
import { Col, Container, Row, Table } from "react-bootstrap"
import { Link } from "react-router-dom"
import { Study, StudySet } from '../../calc/jsonTypes'
import { Copy, getCanonicalSectionTag, PageCopy, PageSidebar, parseMarkdownToContentCards, Section } from "../Shared/MarkdownHandling"
import Preloader from "../Shared/Preloader"
import { toTitleCase } from "../util"
import pageContent from './RecordingPageContent.md'

type Studies = { Studies: Study[] }
type StudySets = { Sets: StudySet[] }

const getCanonicalStudySetName = (rawName: string): string => (
toTitleCase(rawName.replace(/_/g, " ").toLowerCase())
)

const Recordings: FunctionComponent<StudySets> = (Props: StudySets) => {
const [copyMd, setCopyMd] = useState('loading...')
useEffect(() => {
fetch(pageContent)
.then((res) => res.text())
.then((text) => setCopyMd(text))
})
const content: Copy = parseMarkdownToContentCards(copyMd)
const studySetSidebarEntries: Section[] = Props.Sets.map((ss) => {
const name = getCanonicalStudySetName(ss.name)
return {
Header: '',
Content: '',
Label: name,
Tag: getCanonicalSectionTag(ss.name),
}
})

const apiDataNotYetLoaded = (!Props.Sets || Props.Sets.length < 1)
const studySetDynamicContent = apiDataNotYetLoaded
? < PreloaderCard />
: <Fragment>
{Props.Sets.map((studyset) => (<StudySetCard {...studyset} />)) }
</Fragment>

return (
<Container className="container-sidebar">
<Row noGutters>
<PageSidebar
Title={content.Title}
Sections={[...content.Sections, ...studySetSidebarEntries]} />
<PageCopy
Copy={content}
ContentHook={() => undefined}
AdditionalContent={studySetDynamicContent}
/>
</Row>
</Container>
)
}

const PreloaderCard: FunctionComponent = () => {
return (
<Row className="subcontainer justify-content-md-center">
<Col lg={12} sm={12} xl={12}>
<div className="card card__std">
<div className="content">
<Preloader />
</div>
</div>
</Col>
</Row>
)
}

const StudySetCard: FunctionComponent<StudySet> = (Props: StudySet) => {
return (
// hard-coded 'study-set-' is just arbitrary text--we're only
// trying to provide a unique key property value for React.
<div key={`study-set-${Props.name}`}>
<Row className="subcontainer justify-content-md-center">
<Col lg={12} sm={12} xl={12}>
<div className="finder" id={getCanonicalSectionTag(Props.name)} />
<div className="listcard listcard-recording">
<div className="listcard-content">
<div className="listcard-section">
<Link to={`/studyset/${Props.name}`} className="listcard-title">
{Props.name}
</Link>
</div>
<div className="listcard-section">
<StudyTable Studies={Props.studies} />
</div>
</div>
</div>
</Col>
</Row>
</div>
)
}

const StudyTable: FunctionComponent<Studies> = (Props: Studies) => {
return (
<Table striped bordered size="sm">
<thead>
<tr>
<th key="col1">Study name</th>
<th key="col2">Number of recordings</th>
<th key="col3"> </th>
</tr>
</thead>
<tbody>
{ Props.Studies.map((study) => (<StudyTableRow {...study} />)) }
</tbody>
</Table>
)
}

const StudyTableRow: FunctionComponent<Study> = (Props: Study) => {
return (
<tr key={`study-${Props.name}`}>
<td key="col1">{Props.name}</td>
<td key="col2">{Props.recordings.length}</td>
<td key="col3" className="listcard-link">
<Link to={`/study/${Props.name}`} className="listcard-env">
View study details
</Link>
</td>
</tr>
)
}

export default Recordings;
14 changes: 11 additions & 3 deletions src/components/Shared/MarkdownHandling.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ export type ContentHook = (Section: Section) => JSX.Element | undefined
// if you just want to render the Markdown-based cards differently, use a ContentHook
// function that can render the appropriate changes from the Section object (perhaps
// by looking at the Flavor field, for example).
export type CopyHook = { Copy: Copy, ContentHook: ContentHook, AdditionalContent?: JSX.Element }
export type CopyHook = {
Copy: Copy,
ContentHook: ContentHook,
AdditionalContent?: JSX.Element
}

export type SectionHook = { Section: Section, ContentHook: ContentHook }

Expand Down Expand Up @@ -79,9 +83,11 @@ export const parseMarkdownToContentCards = (rawMd: string): Copy => {
// the header and the second element is the (Markdown) section content.

// So finally let's build some Sections. We'll use any parenthesized content
// in the section header as the sidebar label; if none, we'll use the exact header.
// in the section header as the sidebar label--so e.g. ## My Heading (Sidebar Label)
// will appear as "My Heading" in the main page body and "Sidebar Label" in the sidebar.
// If the section in parens is omitted, we'll use the same heading for both.
// The corresponding item ID for the tag system should be the sidebar label,
// normalized to lowercase and with spaces removed.
// lower-cased & with spaces removed.
pairs.forEach((pair) => {
if (pair.length > 2 || !pair[0] || !pair[1]){
console.log(`Error in pair: ${pair}. Skipping...`)
Expand Down Expand Up @@ -131,6 +137,8 @@ export const PageCopy: FunctionComponent<CopyHook> = (Props: CopyHook) => {
</Row>
{ Props.Copy.Sections.map(item => PageCard({Section: item, ContentHook: Props.ContentHook})) }
{ Props.AdditionalContent }
{/* Handles overflow from the bottom margin: */}
<div style={{ margin: "8rem 0" }} />
</Container>
</Col>
)
Expand Down
2 changes: 1 addition & 1 deletion src/components/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ export const isEmpty = (obj: any): boolean => Object.entries(obj).length === 0
// link: (props: any) => {
// return `<a href=${props.href} target="_blank" rel="nofollow noreferrer noopener">${props.children}</a>`
// }
// }
// }
1 change: 1 addition & 0 deletions src/sampleData/studysets-from-api.json

Large diffs are not rendered by default.