@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,216 @@
1
+ import React from 'react';
2
+ import { Alert, Box, FormControl, FormLabel, InputAdornment, TextField } from '@mui/material';
3
+ import TabPanel from '../../../navigation/TabPanel';
4
+ import { usePrimerDesign } from './PrimerDesignContext';
5
+ import StepNavigation from './StepNavigation';
6
+ import { useSelector } from 'react-redux';
7
+ import EnzymeMultiSelect from '../../../form/EnzymeMultiSelect';
8
+ import { isEqual } from 'lodash-es';
9
+ import { getSequenceWithinRange } from '@teselagen/range-utils';
10
+ import { aliasedEnzymesByName, cutSequenceByRestrictionEnzyme } from '@teselagen/sequence-utils';
11
+
12
+ function trimPadding({ templateSequence, padding_left, padding_right, restrictionSitesToAvoid, roi, max_inside, max_outside }) {
13
+ const { start, end } = roi.selectionLayer;
14
+ const leftAnnotationRange = { start: start - padding_left, end: start - 1 };
15
+ const leftArm = getSequenceWithinRange(leftAnnotationRange, templateSequence.sequence);
16
+ const rightAnnotationRange = { start: end + 1, end: end + padding_right };
17
+ const rightArm = getSequenceWithinRange(rightAnnotationRange, templateSequence.sequence);
18
+
19
+ const leftMargin = { start: start - max_outside, end: start + max_inside - 1 };
20
+ const rightMargin = { start: end - max_inside, end: end + max_outside - 1 };
21
+ const leftMarginArm = getSequenceWithinRange(leftMargin, templateSequence.sequence);
22
+ const rightMarginArm = getSequenceWithinRange(rightMargin, templateSequence.sequence);
23
+
24
+ const enzymes = restrictionSitesToAvoid.map((enzyme) => aliasedEnzymesByName[enzyme.toLowerCase()]);
25
+ if (enzymes.length === 0) {
26
+ return { padding_left, padding_right, cutsitesInMargins: false };
27
+ }
28
+
29
+ const cutsInLeftMargin = enzymes.flatMap((enzyme) => cutSequenceByRestrictionEnzyme(
30
+ leftMarginArm,
31
+ true,
32
+ enzyme
33
+ ));
34
+ const cutsInRightMargin = enzymes.flatMap((enzyme) => cutSequenceByRestrictionEnzyme(
35
+ rightMarginArm,
36
+ false,
37
+ enzyme
38
+ ));
39
+
40
+ const cutsitesInMargins = cutsInLeftMargin.length > 0 || cutsInRightMargin.length > 0;
41
+
42
+ const leftCutsites = enzymes.flatMap((enzyme) => cutSequenceByRestrictionEnzyme(
43
+ leftArm,
44
+ true,
45
+ enzyme
46
+ ));
47
+ const rightCutsites = enzymes.flatMap((enzyme) => cutSequenceByRestrictionEnzyme(
48
+ rightArm,
49
+ false,
50
+ enzyme
51
+ ));
52
+
53
+ let paddingLeft = padding_left;
54
+ let paddingRight = padding_right;
55
+ if (leftCutsites.length > 0) {
56
+ paddingLeft = leftArm.length - 1 - Math.max(...leftCutsites.map((cutsite) => cutsite.recognitionSiteRange.end));
57
+ }
58
+ if (rightCutsites.length > 0) {
59
+ paddingRight = Math.min(...rightCutsites.map((cutsite) => cutsite.recognitionSiteRange.start));
60
+ }
61
+ return {
62
+ padding_left: paddingLeft,
63
+ padding_right: paddingRight,
64
+ cutsitesInMargins,
65
+ };
66
+
67
+ }
68
+
69
+ function TabPanelEBICSettings() {
70
+ const { error, selectedTab, sequenceIds, primers, submissionPreventedMessage, designPrimers, primerDesignSettings, rois } = usePrimerDesign();
71
+ const { max_inside, max_outside, target_tm, target_tm_tolerance, updateSettings, restrictionSitesToAvoid, padding_left, padding_right } = primerDesignSettings;
72
+ const [cutsitesInMarginsError, setCutsitesInMarginsError] = React.useState(false);
73
+
74
+ const templateSequence = useSelector((state) => state.cloning.teselaJsonCache[sequenceIds[0]], isEqual);
75
+
76
+ React.useEffect(() => {
77
+ if (rois.length > 0 && rois[0] !== null) {
78
+ const { padding_left: newPaddingLeft, padding_right: newPaddingRight, cutsitesInMargins } = trimPadding({
79
+ templateSequence,
80
+ padding_left,
81
+ padding_right,
82
+ restrictionSitesToAvoid,
83
+ roi: rois[0],
84
+ max_inside,
85
+ max_outside,
86
+ });
87
+ updateSettings({ padding_left: newPaddingLeft, padding_right: newPaddingRight });
88
+ setCutsitesInMarginsError(cutsitesInMargins);
89
+ }
90
+ }, [templateSequence, restrictionSitesToAvoid, rois, max_inside, max_outside, padding_left, padding_right]);
91
+
92
+ return (
93
+ <TabPanel value={selectedTab} index={sequenceIds.length}>
94
+ <Box sx={{ width: '80%', margin: 'auto' }}>
95
+ <Box sx={{ pt: 1 }}>
96
+ <FormLabel>Primer settings</FormLabel>
97
+ <Box sx={{ pt: 1.5 }}>
98
+
99
+ <Box>
100
+ <FormControl sx={{ mr: 2 }}>
101
+ <TextField
102
+ label="Max inside"
103
+ value={max_inside}
104
+ onChange={(e) => { updateSettings({ max_inside: Number(e.target.value) }); }}
105
+ type="number"
106
+ InputProps={{
107
+ endAdornment: <InputAdornment position="end">bp</InputAdornment>,
108
+ sx: { width: '10em' },
109
+ }}
110
+ error={max_inside < 0}
111
+ helperText={max_inside < 0 ? 'Max inside must be greater than 0' : ''}
112
+ />
113
+ </FormControl>
114
+
115
+ <FormControl sx={{ mr: 2 }}>
116
+ <TextField
117
+ label="Max outside"
118
+ value={max_outside}
119
+ onChange={(e) => { updateSettings({ max_outside: Number(e.target.value) }); }}
120
+ type="number"
121
+ InputProps={{
122
+ endAdornment: <InputAdornment position="end">bp</InputAdornment>,
123
+ sx: { width: '10em' },
124
+ }}
125
+ error={max_outside < 0}
126
+ helperText={max_outside < 0 ? 'Max outside must be greater than 0' : ''}
127
+ />
128
+ </FormControl>
129
+ </Box>
130
+
131
+ <Box sx={{ mt: 2 }}>
132
+ <FormControl sx={{ mr: 2 }}>
133
+ <TextField
134
+ label="Target Tm"
135
+ value={target_tm}
136
+ onChange={(e) => { updateSettings({ target_tm: Number(e.target.value) }); }}
137
+ type="number"
138
+ InputProps={{
139
+ endAdornment: <InputAdornment position="end">°C</InputAdornment>,
140
+ sx: { width: '10em' },
141
+ }}
142
+ />
143
+ </FormControl>
144
+
145
+ <FormControl sx={{ mr: 2 }}>
146
+ <TextField
147
+ label="Tm tolerance"
148
+ value={target_tm_tolerance}
149
+ onChange={(e) => { updateSettings({ target_tm_tolerance: Number(e.target.value) }); }}
150
+ type="number"
151
+ InputProps={{
152
+ endAdornment: <InputAdornment position="end">°C</InputAdornment>,
153
+ sx: { width: '10em' },
154
+ }}
155
+ />
156
+ </FormControl>
157
+
158
+ </Box>
159
+ <Box sx={{ mt: 2 }}>
160
+ <FormControl sx={{ mr: 2 }}>
161
+ <TextField
162
+ label="Padding left"
163
+ value={padding_left}
164
+ onChange={(e) => { updateSettings({ padding_left: Number(e.target.value) }); }}
165
+ type="number"
166
+ InputProps={{
167
+ endAdornment: <InputAdornment position="end">bp</InputAdornment>,
168
+ sx: { width: '10em' },
169
+ }}
170
+ />
171
+ </FormControl>
172
+ <FormControl sx={{ mr: 2 }}>
173
+ <TextField
174
+ label="Padding right"
175
+ value={padding_right}
176
+ onChange={(e) => { updateSettings({ padding_right: Number(e.target.value) }); }}
177
+ type="number"
178
+ InputProps={{
179
+ endAdornment: <InputAdornment position="end">bp</InputAdornment>,
180
+ sx: { width: '10em' },
181
+ }}
182
+ />
183
+ </FormControl>
184
+ </Box>
185
+ <Box sx={{ mt: 2 }}>
186
+ <FormControl sx={{ mr: 2, width: '15em' }}>
187
+ <EnzymeMultiSelect
188
+ value={restrictionSitesToAvoid}
189
+ setEnzymes={(v) => updateSettings({ restrictionSitesToAvoid: v })}
190
+ label="Sites to avoid"
191
+ multiple={true}
192
+ />
193
+ </FormControl>
194
+
195
+ </Box>
196
+ </Box>
197
+ </Box>
198
+ </Box>
199
+ {error && <Alert severity="error" sx={{ width: 'fit-content', margin: 'auto', mt: 2 }}>{error}</Alert>}
200
+ {cutsitesInMarginsError && (
201
+ <Alert severity="error" sx={{ width: 'fit-content', margin: 'auto', mt: 2 }}>
202
+ Restriction enzyme cut sites were detected in the margin regions. Please adjust the margin size or select different restriction sites to avoid this issue.
203
+ </Alert>
204
+ )}
205
+ <StepNavigation
206
+ onStepCompletion={designPrimers}
207
+ stepCompletionText="Design primers"
208
+ nextDisabled={primers.length === 0}
209
+ stepCompletionToolTip={submissionPreventedMessage}
210
+ allowStepCompletion={submissionPreventedMessage === ''}
211
+ />
212
+ </TabPanel>
213
+ );
214
+ }
215
+
216
+ export default TabPanelEBICSettings;
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { shallowEqual, useSelector } from 'react-redux';
3
+ import { Box } from '@mui/material';
4
+ import TabPanel from '../../../navigation/TabPanel';
5
+ import { usePrimerDesign } from './PrimerDesignContext';
6
+ import StepNavigation from './StepNavigation';
7
+ import PrimerResultForm from './PrimerResultForm';
8
+
9
+ function TabPanelResults() {
10
+ const { selectedTab, primers, addPrimers, setPrimers, handleBack, sequenceIds } = usePrimerDesign();
11
+ const existingPrimerNames = useSelector((state) => state.cloning.primers.map((p) => p.name), shallowEqual);
12
+ const primersAreValid = primers.length && primers.every((primer) => primer.name && !existingPrimerNames.includes(primer.name));
13
+ return (
14
+ <TabPanel value={selectedTab} index={sequenceIds.length + 1}>
15
+ <Box sx={{ marginTop: 3 }}>
16
+ {primers.map((primer, index) => (
17
+ <PrimerResultForm
18
+ key={index}
19
+ updatePrimerName={(newName) => {
20
+ const updatedPrimers = [...primers];
21
+ updatedPrimers[index] = { ...primer, name: newName };
22
+ setPrimers(updatedPrimers);
23
+ }}
24
+ primer={primer}
25
+ existingPrimerNames={existingPrimerNames}
26
+ />
27
+ ))}
28
+ <StepNavigation
29
+ handleBack={handleBack}
30
+ handleNext={null}
31
+ onStepCompletion={addPrimers}
32
+ stepCompletionText="Save primers"
33
+ nextDisabled
34
+ stepCompletionToolTip="Primers are not valid"
35
+ allowStepCompletion={primersAreValid}
36
+ />
37
+ </Box>
38
+ </TabPanel>
39
+ );
40
+ }
41
+
42
+ export default TabPanelResults;
@@ -0,0 +1,61 @@
1
+ import React from 'react';
2
+ import { Alert, FormControl, TextField } from '@mui/material';
3
+ import { useSelector, useStore } from 'react-redux';
4
+
5
+ import StepNavigation from './StepNavigation';
6
+ import { selectedRegion2String } from '@opencloning/utils/selectedRegionUtils';
7
+ import GatewayRoiSelect from './GatewayRoiSelect';
8
+ import TabPanel from '../../../navigation/TabPanel';
9
+ import { usePrimerDesign } from './PrimerDesignContext';
10
+
11
+ function TabPanelSelectRoi({ step, index }) {
12
+ const { selectedTab, rois, handleSelectRegion, sequenceIds, primerDesignSettings, designType } = usePrimerDesign();
13
+ const [error, setError] = React.useState('');
14
+ const editorHasSelection = useSelector((state) => state.cloning.mainSequenceSelection.caretPosition !== undefined);
15
+ const store = useStore();
16
+ const id = sequenceIds[index];
17
+ const {
18
+ description = `Select the fragment of sequence ${id} to be amplified in the editor and click "Choose region"`,
19
+ inputLabel = `Amplified region (sequence ${id})`,
20
+ allowSinglePosition = false,
21
+ stepCompletionToolTip = 'Select a region in the editor',
22
+ } = step;
23
+
24
+ const mode = designType === 'gateway_bp' && index === 1 ? 'gateway_bp' : 'editor';
25
+ const allowStepCompletion = (mode === 'editor' && editorHasSelection) || (mode === 'gateway_bp' && primerDesignSettings.knownCombination);
26
+ const onStepCompletion = () => {
27
+ const selectedRegion = store.getState().cloning.mainSequenceSelection;
28
+ setError(handleSelectRegion(index, selectedRegion, allowSinglePosition));
29
+ };
30
+
31
+ return (
32
+ <TabPanel value={selectedTab} index={index} className={`select-roi-tab-${index}`}>
33
+ <Alert severity="info">{description}</Alert>
34
+ {error && (<Alert severity="error">{error}</Alert>)}
35
+ {mode === 'editor' && (
36
+ <FormControl sx={{ py: 2 }}>
37
+ <TextField
38
+ label={inputLabel}
39
+ value={selectedRegion2String(rois[index])}
40
+ disabled
41
+ />
42
+ </FormControl>
43
+ )}
44
+ {mode === 'gateway_bp' && (
45
+ <GatewayRoiSelect id={id} />
46
+ )}
47
+ <StepNavigation
48
+ isFirstStep={index === 0}
49
+ nextDisabled={(index === sequenceIds.length - 1) && rois.some((region) => region === null)}
50
+ nextToolTip="You must select all regions before proceeding"
51
+ allowStepCompletion={allowStepCompletion}
52
+ stepCompletionText="Choose region"
53
+ stepCompletionToolTip={stepCompletionToolTip}
54
+ onStepCompletion={onStepCompletion}
55
+ />
56
+
57
+ </TabPanel>
58
+ );
59
+ }
60
+
61
+ export default TabPanelSelectRoi;
@@ -0,0 +1,59 @@
1
+ import React from 'react';
2
+ import { Alert, Box, Checkbox, FormControl, FormControlLabel, FormLabel } from '@mui/material';
3
+ import StepNavigation from './StepNavigation';
4
+ import TabPanel from '../../../navigation/TabPanel';
5
+ import PrimerSettingsForm from './PrimerSettingsForm';
6
+ import PrimerSpacerForm from './PrimerSpacerForm';
7
+ import OrientationPicker from './OrientationPicker';
8
+ import { usePrimerDesign } from './PrimerDesignContext';
9
+ import RestrictionSpacerForm from './RestrictionSpacerForm';
10
+
11
+ function TabPannelSettings() {
12
+ const { error, templateSequenceIds, designType, selectedTab, sequenceIds, circularAssembly, onCircularAssemblyChange, designPrimers, primers, primerDesignSettings, submissionPreventedMessage } = usePrimerDesign();
13
+ return (
14
+ <TabPanel value={selectedTab} index={sequenceIds.length}>
15
+ <Box sx={{ width: '80%', margin: 'auto' }}>
16
+ <PrimerSettingsForm {...primerDesignSettings} />
17
+ <Box sx={{ mt: 2 }}>
18
+ {designType === 'gibson_assembly' && <FormLabel>Fragment orientation</FormLabel>}
19
+ {templateSequenceIds.map((id, index) => (
20
+ <OrientationPicker
21
+ key={id}
22
+ id={id}
23
+ index={index}
24
+ />
25
+ ))}
26
+ </Box>
27
+ {designType === 'restriction_ligation' && <RestrictionSpacerForm />}
28
+ <PrimerSpacerForm />
29
+ {designType === 'gibson_assembly' && (
30
+ <Box sx={{ display: 'flex', justifyContent: 'center', width: '100%' }}>
31
+ <FormControl>
32
+ <FormControlLabel
33
+ control={(
34
+ <Checkbox
35
+ checked={circularAssembly}
36
+ onChange={onCircularAssemblyChange}
37
+ name="circular-assembly"
38
+ />
39
+ )}
40
+ label="Circular assembly"
41
+ />
42
+ </FormControl>
43
+ </Box>
44
+ )}
45
+
46
+ </Box>
47
+ {error && (<Alert severity="error" sx={{ width: 'fit-content', margin: 'auto', mb: 2 }}>{error}</Alert>)}
48
+ <StepNavigation
49
+ onStepCompletion={designPrimers}
50
+ stepCompletionText="Design primers"
51
+ nextDisabled={primers.length === 0}
52
+ stepCompletionToolTip={submissionPreventedMessage}
53
+ allowStepCompletion={submissionPreventedMessage === ''}
54
+ />
55
+ </TabPanel>
56
+ );
57
+ }
58
+
59
+ export default TabPannelSettings;
@@ -0,0 +1,5 @@
1
+ {
2
+ "homology_length": 5,
3
+ "hybridization_length": 5,
4
+ "target_tm": 30
5
+ }
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+
3
+ const getError = (s) => {
4
+ if (s.max_inside < 0) {
5
+ return 'Max inside must be greater than 0';
6
+ }
7
+ if (s.max_outside < 0) {
8
+ return 'Max outside must be greater than 0';
9
+ }
10
+ return '';
11
+ };
12
+
13
+ export default function useEBICPrimerDesignSettings() {
14
+ const [settings, setSettings] = React.useState({
15
+ max_inside: 50,
16
+ max_outside: 20,
17
+ target_tm: 61,
18
+ target_tm_tolerance: 3,
19
+ restrictionSitesToAvoid: [],
20
+ padding_left: 1000,
21
+ padding_right: 1000,
22
+ });
23
+ const [error, setError] = React.useState(getError(settings));
24
+ const updateSettings = (newSettings) => {
25
+ setSettings((prev) => ({ ...prev, ...newSettings }));
26
+ };
27
+ React.useEffect(() => {
28
+ setError(getError(settings));
29
+ }, [settings]);
30
+ return { ...settings, error, updateSettings };
31
+ }
@@ -0,0 +1,57 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { getReverseComplementSequenceString as reverseComplement } from '@teselagen/sequence-utils';
3
+ import { getEnzymeRecognitionSequence } from '@opencloning/utils/enzyme_utils';
4
+ import { stringIsNotDNA } from '@opencloning/store/cloning_utils';
5
+ import usePrimerDesignSettings from './usePrimerDesignSettings';
6
+
7
+ function getError(enzymePrimerDesignSettings) {
8
+ const { left_enzyme: leftEnzyme, right_enzyme: rightEnzyme, filler_bases: fillerBases } = enzymePrimerDesignSettings;
9
+ if (!leftEnzyme && !rightEnzyme) {
10
+ return 'You must select and enzyme';
11
+ }
12
+ if (stringIsNotDNA(fillerBases)) {
13
+ return 'Filler bases not valid';
14
+ }
15
+ return '';
16
+ }
17
+
18
+ export default function useEnzymePrimerDesignSettings(defaultSettings) {
19
+ const primerDesignSettings = usePrimerDesignSettings(defaultSettings);
20
+ const [enzymePrimerDesignSettings, setEnzymePrimerDesignSettings] = useState({
21
+ left_enzyme: null,
22
+ right_enzyme: null,
23
+ left_enzyme_inverted: false,
24
+ right_enzyme_inverted: false,
25
+ filler_bases: 'TTT',
26
+ enzymeSpacers: ['', ''],
27
+ });
28
+ const [enzymeError, setEnzymeError] = useState(getError(enzymePrimerDesignSettings));
29
+
30
+ const updateEnzymeSettings = (newSettings) => {
31
+ setEnzymePrimerDesignSettings((prev) => ({ ...prev, ...newSettings }));
32
+ };
33
+
34
+ useEffect(() => {
35
+ const { left_enzyme: leftEnzyme, right_enzyme: rightEnzyme, filler_bases: fillerBases, left_enzyme_inverted: leftEnzymeInverted, right_enzyme_inverted: rightEnzymeInverted } = enzymePrimerDesignSettings;
36
+ if ((leftEnzyme || rightEnzyme) && !stringIsNotDNA(fillerBases)) {
37
+ const leftEnzymeSeq = leftEnzymeInverted ? reverseComplement(getEnzymeRecognitionSequence(leftEnzyme)) : getEnzymeRecognitionSequence(leftEnzyme);
38
+ const rightEnzymeSeq = rightEnzymeInverted ? getEnzymeRecognitionSequence(rightEnzyme) : reverseComplement(getEnzymeRecognitionSequence(rightEnzyme));
39
+ const leftSpacerStartingSeq = (leftEnzyme ? fillerBases : '') + leftEnzymeSeq;
40
+ const rightSpacerEndingSeq = rightEnzymeSeq + reverseComplement((rightEnzyme ? fillerBases : ''));
41
+ setEnzymePrimerDesignSettings((prev) => ({ ...prev, enzymeSpacers: [leftSpacerStartingSeq, rightSpacerEndingSeq] }));
42
+ } else {
43
+ setEnzymePrimerDesignSettings((prev) => ({ ...prev, enzymeSpacers: ['', ''] }));
44
+ }
45
+ setEnzymeError(getError(enzymePrimerDesignSettings));
46
+ }, [
47
+ enzymePrimerDesignSettings.left_enzyme,
48
+ enzymePrimerDesignSettings.right_enzyme,
49
+ enzymePrimerDesignSettings.filler_bases,
50
+ enzymePrimerDesignSettings.left_enzyme_inverted,
51
+ enzymePrimerDesignSettings.right_enzyme_inverted,
52
+ ]);
53
+
54
+ const error = primerDesignSettings.error || enzymeError;
55
+
56
+ return { ...primerDesignSettings, ...enzymePrimerDesignSettings, updateEnzymeSettings, error };
57
+ }
@@ -0,0 +1,18 @@
1
+ import { useState } from 'react';
2
+ import usePrimerDesignSettings from './usePrimerDesignSettings';
3
+
4
+ export default function useGatewayPrimerDesignSettings(initialSettings) {
5
+ const primerDesignSettings = usePrimerDesignSettings(initialSettings);
6
+
7
+ // Gateway BP
8
+ const [knownCombination, setKnownCombination] = useState(null);
9
+
10
+ const error = primerDesignSettings.error || (!knownCombination && 'No valid combination of attP sites selected');
11
+
12
+ return {
13
+ ...primerDesignSettings,
14
+ knownCombination,
15
+ setKnownCombination,
16
+ error,
17
+ };
18
+ }
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import primerDesignMinimalValues from './primerDesignMinimalValues.json';
3
+
4
+ const getError = (s) => {
5
+ const valid = (s.homology_length === null || s.homology_length >= primerDesignMinimalValues.homology_length)
6
+ && s.minimal_hybridization_length >= primerDesignMinimalValues.hybridization_length
7
+ && s.target_tm >= primerDesignMinimalValues.target_tm;
8
+ if (!valid) {
9
+ return 'Invalid settings';
10
+ }
11
+ return null;
12
+ };
13
+
14
+ export default function usePrimerDesignSettings(defaultSettings) {
15
+ const [settings, setSettings] = React.useState(defaultSettings);
16
+ const [error, setError] = React.useState(getError(defaultSettings));
17
+
18
+ const updateSettings = (newSettings) => {
19
+ setSettings((prev) => ({ ...prev, ...newSettings }));
20
+ };
21
+
22
+ React.useEffect(() => {
23
+ setError(getError(settings));
24
+ }, [settings]);
25
+
26
+ return {
27
+ ...settings,
28
+ error,
29
+ updateSettings,
30
+ };
31
+ }
@@ -0,0 +1,88 @@
1
+ import { Button, Checkbox, FormControl, FormControlLabel } from '@mui/material';
2
+ import React from 'react';
3
+ import { batch, useDispatch } from 'react-redux';
4
+ import SingleInputSelector from '../../../sources/SingleInputSelector';
5
+ import { cloningActions } from '@opencloning/store/cloning';
6
+ import useStoreEditor from '../../../../hooks/useStoreEditor';
7
+ import LabelWithTooltip from '../../../form/LabelWithTooltip';
8
+ import useGatewaySites from '../../../../hooks/useGatewaySites';
9
+ import NoAttPSitesError from '../common/NoAttPSitesError';
10
+ import RetryAlert from '../../../form/RetryAlert';
11
+ import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
12
+
13
+ const { addTemplateChildAndSubsequentSource, setCurrentTab, setMainSequenceId } = cloningActions;
14
+
15
+ function PrimerDesignGatewayBP({ source }) {
16
+ const [target, setTarget] = React.useState('');
17
+ const [greedy, setGreedy] = React.useState(false);
18
+
19
+ const { updateStoreEditor } = useStoreEditor();
20
+ const { requestStatus, sites: sitesInTarget, attemptAgain } = useGatewaySites({ target, greedy });
21
+ const nbOfAttPSites = sitesInTarget.filter((site) => site.siteName.startsWith('attP')).length;
22
+ const inputSequenceId = getPcrTemplateSequenceId(source);
23
+
24
+ const dispatch = useDispatch();
25
+ const onSubmit = (event) => {
26
+ event.preventDefault();
27
+ const newSource = {
28
+ input: [{ sequence: Number(target) }],
29
+ type: 'GatewaySource',
30
+ reaction_type: 'BP',
31
+ greedy,
32
+ };
33
+ const newSequence = {
34
+ type: 'TemplateSequence',
35
+ primer_design: 'gateway_bp',
36
+ circular: false,
37
+ };
38
+
39
+ batch(() => {
40
+ dispatch(addTemplateChildAndSubsequentSource({ newSource, newSequence, sourceId: source.id }));
41
+ dispatch(setMainSequenceId(inputSequenceId));
42
+ updateStoreEditor('mainEditor', inputSequenceId);
43
+ dispatch(setCurrentTab(3));
44
+ // Scroll to the top of the page after 300ms
45
+ setTimeout(() => {
46
+ document.querySelector('.tab-panels-container')?.scrollTo({ top: 0, behavior: 'instant' });
47
+ }, 300);
48
+ });
49
+ };
50
+ return (
51
+ <form onSubmit={onSubmit}>
52
+
53
+ <FormControl fullWidth>
54
+ <SingleInputSelector
55
+ label="Donor vector"
56
+ selectedId={target}
57
+ onChange={(e) => setTarget(e.target.value)}
58
+ inputSequenceIds={[]}
59
+ helperText={requestStatus.status === 'loading' ? 'Validating...' : ''}
60
+ />
61
+ </FormControl>
62
+ <FormControl fullWidth>
63
+ <FormControlLabel
64
+ control={<Checkbox checked={greedy} onChange={() => setGreedy(!greedy)} />}
65
+ label={(
66
+ <LabelWithTooltip label="Greedy attP finder" tooltip="Use a more greedy consensus site to find attP sites (might give false positives)" />
67
+ )}
68
+ />
69
+ </FormControl>
70
+
71
+ {requestStatus.status === 'success' && target && nbOfAttPSites < 2 && (
72
+ <NoAttPSitesError sites={sitesInTarget} />
73
+ )}
74
+ {requestStatus.status === 'error' && (
75
+ <RetryAlert onRetry={attemptAgain}>
76
+ {requestStatus.message}
77
+ </RetryAlert>
78
+ )}
79
+ {target && requestStatus.status === 'success' && nbOfAttPSites > 1 && (
80
+ <Button type="submit" variant="contained" color="success">
81
+ Design primers
82
+ </Button>
83
+ )}
84
+ </form>
85
+ );
86
+ }
87
+
88
+ export default React.memo(PrimerDesignGatewayBP);