@opencloning/ui 1.9.1 → 2.0.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/package.json +5 -4
  3. package/src/components/CloningSpeedDial.jsx +3 -5
  4. package/src/components/EditSequenceNameDialog.jsx +3 -5
  5. package/src/components/ExternalServicesStatusCheck.jsx +3 -5
  6. package/src/components/LoadCloningHistoryWrapper.jsx +21 -46
  7. package/src/components/SequenceViewer.jsx +12 -0
  8. package/src/components/assembler/Assembler.jsx +4 -6
  9. package/src/components/assembler/ExistingSyntaxDialog.jsx +2 -2
  10. package/src/components/assembler/useAssembler.js +6 -8
  11. package/src/components/domesticate/DomesticateDialog.jsx +3 -5
  12. package/src/components/form/EnzymeMultiSelect.jsx +3 -5
  13. package/src/components/form/TextFieldQueryValidated.jsx +22 -4
  14. package/src/components/navigation/MainAppBar.jsx +26 -20
  15. package/src/components/navigation/SelectTemplateDialog.jsx +4 -2
  16. package/src/components/primers/primer_design/SequenceTabComponents/hooks/useDesignPrimers.js +4 -6
  17. package/src/components/primers/primer_details/usePrimerDetailsEndpoints.js +4 -6
  18. package/src/components/sources/CollectionSource.jsx +2 -2
  19. package/src/components/sources/Source.jsx +32 -32
  20. package/src/components/sources/SourceAnnotation.jsx +2 -2
  21. package/src/components/sources/SourceAssembly.jsx +7 -7
  22. package/src/components/sources/SourceDatabase.jsx +2 -2
  23. package/src/components/sources/SourceFile.jsx +21 -51
  24. package/src/components/sources/SourceGenomeRegion.cy.jsx +46 -0
  25. package/src/components/sources/SourceGenomeRegion.jsx +29 -15
  26. package/src/components/sources/SourceHomologousRecombination.jsx +3 -3
  27. package/src/components/sources/SourceKnownGenomeRegion.jsx +3 -3
  28. package/src/components/sources/SourceManuallyTyped.jsx +2 -2
  29. package/src/components/sources/SourcePCRorHybridization.jsx +3 -3
  30. package/src/components/sources/SourcePolymeraseExtension.jsx +2 -2
  31. package/src/components/sources/SourceRepositoryId.jsx +4 -4
  32. package/src/components/sources/SourceRestriction.jsx +2 -2
  33. package/src/components/sources/SourceReverseComplement.jsx +2 -2
  34. package/src/components/sources/SourceServerStaticFile.jsx +3 -3
  35. package/src/components/verification/VerificationFileDialog.cy.jsx +17 -21
  36. package/src/components/verification/VerificationFileDialog.jsx +12 -11
  37. package/src/hooks/index.js +1 -0
  38. package/src/hooks/useBackendHttpClient.js +17 -0
  39. package/src/hooks/useCloningHistoryLoader.js +161 -0
  40. package/src/hooks/useGatewaySites.js +3 -5
  41. package/src/hooks/useInitializeApp.js +3 -4
  42. package/src/hooks/useLoadDatabaseFile.js +17 -34
  43. package/src/hooks/useSequencingAlignment.js +4 -6
  44. package/src/hooks/useServerStaticFiles.js +2 -2
  45. package/src/hooks/{useBackendAPI.js → useSourceRequest.js} +6 -8
  46. package/src/hooks/useStoreEditor.js +2 -1
  47. package/src/hooks/useUrlParamsLoader.js +29 -23
  48. package/src/hooks/useValidateState.js +4 -6
  49. package/src/version.js +1 -1
  50. package/src/hooks/useBackendRoute.js +0 -23
  51. package/src/hooks/useHttpClient.js +0 -20
  52. package/src/hooks/useSnapgeneHistoryEndpoint.js +0 -72
@@ -1,25 +1,22 @@
1
- import { batch, useDispatch, useStore } from 'react-redux';
2
1
  import { jsonToGenbank } from '@teselagen/bio-parsers';
3
2
  import useValidateState from './useValidateState';
4
- import { convertToTeselaJson, loadHistoryFile } from '@opencloning/utils/readNwrite';
3
+ import { convertToTeselaJson, parseHistoryFile } from '@opencloning/utils/readNwrite';
5
4
  import { getIdsOfSequencesWithoutChildSource } from '@opencloning/store/cloning_utils';
6
- import { mergeStates, graftState } from '@opencloning/utils/network';
7
- import { cloningActions } from '@opencloning/store/cloning';
5
+ import useCloningHistoryLoader from './useCloningHistoryLoader';
8
6
  import useDatabase from './useDatabase';
7
+ import { useStore } from 'react-redux';
9
8
 
10
- const { deleteSourceAndItsChildren, setState: setCloningState } = cloningActions;
11
-
12
- export default function useLoadDatabaseFile({ source, sendPostRequest, setHistoryFileError }) {
13
- const dispatch = useDispatch();
9
+ export default function useLoadDatabaseFile({ source, sendSourceRequest, setHistoryFileError }) {
14
10
  const validateState = useValidateState();
15
11
  const store = useStore();
16
12
  const database = useDatabase();
13
+ const { applyCloningStrategy } = useCloningHistoryLoader();
17
14
 
18
15
  const loadDatabaseFile = async (file, databaseId, ancestors = false) => {
19
16
  if (file.name.endsWith('.json')) {
20
17
  let cloningStrategy;
21
18
  try {
22
- ({ cloningStrategy } = await loadHistoryFile(file));
19
+ ({ cloningStrategy } = await parseHistoryFile(file));
23
20
  // If the cloning strategy should end on a single sequence, set the databaseId for the right source
24
21
  const terminalSequences = getIdsOfSequencesWithoutChildSource(cloningStrategy.sources, cloningStrategy.sequences);
25
22
  if (terminalSequences.length === 1) {
@@ -29,7 +26,7 @@ export default function useLoadDatabaseFile({ source, sendPostRequest, setHistor
29
26
  // When importing sources that had inputs that we don't want to load, we turn them into database sources
30
27
  const allSequenceIds = [...cloningStrategy.sequences.map((e) => e.id), ...cloningStrategy.primers.map((e) => e.id)];
31
28
  cloningStrategy.sources = cloningStrategy.sources.map((s) => {
32
- if (s.input.some(({sequence}) => !allSequenceIds.includes(sequence))) {
29
+ if (s.input.some(({ sequence }) => !allSequenceIds.includes(sequence))) {
33
30
  return { id: s.id, type: 'DatabaseSource', input: [], database_id: s.database_id };
34
31
  }
35
32
  return s;
@@ -66,7 +63,6 @@ export default function useLoadDatabaseFile({ source, sendPostRequest, setHistor
66
63
  setHistoryFileError(e.message);
67
64
  return;
68
65
  }
69
- // This one won't have the source.id deleted
70
66
  const prevState = store.getState().cloning;
71
67
  const { backendVersion, schemaVersion, frontendVersion } = prevState.appInfo;
72
68
  cloningStrategy.backend_version = backendVersion;
@@ -74,28 +70,15 @@ export default function useLoadDatabaseFile({ source, sendPostRequest, setHistor
74
70
  cloningStrategy.frontend_version = frontendVersion;
75
71
  cloningStrategy = await validateState(cloningStrategy);
76
72
 
77
- batch(() => {
78
- // Replace the source with the new one if called from a source
79
- if (!ancestors && source) {
80
- dispatch(deleteSourceAndItsChildren(source.id));
81
- }
82
- const cloningState = store.getState().cloning;
83
- try {
84
- let mergedState;
85
- if (ancestors) {
86
- ({ mergedState } = graftState(cloningStrategy, cloningState, source.id));
87
- } else {
88
- ({ mergedState } = mergeStates(cloningStrategy, cloningState));
89
- }
90
- dispatch(setCloningState(mergedState));
91
- } catch (e) {
92
- setHistoryFileError(e.message);
93
- console.error(e);
94
- if (!ancestors) {
95
- dispatch(setCloningState(prevState));
96
- }
97
- }
98
- });
73
+ try {
74
+ await applyCloningStrategy(cloningStrategy, {
75
+ mode: ancestors ? 'graft' : 'merge',
76
+ source,
77
+ onError: setHistoryFileError,
78
+ });
79
+ } catch {
80
+ // Error already reported via onError; state restored in applyCloningStrategy when applicable
81
+ }
99
82
  } else {
100
83
  const requestData = new FormData();
101
84
  requestData.append('file', file);
@@ -105,7 +88,7 @@ export default function useLoadDatabaseFile({ source, sendPostRequest, setHistor
105
88
  },
106
89
  };
107
90
  const modifySource = (s) => ({ ...s, database_id: databaseId });
108
- sendPostRequest({ endpoint: 'read_from_file', requestData, config, source, modifySource });
91
+ sendSourceRequest({ endpoint: 'read_from_file', requestData, config, source, modifySource });
109
92
  }
110
93
  };
111
94
  return { loadDatabaseFile };
@@ -1,17 +1,15 @@
1
1
  import { useCallback } from 'react';
2
2
  import { useMutation } from '@tanstack/react-query';
3
3
  import { buildAlignmentTrack, buildReferenceTrack, buildAlignmentConfig } from '@opencloning/utils/alignmentUtils';
4
- import useBackendRoute from './useBackendRoute';
5
- import useHttpClient from './useHttpClient';
4
+ import useBackendHttpClient from './useBackendHttpClient';
6
5
 
7
6
  export default function useSequencingAlignment({ onError } = {}) {
8
- const backendRoute = useBackendRoute();
9
- const httpClient = useHttpClient();
7
+ const httpClient = useBackendHttpClient();
10
8
 
11
9
  const fetchAlignments = useCallback(async ({ sequenceModel, sequenceData, parsedFiles }) => {
12
10
  // parsedFiles: [{ fileName, fileContent }] where fileContent is getTeselaJsonFromBase64 output
13
11
  const traces = parsedFiles.map((f) => f.fileContent.sequence);
14
- const resp = await httpClient.post(backendRoute('align_sanger'), { sequence: sequenceModel, traces });
12
+ const resp = await httpClient.post('align_sanger', { sequence: sequenceModel, traces });
15
13
 
16
14
  const referenceTrack = buildReferenceTrack(sequenceData, resp.data[0]);
17
15
  const otherTracks = parsedFiles.map((f, i) => {
@@ -20,7 +18,7 @@ export default function useSequencingAlignment({ onError } = {}) {
20
18
  });
21
19
 
22
20
  return buildAlignmentConfig(sequenceModel.id, sequenceData, [referenceTrack, ...otherTracks]);
23
- }, [httpClient, backendRoute]);
21
+ }, [httpClient]);
24
22
 
25
23
  return useMutation({ mutationFn: fetchAlignments, onError });
26
24
  }
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
- import getHttpClient from '@opencloning/utils/getHttpClient';
2
+ import { getExternalHttpClient } from '@opencloning/utils/httpClients';
3
3
  import { useConfig } from '../providers';
4
4
  import useRequestForEffect from './useRequestForEffect';
5
5
 
6
6
 
7
- export const localFilesHttpClient = getHttpClient([]);
7
+ export const localFilesHttpClient = getExternalHttpClient();
8
8
 
9
9
  function sequencesToCategories(sequences) {
10
10
  const allCategories = sequences
@@ -1,22 +1,20 @@
1
1
  import { useState, useCallback } from 'react';
2
2
  import error2String from '@opencloning/utils/error2String';
3
- import useBackendRoute from './useBackendRoute';
4
3
  import useCloningAlerts from './useCloningAlerts';
5
- import useHttpClient from './useHttpClient';
4
+ import useBackendHttpClient from './useBackendHttpClient';
6
5
 
7
- export default function useBackendAPI() {
6
+ export default function useSourceRequest() {
8
7
  const [requestStatus, setRequestStatus] = useState({ status: null, message: '' });
9
8
  const [sources, setSources] = useState([]);
10
9
  const [sequences, setSequences] = useState([]);
11
- const backendRoute = useBackendRoute();
12
- const httpClient = useHttpClient();
10
+ const httpClient = useBackendHttpClient();
13
11
  const { addAlert } = useCloningAlerts();
14
12
 
15
- const sendPostRequest = useCallback(async ({ endpoint, requestData, config = {}, source: { output }, modifySource = (s) => s }) => {
13
+ const sendSourceRequest = useCallback(async ({ endpoint, requestData, config = {}, source: { output }, modifySource = (s) => s }) => {
16
14
  setRequestStatus({ status: 'loading', message: 'loading' });
17
15
 
18
16
  // Url built like this in case trailing slash
19
- const url = backendRoute(endpoint);
17
+ const url = endpoint;
20
18
  // paramsSerializer: { indexes: null } is to correctly serialize arrays in the URL
21
19
  const fullConfig = { ...config, paramsSerializer: { indexes: null } };
22
20
  try {
@@ -50,5 +48,5 @@ export default function useBackendAPI() {
50
48
  }
51
49
  }, []);
52
50
 
53
- return { requestStatus, sources, sequences, sendPostRequest };
51
+ return { requestStatus, sources, sequences, sendSourceRequest };
54
52
  }
@@ -2,6 +2,7 @@ import { useStore } from 'react-redux';
2
2
  import { updateEditor, addAlignment } from '@teselagen/ove';
3
3
  import { getPCRPrimers } from '@opencloning/store/cloning_utils';
4
4
  import { getTeselaJsonFromBase64 } from '@opencloning/utils/readNwrite';
5
+ import { getVerificationFileContent } from '@opencloning/utils/verificationFileStore';
5
6
  import { buildAlignmentTrack, buildReferenceTrack, buildAlignmentConfig, removePanelFromShown, updatePanelsShownWithAlignment } from '@opencloning/utils/alignmentUtils';
6
7
 
7
8
  export default function useStoreEditor() {
@@ -31,7 +32,7 @@ export default function useStoreEditor() {
31
32
  if (alignmentFiles.length > 0) {
32
33
  const referenceTrack = buildReferenceTrack(sequenceData, alignmentFiles[0].alignment[0]);
33
34
  const otherTracks = await Promise.all(alignmentFiles.map(async (aln) => {
34
- const fileContent = await getTeselaJsonFromBase64(sessionStorage.getItem(`verification-${id}-${aln.file_name}`), aln.file_name);
35
+ const fileContent = await getTeselaJsonFromBase64(getVerificationFileContent(`verification-${id}-${aln.file_name}`), aln.file_name);
35
36
  return buildAlignmentTrack(fileContent, aln);
36
37
  }));
37
38
  const alignmentConfig = buildAlignmentConfig(id, sequenceData, [referenceTrack, ...otherTracks]);
@@ -4,12 +4,17 @@ import { cloningActions } from '@opencloning/store/cloning';
4
4
  import useDatabase from './useDatabase';
5
5
  import useLoadDatabaseFile from './useLoadDatabaseFile';
6
6
  import useCloningAlerts from './useCloningAlerts';
7
- import useHttpClient from './useHttpClient';
8
7
  import useValidateState from './useValidateState';
8
+ import useCloningHistoryLoader from './useCloningHistoryLoader';
9
9
  import { formatSequenceLocationString, getUrlParameters } from '@opencloning/utils/other';
10
- import { formatTemplate, loadHistoryFile, loadFilesToSessionStorage } from '@opencloning/utils/readNwrite';
10
+ import { formatTemplate } from '@opencloning/utils/readNwrite';
11
+ import { encodePathSegment } from '@opencloning/utils/urlUtils';
12
+ import { getExternalHttpClient } from '@opencloning/utils/httpClients';
11
13
 
12
- const { setState: setCloningState, updateSource } = cloningActions;
14
+ // For public urls (not the backend)
15
+ const httpClient = getExternalHttpClient();
16
+
17
+ const { updateSource } = cloningActions;
13
18
 
14
19
  /**
15
20
  * Hook to load sequences from URL parameters
@@ -20,9 +25,9 @@ export default function useUrlParamsLoader() {
20
25
  const database = useDatabase();
21
26
  const { addAlert } = useCloningAlerts();
22
27
  const setHistoryFileError = (e) => addAlert({ message: e, severity: 'error' });
23
- const { loadDatabaseFile } = useLoadDatabaseFile({ source: { id: 1 }, sendPostRequest: null, setHistoryFileError });
28
+ const { loadDatabaseFile } = useLoadDatabaseFile({ source: { id: 1 }, sendSourceRequest: null, setHistoryFileError });
24
29
  const validateState = useValidateState();
25
- const httpClient = useHttpClient();
30
+ const { applyCloningStrategy, loadHistoryFromFile } = useCloningHistoryLoader();
26
31
 
27
32
  useEffect(() => {
28
33
  async function loadSequenceFromUrlParams() {
@@ -44,23 +49,25 @@ export default function useUrlParamsLoader() {
44
49
  }
45
50
  } else if (urlParams.source === 'example' && urlParams.example) {
46
51
  try {
47
- const url = `${import.meta.env.BASE_URL}examples/${urlParams.example}`;
48
- let data;
49
- if (urlParams.example.endsWith('.zip')) {
50
- // For zip files, get as blob and process with loadHistoryFile
51
- const { data: blob } = await httpClient.get(url, { responseType: 'blob' });
52
- const fileName = urlParams.example;
53
- // eslint-disable-next-line no-undef
54
- const file = new File([blob], fileName);
55
- const { cloningStrategy, verificationFiles } = await loadHistoryFile(file);
56
- data = await validateState(cloningStrategy);
57
- await loadFilesToSessionStorage(verificationFiles, 0);
52
+ const url = `${import.meta.env.BASE_URL}examples/${encodePathSegment(urlParams.example)}`;
53
+ const fileName = urlParams.example;
54
+ if (fileName.endsWith('.zip')) {
55
+ const resp = await httpClient.get(url, { responseType: 'blob' });
56
+ if (resp.headers['content-type'] !== 'application/zip') {
57
+ throw new Error('Not found');
58
+ }
59
+ const file = new File([resp.data], fileName);
60
+ const prepared = await loadHistoryFromFile(file, { onError: setHistoryFileError });
61
+ await prepared?.replace();
58
62
  } else {
59
- // For JSON files, get as JSON
60
- const { data: jsonData } = await httpClient.get(url);
61
- data = await validateState(jsonData);
63
+ const resp = await httpClient.get(url);
64
+ if (resp.headers['content-type'] !== 'application/json') {
65
+ throw new Error('Not found');
66
+ }
67
+ const file = new File([JSON.stringify(resp.data)], fileName);
68
+ const prepared = await loadHistoryFromFile(file, { onError: setHistoryFileError });
69
+ await prepared?.replace();
62
70
  }
63
- dispatch(setCloningState(data));
64
71
  } catch (error) {
65
72
  addAlert({
66
73
  message: 'Error loading example',
@@ -71,12 +78,12 @@ export default function useUrlParamsLoader() {
71
78
  } else if (urlParams.source === 'template' && urlParams.template && urlParams.key) {
72
79
  try {
73
80
  const baseUrl = 'https://assets.opencloning.org/OpenCloning-submission';
74
- const url = `${baseUrl}/processed/${urlParams.key}/templates/${urlParams.template}`;
81
+ const url = `${baseUrl}/processed/${encodePathSegment(urlParams.key)}/templates/${encodePathSegment(urlParams.template)}`;
75
82
  const { data } = await httpClient.get(url);
76
83
  const validatedData = await validateState(data);
77
84
  const newState = formatTemplate(validatedData, url);
78
85
 
79
- dispatch(setCloningState(newState));
86
+ await applyCloningStrategy(newState, { mode: 'replace' });
80
87
  } catch (error) {
81
88
  addAlert({
82
89
  message: 'Error loading template',
@@ -146,4 +153,3 @@ export default function useUrlParamsLoader() {
146
153
  // eslint-disable-next-line react-hooks/exhaustive-deps
147
154
  }, []);
148
155
  }
149
-
@@ -1,18 +1,16 @@
1
1
  import React from 'react';
2
2
  import useCloningAlerts from './useCloningAlerts';
3
- import useBackendRoute from './useBackendRoute';
4
- import useHttpClient from './useHttpClient';
3
+ import useBackendHttpClient from './useBackendHttpClient';
5
4
 
6
5
  export default function useValidateState() {
7
- const backendRoute = useBackendRoute();
8
6
  const { addAlert } = useCloningAlerts();
9
- const httpClient = useHttpClient();
7
+ const httpClient = useBackendHttpClient();
10
8
 
11
9
  const validateState = React.useCallback(async (newState) => {
12
10
  // Returns null if the state is valid, otherwise the new updated state
13
11
  // It also adds alerts
14
12
  try {
15
- const response = await httpClient.post(backendRoute('validate'), newState);
13
+ const response = await httpClient.post('validate', newState);
16
14
  if (response.data !== null) {
17
15
  response.headers['x-warning'].split(';').forEach((warning) => {
18
16
  addAlert({
@@ -37,7 +35,7 @@ export default function useValidateState() {
37
35
  }
38
36
  return newState;
39
37
  }
40
- }, [addAlert, backendRoute, httpClient]);
38
+ }, [addAlert, httpClient]);
41
39
 
42
40
  return validateState;
43
41
  }
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.9.1";
2
+ export const version = "2.0.0";
@@ -1,23 +0,0 @@
1
- import { useConfig } from './useConfig';
2
-
3
- export default function useBackendRoute() {
4
- const { backendUrl: configBackendUrl } = useConfig();
5
-
6
- if (!configBackendUrl) {
7
- return () => {};
8
- }
9
- const backendUrl = configBackendUrl.endsWith('/') ? configBackendUrl : `${configBackendUrl}/`;
10
-
11
- return function backendRoute(path) {
12
- if (!backendUrl) {
13
- throw new Error('Backend URL not set');
14
- }
15
- // console.log(new URL('/api', window.location.origin).href);
16
- // console.log(new URL(import.meta.env.VITE_REACT_APP_BACKEND_URL, window.location.origin).href);
17
- // This handles both the case where the backend url is absolute and the case where it is relative
18
- const backendRoot = new URL(backendUrl, window.location.origin).href;
19
- // Remove trailing slash from path
20
- const sanitizedPath = path.endsWith('/') ? path.slice(0, -1) : path;
21
- return new URL(sanitizedPath, backendRoot).href;
22
- };
23
- }
@@ -1,20 +0,0 @@
1
- import React from 'react';
2
- import getHttpClient, { getAuthenticatedHttpClient } from '@opencloning/utils/getHttpClient';
3
- import { useConfig } from './useConfig';
4
-
5
- export default function useHttpClient() {
6
- const { backendUrl, requiresBackendAuth = false } = useConfig();
7
-
8
- // Memoize the client creation and interceptor setup
9
- const apiClient = React.useMemo(() => {
10
- const createHttpClient = requiresBackendAuth ? getAuthenticatedHttpClient : getHttpClient;
11
-
12
- if (!backendUrl) {
13
- // Return a client without backend URL if config not loaded yet
14
- return createHttpClient([]);
15
- }
16
- return createHttpClient([backendUrl]);
17
- }, [backendUrl, requiresBackendAuth]);
18
-
19
- return apiClient;
20
- }
@@ -1,72 +0,0 @@
1
- import React from 'react';
2
- import useBackendRoute from './useBackendRoute';
3
- import useHttpClient from './useHttpClient';
4
- import useCloningAlerts from './useCloningAlerts';
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 } = useCloningAlerts();
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: `Not all SnapGene history was parsed: ${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
- }