@openneuro/app 4.35.0-alpha.1 → 4.36.0-alpha.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 +3 -3
- package/src/client.jsx +1 -0
- package/src/scripts/components/button/button.scss +14 -0
- package/src/scripts/components/search-page/SearchResultItem.tsx +3 -1
- package/src/scripts/components/search-page/search-page.scss +1 -13
- package/src/scripts/config.ts +6 -0
- package/src/scripts/dataset/files/__tests__/__snapshots__/file.spec.jsx.snap +2 -2
- package/src/scripts/dataset/files/file.tsx +2 -2
- package/src/scripts/dataset/mutations/__tests__/update-file.spec.tsx +126 -0
- package/src/scripts/dataset/mutations/update-file.jsx +20 -5
- package/src/scripts/dataset/routes/snapshot.tsx +1 -1
- package/src/scripts/errors/errorRoute.tsx +2 -0
- package/src/scripts/pages/orcid-link.tsx +9 -0
- package/src/scripts/queries/user.ts +120 -3
- package/src/scripts/types/user-types.ts +11 -13
- package/src/scripts/uploader/file-select.tsx +42 -57
- package/src/scripts/uploader/upload-select.jsx +1 -1
- package/src/scripts/users/__tests__/dataset-card.spec.tsx +127 -0
- package/src/scripts/users/__tests__/user-account-view.spec.tsx +150 -67
- package/src/scripts/users/__tests__/user-card.spec.tsx +6 -17
- package/src/scripts/users/__tests__/user-query.spec.tsx +133 -38
- package/src/scripts/users/__tests__/user-routes.spec.tsx +156 -27
- package/src/scripts/users/__tests__/user-tabs.spec.tsx +7 -7
- package/src/scripts/users/components/edit-list.tsx +26 -5
- package/src/scripts/users/components/edit-string.tsx +40 -13
- package/src/scripts/users/components/editable-content.tsx +10 -3
- package/src/scripts/users/components/user-dataset-filters.tsx +205 -121
- package/src/scripts/users/dataset-card.tsx +3 -2
- package/src/scripts/users/github-auth-button.tsx +98 -0
- package/src/scripts/users/scss/datasetcard.module.scss +65 -12
- package/src/scripts/users/scss/useraccountview.module.scss +1 -1
- package/src/scripts/users/user-account-view.tsx +43 -34
- package/src/scripts/users/user-card.tsx +12 -17
- package/src/scripts/users/user-container.tsx +9 -5
- package/src/scripts/users/user-datasets-view.tsx +350 -40
- package/src/scripts/users/user-menu.tsx +4 -9
- package/src/scripts/users/user-notifications-view.tsx +9 -7
- package/src/scripts/users/user-query.tsx +3 -3
- package/src/scripts/users/user-routes.tsx +11 -5
- package/src/scripts/users/user-tabs.tsx +4 -2
- package/src/scripts/users/__tests__/datasest-card.spec.tsx +0 -201
- package/src/scripts/users/fragments/query.js +0 -42
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openneuro/app",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.36.0-alpha.0",
|
|
4
4
|
"description": "React JS web frontend for the OpenNeuro platform.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "public/client.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"author": "Squishymedia",
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@apollo/client": "3.
|
|
17
|
+
"@apollo/client": "3.13.8",
|
|
18
18
|
"@artsy/fresnel": "^1.3.1",
|
|
19
19
|
"@bids/validator": "npm:@jsr/bids__validator@^2.0.3",
|
|
20
20
|
"@emotion/react": "11.11.1",
|
|
@@ -75,5 +75,5 @@
|
|
|
75
75
|
"publishConfig": {
|
|
76
76
|
"access": "public"
|
|
77
77
|
},
|
|
78
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "0d754964177331113f4279c48d6c0fae0f59adc2"
|
|
79
79
|
}
|
package/src/client.jsx
CHANGED
|
@@ -138,3 +138,17 @@
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
|
+
|
|
142
|
+
.load-more {
|
|
143
|
+
.on-button {
|
|
144
|
+
display: block;
|
|
145
|
+
border: 1px solid;
|
|
146
|
+
width: 100%;
|
|
147
|
+
background-color: #fff;
|
|
148
|
+
transition: background-color 0.3s;
|
|
149
|
+
&:hover {
|
|
150
|
+
text-decoration: none;
|
|
151
|
+
background-color: $newspaper;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -127,6 +127,8 @@ export const SearchResultItem = ({
|
|
|
127
127
|
const hasEdit = hasEditPermissions(node.permissions, profileSub) || isAdmin
|
|
128
128
|
|
|
129
129
|
const heading = node.latestSnapshot.description?.Name
|
|
130
|
+
? node.latestSnapshot.description?.Name
|
|
131
|
+
: node.id
|
|
130
132
|
const summary = node.latestSnapshot?.summary
|
|
131
133
|
const datasetId = node.id
|
|
132
134
|
const numSessions = summary?.sessions.length > 0 ? summary.sessions.length : 1
|
|
@@ -134,7 +136,7 @@ export const SearchResultItem = ({
|
|
|
134
136
|
const accessionNumber = (
|
|
135
137
|
<span className="result-summary-meta">
|
|
136
138
|
<strong>Openneuro Accession Number:</strong>
|
|
137
|
-
<
|
|
139
|
+
<Link to={"/datasets/" + datasetId}>{node.id}</Link>
|
|
138
140
|
</span>
|
|
139
141
|
)
|
|
140
142
|
const sessions = (
|
|
@@ -302,19 +302,7 @@
|
|
|
302
302
|
border-bottom: 1px solid $newspaper;
|
|
303
303
|
}
|
|
304
304
|
}
|
|
305
|
-
|
|
306
|
-
.on-button {
|
|
307
|
-
display: block;
|
|
308
|
-
border: 1px solid;
|
|
309
|
-
width: 100%;
|
|
310
|
-
background-color: #fff;
|
|
311
|
-
transition: background-color 0.3s;
|
|
312
|
-
&:hover {
|
|
313
|
-
text-decoration: none;
|
|
314
|
-
background-color: $newspaper;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
305
|
+
|
|
318
306
|
}
|
|
319
307
|
|
|
320
308
|
.search-sort {
|
package/src/scripts/config.ts
CHANGED
|
@@ -15,6 +15,9 @@ export interface OpenNeuroConfig {
|
|
|
15
15
|
clientID: string
|
|
16
16
|
ORCID_API_ENDPOINT: string
|
|
17
17
|
}
|
|
18
|
+
github?: {
|
|
19
|
+
clientID: string
|
|
20
|
+
}
|
|
18
21
|
globus?: {
|
|
19
22
|
clientID: string
|
|
20
23
|
}
|
|
@@ -48,6 +51,9 @@ export const config: OpenNeuroConfig = {
|
|
|
48
51
|
clientID: globalThis.OpenNeuroConfig.ORCID_CLIENT_ID,
|
|
49
52
|
ORCID_API_ENDPOINT: globalThis.OpenNeuroConfig.ORCID_API_ENDPOINT,
|
|
50
53
|
},
|
|
54
|
+
github: {
|
|
55
|
+
clientID: globalThis.OpenNeuroConfig.GITHUB_CLIENT_ID,
|
|
56
|
+
},
|
|
51
57
|
},
|
|
52
58
|
analytics: {
|
|
53
59
|
trackingIds: globalThis.OpenNeuroConfig.GOOGLE_TRACKING_IDS.split(",").map(
|
|
@@ -29,7 +29,7 @@ exports[`File component > renders for dataset snapshots 1`] = `
|
|
|
29
29
|
>
|
|
30
30
|
<a
|
|
31
31
|
aria-label="download file"
|
|
32
|
-
download=""
|
|
32
|
+
download="README"
|
|
33
33
|
href="/crn/datasets/ds001/snapshots/1.0.0/files/README"
|
|
34
34
|
>
|
|
35
35
|
<i
|
|
@@ -90,7 +90,7 @@ exports[`File component > renders with common props 1`] = `
|
|
|
90
90
|
>
|
|
91
91
|
<a
|
|
92
92
|
aria-label="download file"
|
|
93
|
-
download=""
|
|
93
|
+
download="README"
|
|
94
94
|
href="/crn/datasets/ds001/files/README"
|
|
95
95
|
>
|
|
96
96
|
<i
|
|
@@ -149,7 +149,7 @@ const File = ({
|
|
|
149
149
|
<a
|
|
150
150
|
href={urls?.[0] ||
|
|
151
151
|
apiPath(datasetId, snapshotTag, filePath(path, filename))}
|
|
152
|
-
download
|
|
152
|
+
download={filename}
|
|
153
153
|
aria-label="download file"
|
|
154
154
|
>
|
|
155
155
|
<i className="fa fa-download" />
|
|
@@ -170,7 +170,7 @@ const File = ({
|
|
|
170
170
|
{editMode && (
|
|
171
171
|
<Media greaterThanOrEqual="medium">
|
|
172
172
|
<Tooltip tooltip="Update">
|
|
173
|
-
<UpdateFile datasetId={datasetId} path={path}>
|
|
173
|
+
<UpdateFile datasetId={datasetId} path={path} filename={filename}>
|
|
174
174
|
<i className="fa fa-cloud-upload" />
|
|
175
175
|
</UpdateFile>
|
|
176
176
|
</Tooltip>
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { fireEvent, render, screen } from "@testing-library/react"
|
|
3
|
+
import { vi } from "vitest"
|
|
4
|
+
import UpdateFile from "../update-file"
|
|
5
|
+
import UploaderContext from "../../../uploader/uploader-context.js"
|
|
6
|
+
|
|
7
|
+
describe("UpdateFile Component", () => {
|
|
8
|
+
const mockResumeDataset = vi.fn()
|
|
9
|
+
const mockUploader = {
|
|
10
|
+
resumeDataset: vi.fn(() => mockResumeDataset),
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const datasetId = "ds000001"
|
|
14
|
+
const path = "sub-01/anat"
|
|
15
|
+
|
|
16
|
+
const renderComponent = (props = {}, uploader = mockUploader) => {
|
|
17
|
+
return render(
|
|
18
|
+
<UploaderContext.Provider value={uploader}>
|
|
19
|
+
<UpdateFile datasetId={datasetId} path={path} {...props}>
|
|
20
|
+
<button>Upload Button</button>
|
|
21
|
+
</UpdateFile>
|
|
22
|
+
</UploaderContext.Provider>,
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
it("renders children correctly", () => {
|
|
27
|
+
renderComponent()
|
|
28
|
+
expect(screen.getByText("Upload Button")).toBeInTheDocument()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("sets webkitdirectory attribute when directory prop is true", () => {
|
|
32
|
+
renderComponent({ directory: true })
|
|
33
|
+
const inputElement = screen.getByRole("button", { name: "Upload Button" })
|
|
34
|
+
.previousSibling // The input is before the children
|
|
35
|
+
expect(inputElement).toHaveAttribute("webkitdirectory", "true")
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it("does not set webkitdirectory attribute when directory prop is false", () => {
|
|
39
|
+
renderComponent({ directory: false })
|
|
40
|
+
const inputElement = screen.getByRole("button", { name: "Upload Button" })
|
|
41
|
+
.previousSibling
|
|
42
|
+
expect(inputElement).not.toHaveAttribute("webkitdirectory")
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("sets multiple attribute when multiple prop is true", () => {
|
|
46
|
+
renderComponent({ multiple: true })
|
|
47
|
+
const inputElement = screen.getByRole("button", { name: "Upload Button" })
|
|
48
|
+
.previousSibling
|
|
49
|
+
expect(inputElement).toHaveAttribute("multiple")
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("does not set multiple attribute when multiple prop is false", () => {
|
|
53
|
+
renderComponent({ multiple: false })
|
|
54
|
+
const inputElement = screen.getByRole("button", { name: "Upload Button" })
|
|
55
|
+
.previousSibling
|
|
56
|
+
expect(inputElement).not.toHaveAttribute("multiple")
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
describe("onChange event", () => {
|
|
60
|
+
const file1 = new File(["content1"], "original1.txt", {
|
|
61
|
+
type: "text/plain",
|
|
62
|
+
})
|
|
63
|
+
const file2 = new File(["content2"], "original2.txt", {
|
|
64
|
+
type: "text/plain",
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it("calls uploader.resumeDataset with renamed file when filename is provided and one file is selected", () => {
|
|
68
|
+
const customFilename = "new_filename.txt"
|
|
69
|
+
renderComponent({ filename: customFilename })
|
|
70
|
+
const inputElement = screen.getByRole("button", { name: "Upload Button" })
|
|
71
|
+
.previousSibling
|
|
72
|
+
|
|
73
|
+
fireEvent.change(inputElement, {
|
|
74
|
+
target: { files: [file1] },
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
expect(mockUploader.resumeDataset).toHaveBeenCalledWith(
|
|
78
|
+
datasetId,
|
|
79
|
+
path,
|
|
80
|
+
false,
|
|
81
|
+
)
|
|
82
|
+
expect(mockResumeDataset).toHaveBeenCalledTimes(1)
|
|
83
|
+
const calledWithArgs = mockResumeDataset.mock.calls[0][0]
|
|
84
|
+
expect(calledWithArgs.files).toHaveLength(1)
|
|
85
|
+
expect(calledWithArgs.files[0].name).toBe(customFilename)
|
|
86
|
+
expect(calledWithArgs.files[0].type).toBe(file1.type)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("calls uploader.resumeDataset with original files when filename is not provided", () => {
|
|
90
|
+
renderComponent()
|
|
91
|
+
const inputElement = screen.getByRole("button", { name: "Upload Button" })
|
|
92
|
+
.previousSibling
|
|
93
|
+
|
|
94
|
+
fireEvent.change(inputElement, {
|
|
95
|
+
target: { files: [file1, file2] },
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
expect(mockUploader.resumeDataset).toHaveBeenCalledWith(
|
|
99
|
+
datasetId,
|
|
100
|
+
path,
|
|
101
|
+
false,
|
|
102
|
+
)
|
|
103
|
+
expect(mockResumeDataset).toHaveBeenCalledTimes(1)
|
|
104
|
+
expect(mockResumeDataset).toHaveBeenCalledWith({ files: [file1, file2] })
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it("calls uploader.resumeDataset with original files when multiple files are selected, even if filename is provided", () => {
|
|
108
|
+
const customFilename = "new_filename.txt"
|
|
109
|
+
renderComponent({ filename: customFilename, multiple: true })
|
|
110
|
+
const inputElement = screen.getByRole("button", { name: "Upload Button" })
|
|
111
|
+
.previousSibling
|
|
112
|
+
|
|
113
|
+
fireEvent.change(inputElement, {
|
|
114
|
+
target: { files: [file1, file2] },
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
expect(mockUploader.resumeDataset).toHaveBeenCalledWith(
|
|
118
|
+
datasetId,
|
|
119
|
+
path,
|
|
120
|
+
false,
|
|
121
|
+
)
|
|
122
|
+
expect(mockResumeDataset).toHaveBeenCalledTimes(1)
|
|
123
|
+
expect(mockResumeDataset).toHaveBeenCalledWith({ files: [file1, file2] })
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -7,6 +7,7 @@ const UpdateFile = ({
|
|
|
7
7
|
directory = false,
|
|
8
8
|
multiple = false,
|
|
9
9
|
path = null,
|
|
10
|
+
filename = null,
|
|
10
11
|
children,
|
|
11
12
|
}) => {
|
|
12
13
|
return (
|
|
@@ -18,11 +19,25 @@ const UpdateFile = ({
|
|
|
18
19
|
className="update-file"
|
|
19
20
|
onChange={(e) => {
|
|
20
21
|
e.preventDefault()
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
path
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
if (filename && e.target.files.length === 1) {
|
|
23
|
+
// In the case that a single file was selected,
|
|
24
|
+
// name that file based on the original path and not the client side name.
|
|
25
|
+
const target = e.target.files[0]
|
|
26
|
+
const files = [
|
|
27
|
+
new File([target], filename, { type: target.type }),
|
|
28
|
+
]
|
|
29
|
+
uploader.resumeDataset(
|
|
30
|
+
datasetId,
|
|
31
|
+
path,
|
|
32
|
+
false,
|
|
33
|
+
)({ files })
|
|
34
|
+
} else {
|
|
35
|
+
uploader.resumeDataset(
|
|
36
|
+
datasetId,
|
|
37
|
+
path,
|
|
38
|
+
false,
|
|
39
|
+
)({ files: e.target.files })
|
|
40
|
+
}
|
|
26
41
|
}}
|
|
27
42
|
webkitdirectory={directory ? "true" : undefined}
|
|
28
43
|
multiple={multiple && true}
|
|
@@ -18,7 +18,7 @@ const FormRow = styled.div`
|
|
|
18
18
|
export const NoErrors = ({ validation, authors, children }) => {
|
|
19
19
|
const noErrors = validation?.errors === 0
|
|
20
20
|
// zero authors will cause DOI minting to fail
|
|
21
|
-
const hasAuthor = authors
|
|
21
|
+
const hasAuthor = authors?.length > 0
|
|
22
22
|
if (noErrors && hasAuthor) {
|
|
23
23
|
return children
|
|
24
24
|
} else {
|
|
@@ -6,12 +6,14 @@ import { Route, Routes } from "react-router-dom"
|
|
|
6
6
|
import OrcidGeneral from "./orcid/general.jsx"
|
|
7
7
|
import { OrcidEmailWarning } from "./orcid/email-warning.js"
|
|
8
8
|
import FourOFourPage from "./404page.js"
|
|
9
|
+
import FourOThreePage from "./403page.js"
|
|
9
10
|
|
|
10
11
|
function ErrorRoute() {
|
|
11
12
|
return (
|
|
12
13
|
<div className="container errors">
|
|
13
14
|
<div className="panel">
|
|
14
15
|
<Routes>
|
|
16
|
+
<Route path="github" element={<FourOThreePage />} />
|
|
15
17
|
<Route path="orcid" element={<OrcidGeneral />} />
|
|
16
18
|
<Route path="email-warning" element={<OrcidEmailWarning />} />
|
|
17
19
|
<Route path="*" element={<FourOFourPage />} />
|
|
@@ -36,6 +36,15 @@ export function OrcidLinkPage() {
|
|
|
36
36
|
have will be merged into the combined OpenNeuro account linked to your
|
|
37
37
|
ORCID iD.
|
|
38
38
|
</p>
|
|
39
|
+
<p>
|
|
40
|
+
</p>
|
|
41
|
+
<p>
|
|
42
|
+
Please see{" "}
|
|
43
|
+
<a href="https://docs.openneuro.org/orcid.html">our documentation</a>
|
|
44
|
+
{" "}
|
|
45
|
+
for additional details on how we use ORCID data and how to link your
|
|
46
|
+
account.
|
|
47
|
+
</p>
|
|
39
48
|
<h3>Why are we making this change?</h3>
|
|
40
49
|
<p>
|
|
41
50
|
ORCID allows richer researcher metadata for contributions and
|
|
@@ -20,6 +20,8 @@ export const GET_USER = gql`
|
|
|
20
20
|
created
|
|
21
21
|
lastSeen
|
|
22
22
|
blocked
|
|
23
|
+
githubSynced
|
|
24
|
+
github
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
`
|
|
@@ -45,17 +47,132 @@ export const UPDATE_USER = gql`
|
|
|
45
47
|
}
|
|
46
48
|
`
|
|
47
49
|
|
|
50
|
+
export const ADVANCED_SEARCH_DATASETS_QUERY = gql`
|
|
51
|
+
query advancedSearchDatasets(
|
|
52
|
+
$query: JSON!
|
|
53
|
+
$cursor: String
|
|
54
|
+
$allDatasets: Boolean
|
|
55
|
+
$datasetStatus: String
|
|
56
|
+
$sortBy: JSON
|
|
57
|
+
$first: Int!
|
|
58
|
+
) {
|
|
59
|
+
datasets: advancedSearch(
|
|
60
|
+
query: $query
|
|
61
|
+
allDatasets: $allDatasets
|
|
62
|
+
datasetStatus: $datasetStatus
|
|
63
|
+
sortBy: $sortBy
|
|
64
|
+
first: $first
|
|
65
|
+
after: $cursor
|
|
66
|
+
) {
|
|
67
|
+
edges {
|
|
68
|
+
id
|
|
69
|
+
node {
|
|
70
|
+
id
|
|
71
|
+
created
|
|
72
|
+
name
|
|
73
|
+
uploader {
|
|
74
|
+
id
|
|
75
|
+
name
|
|
76
|
+
orcid
|
|
77
|
+
}
|
|
78
|
+
public
|
|
79
|
+
permissions {
|
|
80
|
+
id
|
|
81
|
+
userPermissions {
|
|
82
|
+
userId
|
|
83
|
+
level
|
|
84
|
+
access: level
|
|
85
|
+
user {
|
|
86
|
+
id
|
|
87
|
+
name
|
|
88
|
+
email
|
|
89
|
+
provider
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
metadata {
|
|
94
|
+
ages
|
|
95
|
+
}
|
|
96
|
+
latestSnapshot {
|
|
97
|
+
size
|
|
98
|
+
summary {
|
|
99
|
+
modalities
|
|
100
|
+
secondaryModalities
|
|
101
|
+
sessions
|
|
102
|
+
subjects
|
|
103
|
+
subjectMetadata {
|
|
104
|
+
participantId
|
|
105
|
+
age
|
|
106
|
+
sex
|
|
107
|
+
group
|
|
108
|
+
}
|
|
109
|
+
tasks
|
|
110
|
+
size
|
|
111
|
+
totalFiles
|
|
112
|
+
dataProcessed
|
|
113
|
+
pet {
|
|
114
|
+
BodyPart
|
|
115
|
+
ScannerManufacturer
|
|
116
|
+
ScannerManufacturersModelName
|
|
117
|
+
TracerName
|
|
118
|
+
TracerRadionuclide
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
issues {
|
|
122
|
+
severity
|
|
123
|
+
}
|
|
124
|
+
validation {
|
|
125
|
+
errors
|
|
126
|
+
warnings
|
|
127
|
+
}
|
|
128
|
+
description {
|
|
129
|
+
Name
|
|
130
|
+
Authors
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
analytics {
|
|
134
|
+
views
|
|
135
|
+
downloads
|
|
136
|
+
}
|
|
137
|
+
stars {
|
|
138
|
+
userId
|
|
139
|
+
datasetId
|
|
140
|
+
}
|
|
141
|
+
followers {
|
|
142
|
+
userId
|
|
143
|
+
datasetId
|
|
144
|
+
}
|
|
145
|
+
snapshots {
|
|
146
|
+
id
|
|
147
|
+
created
|
|
148
|
+
tag
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
pageInfo {
|
|
153
|
+
startCursor
|
|
154
|
+
endCursor
|
|
155
|
+
hasPreviousPage
|
|
156
|
+
hasNextPage
|
|
157
|
+
count
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
`
|
|
162
|
+
|
|
48
163
|
// Reusable hook to fetch user data
|
|
49
|
-
export const useUser = () => {
|
|
164
|
+
export const useUser = (userId?: string) => {
|
|
50
165
|
const [cookies] = useCookies()
|
|
51
166
|
const profile = getProfile(cookies)
|
|
52
167
|
const profileSub = profile?.sub
|
|
53
168
|
|
|
169
|
+
const finalUserId = userId || profileSub
|
|
170
|
+
|
|
54
171
|
const { data: userData, loading: userLoading, error: userError } = useQuery(
|
|
55
172
|
GET_USER,
|
|
56
173
|
{
|
|
57
|
-
variables: { userId:
|
|
58
|
-
skip: !
|
|
174
|
+
variables: { userId: finalUserId },
|
|
175
|
+
skip: !finalUserId,
|
|
59
176
|
},
|
|
60
177
|
)
|
|
61
178
|
|
|
@@ -8,27 +8,25 @@ export interface User {
|
|
|
8
8
|
avatar?: string
|
|
9
9
|
orcid?: string
|
|
10
10
|
links?: string[]
|
|
11
|
+
githubSynced?: Date
|
|
12
|
+
admin?: boolean
|
|
13
|
+
provider?: string
|
|
14
|
+
created?: Date
|
|
15
|
+
lastSeen?: Date
|
|
16
|
+
blocked?: boolean
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
export interface UserRoutesProps {
|
|
14
|
-
|
|
20
|
+
orcidUser: User
|
|
15
21
|
hasEdit: boolean
|
|
16
22
|
isUser: boolean
|
|
17
23
|
}
|
|
18
24
|
export interface UserCardProps {
|
|
19
|
-
|
|
25
|
+
orcidUser: User
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
export interface UserAccountViewProps {
|
|
23
|
-
|
|
24
|
-
name: string
|
|
25
|
-
email: string
|
|
26
|
-
orcid?: string
|
|
27
|
-
links?: string[]
|
|
28
|
-
location?: string
|
|
29
|
-
institution?: string
|
|
30
|
-
github?: string
|
|
31
|
-
}
|
|
29
|
+
orcidUser: User
|
|
32
30
|
}
|
|
33
31
|
|
|
34
32
|
export interface Dataset {
|
|
@@ -63,12 +61,12 @@ export interface DatasetCardProps {
|
|
|
63
61
|
}
|
|
64
62
|
|
|
65
63
|
export interface UserDatasetsViewProps {
|
|
66
|
-
|
|
64
|
+
orcidUser: User
|
|
67
65
|
hasEdit: boolean
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
export interface AccountContainerProps {
|
|
71
|
-
|
|
69
|
+
orcidUser: User
|
|
72
70
|
hasEdit: boolean
|
|
73
71
|
isUser: boolean
|
|
74
72
|
}
|
|
@@ -1,73 +1,58 @@
|
|
|
1
|
-
// dependencies -------------------------------------------------------
|
|
2
|
-
|
|
3
1
|
import React from "react"
|
|
4
|
-
import PropTypes from "prop-types"
|
|
5
2
|
|
|
6
|
-
type
|
|
3
|
+
type UploadFileSelectProps = {
|
|
7
4
|
resume?: boolean
|
|
8
5
|
onClick?: React.MouseEventHandler<HTMLButtonElement>
|
|
9
|
-
onChange
|
|
6
|
+
onChange: (e?: { files: File[] }) => void
|
|
10
7
|
disabled?: boolean
|
|
11
8
|
}
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const resumeIcon = (
|
|
18
|
-
<span>
|
|
19
|
-
<i className="fa fa-repeat" />
|
|
20
|
-
|
|
21
|
-
</span>
|
|
22
|
-
)
|
|
23
|
-
const icon = this.props.resume ? resumeIcon : null
|
|
24
|
-
const text = this.props.resume ? "Resume" : "Select folder"
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<div
|
|
28
|
-
className={"fileupload-btn" + (this.props.disabled ? " disabled" : "")}
|
|
29
|
-
>
|
|
30
|
-
<span>
|
|
31
|
-
{icon}
|
|
32
|
-
{text}
|
|
33
|
-
</span>
|
|
34
|
-
<input
|
|
35
|
-
type="file"
|
|
36
|
-
id="multifile-select"
|
|
37
|
-
className="multifile-select-btn"
|
|
38
|
-
onClick={this._click.bind(this)}
|
|
39
|
-
onChange={this._onFileSelect.bind(this)}
|
|
40
|
-
webkitdirectory="true"
|
|
41
|
-
directory="true"
|
|
42
|
-
disabled={this.props.disabled}
|
|
43
|
-
/>
|
|
44
|
-
</div>
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// custom methods -----------------------------------------------------
|
|
49
|
-
|
|
50
|
-
_click(e): void {
|
|
10
|
+
export const UploadFileSelect: React.FC<UploadFileSelectProps> = (
|
|
11
|
+
{ resume, onClick, onChange, disabled },
|
|
12
|
+
) => {
|
|
13
|
+
const handleClick = (e: React.MouseEvent<HTMLInputElement>): void => {
|
|
51
14
|
e.stopPropagation()
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
15
|
+
// Reset the input value to allow selecting the same file/folder again
|
|
16
|
+
if (e.target instanceof HTMLInputElement) {
|
|
17
|
+
e.target.value = ""
|
|
55
18
|
}
|
|
19
|
+
if (onClick) onClick(e as React.MouseEvent<HTMLButtonElement>)
|
|
56
20
|
}
|
|
57
21
|
|
|
58
|
-
|
|
59
|
-
if (e.target && e.target.files.length > 0) {
|
|
60
|
-
const files = e.target.files
|
|
61
|
-
|
|
22
|
+
const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>): void => {
|
|
23
|
+
if (e.target && e.target.files && e.target.files.length > 0) {
|
|
24
|
+
const files = Array.from(e.target.files)
|
|
25
|
+
onChange({ files })
|
|
62
26
|
}
|
|
63
27
|
}
|
|
64
|
-
}
|
|
65
28
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
29
|
+
const resumeIcon = (
|
|
30
|
+
<span>
|
|
31
|
+
<i className="fa fa-repeat" />
|
|
32
|
+
|
|
33
|
+
</span>
|
|
34
|
+
)
|
|
35
|
+
const icon = resume ? resumeIcon : null
|
|
36
|
+
const text = resume ? "Resume" : "Select folder"
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className={`fileupload-btn${disabled ? " disabled" : ""}`}>
|
|
40
|
+
<span>
|
|
41
|
+
{icon}
|
|
42
|
+
{text}
|
|
43
|
+
</span>
|
|
44
|
+
<input
|
|
45
|
+
type="file"
|
|
46
|
+
id="multifile-select"
|
|
47
|
+
className="multifile-select-btn"
|
|
48
|
+
onClick={handleClick}
|
|
49
|
+
onChange={handleFileSelect}
|
|
50
|
+
webkitdirectory="true"
|
|
51
|
+
directory="true"
|
|
52
|
+
disabled={disabled}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
71
56
|
}
|
|
72
57
|
|
|
73
|
-
export default
|
|
58
|
+
export default UploadFileSelect
|
|
@@ -6,7 +6,7 @@ import { useUser } from "../queries/user"
|
|
|
6
6
|
const UploadSelect = () => {
|
|
7
7
|
const { user, loading, error } = useUser()
|
|
8
8
|
const disabled = loading || Boolean(error) || !user || !user.email
|
|
9
|
-
const noEmail = !user
|
|
9
|
+
const noEmail = !(user?.email)
|
|
10
10
|
return (
|
|
11
11
|
<div>
|
|
12
12
|
<UploaderContext.Consumer>
|