@opencloning/ui 1.7.1 → 1.7.3
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/CHANGELOG.md +20 -0
- package/package.json +3 -3
- package/src/components/LoadCloningHistoryWrapper.jsx +27 -20
- package/src/components/assembler/UploadPlasmidsButton.jsx +1 -0
- package/src/components/form/ServerStaticFileSelect.jsx +5 -2
- package/src/components/sources/SourceFile.jsx +9 -1
- package/src/hooks/useSnapgeneHistoryEndpoint.js +72 -0
- package/src/version.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @opencloning/ui
|
|
2
2
|
|
|
3
|
+
## 1.7.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#677](https://github.com/manulera/OpenCloning_frontend/pull/677) [`04827d6`](https://github.com/manulera/OpenCloning_frontend/commit/04827d6184c5a1276814f60ef3c3fee2c832a6fd) Thanks [@manulera](https://github.com/manulera)! - Now loads history from Snapgene files
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @opencloning/store@1.7.3
|
|
11
|
+
- @opencloning/utils@1.7.3
|
|
12
|
+
|
|
13
|
+
## 1.7.2
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- [#675](https://github.com/manulera/OpenCloning_frontend/pull/675) [`1f08cca`](https://github.com/manulera/OpenCloning_frontend/commit/1f08ccab8422d9cf9a9355d2770fa8997e53c96b) Thanks [@manulera](https://github.com/manulera)! - Improve loading files from server in Assembler
|
|
18
|
+
|
|
19
|
+
- Updated dependencies []:
|
|
20
|
+
- @opencloning/store@1.7.2
|
|
21
|
+
- @opencloning/utils@1.7.2
|
|
22
|
+
|
|
3
23
|
## 1.7.1
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"license": "MIT",
|
|
3
3
|
"name": "@opencloning/ui",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"scripts": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"@mui/icons-material": "^5.15.17",
|
|
34
34
|
"@mui/material": "^5.15.17",
|
|
35
35
|
"@mui/x-data-grid": "^8.25.0",
|
|
36
|
-
"@opencloning/store": "1.7.
|
|
37
|
-
"@opencloning/utils": "1.7.
|
|
36
|
+
"@opencloning/store": "1.7.3",
|
|
37
|
+
"@opencloning/utils": "1.7.3",
|
|
38
38
|
"@teselagen/bio-parsers": "^0.4.34",
|
|
39
39
|
"@teselagen/ove": "^0.8.34",
|
|
40
40
|
"@teselagen/range-utils": "^0.3.20",
|
|
@@ -10,6 +10,7 @@ import HistoryLoadedDialog from './HistoryLoadedDialog';
|
|
|
10
10
|
import useHttpClient from '../hooks/useHttpClient';
|
|
11
11
|
import { getVerificationFileName } from '@opencloning/utils/readNwrite';
|
|
12
12
|
import { isEqual } from 'lodash-es';
|
|
13
|
+
import useSnapgeneHistoryEndpoint from '../hooks/useSnapgeneHistoryEndpoint';
|
|
13
14
|
|
|
14
15
|
const { setState: setCloningState, deleteSourceAndItsChildren, addSourceAndItsOutputSequence } = cloningActions;
|
|
15
16
|
|
|
@@ -64,7 +65,7 @@ function LoadCloningHistoryWrapper({ fileList, clearFiles, children }) {
|
|
|
64
65
|
const store = useStore();
|
|
65
66
|
const validateState = useValidateState();
|
|
66
67
|
const httpClient = useHttpClient();
|
|
67
|
-
|
|
68
|
+
const { loadSnapgeneHistory } = useSnapgeneHistoryEndpoint();
|
|
68
69
|
const [fileLoaderFunctions, setFileLoaderFunctions] = React.useState(null);
|
|
69
70
|
|
|
70
71
|
React.useEffect(() => {
|
|
@@ -124,34 +125,40 @@ function LoadCloningHistoryWrapper({ fileList, clearFiles, children }) {
|
|
|
124
125
|
}
|
|
125
126
|
// If there are sequences in the cloning state, give the option to merge or replace
|
|
126
127
|
|
|
128
|
+
return;
|
|
127
129
|
// You cannot drop a zip or json file and a sequence file at the same time
|
|
128
130
|
} else if (files.some((file) => file.name.endsWith('.json') || file.name.endsWith('.zip'))) {
|
|
129
131
|
addAlert({
|
|
130
132
|
message: 'Drop either a single JSON/zip file or multiple sequence files. Not both.',
|
|
131
133
|
severity: 'error',
|
|
132
134
|
});
|
|
135
|
+
return;
|
|
133
136
|
// Process a bunch of sequence files
|
|
134
|
-
} else {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// If there is only one source and it is empty, delete it
|
|
139
|
-
if (cloningState.sources.length === 1 && cloningState.sources[0].type === null) {
|
|
140
|
-
dispatch(deleteSourceAndItsChildren(cloningState.sources[0].id));
|
|
141
|
-
}
|
|
142
|
-
for (let i = 0; i < sources.length; i += 1) {
|
|
143
|
-
dispatch(addSourceAndItsOutputSequence({ source: sources[i], sequence: sequences[i] }));
|
|
144
|
-
}
|
|
145
|
-
warnings.forEach((warning) => addAlert({ message: warning, severity: 'warning' }));
|
|
146
|
-
});
|
|
147
|
-
} catch (e) {
|
|
148
|
-
console.error(e);
|
|
149
|
-
addAlert({
|
|
150
|
-
message: e.message,
|
|
151
|
-
severity: 'error',
|
|
152
|
-
});
|
|
137
|
+
} else if (files.length === 1 && files[0].name.endsWith('.dna')) {
|
|
138
|
+
const success = await loadSnapgeneHistory(files[0]);
|
|
139
|
+
if (success) {
|
|
140
|
+
return;
|
|
153
141
|
}
|
|
154
142
|
}
|
|
143
|
+
try {
|
|
144
|
+
const { sources, sequences, warnings } = await processSequenceFiles(files, backendRoute, httpClient);
|
|
145
|
+
batch(() => {
|
|
146
|
+
// If there is only one source and it is empty, delete it
|
|
147
|
+
if (cloningState.sources.length === 1 && cloningState.sources[0].type === null) {
|
|
148
|
+
dispatch(deleteSourceAndItsChildren(cloningState.sources[0].id));
|
|
149
|
+
}
|
|
150
|
+
for (let i = 0; i < sources.length; i += 1) {
|
|
151
|
+
dispatch(addSourceAndItsOutputSequence({ source: sources[i], sequence: sequences[i] }));
|
|
152
|
+
}
|
|
153
|
+
warnings.forEach((warning) => addAlert({ message: warning, severity: 'warning' }));
|
|
154
|
+
});
|
|
155
|
+
} catch (e) {
|
|
156
|
+
console.error(e);
|
|
157
|
+
addAlert({
|
|
158
|
+
message: e.message,
|
|
159
|
+
severity: 'error',
|
|
160
|
+
});
|
|
161
|
+
}
|
|
155
162
|
};
|
|
156
163
|
processFileSubmission();
|
|
157
164
|
}, [fileList]);
|
|
@@ -16,11 +16,13 @@ function ServerStaticFileSelect({ onFileSelected, multiple = false, type = 'sequ
|
|
|
16
16
|
if (type !== 'sequence') {
|
|
17
17
|
return index.syntaxes;
|
|
18
18
|
}
|
|
19
|
+
const sequencesIdAdded = index.sequences.map((sequence, index) => ({...sequence, id: index}));
|
|
19
20
|
const prePendArray = multiple ? ['__all__'] : [];
|
|
20
21
|
if (selectedCategory === '') {
|
|
21
|
-
return [...prePendArray, ...
|
|
22
|
+
return [...prePendArray, ...sequencesIdAdded];
|
|
22
23
|
}
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
return [...prePendArray, ...sequencesIdAdded.filter((sequence) => sequence.categories?.includes(selectedCategory))];
|
|
24
26
|
}, [type, index, selectedCategory, multiple]);
|
|
25
27
|
|
|
26
28
|
const categoryOptions = React.useMemo(() => {
|
|
@@ -112,6 +114,7 @@ function ServerStaticFileSelect({ onFileSelected, multiple = false, type = 'sequ
|
|
|
112
114
|
options={options}
|
|
113
115
|
value={selectedOptions}
|
|
114
116
|
onChange={onOptionsChange}
|
|
117
|
+
getOptionKey={(option) => option.id || option}
|
|
115
118
|
getOptionLabel={(option) => (option === '__all__' ? 'Select all' : option?.name || option?.path || '')}
|
|
116
119
|
disableCloseOnSelect={multiple}
|
|
117
120
|
renderInput={(params) => (
|
|
@@ -7,6 +7,7 @@ import { cloningActions } from '@opencloning/store/cloning';
|
|
|
7
7
|
import { loadFilesToSessionStorage, loadHistoryFile, updateVerificationFileNames } from '@opencloning/utils/readNwrite';
|
|
8
8
|
import useValidateState from '../../hooks/useValidateState';
|
|
9
9
|
import { mergeStates, getGraftSequenceId, graftState } from '@opencloning/utils/network';
|
|
10
|
+
import useSnapgeneHistoryEndpoint from '../../hooks/useSnapgeneHistoryEndpoint';
|
|
10
11
|
|
|
11
12
|
const { deleteSourceAndItsChildren, restoreSource, setState: setCloningState } = cloningActions;
|
|
12
13
|
|
|
@@ -30,7 +31,7 @@ function SourceFile({ source, requestStatus, sendPostRequest }) {
|
|
|
30
31
|
const dispatch = useDispatch();
|
|
31
32
|
const validateState = useValidateState();
|
|
32
33
|
const store = useStore();
|
|
33
|
-
|
|
34
|
+
const { loadSnapgeneHistory } = useSnapgeneHistoryEndpoint();
|
|
34
35
|
const onChange = async (event) => {
|
|
35
36
|
setAlert(null);
|
|
36
37
|
const files = Array.from(event.target.files);
|
|
@@ -94,6 +95,13 @@ function SourceFile({ source, requestStatus, sendPostRequest }) {
|
|
|
94
95
|
});
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
98
|
+
if (fileFormat === 'snapgene' || (fileFormat === '' && files[0].name.endsWith('.dna'))) {
|
|
99
|
+
console.log('loading snapgene history');
|
|
100
|
+
const success = await loadSnapgeneHistory(files[0], source.id);
|
|
101
|
+
if (success) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
97
105
|
const requestData = new FormData();
|
|
98
106
|
requestData.append('file', files[0]);
|
|
99
107
|
const config = {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import useBackendRoute from './useBackendRoute';
|
|
3
|
+
import useHttpClient from './useHttpClient';
|
|
4
|
+
import useAlerts from './useAlerts';
|
|
5
|
+
import { batch, useDispatch, useStore } from 'react-redux';
|
|
6
|
+
import { mergeStates } from '@opencloning/utils/network';
|
|
7
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
8
|
+
|
|
9
|
+
const { setState: setCloningState, deleteSourceAndItsChildren } = cloningActions;
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Custom React hook that provides a function to load and process a SnapGene .dna file's cloning history
|
|
14
|
+
* via the backend endpoint. If successful, updates the Redux cloning state (merging or replacing as appropriate)
|
|
15
|
+
* and handles warnings. On failure, triggers a user alert.
|
|
16
|
+
*
|
|
17
|
+
* @returns {Object} An object with a single method `loadSnapgeneHistory(file)`:
|
|
18
|
+
* - {Promise<boolean>} loadSnapgeneHistory(file): Attempt to load and merge/replace SnapGene history.
|
|
19
|
+
* Returns true on successfully parsing the SnapGene file, otherwise returns false and triggers a warning alert.
|
|
20
|
+
*/
|
|
21
|
+
export default function useSnapgeneHistoryEndpoint() {
|
|
22
|
+
const backendRoute = useBackendRoute();
|
|
23
|
+
const httpClient = useHttpClient();
|
|
24
|
+
const { addAlert } = useAlerts();
|
|
25
|
+
const dispatch = useDispatch();
|
|
26
|
+
const store = useStore();
|
|
27
|
+
const url = backendRoute('read_snapgene_history');
|
|
28
|
+
|
|
29
|
+
const loadSnapgeneHistory = React.useCallback(async (file, sourceIdToDelete = null) => {
|
|
30
|
+
const cloningState = store.getState().cloning;
|
|
31
|
+
const formData = new FormData();
|
|
32
|
+
formData.append('file', file);
|
|
33
|
+
const config = {
|
|
34
|
+
headers: {
|
|
35
|
+
'content-type': 'multipart/form-data',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
// If it fails to parse the history return false, true otherwise
|
|
39
|
+
try {
|
|
40
|
+
const resp = await httpClient.post(url, formData, config);
|
|
41
|
+
const { data: cloningStrategy } = resp;
|
|
42
|
+
let mergedState;
|
|
43
|
+
if (!(cloningState.sources.length === 1 && cloningState.sources[0].type === null)) {
|
|
44
|
+
({ mergedState } = mergeStates(cloningStrategy, cloningState));
|
|
45
|
+
} else {
|
|
46
|
+
// If there is only one source and it is empty, replace the whole state
|
|
47
|
+
mergedState = cloningStrategy;
|
|
48
|
+
}
|
|
49
|
+
if (resp.headers['x-warning']) {
|
|
50
|
+
addAlert({
|
|
51
|
+
message: `SnapGene parsing warning: ${resp.headers['x-warning']}`,
|
|
52
|
+
severity: 'warning',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
batch(() => {
|
|
56
|
+
dispatch(setCloningState(mergedState));
|
|
57
|
+
if (sourceIdToDelete !== null) {
|
|
58
|
+
console.log('deleting source', sourceIdToDelete);
|
|
59
|
+
dispatch(deleteSourceAndItsChildren(sourceIdToDelete));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
addAlert({
|
|
65
|
+
message: 'Failed to parse the history from the SnapGene file, will only read the sequence.',
|
|
66
|
+
severity: 'warning',
|
|
67
|
+
});
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}, [httpClient, addAlert, dispatch, store, url]);
|
|
71
|
+
return { loadSnapgeneHistory };
|
|
72
|
+
}
|
package/src/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Version placeholder - replaced at publish time via prepack script
|
|
2
|
-
export const version = "1.7.
|
|
2
|
+
export const version = "1.7.3";
|