@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,26 @@
1
+ import React from 'react';
2
+ import { PrimerDesignProvider } from './PrimerDesignContext';
3
+ import PrimerDesignForm from './PrimerDesignForm';
4
+ import usePrimerDesignSettings from './usePrimerDesignSettings';
5
+ import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
6
+
7
+ export default function PrimerDesignGibsonAssembly({ pcrSources }) {
8
+ const templateSequencesIds = React.useMemo(() => pcrSources.map(getPcrTemplateSequenceId), [pcrSources]);
9
+ const steps = React.useMemo(() => [
10
+ ...templateSequencesIds.map((id, index) => (
11
+ { label: `Seq ${id}`, selectOrientation: true }
12
+ )),
13
+ ], [pcrSources]);
14
+
15
+ const primerDesignSettings = usePrimerDesignSettings({ homology_length: 35, minimal_hybridization_length: 20, target_tm: 55 });
16
+ return (
17
+ <PrimerDesignProvider
18
+ designType="gibson_assembly"
19
+ sequenceIds={templateSequencesIds}
20
+ primerDesignSettings={primerDesignSettings}
21
+ steps={steps}
22
+ >
23
+ <PrimerDesignForm />
24
+ </PrimerDesignProvider>
25
+ );
26
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import { PrimerDesignProvider } from './PrimerDesignContext';
3
+ import PrimerDesignForm from './PrimerDesignForm';
4
+ import usePrimerDesignSettings from './usePrimerDesignSettings';
5
+ import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
6
+
7
+ export default function PrimerDesignHomologousRecombination({ homologousRecombinationTargetId, pcrSource }) {
8
+ const templateSequenceId = getPcrTemplateSequenceId(pcrSource);
9
+ const sequenceIds = React.useMemo(() => [templateSequenceId, homologousRecombinationTargetId], [templateSequenceId, homologousRecombinationTargetId]);
10
+ const steps = React.useMemo(() => [
11
+ { label: 'Amplified region',
12
+ description: `Select the fragment of sequence ${templateSequenceId} to be amplified in the editor and click "Choose region"`,
13
+ inputLabel: `Amplified region (sequence ${templateSequenceId})` },
14
+ { label: 'Replaced region',
15
+ description: 'Select the single position (insertion) or region (replacement) where recombination will introduce the amplified fragment',
16
+ inputLabel: `Replaced region (sequence ${homologousRecombinationTargetId})`,
17
+ allowSinglePosition: true,
18
+ },
19
+ ], [templateSequenceId, homologousRecombinationTargetId]);
20
+
21
+ const primerDesignSettings = usePrimerDesignSettings({ homology_length: 80, minimal_hybridization_length: 20, target_tm: 55 });
22
+ return (
23
+ <PrimerDesignProvider
24
+ designType="homologous_recombination"
25
+ sequenceIds={sequenceIds}
26
+ primerDesignSettings={primerDesignSettings}
27
+ steps={steps}
28
+ >
29
+ <PrimerDesignForm />
30
+ </PrimerDesignProvider>
31
+ );
32
+ }
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { PrimerDesignProvider } from './PrimerDesignContext';
3
+ import PrimerDesignForm from './PrimerDesignForm';
4
+ import useEnzymePrimerDesignSettings from './useEnzymePrimerDesignSettings';
5
+ import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
6
+
7
+ function PrimerDesignRestriction({ pcrSource }) {
8
+ const templateSequenceId = getPcrTemplateSequenceId(pcrSource);
9
+ const sequenceIds = React.useMemo(() => [templateSequenceId], [templateSequenceId]);
10
+
11
+ const steps = React.useMemo(() => [
12
+ { label: 'Amplified region' },
13
+ ], []);
14
+
15
+ const primerDesignSettings = useEnzymePrimerDesignSettings({ homology_length: null, minimal_hybridization_length: 20, target_tm: 55 });
16
+
17
+ return (
18
+ <PrimerDesignProvider designType="restriction_ligation" sequenceIds={sequenceIds} primerDesignSettings={primerDesignSettings} steps={steps}>
19
+ <PrimerDesignForm />
20
+ </PrimerDesignProvider>
21
+
22
+ );
23
+ }
24
+
25
+ export default PrimerDesignRestriction;
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { PrimerDesignProvider } from './PrimerDesignContext';
3
+ import PrimerDesignForm from './PrimerDesignForm';
4
+ import usePrimerDesignSettings from './usePrimerDesignSettings';
5
+ import { getPcrTemplateSequenceId } from '@opencloning/store/cloning_utils';
6
+
7
+ function PrimerDesignSimplePair({ pcrSource }) {
8
+ const templateSequenceId = getPcrTemplateSequenceId(pcrSource);
9
+ const sequenceIds = React.useMemo(() => [templateSequenceId], [templateSequenceId]);
10
+
11
+ const steps = React.useMemo(() => [
12
+ { label: 'Amplified region' },
13
+ ], []);
14
+
15
+ const primerDesignSettings = usePrimerDesignSettings({ homology_length: null, minimal_hybridization_length: 20, target_tm: 55 });
16
+
17
+ return (
18
+ <PrimerDesignProvider designType="simple_pair" sequenceIds={sequenceIds} primerDesignSettings={primerDesignSettings} steps={steps}>
19
+ <PrimerDesignForm />
20
+ </PrimerDesignProvider>
21
+
22
+ );
23
+ }
24
+
25
+ export default PrimerDesignSimplePair;
@@ -0,0 +1,53 @@
1
+ import { Step, StepButton, Stepper } from '@mui/material';
2
+ import React from 'react';
3
+ import { usePrimerDesign } from './PrimerDesignContext';
4
+
5
+ function PrimerDesignStepper() {
6
+ const { steps, selectedTab, onTabChange, rois, primers, submissionPreventedMessage } = usePrimerDesign();
7
+
8
+ const allSteps = [
9
+ ...steps,
10
+ { label: 'Other settings' },
11
+ { label: 'Results' },
12
+ ];
13
+ const completedSteps = [
14
+ ...rois.map((roi) => roi !== null),
15
+ // We check submissionPreventedMessage, because you could design primers and then come back
16
+ // and have bad settings
17
+ primers.length > 0 && submissionPreventedMessage === '',
18
+ false,
19
+ ];
20
+ const disabledSteps = [
21
+ ...rois.map(() => false),
22
+ rois.some((roi) => roi === null),
23
+ primers.length === 0,
24
+ ];
25
+
26
+ return (
27
+ <Stepper
28
+ nonLinear
29
+ activeStep={selectedTab}
30
+ sx={{
31
+ pt: 1,
32
+ pb: 1,
33
+ '& .Mui-active.MuiStepLabel-label': {
34
+ fontWeight: 'bold',
35
+ color: 'primary.main',
36
+ },
37
+
38
+ }}
39
+ >
40
+ {allSteps.map(({ label }, index) => (
41
+
42
+ <Step key={label} completed={!disabledSteps[index] && completedSteps[index]}>
43
+ <StepButton disabled={disabledSteps[index]} onClick={() => onTabChange(null, index)}>
44
+ {label}
45
+ </StepButton>
46
+ </Step>
47
+ ))}
48
+
49
+ </Stepper>
50
+ );
51
+ }
52
+
53
+ export default PrimerDesignStepper;
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import { isEqual } from 'lodash-es';
4
+ import { Box, Button } from '@mui/material';
5
+ import { getPcrTemplateSequenceId, getPrimerDesignObject } from '@opencloning/store/cloning_utils';
6
+ import PrimerDesignHomologousRecombination from './PrimerDesignHomologousRecombination';
7
+ import useStoreEditor from '../../../../hooks/useStoreEditor';
8
+ import { cloningActions } from '@opencloning/store/cloning';
9
+ import PrimerDesignGibsonAssembly from './PrimerDesignGibsonAssembly';
10
+ import PrimerDesignSimplePair from './PrimerDesignSimplePair';
11
+ import PrimerDesignGatewayBP from './PrimerDesignGatewayBP';
12
+ import PrimerDesignEBIC from './PrimerDesignEBIC';
13
+ import PrimerDesignRestriction from './PrimerDesignRestriction';
14
+
15
+ function PrimerDesigner() {
16
+ const { updateStoreEditor } = useStoreEditor();
17
+ const dispatch = useDispatch();
18
+ const { setMainSequenceId } = cloningActions;
19
+
20
+ const { finalSource, otherInputIds, pcrSources, outputSequences } = useSelector((state) => getPrimerDesignObject(state.cloning), isEqual);
21
+
22
+ const mainSequenceId = useSelector((state) => state.cloning.mainSequenceId);
23
+
24
+ const templateSequencesIds = pcrSources.map((pcrSource) => getPcrTemplateSequenceId(pcrSource));
25
+ const openPrimerDesigner = () => {
26
+ updateStoreEditor('mainEditor', templateSequencesIds[0]);
27
+ dispatch(setMainSequenceId(templateSequencesIds[0]));
28
+ };
29
+
30
+ // Nothing to design
31
+ if (templateSequencesIds.length === 0) {
32
+ return null;
33
+ }
34
+
35
+ // The network supports design of primers, but the current main sequence is not part of it
36
+ const showPrimerDesigner = [...templateSequencesIds, ...otherInputIds].includes(mainSequenceId);
37
+
38
+ let component = null;
39
+ // Check conditions for different types of primer design
40
+ if (finalSource === null && pcrSources.length === 1 && outputSequences[0].primer_design === 'restriction_ligation') {
41
+ component = <PrimerDesignRestriction pcrSource={pcrSources[0]} />;
42
+ }
43
+ if (finalSource === null && pcrSources.length === 1 && outputSequences[0].primer_design === 'simple_pair') {
44
+ component = <PrimerDesignSimplePair pcrSource={pcrSources[0]} />;
45
+ }
46
+ if (finalSource?.type === 'GibsonAssemblySource' || finalSource?.type === 'InFusionSource' || finalSource?.type === 'InVivoAssemblySource' || finalSource?.type === 'CreLoxRecombinationSource') {
47
+ component = <PrimerDesignGibsonAssembly pcrSources={pcrSources} />;
48
+ }
49
+ if (finalSource?.type === 'HomologousRecombinationSource' && otherInputIds.length === 1 && pcrSources.length === 1) {
50
+ component = (
51
+ <PrimerDesignHomologousRecombination
52
+ homologousRecombinationTargetId={otherInputIds[0]}
53
+ pcrSource={pcrSources[0]}
54
+ />
55
+ );
56
+ }
57
+ if (finalSource?.type === 'GatewaySource' && otherInputIds.length === 1 && pcrSources.length === 1 && outputSequences[0].primer_design === 'gateway_bp') {
58
+ component = <PrimerDesignGatewayBP donorVectorId={otherInputIds[0]} pcrSource={pcrSources[0]} />;
59
+ }
60
+ if (finalSource?.type === 'RestrictionAndLigationSource' && outputSequences.every((outputSequence) => outputSequence.primer_design === 'ebic')) {
61
+ component = <PrimerDesignEBIC pcrSources={pcrSources} />;
62
+ }
63
+ return (
64
+ <>
65
+ {!showPrimerDesigner && (
66
+ <div>
67
+ <Button sx={{ mb: 4 }} variant="contained" color="success" onClick={openPrimerDesigner}>Open primer designer</Button>
68
+ </div>
69
+ )}
70
+ <Box className="primer-design" sx={{ display: showPrimerDesigner ? 'auto' : 'none', width: '60%', minWidth: '600px', margin: 'auto', border: 1, borderRadius: 2, overflow: 'hidden', borderColor: 'primary.main', marginBottom: 5 }}>
71
+ <Box sx={{ margin: 'auto', display: 'flex', height: 'auto', borderBottom: 2, borderColor: 'primary.main', backgroundColor: 'primary.main' }}>
72
+ <Box component="h2" sx={{ margin: 'auto', py: 1, color: 'white' }}>Primer designer</Box>
73
+ </Box>
74
+ {component}
75
+ </Box>
76
+ </>
77
+ );
78
+ }
79
+
80
+ export default React.memo(PrimerDesigner);
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import { FormControl, TextField } from '@mui/material';
3
+ import '../../PrimerForm.css';
4
+ import '../../PrimerList.css';
5
+
6
+ function PrimerResultForm({
7
+ primer: { name, sequence }, updatePrimerName, existingPrimerNames,
8
+ }) {
9
+ let nameError = '';
10
+ if (existingPrimerNames.includes(name)) {
11
+ nameError = 'Name exists';
12
+ }
13
+ if (name === '') {
14
+ nameError = 'Name is required';
15
+ }
16
+ return (
17
+ <div className="primer-design-form">
18
+ <FormControl sx={{ mx: 1, width: '30%', mb: 1 }}>
19
+ <TextField
20
+ label="Name"
21
+ value={name}
22
+ onChange={(e) => updatePrimerName(e.target.value)}
23
+ error={nameError !== ''}
24
+ FormHelperTextProps={{ component: 'div' }}
25
+ helperText={nameError}
26
+ />
27
+ </FormControl>
28
+ <FormControl sx={{ mx: 1, width: '60%' }}>
29
+ <TextField
30
+ label="Sequence"
31
+ value={sequence}
32
+ inputProps={{
33
+ id: 'sequence',
34
+ style: { fontSize: '0.8rem' }, // Reduce font size
35
+ }}
36
+ disabled
37
+ helperText=" " // Add an empty helper text to maintain consistent height
38
+ sx={{
39
+ '& .MuiInputBase-root': {
40
+ height: '56px', // Maintain the default height of TextField
41
+ },
42
+ }}
43
+ />
44
+ </FormControl>
45
+ </div>
46
+ );
47
+ }
48
+
49
+ export default PrimerResultForm;
@@ -0,0 +1,68 @@
1
+ import { Box, FormControl, FormLabel, InputAdornment, TextField } from '@mui/material';
2
+ import React from 'react';
3
+
4
+ import primerDesignMinimalValues from './primerDesignMinimalValues.json';
5
+
6
+ function PrimerSettingsForm({
7
+ homology_length, minimal_hybridization_length, target_tm, updateSettings,
8
+ }) {
9
+ return (
10
+ <Box sx={{ pt: 1 }}>
11
+ <FormLabel>Primer settings</FormLabel>
12
+ <Box sx={{ pt: 1.5 }}>
13
+ {homology_length !== null && (
14
+ <Box>
15
+ <FormControl>
16
+ <TextField
17
+ label="Homology length"
18
+ value={homology_length}
19
+ onChange={(e) => { updateSettings({ homology_length: Number(e.target.value) }); }}
20
+ type="number"
21
+ InputProps={{
22
+ endAdornment: <InputAdornment position="end">bp</InputAdornment>,
23
+ sx: { width: '10em' },
24
+ }}
25
+ error={homology_length < primerDesignMinimalValues.homology_length}
26
+ helperText={homology_length < primerDesignMinimalValues.homology_length ? `Min. ${primerDesignMinimalValues.homology_length} bp` : ''}
27
+ />
28
+ </FormControl>
29
+ </Box>
30
+ )}
31
+ <Box sx={{ pt: 2 }}>
32
+ <FormControl sx={{ mr: 2 }}>
33
+ <TextField
34
+ label="Target hybridization Tm"
35
+ value={target_tm}
36
+ onChange={(e) => { updateSettings({ target_tm: Number(e.target.value) }); }}
37
+ type="number"
38
+ InputProps={{
39
+ endAdornment: <InputAdornment position="end">°C</InputAdornment>,
40
+ sx: { width: '10em' },
41
+ }}
42
+ error={target_tm < primerDesignMinimalValues.target_tm}
43
+ helperText={target_tm < primerDesignMinimalValues.target_tm ? `Min. ${primerDesignMinimalValues.target_tm} °C` : ''}
44
+ />
45
+ </FormControl>
46
+
47
+ <FormControl>
48
+ <TextField
49
+ sx={{ minWidth: 'max-content' }}
50
+ label="Min. hybridization length"
51
+ value={minimal_hybridization_length}
52
+ onChange={(e) => { updateSettings({ minimal_hybridization_length: Number(e.target.value) }); }}
53
+ type="number"
54
+ InputProps={{
55
+ endAdornment: <InputAdornment position="end">bp</InputAdornment>,
56
+ sx: { width: '10em' },
57
+ }}
58
+ error={minimal_hybridization_length < primerDesignMinimalValues.hybridization_length}
59
+ helperText={minimal_hybridization_length < primerDesignMinimalValues.hybridization_length ? `Min. ${primerDesignMinimalValues.hybridization_length} bp` : ''}
60
+ />
61
+ </FormControl>
62
+ </Box>
63
+ </Box>
64
+ </Box>
65
+ );
66
+ }
67
+
68
+ export default PrimerSettingsForm;
@@ -0,0 +1,84 @@
1
+ import React from 'react';
2
+ import { FormControl, TextField, Box } from '@mui/material';
3
+ import { stringIsNotDNA } from '@opencloning/store/cloning_utils';
4
+ import CollapsableLabel from './CollapsableLabel';
5
+ import { usePrimerDesign } from './PrimerDesignContext';
6
+
7
+ function PrimerSpacerForm({ open = true }) {
8
+ const { spacers, setSpacers, circularAssembly, templateSequenceNames, templateSequenceIds } = usePrimerDesign();
9
+ const [localSpacers, setLocalSpacers] = React.useState(spacers);
10
+ const timeoutRef = React.useRef();
11
+
12
+ // Debounced upstream updates to avoid heavy re-rendering
13
+ const handleSpacerChange = (index, value) => {
14
+ setLocalSpacers((current) => current.map((spacer, i) => (i === index ? value : spacer)));
15
+
16
+ // Clear any existing timeout
17
+ clearTimeout(timeoutRef.current);
18
+
19
+ // Set new timeout and store its ID in the ref
20
+ timeoutRef.current = setTimeout(() => {
21
+ setSpacers((current) => current.map((spacer, i) => (i === index ? value : spacer)));
22
+ }, 500);
23
+ };
24
+
25
+ React.useEffect(() => {
26
+ if (!localSpacers.every((spacer, index) => spacer === spacers[index])) {
27
+ setLocalSpacers(spacers);
28
+ }
29
+ }, [spacers]);
30
+
31
+ const fragmentCount = templateSequenceIds.length;
32
+
33
+ const sequenceNamesWrapped = [...templateSequenceNames, templateSequenceNames[0]];
34
+ const templateSequenceIdsWrapped = [...templateSequenceIds, templateSequenceIds[0]];
35
+
36
+ const getSequenceName = (seqIndex) => {
37
+ const name = sequenceNamesWrapped[seqIndex];
38
+ const id = templateSequenceIdsWrapped[seqIndex];
39
+ return name && name !== 'name' ? `Seq. ${id} (${name})` : `Seq. ${id}`;
40
+ };
41
+
42
+ const getSpacerLabel = (index) => {
43
+ if (index === 0 && !circularAssembly) {
44
+ return `Before ${getSequenceName(index)}`;
45
+ } if (index === fragmentCount && !circularAssembly) {
46
+ return `After ${getSequenceName(fragmentCount - 1)}`;
47
+ }
48
+ if (circularAssembly) {
49
+ return `Between ${getSequenceName(index)} and ${getSequenceName(index + 1)}`;
50
+ }
51
+ return `Between ${getSequenceName(index - 1)} and ${getSequenceName(index)}`;
52
+ };
53
+
54
+ return (
55
+ <CollapsableLabel label="Spacer sequences" className="primer-spacer-form" open={open}>
56
+ <Box sx={{ pt: 1, width: '80%', margin: 'auto' }}>
57
+ <Box>
58
+ {localSpacers.map((spacer, index) => {
59
+ const error = stringIsNotDNA(spacer) ? 'Invalid DNA sequence' : '';
60
+ return (
61
+ <FormControl key={index} fullWidth sx={{ mb: 2 }}>
62
+ <TextField
63
+ label={getSpacerLabel(index)}
64
+ value={spacer}
65
+ onChange={(e) => handleSpacerChange(index, e.target.value)}
66
+ variant="outlined"
67
+ size="small"
68
+ inputProps={{
69
+ id: 'sequence',
70
+ }}
71
+ // Error if not DNA
72
+ error={error !== ''}
73
+ helperText={error}
74
+ />
75
+ </FormControl>
76
+ );
77
+ })}
78
+ </Box>
79
+ </Box>
80
+ </CollapsableLabel>
81
+ );
82
+ }
83
+
84
+ export default PrimerSpacerForm;
@@ -0,0 +1,85 @@
1
+ import React from 'react';
2
+ import { FormLabel, Box, FormControl, TextField, Tooltip, FormControlLabel, Checkbox } from '@mui/material';
3
+ import InfoIcon from '@mui/icons-material/Info';
4
+ import { useStore } from 'react-redux';
5
+ import { updateEditor } from '@teselagen/ove';
6
+ import EnzymeMultiSelect from '../../../form/EnzymeMultiSelect';
7
+ import { stringIsNotDNA } from '@opencloning/store/cloning_utils';
8
+ import { usePrimerDesign } from './PrimerDesignContext';
9
+ import { isEnzymePalyndromic } from '@opencloning/utils/enzyme_utils';
10
+
11
+ function RestrictionSpacerForm() {
12
+ const { primerDesignSettings } = usePrimerDesign();
13
+ const {
14
+ left_enzyme: leftEnzyme,
15
+ right_enzyme: rightEnzyme,
16
+ left_enzyme_inverted: leftEnzymeInverted,
17
+ right_enzyme_inverted: rightEnzymeInverted,
18
+ filler_bases: fillerBases,
19
+ updateEnzymeSettings,
20
+ } = primerDesignSettings;
21
+
22
+ const store = useStore();
23
+
24
+ // When enzymes change, update the displayed enzymes in the editor
25
+ React.useEffect(() => {
26
+ const allEnzymes = [leftEnzyme, rightEnzyme].filter((enzyme) => enzyme !== null);
27
+ const filteredRestrictionEnzymes = allEnzymes.map((enzyme) => ({
28
+ canBeHidden: true,
29
+ value: enzyme,
30
+ }));
31
+ updateEditor(store, 'mainEditor', { annotationVisibility: { cutsites: leftEnzyme || rightEnzyme }, restrictionEnzymes: { filteredRestrictionEnzymes, isEnzymeFilterAnd: false } });
32
+ }, [leftEnzyme, rightEnzyme]);
33
+
34
+ return (
35
+ <>
36
+ <FormLabel>Restriction enzyme sites</FormLabel>
37
+ <Box sx={{ display: 'flex', flexDirection: 'row', justifyContent: 'center' }}>
38
+ <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
39
+ <FormControl sx={{ width: '10em', mt: 1.5, mr: 2 }}>
40
+ <EnzymeMultiSelect value={leftEnzyme} setEnzymes={(v) => updateEnzymeSettings({ left_enzyme: v })} label="Left enzyme" multiple={false} />
41
+ </FormControl>
42
+ {leftEnzyme && !isEnzymePalyndromic(leftEnzyme) && (
43
+ <FormControlLabel
44
+ control={<Checkbox checked={leftEnzymeInverted} onChange={(e) => updateEnzymeSettings({ left_enzyme_inverted: e.target.checked })} />}
45
+ label="Invert site"
46
+ />
47
+ )}
48
+ </Box>
49
+ <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
50
+ <FormControl sx={{ width: '10em', mt: 1.5, mr: 2 }}>
51
+ <EnzymeMultiSelect value={rightEnzyme} setEnzymes={(v) => updateEnzymeSettings({ right_enzyme: v })} label="Right enzyme" multiple={false} />
52
+ </FormControl>
53
+ {rightEnzyme && !isEnzymePalyndromic(rightEnzyme) && (
54
+ <FormControlLabel
55
+ control={<Checkbox checked={rightEnzymeInverted} onChange={(e) => updateEnzymeSettings({ right_enzyme_inverted: e.target.checked })} />}
56
+ label="Invert site"
57
+ />
58
+ )}
59
+ </Box>
60
+ <FormControl sx={{ width: '10em', mt: 1.5 }}>
61
+ <TextField
62
+ label={(
63
+ <Box sx={{ display: 'flex', alignItems: 'center' }}>
64
+ 5&apos; filler bases
65
+ <Tooltip arrow placement="top" title="These bases are added to the 5&apos; end of the primer to ensure proper restriction enzyme digestion.">
66
+ <InfoIcon sx={{ fontSize: '1.2em', ml: 0.5 }} />
67
+ </Tooltip>
68
+ </Box>
69
+ )}
70
+ value={fillerBases}
71
+ onChange={(e) => updateEnzymeSettings({ filler_bases: e.target.value })}
72
+ variant="outlined"
73
+ inputProps={{
74
+ id: 'sequence',
75
+ }}
76
+ error={stringIsNotDNA(fillerBases)}
77
+ helperText={stringIsNotDNA(fillerBases) ? 'Invalid DNA sequence' : ''}
78
+ />
79
+ </FormControl>
80
+ </Box>
81
+ </>
82
+ );
83
+ }
84
+
85
+ export default RestrictionSpacerForm;
@@ -0,0 +1,48 @@
1
+ import { Box, Button, Tooltip } from '@mui/material';
2
+ import React from 'react';
3
+ import { usePrimerDesign } from './PrimerDesignContext';
4
+
5
+ function ToolTipWrapper({ title, children, enabled = false }) {
6
+ if (enabled) {
7
+ return (
8
+ <Tooltip title={title} placement="top" arrow>
9
+ <span>
10
+ {children}
11
+ </span>
12
+ </Tooltip>
13
+ );
14
+ }
15
+ return children;
16
+ }
17
+
18
+ function StepNavigation({ onStepCompletion, allowStepCompletion, stepCompletionText, isFirstStep = false, nextDisabled = false, nextToolTip = '', stepCompletionToolTip = '' }) {
19
+ const { handleBack, handleNext } = usePrimerDesign();
20
+ return (
21
+ <Box sx={{ display: 'flex', flexDirection: 'row', pt: 2 }}>
22
+ <Button
23
+ color="inherit"
24
+ disabled={isFirstStep}
25
+ onClick={handleBack}
26
+ sx={{ mr: 1 }}
27
+ >
28
+ Back
29
+ </Button>
30
+ <Box sx={{ flex: '1 1 auto' }} />
31
+
32
+ <ToolTipWrapper title={stepCompletionToolTip} enabled={!allowStepCompletion}>
33
+ <Button onClick={onStepCompletion} disabled={!allowStepCompletion} sx={{ mr: 1 }}>
34
+ {stepCompletionText}
35
+ </Button>
36
+ </ToolTipWrapper>
37
+
38
+ <ToolTipWrapper title={nextToolTip} enabled={nextDisabled}>
39
+ <Button color="inherit" disabled={nextDisabled} onClick={handleNext} sx={{ mr: 1 }}>
40
+ Next
41
+ </Button>
42
+ </ToolTipWrapper>
43
+
44
+ </Box>
45
+ );
46
+ }
47
+
48
+ export default StepNavigation;