@openneuro/app 4.22.0-alpha.0 → 4.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openneuro/app",
3
- "version": "4.22.0-alpha.0",
3
+ "version": "4.23.0",
4
4
  "description": "React JS web frontend for the OpenNeuro platform.",
5
5
  "license": "MIT",
6
6
  "main": "public/client.js",
@@ -20,8 +20,8 @@
20
20
  "@emotion/react": "11.11.1",
21
21
  "@emotion/styled": "11.11.0",
22
22
  "@niivue/niivue": "0.34.0",
23
- "@openneuro/client": "^4.22.0-alpha.0",
24
- "@openneuro/components": "^4.22.0-alpha.0",
23
+ "@openneuro/client": "^4.23.0",
24
+ "@openneuro/components": "^4.23.0",
25
25
  "@tanstack/react-table": "^8.9.3",
26
26
  "bids-validator": "1.13.0",
27
27
  "bytes": "^3.0.0",
@@ -32,29 +32,31 @@
32
32
  "email-validator": "^2.0.4",
33
33
  "graphql": "16.8.1",
34
34
  "jwt-decode": "^2.2.0",
35
- "markdown-to-jsx": "^7.4.0",
36
35
  "pluralize": "8.0.0",
37
36
  "prop-types": "^15.6.0",
38
- "react": "^17.0.1",
37
+ "react": "^18.2.0",
39
38
  "react-cookie": "4.0.3",
40
39
  "react-copy-to-clipboard": "^5.0.1",
41
- "react-dom": "^17.0.1",
40
+ "react-dom": "^18.2.0",
42
41
  "react-helmet": "6.1.0",
42
+ "react-markdown": "^9.0.1",
43
43
  "react-router-dom": "6.3.0",
44
44
  "react-toastify": "6.0.9",
45
45
  "react-usestateref": "^1.0.8",
46
+ "rehype-raw": "^7.0.0",
47
+ "remark-gfm": "^4.0.0",
46
48
  "semver": "^5.5.0",
47
49
  "subscriptions-transport-ws": "0.11.0",
48
50
  "universal-cookie": "^4.0.4"
49
51
  },
50
52
  "devDependencies": {
51
- "@testing-library/jest-dom": "6.1.3",
52
- "@testing-library/react": "^11.1.0",
53
+ "@testing-library/jest-dom": "6.4.2",
54
+ "@testing-library/react": "^14.2.1",
53
55
  "@types/dompurify": "^3",
54
56
  "@types/jsdom": "^16",
55
57
  "@types/node": "18.11.9",
56
58
  "@types/react": "^17.0.8",
57
- "@types/react-dom": "^17.0.5",
59
+ "@types/react-dom": "^18.2.19",
58
60
  "@types/react-router-dom": "5.3.3",
59
61
  "@types/testing-library__jest-dom": "5.14.5",
60
62
  "core-js": "3.25.1",
@@ -72,5 +74,5 @@
72
74
  "publishConfig": {
73
75
  "access": "public"
74
76
  },
75
- "gitHead": "9931842bb7afcac659af2a7b5a5d045aae1f4776"
77
+ "gitHead": "e86df78635d7ac6cfa03a2add5760b8539103583"
76
78
  }
package/src/client.jsx CHANGED
@@ -6,7 +6,7 @@ import "./scripts/apm.js"
6
6
  import { ApolloProvider, InMemoryCache } from "@apollo/client"
7
7
  import { createClient } from "@openneuro/client"
8
8
  import React from "react"
9
- import ReactDOM from "react-dom"
9
+ import { createRoot } from "react-dom/client"
10
10
  import { BrowserRouter, Route, Routes } from "react-router-dom"
11
11
  import App from "./scripts/app"
12
12
  import Index from "./scripts/index"
@@ -18,7 +18,9 @@ import "@openneuro/components/page/page.scss"
18
18
 
19
19
  gtag.initialize(config.analytics.trackingIds)
20
20
 
21
- ReactDOM.render(
21
+ const mainElement = document.getElementById("main")
22
+ const container = createRoot(mainElement)
23
+ container.render(
22
24
  <App>
23
25
  <ApolloProvider
24
26
  client={createClient(`${config.url}/crn/graphql`, {
@@ -41,5 +43,4 @@ ReactDOM.render(
41
43
  </BrowserRouter>
42
44
  </ApolloProvider>
43
45
  </App>,
44
- document.getElementById("main"),
45
46
  )
@@ -8,7 +8,7 @@ import {
8
8
  useReactTable,
9
9
  } from "@tanstack/react-table"
10
10
  import styled from "@emotion/styled"
11
- import { format, isValid, parseISO } from "date-fns"
11
+ import { format, isValid, parse, parseISO } from "date-fns"
12
12
 
13
13
  interface DataTableProps {
14
14
  data: any[]
@@ -33,10 +33,32 @@ const TD = styled.td`
33
33
  padding: 3px;
34
34
  `
35
35
 
36
+ function extractDateString(dateString) {
37
+ const formats = [
38
+ "yyyy-MM-dd", // ISO 8601
39
+ "yyyy-MM-ddTHH:mm:ss", // ISO 8601 with time
40
+ "MM/dd/yyyy", // US (M/D/YYYY)
41
+ "dd/MM/yyyy", // European (D/M/YYYY)
42
+ ]
43
+
44
+ for (const format of formats) {
45
+ const parsedDate = parse(dateString, format, new Date())
46
+ if (isValid(parsedDate)) {
47
+ return parsedDate
48
+ }
49
+ }
50
+
51
+ return false
52
+ }
53
+
36
54
  function CellFormat(props): any {
37
55
  const value = props.getValue()
38
- if (typeof value === "string" && isValid(parseISO(value))) {
39
- return format(parseISO(value), "yyyy-MM-dd")
56
+ let extractedDate
57
+ if (typeof value === "string") {
58
+ extractedDate = extractDateString(value)
59
+ }
60
+ if (extractedDate instanceof Date) {
61
+ return format(extractedDate, "yyyy-MM-dd")
40
62
  } else if (typeof value === "string" && /^ds[0-9]{6}$/.exec(value)) {
41
63
  return <a href={`/datasets/${value}`}>{value}</a>
42
64
  } else if (Array.isArray(value)) {
@@ -498,46 +498,48 @@ exports[`SnapshotContainer component > renders successfully 1`] = `
498
498
  <div>
499
499
  <article>
500
500
  <section>
501
- <div>
502
- <p>
503
- This dataset was obtained from the OpenfMRI project (
504
- <a
505
- href="http://www.openfmri.org"
506
- >
507
- http://www.openfmri.org
508
- </a>
509
- ).
501
+ <p>
502
+ This dataset was obtained from the OpenfMRI project (
503
+ <a
504
+ href="http://www.openfmri.org"
505
+ >
506
+ http://www.openfmri.org
507
+ </a>
508
+ ).
510
509
  Accession #: ds003
511
510
  Description: Rhyme judgment
512
- </p>
513
- <p>
514
- Release history:
511
+ </p>
512
+
513
+
514
+ <p>
515
+ Release history:
515
516
  10/06/2011: initial release
516
517
  3/21/2013: Updated release with QA information
517
- </p>
518
- <p>
519
- This dataset is made available under the Public Domain Dedication and License
520
- v1.0, whose full text can be found at
518
+ </p>
519
+
521
520
 
522
- <a
523
- href="http://www.opendatacommons.org/licenses/pddl/1.0/"
524
- >
525
- http://www.opendatacommons.org/licenses/pddl/1.0/
526
- </a>
527
- .
528
- We hope that all users will follow the ODC Attribution/Share-Alike
521
+ <p>
522
+ This dataset is made available under the Public Domain Dedication and License
523
+ v1.0, whose full text can be found at
524
+
525
+ <a
526
+ href="http://www.opendatacommons.org/licenses/pddl/1.0/"
527
+ >
528
+ http://www.opendatacommons.org/licenses/pddl/1.0/
529
+ </a>
530
+ .
531
+ We hope that all users will follow the ODC Attribution/Share-Alike
529
532
  Community Norms (
530
- <a
531
- href="http://www.opendatacommons.org/norms/odc-by-sa/"
532
- >
533
- http://www.opendatacommons.org/norms/odc-by-sa/
534
- </a>
535
- );
536
- in particular, while not legally required, we hope that all users
537
- of the data will acknowledge the OpenfMRI project and NSF Grant
533
+ <a
534
+ href="http://www.opendatacommons.org/norms/odc-by-sa/"
535
+ >
536
+ http://www.opendatacommons.org/norms/odc-by-sa/
537
+ </a>
538
+ );
539
+ in particular, while not legally required, we hope that all users
540
+ of the data will acknowledge the OpenfMRI project and NSF Grant
538
541
  OCI-1131441 (R. Poldrack, PI) in any publications.
539
- </p>
540
- </div>
542
+ </p>
541
543
  </section>
542
544
  </article>
543
545
  </div>
@@ -115,6 +115,7 @@ export const DatasetTools = ({
115
115
  : `/datasets/${datasetId}/derivatives`}
116
116
  icon="fa-cubes"
117
117
  label="Derivatives"
118
+ disable={!agree}
118
119
  />
119
120
  )}
120
121
  <DatasetToolButton
@@ -1,8 +1,10 @@
1
1
  import React from "react"
2
+ import { Navigate, useParams } from "react-router-dom"
2
3
  import DownloadS3Derivative from "../download/download-derivative-s3"
3
4
  import DownloadDataLadDerivative from "../download/download-derivative-datalad"
4
5
  import { DatasetPageBorder } from "./styles/dataset-page-border"
5
6
  import { HeaderRow3 } from "./styles/header-row"
7
+ import { useAgreement } from "../../components/agreement"
6
8
 
7
9
  interface DerivativeElementProps {
8
10
  name: string
@@ -38,6 +40,12 @@ interface DerivativesProps {
38
40
  }
39
41
 
40
42
  const Derivatives = ({ derivatives }: DerivativesProps): JSX.Element => {
43
+ const { datasetId, tag: snapshotTag } = useParams()
44
+ const [agreed] = useAgreement()
45
+ // If the derivatives page is directly visited without the agreement, return to the dataset page
46
+ if (!agreed) {
47
+ return <Navigate to={`/datasets/${datasetId}`} replace={true} />
48
+ }
41
49
  return (
42
50
  <DatasetPageBorder>
43
51
  <HeaderRow3>Available Derivatives</HeaderRow3>
@@ -272,6 +272,9 @@
272
272
  content: '';
273
273
  }
274
274
  }
275
+ p {
276
+ display: inline-block;
277
+ }
275
278
  }
276
279
  }
277
280
  }
@@ -2,38 +2,46 @@
2
2
 
3
3
  exports[`Test <Markdown> component > allows a href with certain protocols 1`] = `
4
4
  <DocumentFragment>
5
- <a
6
- href="https://example.com"
7
- >
8
- Example link that should work.
9
- </a>
5
+ <p>
6
+ <a
7
+ href="https://example.com"
8
+ >
9
+ Example link that should work.
10
+ </a>
11
+ </p>
10
12
  </DocumentFragment>
11
13
  `;
12
14
 
13
15
  exports[`Test <Markdown> component > does not allow href with unknown protocols 1`] = `
14
16
  <DocumentFragment>
15
- <a>
16
- Example link that should not work.
17
- </a>
17
+ <p>
18
+ <a>
19
+ Example link that should not work.
20
+ </a>
21
+ </p>
18
22
  </DocumentFragment>
19
23
  `;
20
24
 
21
25
  exports[`Test <Markdown> component > filters close-break tags 1`] = `
22
26
  <DocumentFragment>
23
- <span>
27
+ <p>
24
28
  <br />
25
29
  sample text
26
30
  <br />
27
- </span>
31
+ </p>
28
32
  </DocumentFragment>
29
33
  `;
30
34
 
31
35
  exports[`Test <Markdown> component > filters out disallowed tags 1`] = `
32
36
  <DocumentFragment>
33
37
  <ul>
38
+
39
+
34
40
  <li>
35
41
  Markdown document
36
42
  </li>
43
+
44
+
37
45
  </ul>
38
46
  </DocumentFragment>
39
47
  `;
@@ -41,17 +49,21 @@ exports[`Test <Markdown> component > filters out disallowed tags 1`] = `
41
49
  exports[`Test <Markdown> component > safely handles broken HTML tags 1`] = `
42
50
  <DocumentFragment>
43
51
  <ul>
52
+
53
+
44
54
  <li>
45
55
  Markdown document
46
56
 
47
57
  <br />
48
58
  <br />
49
- <ul>
50
- <li>
51
- test content
52
- </li>
53
- </ul>
54
59
  </li>
60
+
61
+
62
+ <li>
63
+ test content
64
+ </li>
65
+
66
+
55
67
  </ul>
56
68
  </DocumentFragment>
57
69
  `;
@@ -1,6 +1,8 @@
1
1
  import React from "react"
2
- import MarkdownToJsx from "markdown-to-jsx"
2
+ import ReactMarkdown from "react-markdown"
3
3
  import DOMPurify from "dompurify"
4
+ import rehypeRaw from "rehype-raw"
5
+ import remarkGfm from "remark-gfm"
4
6
 
5
7
  interface MarkdownProps {
6
8
  children: string
@@ -161,7 +163,9 @@ export function Markdown({ children }: MarkdownProps) {
161
163
  })
162
164
  return (
163
165
  <>
164
- <MarkdownToJsx>{sanitizedMarkdown}</MarkdownToJsx>
166
+ <ReactMarkdown rehypePlugins={[rehypeRaw]} remarkPlugins={[remarkGfm]}>
167
+ {sanitizedMarkdown}
168
+ </ReactMarkdown>
165
169
  </>
166
170
  )
167
171
  }
@@ -73,7 +73,7 @@ class Issue extends React.Component {
73
73
  <span className="e-meta">
74
74
  <label>File Metadata:</label>
75
75
  <p>
76
- {file.size / 1000} KB | {file.type}
76
+ {file.size / 1000} KB {file.type ? ` | ${file.type}` : ""}
77
77
  </p>
78
78
  </span>
79
79
  )
@@ -31,23 +31,28 @@ class Issues extends React.Component {
31
31
  </span>
32
32
  )
33
33
 
34
- // issue sub-errors
35
- const subErrors = Array.from(issue.files).map((error, index2) => {
36
- // Schema validator returns multiple files here
34
+ let subErrors = []
35
+ if (issue.files instanceof Map) {
36
+ // Schema validator returns multiple files here as a map
37
37
  // map those to the old sub-issue model to display them
38
- if (error?.length) {
39
- return error.filter(Boolean).map((i, index3) => {
40
- return (
41
- <Issue
42
- type={this.props.issueType}
43
- file={i.file}
44
- error={i}
45
- index={index3}
46
- key={index3}
47
- />
48
- )
49
- })
50
- } else {
38
+ let index = 0
39
+ for (const [path, fObj] of issue.files) {
40
+ const error = {
41
+ reason: path,
42
+ file: fObj,
43
+ }
44
+ subErrors.push(
45
+ (<Issue
46
+ type={this.props.issueType}
47
+ error={error}
48
+ index={index}
49
+ key={index}
50
+ />),
51
+ )
52
+ index += 1
53
+ }
54
+ } else {
55
+ subErrors = Array.from(issue.files).map((error, index2) => {
51
56
  return error
52
57
  ? (
53
58
  <Issue
@@ -59,8 +64,8 @@ class Issues extends React.Component {
59
64
  />
60
65
  )
61
66
  : null
62
- }
63
- })
67
+ })
68
+ }
64
69
 
65
70
  if (issue.additionalFileCount > 0) {
66
71
  subErrors.push(