@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,102 @@
1
+ import { Accordion, AccordionDetails, AccordionSummary, FormControl, InputLabel, ListItemText, MenuItem, Select, Tooltip } from '@mui/material';
2
+ import React from 'react';
3
+ import { useDispatch } from 'react-redux';
4
+ import AddCircleIcon from '@mui/icons-material/AddCircle';
5
+ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
6
+ import CancelIcon from '@mui/icons-material/Cancel';
7
+ import { cloningActions } from '@opencloning/store/cloning';
8
+ import SingleInputSelector from './SingleInputSelector';
9
+ import SelectPrimerForm from '../primers/SelectPrimerForm';
10
+
11
+ function PCRUnitWrapper({ index, children, onDelete }) {
12
+ if (index === null) {
13
+ return (
14
+ <div className="pcr-unit">
15
+ {children}
16
+ </div>
17
+ );
18
+ }
19
+
20
+ return (
21
+ <Accordion className="pcr-unit" defaultExpanded>
22
+ <AccordionSummary
23
+ expandIcon={<ExpandMoreIcon />}
24
+ aria-controls={`${index}-content`}
25
+ id={`${index}-header`}
26
+ sx={{ backgroundColor: 'lightgray' }}
27
+ >
28
+ {(index !== 0) && (
29
+ <Tooltip onClick={onDelete} title="Delete primer pair" arrow placement="left">
30
+ <CancelIcon color="gray" />
31
+ </Tooltip>
32
+ )}
33
+ <ListItemText sx={{ my: 0 }}>
34
+ Primer pair
35
+ {' '}
36
+ {index + 1}
37
+ </ListItemText>
38
+ </AccordionSummary>
39
+ <AccordionDetails sx={{ py: 0, my: 1 }}>
40
+ {children}
41
+ </AccordionDetails>
42
+ </Accordion>
43
+ );
44
+ }
45
+
46
+ function PCRUnitForm({ primers, forwardPrimerId, reversePrimerId, onChangeForward, onChangeReverse, sourceId, sourceInput = [], index = null, deletePrimerPair = null }) {
47
+ const { setCurrentTab, updateSource } = cloningActions;
48
+ const dispatch = useDispatch();
49
+ const goToPrimerTab = () => {
50
+ dispatch(setCurrentTab(1));
51
+ };
52
+
53
+ const updateInput = (value) => {
54
+ if (index !== null) {
55
+ const newInput = [...sourceInput];
56
+ newInput[index] = value;
57
+ dispatch(updateSource({ id: sourceId, input: newInput }));
58
+ }
59
+ };
60
+
61
+ const onDelete = () => {
62
+ if (index !== null) {
63
+ const newInput = [...sourceInput];
64
+ newInput.splice(index, 1);
65
+ deletePrimerPair();
66
+ dispatch(updateSource({ id: sourceId, input: newInput }));
67
+ }
68
+ };
69
+
70
+ return (
71
+ <PCRUnitWrapper index={index} key={index && `pcr-unit-${index}`} onDelete={onDelete}>
72
+ {(index !== null) ? (
73
+ <FormControl fullWidth>
74
+ <SingleInputSelector
75
+ label="Target sequence"
76
+ selectedId={sourceInput[index]?.sequence || ''}
77
+ onChange={(e) => updateInput({ sequence: e.target.value })}
78
+ inputSequenceIds={sourceInput.map(({sequence}) => sequence)}
79
+ disabled={index === 0}
80
+ />
81
+ </FormControl>
82
+ ) : null}
83
+
84
+ <SelectPrimerForm
85
+ primers={primers}
86
+ selected={forwardPrimerId}
87
+ onChange={onChangeForward}
88
+ goToPrimerTab={goToPrimerTab}
89
+ label="Forward primer"
90
+ />
91
+ <SelectPrimerForm
92
+ primers={primers}
93
+ selected={reversePrimerId}
94
+ onChange={onChangeReverse}
95
+ goToPrimerTab={goToPrimerTab}
96
+ label="Reverse primer"
97
+ />
98
+ </PCRUnitWrapper>
99
+ );
100
+ }
101
+
102
+ export default PCRUnitForm;
@@ -0,0 +1,36 @@
1
+ import React from 'react';
2
+ import { shallowEqual, useSelector } from 'react-redux';
3
+ import { FormHelperText, InputLabel, MenuItem, Select } from '@mui/material';
4
+ import { isEqual } from 'lodash-es';
5
+ import { getIdsOfSequencesWithoutChildSource } from '@opencloning/store/cloning_utils';
6
+
7
+ function SingleInputSelector({ selectedId, onChange, label, inputSequenceIds, allowUnset = false, helperText = '', disabled = false }) {
8
+ const idsWithoutChild = useSelector(({ cloning }) => getIdsOfSequencesWithoutChildSource(cloning.sources, cloning.sequences), shallowEqual);
9
+ const options = [...new Set([...idsWithoutChild, ...inputSequenceIds])];
10
+ const sequenceNames = useSelector(({ cloning }) => options.map((id) => ({ id, name: cloning.teselaJsonCache[id]?.name || 'template' })), isEqual);
11
+ const renderedOptions = options.sort((a, b) => (a - b)).map((id) => (
12
+ <MenuItem key={id} value={id}>
13
+ {`${id} - ${sequenceNames.find(({ id: id2 }) => id2 === id).name}`}
14
+ </MenuItem>
15
+ ));
16
+ if (allowUnset) {
17
+ renderedOptions.unshift(<MenuItem key="unset" value=""><em>None</em></MenuItem>);
18
+ }
19
+ return (
20
+ <>
21
+ <InputLabel id="select-single-inputs">{label}</InputLabel>
22
+ <Select
23
+ value={selectedId !== null ? selectedId : ''}
24
+ onChange={onChange}
25
+ labelId="select-single-inputs"
26
+ label={label}
27
+ disabled={disabled}
28
+ >
29
+ {renderedOptions}
30
+ </Select>
31
+ <FormHelperText>{helperText}</FormHelperText>
32
+ </>
33
+ );
34
+ }
35
+
36
+ export default SingleInputSelector;
@@ -0,0 +1,125 @@
1
+ import React from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import { isEqual } from 'lodash-es';
4
+ import SourceFile from './SourceFile';
5
+ import SourceRepositoryId from './SourceRepositoryId';
6
+ import SourceRestriction from './SourceRestriction';
7
+ import SourceAssembly from './SourceAssembly';
8
+ import SourceTypeSelector from './SourceTypeSelector';
9
+ import SourcePCRorHybridization from './SourcePCRorHybridization';
10
+ import SourceHomologousRecombination from './SourceHomologousRecombination';
11
+ import { SourceGenomeRegion } from './SourceGenomeRegion';
12
+ import SourceManuallyTyped from './SourceManuallyTyped';
13
+ import SourceAnnotation from './SourceAnnotation';
14
+ import SourceDatabase from './SourceDatabase';
15
+ import SourcePolymeraseExtension from './SourcePolymeraseExtension';
16
+ import CollectionSource from './CollectionSource';
17
+ import KnownSourceErrors from './KnownSourceErrors';
18
+ import useBackendAPI from '../../hooks/useBackendAPI';
19
+ import MultipleOutputsSelector from './MultipleOutputsSelector';
20
+ import { cloningActions } from '@opencloning/store/cloning';
21
+ import SourceCopySequence from './SourceCopySequence';
22
+ import SourceReverseComplement from './SourceReverseComplement';
23
+ import SourceKnownGenomeRegion from './SourceKnownGenomeRegion';
24
+ import { doesSourceHaveOutput } from '@opencloning/store/cloning_utils';
25
+
26
+ // There are several types of source, this components holds the common part,
27
+ // which for now is a select element to pick which kind of source is created
28
+ function Source({ sourceId }) {
29
+ const source = useSelector((state) => state.cloning.sources.find((s) => s.id === sourceId), isEqual);
30
+ const hasOutput = useSelector((state) => doesSourceHaveOutput(state.cloning, sourceId));
31
+ const { type: sourceType } = source;
32
+ let specificSource = null;
33
+ const templateOnlySources = ['CollectionSource', 'KnownGenomeCoordinatesSource'];
34
+ const knownErrors = useSelector((state) => state.cloning.knownErrors, isEqual);
35
+ const { requestStatus, sendPostRequest, sources, sequences } = useBackendAPI();
36
+ const { addSequenceAndUpdateItsSource, updateSequenceAndItsSource } = cloningActions;
37
+ const [chosenFragment, setChosenFragment] = React.useState(null);
38
+ const dispatch = useDispatch();
39
+
40
+ React.useEffect(() => {
41
+ const dispatchedAction = hasOutput ? updateSequenceAndItsSource : addSequenceAndUpdateItsSource;
42
+ // If there is only a single product, commit the result, else allow choosing via MultipleOutputsSelector
43
+ if (sources.length === 1) {
44
+ dispatch(dispatchedAction({ newSource: { ...sources[0], id: sourceId }, newSequence: sequences[0] }));
45
+ } else if (chosenFragment !== null) {
46
+ dispatch(dispatchedAction({ newSource: { ...sources[chosenFragment], id: sourceId }, newSequence: sequences[chosenFragment] }));
47
+ }
48
+ }, [sources, sequences, chosenFragment]);
49
+
50
+ switch (sourceType) {
51
+ /* eslint-disable */
52
+ case 'UploadedFileSource':
53
+ specificSource = <SourceFile {...{ source, requestStatus, sendPostRequest }} />; break;
54
+ case 'RestrictionEnzymeDigestionSource':
55
+ specificSource = <SourceRestriction {...{ source, requestStatus, sendPostRequest }} />; break;
56
+ case 'RepositoryIdSource':
57
+ specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
58
+ case 'AddgeneIdSource':
59
+ specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
60
+ case 'SnapGenePlasmidSource':
61
+ specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
62
+ case 'EuroscarfSource':
63
+ specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
64
+ case 'WekWikGeneIdSource':
65
+ specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
66
+ case 'OpenDNACollectionsSource':
67
+ specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
68
+ case 'LigationSource':
69
+ specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
70
+ case 'GibsonAssemblySource':
71
+ specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
72
+ case 'OverlapExtensionPCRLigationSource':
73
+ specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
74
+ case 'InFusionSource':
75
+ specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
76
+ case 'InVivoAssemblySource':
77
+ specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
78
+ case 'GatewaySource':
79
+ specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
80
+ case 'CreLoxRecombinationSource':
81
+ specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
82
+ case 'HomologousRecombinationSource':
83
+ specificSource = <SourceHomologousRecombination {...{ source, requestStatus, sendPostRequest }} />; break;
84
+ case 'PCRSource':
85
+ specificSource = <SourcePCRorHybridization {...{ source, requestStatus, sendPostRequest }} />; break;
86
+ case 'RestrictionAndLigationSource':
87
+ specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
88
+ case 'GenomeCoordinatesSource':
89
+ specificSource = <SourceGenomeRegion {...{ source, requestStatus, sendPostRequest }} />; break;
90
+ case 'KnownGenomeCoordinatesSource':
91
+ specificSource = <SourceKnownGenomeRegion {...{ source, requestStatus, sendPostRequest }} />; break;
92
+ case 'ManuallyTypedSource':
93
+ specificSource = <SourceManuallyTyped {...{ source, requestStatus, sendPostRequest }} />; break;
94
+ case 'CRISPRSource':
95
+ specificSource = <SourceHomologousRecombination {...{ source, requestStatus, sendPostRequest }} />; break;
96
+ case 'OligoHybridizationSource':
97
+ specificSource = <SourcePCRorHybridization {...{ source, requestStatus, sendPostRequest }} />; break;
98
+ case 'PolymeraseExtensionSource':
99
+ specificSource = <SourcePolymeraseExtension {...{ source, requestStatus, sendPostRequest }} />; break;
100
+ case 'DatabaseSource':
101
+ specificSource = <SourceDatabase {...{ source, requestStatus, sendPostRequest }} />; break;
102
+ case 'CollectionSource':
103
+ specificSource = <CollectionSource {...{ source, requestStatus, sendPostRequest }} />; break;
104
+ case 'CopySequence':
105
+ specificSource = <SourceCopySequence {...{ source }} />; break;
106
+ case 'AnnotationSource':
107
+ specificSource = <SourceAnnotation {...{ source, requestStatus, sendPostRequest }} />; break;
108
+ case 'ReverseComplementSource':
109
+ specificSource = <SourceReverseComplement {...{ source, requestStatus, sendPostRequest }} />; break;
110
+ default:
111
+ break;
112
+ /* eslint-enable */
113
+ }
114
+
115
+ return (
116
+ <>
117
+ {!templateOnlySources.includes(sourceType) && (<SourceTypeSelector {...{ source }} />)}
118
+ {sourceType && knownErrors[sourceType] && <KnownSourceErrors errors={knownErrors[sourceType]} />}
119
+ {specificSource}
120
+ {sources.length > 1 && (<MultipleOutputsSelector {...{ sources, sequences, sourceId, onFragmentChosen: setChosenFragment }} />)}
121
+ </>
122
+ );
123
+ }
124
+
125
+ export default React.memo(Source);
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { FormControl, InputLabel, MenuItem, Select } from '@mui/material';
3
+ import { useSelector } from 'react-redux';
4
+ import { isEqual } from 'lodash-es';
5
+ import { getInputSequencesFromSourceId } from '@opencloning/store/cloning_utils';
6
+ import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
7
+
8
+ function SourceAnnotation({ source, requestStatus, sendPostRequest }) {
9
+ const [annotationTool, setAnnotationTool] = React.useState('plannotate');
10
+
11
+ const inputSequences = useSelector((state) => getInputSequencesFromSourceId(state, source.id), isEqual);
12
+ const onSubmit = (event) => {
13
+ event.preventDefault();
14
+
15
+ const requestData = {
16
+ sequence: inputSequences[0],
17
+ source: { id: source.id, input: inputSequences.map((e) => ({ sequence: e.id })), annotation_tool: annotationTool },
18
+ };
19
+ sendPostRequest({ endpoint: 'annotate/plannotate', requestData, source });
20
+ };
21
+
22
+ return (
23
+ <form onSubmit={onSubmit}>
24
+ <FormControl fullWidth>
25
+ <InputLabel id="annotation-tool-label">Annotation Tool</InputLabel>
26
+ <Select
27
+ labelId="annotation-tool-label"
28
+ id="annotation-tool"
29
+ value={annotationTool}
30
+ label="Annotation Tool"
31
+ onChange={(e) => setAnnotationTool(e.target.value)}
32
+ >
33
+ <MenuItem value="plannotate">pLannotate</MenuItem>
34
+ </Select>
35
+ </FormControl>
36
+ <SubmitButtonBackendAPI
37
+ requestStatus={requestStatus}
38
+ {...(import.meta.env.VITE_UMAMI_WEBSITE_ID && { "data-umami-event": "submit-annotation" })}
39
+ >Annotate</SubmitButtonBackendAPI>
40
+ </form>
41
+ );
42
+ }
43
+
44
+ export default React.memo(SourceAnnotation);
@@ -0,0 +1,201 @@
1
+ import React from 'react';
2
+ import { useSelector, shallowEqual, useDispatch } from 'react-redux';
3
+ import { Checkbox, FormControlLabel, InputLabel, MenuItem, Select, TextField, FormControl, InputAdornment } from '@mui/material';
4
+ import MultipleInputsSelector from './MultipleInputsSelector';
5
+ import { getInputSequencesFromSourceId } from '@opencloning/store/cloning_utils';
6
+ import EnzymeMultiSelect from '../form/EnzymeMultiSelect';
7
+ import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
8
+ import { classNameToEndPointMap } from '@opencloning/utils/sourceFunctions';
9
+ import { cloningActions } from '@opencloning/store/cloning';
10
+ import LabelWithTooltip from '../form/LabelWithTooltip';
11
+
12
+ const helpSingleSite = 'Even if input sequences contain multiple att sites '
13
+ + '(typically 2), a product could be generated where only one site recombines. '
14
+ + 'Select this option to get those products.';
15
+
16
+ // A component representing the ligation or gibson assembly of several fragments
17
+ function SourceAssembly({ source, requestStatus, sendPostRequest }) {
18
+ const assemblyType = source.type;
19
+ const { id: sourceId, input: sourceInput } = source;
20
+ const inputSequences = useSelector((state) => getInputSequencesFromSourceId(state, sourceId), shallowEqual);
21
+ const inputContainsTemplates = inputSequences.some((sequence) => sequence.type === 'TemplateSequence');
22
+ const [minimalHomology, setMinimalHomology] = React.useState(20);
23
+ const [allowPartialOverlap, setAllowPartialOverlap] = React.useState(false);
24
+ const [circularOnly, setCircularOnly] = React.useState(false);
25
+ const [bluntLigation, setBluntLigation] = React.useState(false);
26
+ const [gatewaySettings, setGatewaySettings] = React.useState({ greedy: false, reactionType: null, onlyMultiSite: true });
27
+ const [enzymes, setEnzymes] = React.useState([]);
28
+
29
+ const dispatch = useDispatch();
30
+
31
+ React.useEffect(() => {
32
+ if (assemblyType === 'GatewaySource') {
33
+ setCircularOnly(true);
34
+ }
35
+ }, [assemblyType]);
36
+
37
+ React.useEffect(() => {
38
+ if (assemblyType === 'GatewaySource' && source.reaction_type) {
39
+ setGatewaySettings({ ...gatewaySettings, reactionType: source.reaction_type });
40
+ }
41
+ }, [source]);
42
+
43
+ const { updateSource } = cloningActions;
44
+
45
+ const preventSubmit = (
46
+ (assemblyType === 'RestrictionAndLigationSource' && enzymes.length === 0)
47
+ || (assemblyType === 'GatewaySource' && gatewaySettings.reactionType === null)
48
+ || inputContainsTemplates
49
+ );
50
+
51
+ const flipAllowPartialOverlap = () => {
52
+ setAllowPartialOverlap(!allowPartialOverlap);
53
+ if (!allowPartialOverlap) {
54
+ setBluntLigation(false);
55
+ }
56
+ };
57
+
58
+ const flipBluntLigation = () => {
59
+ setBluntLigation(!bluntLigation);
60
+ if (!bluntLigation) {
61
+ setAllowPartialOverlap(false);
62
+ }
63
+ };
64
+
65
+ const onSubmit = (event) => {
66
+ event.preventDefault();
67
+ const requestData = {
68
+ source: { id: sourceId, input: inputSequences.map((e) => ({ sequence: e.id })), output_name: source.output_name },
69
+ sequences: inputSequences,
70
+ };
71
+ if (['GibsonAssemblySource', 'OverlapExtensionPCRLigationSource', 'InFusionSource', 'InVivoAssemblySource'].includes(assemblyType)) {
72
+ const config = { params: {
73
+ minimal_homology: minimalHomology,
74
+ circular_only: circularOnly,
75
+ } };
76
+ // To instantiate the correct class on the backend
77
+ requestData.source.type = assemblyType;
78
+ sendPostRequest({ endpoint: 'gibson_assembly', requestData, config, source });
79
+ } else if (assemblyType === 'RestrictionAndLigationSource') {
80
+ if (enzymes.length === 0) { return; }
81
+ requestData.source.restriction_enzymes = enzymes;
82
+ const config = { params: {
83
+ allow_partial_overlap: allowPartialOverlap,
84
+ circular_only: circularOnly,
85
+ } };
86
+ sendPostRequest({ endpoint: 'restriction_and_ligation', requestData, config, source });
87
+ } else if (assemblyType === 'GatewaySource') {
88
+ requestData.source.greedy = gatewaySettings.greedy;
89
+ requestData.source.reaction_type = gatewaySettings.reactionType;
90
+ const config = { params: { circular_only: circularOnly, only_multi_site: gatewaySettings.onlyMultiSite } };
91
+ sendPostRequest({ endpoint: 'gateway', requestData, config, source });
92
+ } else if (assemblyType === 'CreLoxRecombinationSource') {
93
+ sendPostRequest({ endpoint: 'cre_lox_recombination', requestData, source });
94
+ } else {
95
+ const config = { params: {
96
+ allow_partial_overlap: allowPartialOverlap,
97
+ circular_only: circularOnly,
98
+ blunt: bluntLigation,
99
+ } };
100
+ sendPostRequest({ endpoint: classNameToEndPointMap[assemblyType], requestData, config, source });
101
+ }
102
+ };
103
+
104
+ const onChangeInput = (newInputSequenceIds) => {
105
+ const newInput = newInputSequenceIds.map((id) => ({ sequence: id }));
106
+ // We prevent setting empty input
107
+ if (newInput.length === 0) {
108
+ return;
109
+ }
110
+ dispatch(updateSource({ id: sourceId, input: newInput, type: assemblyType }));
111
+ };
112
+
113
+ return (
114
+ <div className="assembly">
115
+ <form onSubmit={onSubmit}>
116
+ <FormControl fullWidth>
117
+ <MultipleInputsSelector {...{
118
+ inputSequenceIds: sourceInput.map(({sequence}) => sequence), onChange: onChangeInput, label: 'Assembly inputs',
119
+ }}
120
+ />
121
+ </FormControl>
122
+ { ['GibsonAssemblySource', 'OverlapExtensionPCRLigationSource', 'InFusionSource', 'InVivoAssemblySource'].includes(assemblyType) && (
123
+ // I don't really understand why fullWidth is required here
124
+ <FormControl fullWidth>
125
+ <TextField
126
+ label="Minimal homology length"
127
+ value={minimalHomology}
128
+ onChange={(e) => { setMinimalHomology(e.target.value); }}
129
+ type="number"
130
+ defaultValue={20}
131
+ InputProps={{
132
+ endAdornment: <InputAdornment position="end">bp</InputAdornment>,
133
+ sx: { '& input': { textAlign: 'center' } },
134
+ }}
135
+ />
136
+ </FormControl>
137
+ )}
138
+ { (assemblyType === 'RestrictionAndLigationSource') && (
139
+ <EnzymeMultiSelect setEnzymes={setEnzymes} />
140
+ )}
141
+ { (assemblyType === 'GatewaySource') && (
142
+ <>
143
+ <FormControl fullWidth>
144
+ <InputLabel id="gateway-reaction-type-label">Reaction type</InputLabel>
145
+ <Select
146
+ labelId="gateway-reaction-type-label"
147
+ id="gateway-reaction-type"
148
+ value={gatewaySettings.reactionType || ''}
149
+ onChange={(e) => setGatewaySettings({ ...gatewaySettings, reactionType: e.target.value })}
150
+ label="Reaction type"
151
+ >
152
+ <MenuItem value="BP">BP</MenuItem>
153
+ <MenuItem value="LR">LR</MenuItem>
154
+ </Select>
155
+ </FormControl>
156
+ <FormControl fullWidth>
157
+ <FormControlLabel
158
+ control={<Checkbox checked={gatewaySettings.greedy} onChange={() => setGatewaySettings({ ...gatewaySettings, greedy: !gatewaySettings.greedy })} />}
159
+ label={(
160
+ <LabelWithTooltip label="Greedy attP finder" tooltip="Use a more greedy consensus site to find attP sites (might give false positives)" />
161
+ )}
162
+ />
163
+ </FormControl>
164
+ <FormControl fullWidth>
165
+ <FormControlLabel
166
+ control={<Checkbox checked={!gatewaySettings.onlyMultiSite} onChange={() => setGatewaySettings({ ...gatewaySettings, onlyMultiSite: !gatewaySettings.onlyMultiSite })} />}
167
+ label={(
168
+ <LabelWithTooltip label="Single-site recombination" tooltip={helpSingleSite} />
169
+ )}
170
+ />
171
+ </FormControl>
172
+ </>
173
+ )}
174
+ { ['RestrictionAndLigationSource', 'GibsonAssemblySource', 'LigationSource', 'OverlapExtensionPCRLigationSource', 'GatewaySource', 'InFusionSource', 'InVivoAssemblySource'].includes(assemblyType) && (
175
+ <FormControl fullWidth style={{ textAlign: 'left' }}>
176
+ <FormControlLabel control={<Checkbox checked={circularOnly} onChange={() => setCircularOnly(!circularOnly)} />} label="Circular assemblies only" />
177
+ </FormControl>
178
+ )}
179
+ { ['RestrictionAndLigationSource', 'LigationSource'].includes(assemblyType) && (
180
+ <FormControl fullWidth style={{ textAlign: 'left' }}>
181
+ <FormControlLabel control={<Checkbox checked={allowPartialOverlap} onChange={flipAllowPartialOverlap} />} label="Allow partial overlaps" />
182
+ </FormControl>
183
+ )}
184
+ { (assemblyType === 'LigationSource') && (
185
+ <FormControl fullWidth style={{ textAlign: 'left' }}>
186
+ <FormControlLabel control={<Checkbox checked={bluntLigation} onChange={flipBluntLigation} />} label="Blunt ligation" />
187
+ </FormControl>
188
+ )}
189
+
190
+ {!preventSubmit && (
191
+ <SubmitButtonBackendAPI
192
+ requestStatus={requestStatus}
193
+ {...(import.meta.env.VITE_UMAMI_WEBSITE_ID && { "data-umami-event": `submit-assembly-${assemblyType}` })}
194
+ >Submit</SubmitButtonBackendAPI>
195
+ )}
196
+ </form>
197
+ </div>
198
+ );
199
+ }
200
+
201
+ export default SourceAssembly;
@@ -0,0 +1,18 @@
1
+ div.icon-corner {
2
+ position: absolute;
3
+ right: 0px;
4
+ top: 0px;
5
+ margin-left: .3em;
6
+ margin-bottom: .3em;
7
+ }
8
+
9
+ div.icon-corner button{
10
+ padding: 0;
11
+ border: none;
12
+ background: none;
13
+ color:#d32f2f;
14
+ }
15
+
16
+ div.icon-corner button:hover {
17
+ filter: brightness(70%);
18
+ }
@@ -0,0 +1,60 @@
1
+ import React, { useState } from 'react';
2
+ import { useDispatch, useStore } from 'react-redux';
3
+ import Tooltip from '@mui/material/Tooltip';
4
+ import DeleteIcon from '@mui/icons-material/Delete';
5
+ import { cloningActions } from '@opencloning/store/cloning';
6
+ import './SourceBox.css';
7
+ import VerifyDeleteDialog from './VerifyDeleteDialog';
8
+ import useStoreEditor from '../../hooks/useStoreEditor';
9
+ import { isSequenceInputOfAnySource } from '@opencloning/store/cloning_utils';
10
+
11
+ const { deleteSourceAndItsChildren, setMainSequenceId } = cloningActions;
12
+
13
+ function SourceBox({ children, sourceId }) {
14
+ const dispatch = useDispatch();
15
+ const [dialogOpen, setDialogOpen] = useState(false);
16
+ const store = useStore();
17
+ const { updateStoreEditor } = useStoreEditor();
18
+
19
+ const tooltipText = <div className="tooltip-text">Delete source and children</div>;
20
+
21
+ const deleteSource = () => {
22
+ const { mainSequenceId, sources } = store.getState().cloning;
23
+ const source = sources.find((s) => s.id === sourceId);
24
+ dispatch(deleteSourceAndItsChildren(sourceId));
25
+ if (mainSequenceId && mainSequenceId === sourceId) {
26
+ updateStoreEditor('mainEditor', null);
27
+ dispatch(setMainSequenceId(null));
28
+ }
29
+ };
30
+ const onClickDeleteSource = () => {
31
+ const state = store.getState().cloning;
32
+ if (isSequenceInputOfAnySource(sourceId, state.sources)) {
33
+ setDialogOpen(true);
34
+ } else {
35
+ deleteSource();
36
+ }
37
+ };
38
+ return (
39
+ <div className="select-source">
40
+ <div className="icon-corner">
41
+ <Tooltip title={tooltipText} arrow placement="top">
42
+ <button type="submit" onClick={onClickDeleteSource}>
43
+ <DeleteIcon sx={{ fontSize: '2em' }} />
44
+ </button>
45
+ </Tooltip>
46
+ </div>
47
+ <VerifyDeleteDialog
48
+ dialogOpen={dialogOpen}
49
+ setDialogOpen={setDialogOpen}
50
+ onClickDelete={() => {
51
+ deleteSource();
52
+ setDialogOpen(false);
53
+ }}
54
+ />
55
+ {children}
56
+ </div>
57
+ );
58
+ }
59
+
60
+ export default React.memo(SourceBox);
@@ -0,0 +1,38 @@
1
+ import { Button, FormControl } from '@mui/material';
2
+ import React from 'react';
3
+ import { batch, shallowEqual, useDispatch, useSelector } from 'react-redux';
4
+ import SingleInputSelector from './SingleInputSelector';
5
+ import { CopySequenceThunk } from '@opencloning/utils/thunks';
6
+ import { cloningActions } from '@opencloning/store/cloning';
7
+
8
+ const { deleteSourceAndItsChildren } = cloningActions;
9
+
10
+ function SourceCopySequence({ source }) {
11
+ const [id, setId] = React.useState(null);
12
+ const allSequenceIds = useSelector((state) => state.cloning.sequences.map((sequence) => sequence.id), shallowEqual);
13
+ const dispatch = useDispatch();
14
+
15
+ const onSubmit = (e) => {
16
+ e.preventDefault();
17
+ batch(() => {
18
+ dispatch(deleteSourceAndItsChildren(source.id));
19
+ dispatch(CopySequenceThunk(id, source.id));
20
+ });
21
+ };
22
+
23
+ return (
24
+ <form onSubmit={onSubmit}>
25
+ <FormControl fullWidth>
26
+ <SingleInputSelector
27
+ label="Sequence to copy"
28
+ selectedId={id}
29
+ onChange={(e) => setId(e.target.value)}
30
+ inputSequenceIds={allSequenceIds}
31
+ />
32
+ </FormControl>
33
+ <Button type="submit" variant="contained" style={{ marginTop: 15 }}>Copy sequence</Button>
34
+ </form>
35
+ );
36
+ }
37
+
38
+ export default SourceCopySequence;
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { Alert } from '@mui/material';
3
+ import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
4
+ import useDatabase from '../../hooks/useDatabase';
5
+ import useLoadDatabaseFile from '../../hooks/useLoadDatabaseFile';
6
+
7
+ function SourceDatabase({ source, requestStatus, sendPostRequest }) {
8
+ const [file, setFile] = React.useState(null);
9
+ const [databaseId, setDatabaseId] = React.useState(null);
10
+ const database = useDatabase();
11
+ const [historyFileError, setHistoryFileError] = React.useState(null);
12
+ const { loadDatabaseFile } = useLoadDatabaseFile({ source, sendPostRequest, setHistoryFileError });
13
+
14
+ const onSubmit = async (e) => {
15
+ e.preventDefault();
16
+ loadDatabaseFile(file, databaseId);
17
+ };
18
+
19
+ return (
20
+ <form onSubmit={onSubmit}>
21
+ {database && <database.GetSequenceFileAndDatabaseIdComponent setFile={setFile} setDatabaseId={setDatabaseId} />}
22
+ {historyFileError && <Alert severity="error">{historyFileError}</Alert>}
23
+ {file && databaseId && <SubmitButtonBackendAPI requestStatus={requestStatus}>Submit </SubmitButtonBackendAPI>}
24
+ </form>
25
+ );
26
+ }
27
+
28
+ export default SourceDatabase;