@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,69 @@
1
+ import { Dialog, DialogTitle, List, ListItem, ListItemButton, ListItemText } from '@mui/material';
2
+ import React from 'react';
3
+
4
+ const examples = [
5
+ {
6
+ title: 'Arabidopsis CRISPR-HDR genome editing',
7
+ link: 'arabidopsis_CRISPR_HDR.zip',
8
+ },
9
+ {
10
+ title: 'Gibson assembly',
11
+ link: 'gibson_assembly.json',
12
+ },
13
+ {
14
+ title: 'Golden gate assembly',
15
+ link: 'golden_gate.json',
16
+ },
17
+ {
18
+ title: 'Integration of cassette by homologous recombination',
19
+ link: 'homologous_recombination.json',
20
+ },
21
+ {
22
+ title: 'Restriction + ligation assembly (v1)',
23
+ link: 'restriction_ligation_assembly.json',
24
+ },
25
+ {
26
+ title: 'Restriction + ligation assembly (v2)',
27
+ link: 'restriction_then_ligation.json',
28
+ },
29
+ {
30
+ title: 'Templateless PCR',
31
+ link: 'templateless_PCR.json',
32
+ },
33
+ {
34
+ title: 'CRISPR HDR',
35
+ link: 'crispr_hdr.json',
36
+ },
37
+ {
38
+ title: 'Gateway cloning',
39
+ link: 'gateway.json',
40
+ },
41
+ {
42
+ title: 'Annotate features with pLannotate',
43
+ link: 'plannotate.json',
44
+ },
45
+ {
46
+ title: 'Cre/Lox recombination',
47
+ link: 'cre_lox_recombination.json',
48
+ },
49
+ ].sort((a, b) => a.title.localeCompare(b.title));
50
+
51
+ function SelectExampleDialog({ onClose, open }) {
52
+ return (
53
+ <Dialog open={open} onClose={() => onClose('')} className="load-example-dialog">
54
+ <DialogTitle>Load an example</DialogTitle>
55
+ <List>
56
+ {
57
+ examples.map((example) => (
58
+ <ListItem key={example.link} className="load-example-item">
59
+ <ListItemButton onClick={() => onClose(`${import.meta.env.BASE_URL}examples/${example.link}`)}><ListItemText>{example.title}</ListItemText></ListItemButton>
60
+ </ListItem>
61
+ ))
62
+ }
63
+ </List>
64
+
65
+ </Dialog>
66
+ );
67
+ }
68
+
69
+ export default SelectExampleDialog;
@@ -0,0 +1,107 @@
1
+ import { Accordion, AccordionDetails, AccordionSummary, Alert, Button, CircularProgress, Dialog, DialogContent, DialogTitle, IconButton, List, ListItem, ListItemButton, ListItemText } from '@mui/material';
2
+
3
+ import React from 'react';
4
+ import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
5
+ import useHttpClient from '../../hooks/useHttpClient';
6
+
7
+ function SelectTemplateDialog({ onClose, open }) {
8
+ const [templates, setTemplates] = React.useState(null);
9
+ const [connectAttempt, setConnectAttemp] = React.useState(0);
10
+ const [error, setError] = React.useState(null);
11
+ const baseUrl = 'https://assets.opencloning.org/OpenCloning-submission';
12
+ const httpClient = useHttpClient();
13
+
14
+ // const baseUrl = '';
15
+ React.useEffect(() => {
16
+ const fetchData = async () => {
17
+ try {
18
+ const resp = await httpClient.get(`${baseUrl}/index.json`);
19
+ setTemplates(resp.data);
20
+ setError('');
21
+ } catch {
22
+ setTemplates(null);
23
+ setError('Failed to load templates from GitHub');
24
+ }
25
+ };
26
+ fetchData();
27
+ }, [connectAttempt]);
28
+ return (
29
+ <Dialog open={open} onClose={() => onClose('')} className="load-template-dialog">
30
+ <DialogTitle>Load a template</DialogTitle>
31
+ {!templates
32
+ && (
33
+ <DialogContent>
34
+ {error ? (
35
+ <Alert
36
+ severity="error"
37
+ action={(
38
+ <Button color="inherit" size="small" onClick={() => { setError(''); setConnectAttemp((p) => p + 1); }}>
39
+ Retry
40
+ </Button>
41
+ )}
42
+ >
43
+ {error}
44
+ </Alert>
45
+ ) : (
46
+ <>
47
+ Loading templates...
48
+ {' '}
49
+ <CircularProgress className="loading-progress" color="inherit" size="2em" />
50
+ </>
51
+ )}
52
+ </DialogContent>
53
+ )}
54
+ <List>
55
+ {
56
+ templates && Object.keys(templates).map((key) => {
57
+ const { kit, assemblies } = templates[key];
58
+ return (
59
+ <Accordion key={key} className="load-template-item">
60
+ <AccordionSummary
61
+ expandIcon={<ExpandMoreIcon />}
62
+ aria-controls={`${key}-content`}
63
+ id={`${key}-header`}
64
+ >
65
+ <ListItemText sx={{ my: 0 }} primary={kit.title} secondary={kit.description} />
66
+ </AccordionSummary>
67
+ <AccordionDetails sx={{ py: 0 }}>
68
+ <List sx={{ py: 0, my: 0 }}>
69
+ {
70
+ assemblies.map((assembly) => (
71
+ <ListItem sx={{ px: 0, pt: 0 }} key={assembly.template_file} className="load-template-item">
72
+ <ListItemButton sx={{ py: 0 }} onClick={() => onClose(`${baseUrl}/processed/${key}/templates/${assembly.template_file}`, true)}>
73
+ <ListItemText primary={assembly.title} secondary={assembly.description} />
74
+ </ListItemButton>
75
+ </ListItem>
76
+ ))
77
+ }
78
+ </List>
79
+ </AccordionDetails>
80
+ </Accordion>
81
+
82
+ );
83
+ })
84
+ }
85
+ <ListItem key="blah" className="load-template-item">
86
+
87
+ <ListItemText
88
+ primary="🔎 Can't find your favourite kit?"
89
+ secondary={(
90
+ <>
91
+ Create it from an Addgene kit.
92
+ <br />
93
+ It&apos;s very easy!
94
+ </>
95
+ )}
96
+ />
97
+ <IconButton edge="end" aria-label="submit-button">
98
+ <Button className="button-hyperlink" variant="contained" color="success" href="https://github.com/OpenCloning/OpenCloning-submission/blob/master/docs/index.md" target="_blank" rel="noopener noreferrer">Create templates</Button>
99
+ </IconButton>
100
+
101
+ </ListItem>
102
+ </List>
103
+ </Dialog>
104
+ );
105
+ }
106
+
107
+ export default SelectTemplateDialog;
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+
3
+ const TabPanel = React.forwardRef(({ children, value, index, ...other }, ref) => {
4
+ const style = value === index ? {
5
+ visibility: 'visible',
6
+
7
+ } : {
8
+ visibility: 'hidden',
9
+ position: 'absolute',
10
+ width: '1px',
11
+ height: '0px',
12
+ overflow: 'hidden',
13
+ };
14
+
15
+ return (
16
+ <div
17
+ role="tabpanel"
18
+ style={style}
19
+ id={`tab-panel-${index}`}
20
+ aria-labelledby={`tab-${index}`}
21
+ {...other}
22
+ >
23
+ {children}
24
+ </div>
25
+ );
26
+ });
27
+
28
+ export default TabPanel;
@@ -0,0 +1,33 @@
1
+ import { Dialog, DialogContent, DialogTitle, List, ListItem, ListItemText } from '@mui/material';
2
+ import React from 'react';
3
+ import { useSelector } from 'react-redux';
4
+ import { isEqual } from 'lodash-es';
5
+
6
+ function VersionDialog({ open, setOpen }) {
7
+ const { backendVersion, schemaVersion, frontendVersion } = useSelector(({ cloning }) => cloning.appInfo, isEqual);
8
+
9
+ return (
10
+ <Dialog
11
+ open={open}
12
+ onClose={() => setOpen(false)}
13
+ className="version-dialog"
14
+ >
15
+ <DialogTitle sx={{ textAlign: 'center' }}> App version </DialogTitle>
16
+ <DialogContent>
17
+ <List>
18
+ <ListItem fullWidth>
19
+ <ListItemText primary="Frontend" secondary={frontendVersion || 'N.A.'} />
20
+ </ListItem>
21
+ <ListItem fullWidth>
22
+ <ListItemText primary="Backend" secondary={backendVersion || 'N.A.'} />
23
+ </ListItem>
24
+ <ListItem fullWidth>
25
+ <ListItemText primary="Schema" secondary={schemaVersion || 'N.A.'} />
26
+ </ListItem>
27
+ </List>
28
+ </DialogContent>
29
+ </Dialog>
30
+ );
31
+ }
32
+
33
+ export default VersionDialog;
@@ -0,0 +1,42 @@
1
+ import React from 'react';
2
+ import { FormControl, TextField } from '@mui/material';
3
+ import './PrimerForm.css';
4
+ import './PrimerList.css';
5
+ import CustomFormHelperText from '../form/CustomFormHelperText';
6
+
7
+ function CreatePrimerFromSequenceForm({
8
+ primer: { name, sequence }, setName, setSequence, existingPrimerNames,
9
+ }) {
10
+ let nameError = '';
11
+ if (existingPrimerNames.includes(name)) {
12
+ nameError = 'Name exists';
13
+ }
14
+ if (name === '') {
15
+ nameError = 'Name is required';
16
+ }
17
+ return (
18
+ <div className="primer-result-form">
19
+ <FormControl sx={{ m: 1, pb: 2, display: { width: '30%' } }}>
20
+ <TextField
21
+ label="Name"
22
+ value={name}
23
+ onChange={(e) => setName(e.target.value)}
24
+ error={nameError !== ''}
25
+ FormHelperTextProps={{ component: 'div' }}
26
+ helperText={<CustomFormHelperText>{nameError}</CustomFormHelperText>}
27
+ />
28
+ </FormControl>
29
+ <FormControl sx={{ m: 1, pb: 2, display: { width: '60%' } }}>
30
+ <TextField
31
+ label="Sequence"
32
+ value={sequence}
33
+ onChange={(e) => setSequence(e.target.value)}
34
+ inputProps={{ id: 'sequence' }}
35
+ FormHelperTextProps={{ component: 'div' }}
36
+ />
37
+ </FormControl>
38
+ </div>
39
+ );
40
+ }
41
+
42
+ export default CreatePrimerFromSequenceForm;
@@ -0,0 +1,104 @@
1
+ import React from 'react';
2
+
3
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, FormLabel, RadioGroup, FormControlLabel, Radio, TextField } from '@mui/material';
4
+ import { useSelector } from 'react-redux';
5
+ import primersToTabularFile from './primersToTabularFile';
6
+
7
+ import { downloadTextFile } from '@opencloning/utils/readNwrite';
8
+ import { usePCRDetails } from './primer_details/usePCRDetails';
9
+ import RequestStatusWrapper from '../form/RequestStatusWrapper';
10
+ import useMultiplePrimerDetails from './primer_details/useMultiplePrimerDetails';
11
+ import { isCompleteOligoHybridizationSource, isCompletePCRSource } from '@opencloning/store/cloning_utils';
12
+
13
+ function DownloadPrimersDialog({ primers, open, onClose }) {
14
+ const pcrSourceIds = useSelector((state) => state.cloning.sources
15
+ .filter((source) => isCompletePCRSource(source) || isCompleteOligoHybridizationSource(source))
16
+ .map((source) => source.id));
17
+ const [fileName, setFileName] = React.useState('primers');
18
+ const [extension, setExtension] = React.useState('.csv');
19
+
20
+ const { pcrDetails, retryGetPCRDetails, requestStatus: pcrDetailsRequestStatus } = usePCRDetails(pcrSourceIds);
21
+ const { primerDetails, retryGetPrimerDetails, requestStatus: primerDetailsRequestStatus } = useMultiplePrimerDetails(primers);
22
+
23
+ const handleDownload = () => {
24
+ const fullFileName = fileName + extension;
25
+ const separator = extension === '.csv' ? ',' : '\t';
26
+ const fileContent = primersToTabularFile(primerDetails, pcrDetails, separator);
27
+
28
+ downloadTextFile(fileContent, fullFileName);
29
+ onClose();
30
+ };
31
+ return (
32
+ <Dialog
33
+ open={open}
34
+ onClose={onClose}
35
+ PaperProps={{
36
+ component: 'form',
37
+ onSubmit: (event) => {
38
+ event.preventDefault();
39
+ handleDownload();
40
+ },
41
+ }}
42
+ >
43
+ <DialogTitle>Save Primers to File</DialogTitle>
44
+ <DialogContent>
45
+ <FormControl fullWidth>
46
+ <TextField
47
+ autoFocus
48
+ required
49
+ id="file_name"
50
+ label="File name"
51
+ variant="standard"
52
+ value={fileName}
53
+ onChange={(e) => setFileName(e.target.value)}
54
+ sx={{ mb: 2 }}
55
+ />
56
+ <FormLabel id="save-primers-radio-group-label">File format</FormLabel>
57
+ <RadioGroup
58
+ aria-labelledby="save-primers-radio-group-label"
59
+ value={extension}
60
+ variant="standard"
61
+ onChange={(e) => setExtension(e.target.value)}
62
+ >
63
+ <FormControlLabel value=".csv" control={<Radio />} label="csv" />
64
+ <FormControlLabel value=".tsv" control={<Radio />} label="tsv" />
65
+ </RadioGroup>
66
+ </FormControl>
67
+ </DialogContent>
68
+ <DialogActions>
69
+ <RequestStatusWrapper requestStatus={primerDetailsRequestStatus} retry={() => { retryGetPrimerDetails(); retryGetPCRDetails(); }}>
70
+ <RequestStatusWrapper requestStatus={pcrDetailsRequestStatus} retry={retryGetPCRDetails}>
71
+ <Button onClick={onClose}>
72
+ Cancel
73
+ </Button>
74
+ <Button type="submit">Save file</Button>
75
+ </RequestStatusWrapper>
76
+ </RequestStatusWrapper>
77
+ </DialogActions>
78
+ </Dialog>
79
+ );
80
+ }
81
+
82
+ function DownloadPrimersButton({ primers }) {
83
+ const [dialogOpen, setDialogOpen] = React.useState(false);
84
+
85
+ return (
86
+ <>
87
+ <Button
88
+ variant="contained"
89
+ onClick={() => setDialogOpen(true)}
90
+ >
91
+ Download Primers
92
+ </Button>
93
+ {dialogOpen && (
94
+ <DownloadPrimersDialog
95
+ primers={primers}
96
+ open={dialogOpen}
97
+ onClose={() => setDialogOpen(false)}
98
+ />
99
+ )}
100
+ </>
101
+ );
102
+ }
103
+
104
+ export default DownloadPrimersButton;
@@ -0,0 +1,14 @@
1
+ .primer-row {
2
+ display: flex;
3
+ align-items: center;
4
+ }
5
+
6
+ .primer-result-form {
7
+ display: flex;
8
+ align-items: center;
9
+ justify-content: center;
10
+ }
11
+
12
+ .primer-row svg[data-testid] {
13
+ font-size: 1.2em;
14
+ }
@@ -0,0 +1,107 @@
1
+ import React from 'react';
2
+ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
3
+ import CancelIcon from '@mui/icons-material/Cancel';
4
+ import Tooltip from '@mui/material/Tooltip';
5
+ import { IconButton } from '@mui/material';
6
+ import ValidatedTextField from '../form/ValidatedTextField';
7
+
8
+ import './PrimerForm.css';
9
+ import { stringIsNotDNA } from '@opencloning/store/cloning_utils';
10
+
11
+ function PrimerForm({
12
+ primer = { name: '', sequence: '', id: null }, submitPrimer, cancelForm, existingNames, disabledSequenceText = '',
13
+ }) {
14
+ const [errorStatus, setErrorStatus] = React.useState(primer.id ? { name: false, sequence: false } : { name: true, sequence: true });
15
+ const [touched, setTouched] = React.useState(false);
16
+ const [submissionAttempted, setSubmissionAttempted] = React.useState(false);
17
+ const [name, setName] = React.useState(primer.name);
18
+ const [sequence, setSequence] = React.useState(primer.sequence);
19
+
20
+ React.useEffect(() => {
21
+ setName(primer.name);
22
+ setSequence(primer.sequence);
23
+ }, [primer.name, primer.sequence]);
24
+
25
+ const updateValidationStatus = (fieldName, valid) => {
26
+ setTouched(true);
27
+ // Update the validation status for the given field (taken from the ref.id)
28
+ setErrorStatus((prevErrorStatus) => ({
29
+ ...prevErrorStatus,
30
+ [fieldName]: valid,
31
+ }));
32
+ };
33
+ const submissionAllowed = Object.values(errorStatus).every((error) => !error);
34
+
35
+ const onSubmit = (e) => {
36
+ e.preventDefault();
37
+ setSubmissionAttempted(true);
38
+ if (submissionAllowed) {
39
+ // Id is null in the case of adding new primer
40
+ const { id } = primer;
41
+ submitPrimer({ id, name, sequence });
42
+ cancelForm();
43
+ }
44
+ };
45
+
46
+ const onSequenceChange = (event) => {
47
+ // Remove all non-letter characters on paste
48
+ if (event.nativeEvent.inputType === 'insertFromPaste') {
49
+ setSequence(event.target.value.replace(/[^a-zA-Z]/g, ''));
50
+ } else {
51
+ setSequence(event.target.value);
52
+ }
53
+ };
54
+
55
+ const sequenceErrorChecker = (s) => (stringIsNotDNA(s) ? { error: true, helperText: 'Invalid DNA sequence' } : { error: false, helperText: '' });
56
+ const nameErrorChecker = (s) => (existingNames.includes(s) ? { error: true, helperText: 'Name exists' } : { error: false, helperText: '' });
57
+
58
+ return (
59
+ <form className="primer-row" onSubmit={onSubmit} style={{ margin: 'auto' }}>
60
+ <ValidatedTextField
61
+ id="name"
62
+ label="Name"
63
+ value={name}
64
+ onInputChange={(e) => setName(e.target.value)}
65
+ sx={{ m: 1, display: { width: '20%' } }}
66
+ submissionAttempted={submissionAttempted}
67
+ required
68
+ errorChecker={nameErrorChecker}
69
+ updateValidationStatus={updateValidationStatus}
70
+ floatingHelperText
71
+ />
72
+ <ValidatedTextField
73
+ id="sequence"
74
+ label="Sequence"
75
+ value={sequence}
76
+ onInputChange={onSequenceChange}
77
+ sx={{ m: 1, display: { width: '60%' } }}
78
+ className="sequence"
79
+ submissionAttempted={submissionAttempted}
80
+ required
81
+ errorChecker={sequenceErrorChecker}
82
+ updateValidationStatus={updateValidationStatus}
83
+ floatingHelperText
84
+ disabled={disabledSequenceText !== ''}
85
+ initialHelperText={disabledSequenceText}
86
+ />
87
+ {touched
88
+ && (
89
+ <IconButton type="submit" sx={{ height: 'fit-content' }}>
90
+ <Tooltip title={submissionAllowed ? 'Save changes' : 'Incorrect values'} arrow placement="top">
91
+ <CheckCircleIcon size={25} className={submissionAllowed ? '' : 'form-invalid'} color={submissionAllowed ? 'success' : 'grey'} />
92
+ </Tooltip>
93
+ </IconButton>
94
+ )}
95
+ {cancelForm !== null && (
96
+ <IconButton onClick={cancelForm} type="button" sx={{ height: 'fit-content' }}>
97
+ <Tooltip title="Discard changes" arrow placement="top">
98
+ <CancelIcon color="error" />
99
+ </Tooltip>
100
+ </IconButton>
101
+ )}
102
+
103
+ </form>
104
+ );
105
+ }
106
+
107
+ export default PrimerForm;
@@ -0,0 +1,46 @@
1
+ table {
2
+ width: auto;
3
+ border-collapse: collapse;
4
+ width: 100%;
5
+ }
6
+
7
+ .primer-table-container {
8
+ width: 60%;
9
+ margin: auto;
10
+ margin-bottom: 10px;
11
+ }
12
+
13
+ .primer-form-container {
14
+ width: 60%;
15
+ margin: auto;
16
+ margin-bottom: 10px;
17
+ }
18
+
19
+ table th:first-child, td.icons {
20
+ width: 15%;
21
+ /* Prevent icons from appear as column is sequence is very long */
22
+ white-space: nowrap;
23
+ }
24
+
25
+ th, td {
26
+ border-bottom: 1px solid #ddd;
27
+ padding: 8px;
28
+ text-align: left;
29
+ }
30
+
31
+ th {
32
+ background-color: #f0f0f0;
33
+ }
34
+
35
+
36
+ tr:hover {
37
+ background-color: #f0f0f0;
38
+ }
39
+
40
+ div.primer-table-container {
41
+ overflow-x: scroll;
42
+ }
43
+
44
+ .sequence, #sequence {
45
+ font-family: monospace;
46
+ }
@@ -0,0 +1,95 @@
1
+ import React from 'react';
2
+ import PrimerList from './PrimerList';
3
+ import store from '@opencloning/store';
4
+ import { cloningActions } from '@opencloning/store/cloning';
5
+ import { Provider } from 'react-redux';
6
+
7
+ const { setConfig, setPrimers, setGlobalPrimerSettings } = cloningActions;
8
+
9
+ const mockReply = {
10
+ statusCode: 200, body: {
11
+ melting_temperature: 60, gc_content: .5, homodimer: {
12
+ melting_temperature: 0,
13
+ deltaG: 0,
14
+ figure: "dummy_figure"
15
+ },
16
+ hairpin: {
17
+ melting_temperature: 0,
18
+ deltaG: 0,
19
+ figure: "dummy_figure"
20
+ },
21
+ }
22
+ }
23
+
24
+ describe('PrimerList', () => {
25
+ beforeEach(() => {
26
+ store.dispatch(setConfig({ backendUrl: 'http://127.0.0.1:8000' }));
27
+ });
28
+ it('displays the right information', () => {
29
+ store.dispatch(setPrimers([
30
+ { id: 1, name: 'P1', sequence: 'TCATTAAAGTTAACG' },
31
+ ]));
32
+
33
+ cy.mount(
34
+ <Provider store={store}>
35
+ <PrimerList />
36
+ </Provider>
37
+ );
38
+ cy.get('td.name').contains('P1');
39
+ cy.get('td.length').contains('15');
40
+ cy.get('td.gc-content').contains('27');
41
+ cy.get('td.melting-temperature').contains('37.5');
42
+ cy.get('td.sequence').contains('TCATTAAAGTTAACG');
43
+ });
44
+
45
+ it('caches primer details across re-renders and re-renders on global settings change', () => {
46
+ let calls = 0;
47
+ store.dispatch(setPrimers([
48
+ { id: 1, name: 'P1', sequence: 'AAA' },
49
+ ]));
50
+ cy.intercept('POST', 'http://127.0.0.1:8000/primer_details*', (req) => {
51
+ calls += 1;
52
+ const respReply = calls === 1 ? mockReply : {
53
+ statusCode: 200, body: {
54
+ ...mockReply.body,
55
+ melting_temperature: calls === 1 ? 60 : 70,
56
+ }
57
+ }
58
+ expect(req.body).to.deep.equal({
59
+ sequence: 'AAA',
60
+ settings: {
61
+ primer_dna_conc: calls === 1 ? 50 : 100,
62
+ primer_salt_monovalent: 50,
63
+ primer_salt_divalent: 1.5,
64
+ },
65
+ });
66
+ req.reply(respReply);
67
+ }).as('primerDetails');
68
+
69
+ // First mount triggers two network calls (one per unique primer sequence)
70
+ cy.mount(
71
+ <Provider store={store}>
72
+ <PrimerList />
73
+ </Provider>)
74
+ cy.contains('Loading...').should('not.exist');
75
+ cy.wait('@primerDetails');
76
+ cy.then(() => {
77
+ expect(calls).to.equal(1);
78
+ cy.mount(
79
+ <Provider store={store}>
80
+ <PrimerList />
81
+ </Provider>)
82
+ cy.then(() => {
83
+ expect(calls).to.equal(1);
84
+ });
85
+ store.dispatch(setGlobalPrimerSettings({ primer_dna_conc: 100 }))
86
+ cy.wait('@primerDetails');
87
+ cy.then(() => {
88
+ expect(calls).to.equal(2);
89
+ cy.get('td.melting-temperature').contains('70');
90
+ });
91
+
92
+ });
93
+
94
+ });
95
+ });