@opencloning/ui 1.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 (207) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +37 -0
  3. package/src/components/AppAlerts.jsx +19 -0
  4. package/src/components/CloningHistory.jsx +40 -0
  5. package/src/components/DataModelDisplayer.jsx +30 -0
  6. package/src/components/DescriptionEditor.jsx +68 -0
  7. package/src/components/DownloadCloningStrategyDialog.jsx +84 -0
  8. package/src/components/DownloadSequenceFileDialog.jsx +90 -0
  9. package/src/components/DragAndDropCloningHistoryWrapper.jsx +39 -0
  10. package/src/components/DraggableDialogPaper.jsx +16 -0
  11. package/src/components/EditSequenceNameDialog.jsx +90 -0
  12. package/src/components/ExternalServicesStatusCheck.jsx +92 -0
  13. package/src/components/HistoryLoadedDialog.jsx +49 -0
  14. package/src/components/LoadCloningHistoryWrapper.jsx +166 -0
  15. package/src/components/MainSequenceCheckBox.jsx +83 -0
  16. package/src/components/MainSequenceEditor.jsx +165 -0
  17. package/src/components/NetworkNode.jsx +159 -0
  18. package/src/components/NetworkTree.css +127 -0
  19. package/src/components/ObjectTable.jsx +24 -0
  20. package/src/components/OpenCloning.jsx +102 -0
  21. package/src/components/OverhangsDisplay.jsx +25 -0
  22. package/src/components/SequenceEditor.jsx +120 -0
  23. package/src/components/SequenceTab.jsx +14 -0
  24. package/src/components/TemplateSequence.jsx +38 -0
  25. package/src/components/annotation/PlannotateAnnotationReport.jsx +33 -0
  26. package/src/components/annotation/useUpdateAnnotationInMainSequence.js +39 -0
  27. package/src/components/assembler/AssemblePartWidget.jsx +252 -0
  28. package/src/components/assembler/Assembler.jsx +273 -0
  29. package/src/components/assembler/AssemblerPart.jsx +99 -0
  30. package/src/components/assembler/StopIcon.jsx +34 -0
  31. package/src/components/assembler/assembler_data2.json +50 -0
  32. package/src/components/assembler/assembly_component.module.css +81 -0
  33. package/src/components/assembler/moclo.json +110 -0
  34. package/src/components/assembler/sbol_visual_glyphs/LICENSE.html +21 -0
  35. package/src/components/assembler/sbol_visual_glyphs/assembly-scar.svg +63 -0
  36. package/src/components/assembler/sbol_visual_glyphs/cds-stop.svg +85 -0
  37. package/src/components/assembler/sbol_visual_glyphs/cds.svg +60 -0
  38. package/src/components/assembler/sbol_visual_glyphs/chromosomal-locus.svg +78 -0
  39. package/src/components/assembler/sbol_visual_glyphs/engineered-region.svg +56 -0
  40. package/src/components/assembler/sbol_visual_glyphs/five-prime-sticky-restriction-site.svg +56 -0
  41. package/src/components/assembler/sbol_visual_glyphs/origin-of-replication.svg +57 -0
  42. package/src/components/assembler/sbol_visual_glyphs/primer-binding-site.svg +59 -0
  43. package/src/components/assembler/sbol_visual_glyphs/promoter.svg +60 -0
  44. package/src/components/assembler/sbol_visual_glyphs/ribosome-entry-site.svg +56 -0
  45. package/src/components/assembler/sbol_visual_glyphs/specific-recombination-site.svg +59 -0
  46. package/src/components/assembler/sbol_visual_glyphs/terminator.svg +60 -0
  47. package/src/components/assembler/sbol_visual_glyphs/three-prime-sticky-restriction-site.svg +56 -0
  48. package/src/components/assembler/sbol_visual_glyphs.js +36 -0
  49. package/src/components/assembler/useAssembler.js +71 -0
  50. package/src/components/dummy/DummyInterface.js +41 -0
  51. package/src/components/dummy/GetSequenceFileAndDatabaseIdComponent.jsx +59 -0
  52. package/src/components/eLabFTW/ELabFTWCategorySelect.cy.jsx +86 -0
  53. package/src/components/eLabFTW/ELabFTWCategorySelect.jsx +43 -0
  54. package/src/components/eLabFTW/ELabFTWFileSelect.cy.jsx +43 -0
  55. package/src/components/eLabFTW/ELabFTWFileSelect.jsx +29 -0
  56. package/src/components/eLabFTW/ELabFTWResourceSelect.cy.jsx +107 -0
  57. package/src/components/eLabFTW/ELabFTWResourceSelect.jsx +23 -0
  58. package/src/components/eLabFTW/GetPrimerComponent.cy.jsx +261 -0
  59. package/src/components/eLabFTW/GetPrimerComponent.jsx +55 -0
  60. package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.cy.jsx +184 -0
  61. package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.jsx +62 -0
  62. package/src/components/eLabFTW/LoadHistoryComponent.cy.jsx +235 -0
  63. package/src/components/eLabFTW/LoadHistoryComponent.jsx +51 -0
  64. package/src/components/eLabFTW/PrimersNotInDatabaseComponent.cy.jsx +159 -0
  65. package/src/components/eLabFTW/PrimersNotInDatabaseComponent.jsx +54 -0
  66. package/src/components/eLabFTW/SubmitToDatabaseComponent.cy.jsx +185 -0
  67. package/src/components/eLabFTW/SubmitToDatabaseComponent.jsx +51 -0
  68. package/src/components/eLabFTW/common.js +26 -0
  69. package/src/components/eLabFTW/eLabFTWInterface.js +294 -0
  70. package/src/components/eLabFTW/eLabFTWInterface.test.js +839 -0
  71. package/src/components/eLabFTW/envValues.js +7 -0
  72. package/src/components/eLabFTW/utils.js +39 -0
  73. package/src/components/form/CustomFormHelperText.jsx +10 -0
  74. package/src/components/form/EnzymeMultiSelect.cy.jsx +61 -0
  75. package/src/components/form/EnzymeMultiSelect.jsx +34 -0
  76. package/src/components/form/GetRequestMultiSelect.jsx +107 -0
  77. package/src/components/form/LabelWithTooltip.jsx +16 -0
  78. package/src/components/form/PostRequestSelect.cy.jsx +70 -0
  79. package/src/components/form/PostRequestSelect.jsx +86 -0
  80. package/src/components/form/RequestStatusWrapper.jsx +17 -0
  81. package/src/components/form/RetryAlert.jsx +20 -0
  82. package/src/components/form/ServerErrorMessage.jsx +10 -0
  83. package/src/components/form/SubmitButtonBackendAPI.jsx +15 -0
  84. package/src/components/form/SubmitToDatabaseDialog.jsx +133 -0
  85. package/src/components/form/TextFieldValidate.jsx +67 -0
  86. package/src/components/form/ValidatedTextField.jsx +33 -0
  87. package/src/components/form/intermediates_disclaimer.svg +181 -0
  88. package/src/components/navigation/ButtonWithMenu.jsx +43 -0
  89. package/src/components/navigation/CustomTab.jsx +14 -0
  90. package/src/components/navigation/FeedbackDialog.jsx +34 -0
  91. package/src/components/navigation/GithubCornerRight.jsx +29 -0
  92. package/src/components/navigation/MainAppBar.css +26 -0
  93. package/src/components/navigation/MainAppBar.jsx +205 -0
  94. package/src/components/navigation/SelectExampleDialog.jsx +69 -0
  95. package/src/components/navigation/SelectTemplateDialog.jsx +107 -0
  96. package/src/components/navigation/TabPanel.jsx +28 -0
  97. package/src/components/navigation/VersionDialog.jsx +33 -0
  98. package/src/components/primers/CreatePrimerFromSequenceForm.jsx +42 -0
  99. package/src/components/primers/DownloadPrimersButton.jsx +104 -0
  100. package/src/components/primers/PrimerForm.css +14 -0
  101. package/src/components/primers/PrimerForm.jsx +107 -0
  102. package/src/components/primers/PrimerList.css +46 -0
  103. package/src/components/primers/PrimerList.cy.jsx +95 -0
  104. package/src/components/primers/PrimerList.jsx +126 -0
  105. package/src/components/primers/PrimerTableRow.cy.jsx +57 -0
  106. package/src/components/primers/PrimerTableRow.jsx +84 -0
  107. package/src/components/primers/SelectPrimerForm.jsx +66 -0
  108. package/src/components/primers/import_primers/ImportPrimersButton.jsx +101 -0
  109. package/src/components/primers/import_primers/ImportPrimersTable.jsx +51 -0
  110. package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +107 -0
  111. package/src/components/primers/import_primers/styles.css +60 -0
  112. package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +31 -0
  113. package/src/components/primers/primer_design/SequenceTabComponents/GatewayRoiSelect.jsx +164 -0
  114. package/src/components/primers/primer_design/SequenceTabComponents/OrientationPicker.jsx +37 -0
  115. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignContext.jsx +369 -0
  116. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignEBIC.jsx +24 -0
  117. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +29 -0
  118. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGatewayBP.jsx +36 -0
  119. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx +26 -0
  120. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignHomologousRecombination.jsx +32 -0
  121. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignRestriction.jsx +25 -0
  122. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignSimplePair.jsx +25 -0
  123. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignStepper.jsx +53 -0
  124. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesigner.jsx +80 -0
  125. package/src/components/primers/primer_design/SequenceTabComponents/PrimerResultForm.jsx +49 -0
  126. package/src/components/primers/primer_design/SequenceTabComponents/PrimerSettingsForm.jsx +68 -0
  127. package/src/components/primers/primer_design/SequenceTabComponents/PrimerSpacerForm.jsx +84 -0
  128. package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +85 -0
  129. package/src/components/primers/primer_design/SequenceTabComponents/StepNavigation.jsx +48 -0
  130. package/src/components/primers/primer_design/SequenceTabComponents/TabPanelEBICSettings.jsx +216 -0
  131. package/src/components/primers/primer_design/SequenceTabComponents/TabPanelResults.jsx +42 -0
  132. package/src/components/primers/primer_design/SequenceTabComponents/TabPanelSelectRoi.jsx +61 -0
  133. package/src/components/primers/primer_design/SequenceTabComponents/TabPannelSettings.jsx +59 -0
  134. package/src/components/primers/primer_design/SequenceTabComponents/primerDesignMinimalValues.json +5 -0
  135. package/src/components/primers/primer_design/SequenceTabComponents/useEBICPrimerDesignSettings.js +31 -0
  136. package/src/components/primers/primer_design/SequenceTabComponents/useEnzymePrimerDesignSettings.js +57 -0
  137. package/src/components/primers/primer_design/SequenceTabComponents/useGatewayPrimerDesignSettings.js +18 -0
  138. package/src/components/primers/primer_design/SequenceTabComponents/usePrimerDesignSettings.js +31 -0
  139. package/src/components/primers/primer_design/SourceComponents/PrimerDesignGatewayBP.jsx +88 -0
  140. package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.jsx +65 -0
  141. package/src/components/primers/primer_design/SourceComponents/PrimerDesignHomologousRecombination.jsx +84 -0
  142. package/src/components/primers/primer_design/SourceComponents/PrimerDesignSourceForm.jsx +74 -0
  143. package/src/components/primers/primer_design/common/NoAttPSitesError.jsx +31 -0
  144. package/src/components/primers/primer_details/PCRTable.cy.jsx +51 -0
  145. package/src/components/primers/primer_details/PCRTable.jsx +35 -0
  146. package/src/components/primers/primer_details/Primer3Figure.jsx +25 -0
  147. package/src/components/primers/primer_details/PrimerDetailsTds.jsx +39 -0
  148. package/src/components/primers/primer_details/PrimerInfoIcon.cy.jsx +137 -0
  149. package/src/components/primers/primer_details/PrimerInfoIcon.jsx +132 -0
  150. package/src/components/primers/primer_details/TableSection.jsx +17 -0
  151. package/src/components/primers/primer_details/primerDetailsFormatting.js +3 -0
  152. package/src/components/primers/primer_details/useMultiplePrimerDetails.js +29 -0
  153. package/src/components/primers/primer_details/usePCRDetails.js +47 -0
  154. package/src/components/primers/primer_details/usePrimerDetailsEndpoints.js +49 -0
  155. package/src/components/primers/primer_details/useSinglePrimerSequenceDetails.js +25 -0
  156. package/src/components/primers/primersToTabularFile.js +49 -0
  157. package/src/components/primers/primersToTabularFile.test.js +108 -0
  158. package/src/components/settings/SettingsTab.cy.jsx +267 -0
  159. package/src/components/settings/SettingsTab.jsx +170 -0
  160. package/src/components/sources/AssemblyPlanDisplayer.cy.jsx +22 -0
  161. package/src/components/sources/AssemblyPlanDisplayer.jsx +27 -0
  162. package/src/components/sources/CollectionSource.jsx +97 -0
  163. package/src/components/sources/FinishedSource.jsx +397 -0
  164. package/src/components/sources/KnownSourceErrors.jsx +50 -0
  165. package/src/components/sources/MultipleInputsSelector.jsx +63 -0
  166. package/src/components/sources/MultipleOutputsSelector.jsx +63 -0
  167. package/src/components/sources/NewSourceBox.jsx +37 -0
  168. package/src/components/sources/PCRUnitForm.jsx +102 -0
  169. package/src/components/sources/SingleInputSelector.jsx +36 -0
  170. package/src/components/sources/Source.jsx +125 -0
  171. package/src/components/sources/SourceAnnotation.jsx +44 -0
  172. package/src/components/sources/SourceAssembly.jsx +201 -0
  173. package/src/components/sources/SourceBox.css +18 -0
  174. package/src/components/sources/SourceBox.jsx +60 -0
  175. package/src/components/sources/SourceCopySequence.jsx +38 -0
  176. package/src/components/sources/SourceDatabase.jsx +28 -0
  177. package/src/components/sources/SourceFile.jsx +188 -0
  178. package/src/components/sources/SourceGenomeRegion.cy.jsx +131 -0
  179. package/src/components/sources/SourceGenomeRegion.jsx +486 -0
  180. package/src/components/sources/SourceHomologousRecombination.jsx +125 -0
  181. package/src/components/sources/SourceKnownGenomeRegion.jsx +60 -0
  182. package/src/components/sources/SourceManuallyTyped.jsx +116 -0
  183. package/src/components/sources/SourcePCRorHybridization.jsx +165 -0
  184. package/src/components/sources/SourcePolymeraseExtension.jsx +44 -0
  185. package/src/components/sources/SourceRepositoryId.jsx +409 -0
  186. package/src/components/sources/SourceRestriction.jsx +41 -0
  187. package/src/components/sources/SourceReverseComplement.jsx +33 -0
  188. package/src/components/sources/SourceTypeSelector.jsx +94 -0
  189. package/src/components/sources/SubSequenceDisplayer.jsx +70 -0
  190. package/src/components/sources/VerifyDeleteDialog.jsx +23 -0
  191. package/src/components/sources/repositoryMetadata.js +14 -0
  192. package/src/components/verification/LoadFromDatabaseButton.jsx +90 -0
  193. package/src/components/verification/SequencingFileRow.jsx +34 -0
  194. package/src/components/verification/VerificationFileDialog.cy.jsx +176 -0
  195. package/src/components/verification/VerificationFileDialog.jsx +248 -0
  196. package/src/config/defaultMainEditorProps.js +44 -0
  197. package/src/hooks/useAlerts.js +16 -0
  198. package/src/hooks/useBackendAPI.js +51 -0
  199. package/src/hooks/useBackendRoute.js +22 -0
  200. package/src/hooks/useDatabase.js +18 -0
  201. package/src/hooks/useDragAndDropFile.js +31 -0
  202. package/src/hooks/useGatewaySites.js +40 -0
  203. package/src/hooks/useHttpClient.js +12 -0
  204. package/src/hooks/useLoadDatabaseFile.js +108 -0
  205. package/src/hooks/useStoreEditor.js +101 -0
  206. package/src/hooks/useValidateState.js +43 -0
  207. package/vitest.config.js +18 -0
@@ -0,0 +1,22 @@
1
+ import { useSelector } from 'react-redux';
2
+
3
+ export default function useBackendRoute() {
4
+ const configBackendUrl = useSelector((state) => state.cloning.config.backendUrl);
5
+ if (!configBackendUrl) {
6
+ return () => {};
7
+ }
8
+ const backendUrl = configBackendUrl.endsWith('/') ? configBackendUrl : `${configBackendUrl}/`;
9
+
10
+ return function backendRoute(path) {
11
+ if (!backendUrl) {
12
+ throw new Error('Backend URL not set');
13
+ }
14
+ // console.log(new URL('/api', window.location.origin).href);
15
+ // console.log(new URL(import.meta.env.VITE_REACT_APP_BACKEND_URL, window.location.origin).href);
16
+ // This handles both the case where the backend url is absolute and the case where it is relative
17
+ const backendRoot = new URL(backendUrl, window.location.origin).href;
18
+ // Remove trailing slash from path
19
+ const sanitizedPath = path.endsWith('/') ? path.slice(0, -1) : path;
20
+ return new URL(sanitizedPath, backendRoot).href;
21
+ };
22
+ }
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import eLabFTWInterface from '../components/eLabFTW/eLabFTWInterface';
4
+ import dummyInterface from '../components/dummy/DummyInterface';
5
+
6
+ export default function useDatabase() {
7
+ const databaseName = useSelector((state) => state.cloning.config.database);
8
+
9
+ return React.useMemo(() => {
10
+ if (databaseName === 'elabftw') {
11
+ return eLabFTWInterface;
12
+ }
13
+ if (databaseName === 'dummy') {
14
+ return dummyInterface;
15
+ }
16
+ return null;
17
+ }, [databaseName]);
18
+ }
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+
3
+ export default function useDragAndDropFile() {
4
+ const [isDragging, setIsDragging] = React.useState(false);
5
+ const [files, setFiles] = React.useState([]);
6
+
7
+ const handleDragOver = React.useCallback((e) => {
8
+ e.preventDefault();
9
+ e.stopPropagation();
10
+ setIsDragging(true);
11
+ }, []);
12
+
13
+ const handleDragLeave = React.useCallback((e) => {
14
+ e.preventDefault();
15
+ e.stopPropagation();
16
+ setIsDragging(false);
17
+ }, []);
18
+
19
+ const handleDrop = React.useCallback(async (e) => {
20
+ e.preventDefault();
21
+ e.stopPropagation();
22
+ setIsDragging(false);
23
+ setFiles(e.dataTransfer.files);
24
+ }, []);
25
+
26
+ const clearFiles = React.useCallback(() => {
27
+ setFiles([]);
28
+ }, []);
29
+
30
+ return { handleDragLeave, handleDragOver, handleDrop, isDragging, files, clearFiles };
31
+ }
@@ -0,0 +1,40 @@
1
+ import React, { useCallback, useState } from 'react';
2
+ import { useStore } from 'react-redux';
3
+ import error2String from '@opencloning/utils/error2String';
4
+ import { formatGatewaySites } from '@opencloning/store/cloning_utils';
5
+ import useBackendRoute from './useBackendRoute';
6
+ import useHttpClient from './useHttpClient';
7
+
8
+ export default function useGatewaySites({ target, greedy }) {
9
+ const store = useStore();
10
+ const [requestStatus, setRequestStatus] = useState({ status: null, message: '' });
11
+ const [connectAttempt, setConnectAttempt] = useState(0);
12
+ const [sites, setSites] = useState([]);
13
+ const backendRoute = useBackendRoute();
14
+ const httpClient = useHttpClient();
15
+
16
+ const attemptAgain = useCallback(() => {
17
+ setConnectAttempt((p) => p + 1);
18
+ }, []);
19
+
20
+ React.useEffect(() => {
21
+ async function getSitesInTarget() {
22
+ setRequestStatus({ status: 'loading', message: 'loading' });
23
+ const url = backendRoute('annotation/get_gateway_sites');
24
+ const state = store.getState();
25
+ const sequence = state.cloning.sequences.find((seq) => seq.id === target);
26
+ try {
27
+ const { data: donorSites } = await httpClient.post(url, sequence, { params: { greedy } });
28
+ setRequestStatus({ status: 'success', message: '' });
29
+ setSites(formatGatewaySites(donorSites, 'att'));
30
+ } catch (error) {
31
+ setRequestStatus({ status: 'error', message: error2String(error) });
32
+ setSites([]);
33
+ }
34
+ }
35
+ if (target) {
36
+ getSitesInTarget();
37
+ }
38
+ }, [store, connectAttempt, target, greedy]);
39
+ return { requestStatus, attemptAgain, sites };
40
+ }
@@ -0,0 +1,12 @@
1
+ import { useSelector } from 'react-redux';
2
+ import React from 'react';
3
+ import getHttpClient from '@opencloning/utils/getHttpClient';
4
+
5
+ export default function useHttpClient() {
6
+ const backendUrl = useSelector((state) => state.cloning.config.backendUrl);
7
+
8
+ // Memoize the client creation and interceptor setup
9
+ const apiClient = React.useMemo(() => getHttpClient([backendUrl]), [backendUrl]);
10
+
11
+ return apiClient;
12
+ }
@@ -0,0 +1,108 @@
1
+ import { batch, useDispatch, useStore } from 'react-redux';
2
+ import { jsonToGenbank } from '@teselagen/bio-parsers';
3
+ import useValidateState from './useValidateState';
4
+ import { convertToTeselaJson, loadHistoryFile } from '@opencloning/utils/readNwrite';
5
+ import { getIdsOfSequencesWithoutChildSource } from '@opencloning/store/cloning_utils';
6
+ import { mergeStates, graftState } from '@opencloning/utils/network';
7
+ import { cloningActions } from '@opencloning/store/cloning';
8
+ import useDatabase from './useDatabase';
9
+
10
+ const { deleteSourceAndItsChildren, setState: setCloningState } = cloningActions;
11
+
12
+ export default function useLoadDatabaseFile({ source, sendPostRequest, setHistoryFileError }) {
13
+ const dispatch = useDispatch();
14
+ const validateState = useValidateState();
15
+ const store = useStore();
16
+ const database = useDatabase();
17
+
18
+ const loadDatabaseFile = async (file, databaseId, ancestors = false) => {
19
+ if (file.name.endsWith('.json')) {
20
+ let cloningStrategy;
21
+ try {
22
+ ({ cloningStrategy } = await loadHistoryFile(file));
23
+ // If the cloning strategy should end on a single sequence, set the databaseId for the right source
24
+ const terminalSequences = getIdsOfSequencesWithoutChildSource(cloningStrategy.sources, cloningStrategy.sequences);
25
+ if (terminalSequences.length === 1) {
26
+ const lastSource = cloningStrategy.sources.find((s) => s.id === terminalSequences[0]);
27
+ lastSource.database_id = databaseId;
28
+ }
29
+ // When importing sources that had inputs that we don't want to load, we turn them into database sources
30
+ const allSequenceIds = [...cloningStrategy.sequences.map((e) => e.id), ...cloningStrategy.primers.map((e) => e.id)];
31
+ cloningStrategy.sources = cloningStrategy.sources.map((s) => {
32
+ if (s.input.some(({sequence}) => !allSequenceIds.includes(sequence))) {
33
+ return { id: s.id, type: 'DatabaseSource', input: [], database_id: s.database_id };
34
+ }
35
+ return s;
36
+ });
37
+
38
+ // Get primer names (in case they have changed with respect to what was in the file)
39
+ // and verify that the sequence of the primer in the database is the same as the sequence in the cloning strategy
40
+ const primerDatabaseIds = cloningStrategy.primers.filter((p) => p.database_id).map((p) => p.database_id);
41
+ const databasePrimers = await Promise.all(primerDatabaseIds.map(database.getPrimer));
42
+ databasePrimers.forEach((databasePrimer, index) => {
43
+ const primerInCloningStrategy = cloningStrategy.primers.find((p) => p.database_id === databasePrimer.database_id);
44
+ primerInCloningStrategy.name = databasePrimer.name;
45
+ if (primerInCloningStrategy.sequence !== databasePrimer.sequence) {
46
+ throw new Error(`The sequence of primer ${primerInCloningStrategy.name} (${primerInCloningStrategy.database_id}) conflicts with the sequence in the database`);
47
+ }
48
+ });
49
+
50
+ // Get the sequence name from the database and update the cloning strategy with it if it is different
51
+ await Promise.all(cloningStrategy.sources.filter((s) => s.database_id).map(async (cloningSource) => {
52
+ const seqDatabaseId = cloningSource.database_id;
53
+ const sequence = cloningStrategy.sequences.find((e) => e.id === cloningSource.id);
54
+ const seq = convertToTeselaJson(sequence);
55
+ const databaseName = await database.getSequenceName(seqDatabaseId);
56
+ if (seq.name !== databaseName) {
57
+ seq.name = databaseName;
58
+ const genbank = jsonToGenbank(seq);
59
+ sequence.file_content = genbank;
60
+ // Maybe this is unnecessary
61
+ cloningSource.output_name = databaseName;
62
+ }
63
+ }));
64
+ } catch (e) {
65
+ console.error(e);
66
+ setHistoryFileError(e.message);
67
+ return;
68
+ }
69
+ // This one won't have the source.id deleted
70
+ const prevState = store.getState().cloning;
71
+ cloningStrategy = await validateState(cloningStrategy);
72
+
73
+ batch(() => {
74
+ // Replace the source with the new one if called from a source
75
+ if (!ancestors) {
76
+ dispatch(deleteSourceAndItsChildren(source.id));
77
+ }
78
+ const cloningState = store.getState().cloning;
79
+ try {
80
+ let mergedState;
81
+ if (ancestors) {
82
+ ({ mergedState } = graftState(cloningStrategy, cloningState, source.id));
83
+ } else {
84
+ ({ mergedState } = mergeStates(cloningStrategy, cloningState));
85
+ }
86
+ dispatch(setCloningState(mergedState));
87
+ } catch (e) {
88
+ setHistoryFileError(e.message);
89
+ console.error(e);
90
+ if (!ancestors) {
91
+ dispatch(setCloningState(prevState));
92
+ }
93
+ }
94
+ });
95
+ } else {
96
+ const requestData = new FormData();
97
+ requestData.append('file', file);
98
+ const config = {
99
+ headers: {
100
+ 'content-type': 'multipart/form-data',
101
+ },
102
+ };
103
+ const modifySource = (s) => ({ ...s, database_id: databaseId });
104
+ sendPostRequest({ endpoint: 'read_from_file', requestData, config, source, modifySource });
105
+ }
106
+ };
107
+ return { loadDatabaseFile };
108
+ }
@@ -0,0 +1,101 @@
1
+ import { useStore } from 'react-redux';
2
+ import { updateEditor, addAlignment } from '@teselagen/ove';
3
+ import { getPCRPrimers } from '@opencloning/store/cloning_utils';
4
+ import { getTeselaJsonFromBase64 } from '@opencloning/utils/readNwrite';
5
+ import { findRotation, syncChromatogramDataWithAlignment } from '@opencloning/utils/sequenceManipulation';
6
+ import { getReverseComplementSequenceAndAnnotations, getReverseComplementSequenceString, rotateSequenceDataToPosition } from '@teselagen/sequence-utils';
7
+
8
+ export default function useStoreEditor() {
9
+ const store = useStore();
10
+
11
+ const updateStoreEditor = async (editorName, id, selectionLayer = {}) => {
12
+ if (id === null) {
13
+ // if id is null and selectionLayer is empty, clear the sequenceData
14
+ if (Object.keys(selectionLayer).length === 0) {
15
+ updateEditor(store, editorName, { sequenceData: {}, selectionLayer, sequenceDataHistory: {} });
16
+ } else {
17
+ updateEditor(store, editorName, { selectionLayer, sequenceDataHistory: {} });
18
+ }
19
+ } else {
20
+ // otherwise, update the sequenceData with the new id
21
+ const { cloning } = store.getState();
22
+ const { teselaJsonCache } = cloning;
23
+ const sequenceData = { ...teselaJsonCache[id] };
24
+ const sequence = cloning.sequences.find((e) => e.id === id);
25
+ const sequenceWithoutSequencingField = { ...sequence };
26
+ delete sequenceWithoutSequencingField.sequencing;
27
+ const pcrPrimers = getPCRPrimers(cloning, id);
28
+ const alignmentFiles = cloning.files.filter((e) => e.sequence_id === id && e.file_type === 'Sequencing file');
29
+ let { panelsShown } = store.getState().VectorEditor.mainEditor;
30
+ if (alignmentFiles.length > 0) {
31
+ addAlignment(store, {
32
+ id: 'simpleAlignment',
33
+ alignmentType: 'Sequencing alignment',
34
+ name: `Seq. ${id}`,
35
+ // set the visibilities of the annotations you'd like to see
36
+ alignmentAnnotationVisibility: {
37
+ features: true,
38
+ parts: true,
39
+ translations: true,
40
+ },
41
+ alignmentTracks: [
42
+ {
43
+ sequenceData,
44
+ alignmentData: {
45
+ // the alignmentData just needs the sequence < TODO this has to be changed to be the largest ---
46
+ sequence: alignmentFiles[0].alignment[0],
47
+ },
48
+ },
49
+ ...await Promise.all(alignmentFiles.map(async (aln) => {
50
+ const fileContent = await getTeselaJsonFromBase64(sessionStorage.getItem(`verification-${id}-${aln.file_name}`), aln.file_name);
51
+ let chromatogramData = fileContent.chromatogramData;
52
+ let alignmentSequenceData = {
53
+ name: aln.file_name,
54
+ sequence: aln.alignment[1].replaceAll('-', ''),
55
+ };
56
+ if (chromatogramData) {
57
+ chromatogramData = syncChromatogramDataWithAlignment(chromatogramData, aln.alignment[1]);
58
+ }
59
+ if (fileContent.features && fileContent.features.length > 0) {
60
+ alignmentSequenceData = fileContent;
61
+ const alignmentSequence = aln.alignment[1].replaceAll('-', '');
62
+ let rotation = findRotation(fileContent.sequence, alignmentSequence);
63
+ // If the rotation is -1, it may be reverse complemented
64
+ const reverseComplemented = rotation === -1;
65
+ if (reverseComplemented) {
66
+ rotation = findRotation(fileContent.sequence, getReverseComplementSequenceString(alignmentSequence));
67
+ alignmentSequenceData = getReverseComplementSequenceAndAnnotations(fileContent);
68
+ }
69
+ if (rotation !== -1 && rotation !== 0) {
70
+ rotation = reverseComplemented ? fileContent.sequence.length - rotation : rotation;
71
+ alignmentSequenceData = rotateSequenceDataToPosition(alignmentSequenceData, rotation);
72
+ }
73
+ }
74
+ return {
75
+ sequenceData: {...alignmentSequenceData, name: aln.file_name},
76
+ alignmentData: {
77
+ sequence: aln.alignment[1],
78
+ },
79
+ chromatogramData,
80
+ };
81
+ })),
82
+ ],
83
+ });
84
+ panelsShown = [[
85
+ ...panelsShown[0].filter((p) => p.id !== 'simpleAlignment'),
86
+ {
87
+ id: 'simpleAlignment',
88
+ type: 'alignment',
89
+ name: 'Alignments',
90
+ active: true,
91
+ isFullscreen: false,
92
+ },
93
+ ]];
94
+ }
95
+ sequenceData.primers = sequenceData.primers.concat([...pcrPrimers]);
96
+ updateEditor(store, editorName, { sequenceData, selectionLayer, panelsShown, sequenceDataHistory: {} });
97
+ }
98
+ };
99
+
100
+ return { updateStoreEditor };
101
+ }
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import useAlerts from './useAlerts';
3
+ import useBackendRoute from './useBackendRoute';
4
+ import useHttpClient from './useHttpClient';
5
+
6
+ export default function useValidateState() {
7
+ const backendRoute = useBackendRoute();
8
+ const { addAlert } = useAlerts();
9
+ const httpClient = useHttpClient();
10
+
11
+ const validateState = React.useCallback(async (newState) => {
12
+ // Returns null if the state is valid, otherwise the new updated state
13
+ // It also adds alerts
14
+ try {
15
+ const response = await httpClient.post(backendRoute('validate'), newState);
16
+ if (response.data !== null) {
17
+ response.headers['x-warning'].split(';').forEach((warning) => {
18
+ addAlert({
19
+ message: warning,
20
+ severity: 'warning',
21
+ });
22
+ });
23
+ }
24
+ // Either return the updated state or the original state if it was valid
25
+ return response.data || newState;
26
+ } catch (e) {
27
+ if (e.code === 'ERR_NETWORK') {
28
+ addAlert({
29
+ message: 'Cannot connect to backend server to validate the JSON file',
30
+ severity: 'error',
31
+ });
32
+ } else {
33
+ addAlert({
34
+ message: 'Cloning strategy could be loaded, but it is not valid',
35
+ severity: 'warning',
36
+ });
37
+ }
38
+ return newState;
39
+ }
40
+ }, [addAlert, backendRoute]);
41
+
42
+ return validateState;
43
+ }
@@ -0,0 +1,18 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import { resolve } from 'path';
3
+
4
+ export default defineConfig({
5
+ test: {
6
+ globals: true,
7
+ environment: 'jsdom',
8
+ setupFiles: '../../tests/setup.js',
9
+ include: ['src/**/*.{test,spec}.{js,jsx}'],
10
+ },
11
+ resolve: {
12
+ alias: {
13
+ '@opencloning/ui': resolve(__dirname, './src'),
14
+ '@opencloning/store': resolve(__dirname, '../store/src'),
15
+ '@opencloning/utils': resolve(__dirname, '../utils/src/utils'),
16
+ },
17
+ },
18
+ });