@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 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.1",
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.1",
37
- "@opencloning/utils": "1.7.1",
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
- try {
136
- const { sources, sequences, warnings } = await processSequenceFiles(files, backendRoute, httpClient);
137
- batch(() => {
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]);
@@ -39,6 +39,7 @@ export function UploadPlasmidsFromLocalServerButton({ handleFileChange }) {
39
39
  const onFileSelected = React.useCallback(async (files) => {
40
40
  try {
41
41
  await handleFileChange(files)
42
+ setDialogOpen(false)
42
43
  } catch (e) {
43
44
  setError(e.message)
44
45
  }
@@ -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, ...index.sequences];
22
+ return [...prePendArray, ...sequencesIdAdded];
22
23
  }
23
- return [...prePendArray, ...index.sequences.filter((sequence) => sequence.categories?.includes(selectedCategory))];
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.1";
2
+ export const version = "1.7.3";