@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,397 @@
1
+ import React from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import { Alert, Button, Dialog, DialogContent } from '@mui/material';
4
+ import { isEqual } from 'lodash-es';
5
+ import { enzymesInRestrictionEnzymeDigestionSource } from '@opencloning/utils/sourceFunctions';
6
+ import PlannotateAnnotationReport from '../annotation/PlannotateAnnotationReport';
7
+ import useDatabase from '../../hooks/useDatabase';
8
+ import useLoadDatabaseFile from '../../hooks/useLoadDatabaseFile';
9
+ import { usePCRDetails } from '../primers/primer_details/usePCRDetails';
10
+ import PCRTable from '../primers/primer_details/PCRTable';
11
+ import RequestStatusWrapper from '../form/RequestStatusWrapper';
12
+ import repositoryMetadata from './repositoryMetadata';
13
+
14
+ function DatabaseMessage({ source }) {
15
+ const [loadingHistory, setLoadingHistory] = React.useState(false);
16
+ const database = useDatabase();
17
+ const handleClose = React.useCallback(() => setLoadingHistory(false), [setLoadingHistory]);
18
+ const [historyFileError, setHistoryFileError] = React.useState(null);
19
+ const { loadDatabaseFile } = useLoadDatabaseFile({ source, setHistoryFileError });
20
+ const { LoadHistoryComponent } = database;
21
+ return (
22
+ <>
23
+ <div>
24
+ Imported from
25
+ {' '}
26
+ <a target="_blank" rel="noopener noreferrer" href={database.getSequenceLink(source.database_id)}>{database.name}</a>
27
+ </div>
28
+ {/* If the database interface has a LoadHistoryComponent, show a button to load the history */}
29
+ {LoadHistoryComponent && (
30
+ <>
31
+ {!loadingHistory && (
32
+ <div>
33
+ <Button sx={{ marginTop: 2 }} variant="contained" color="primary" onClick={() => setLoadingHistory(true)}>
34
+ Load history
35
+ </Button>
36
+ </div>
37
+ )}
38
+ {loadingHistory && (
39
+ <>
40
+ <div>
41
+ <LoadHistoryComponent loadDatabaseFile={loadDatabaseFile} handleClose={handleClose} databaseId={source.database_id} />
42
+ </div>
43
+ {historyFileError && <Alert sx={{ marginTop: 2 }} severity="error">{historyFileError}</Alert>}
44
+ </>
45
+ )}
46
+
47
+ </>
48
+ )}
49
+ </>
50
+ );
51
+ }
52
+
53
+ function EuroscarfMessage({ source }) {
54
+ const { repository_id: repositoryId } = source;
55
+ return (
56
+ <>
57
+ Plasmid
58
+ {' '}
59
+ <strong>
60
+ <a href={`http://www.euroscarf.de/plasmid_details.php?accno=${repositoryId}`} target="_blank" rel="noopener noreferrer">
61
+ {repositoryId}
62
+ </a>
63
+ </strong>
64
+ {' '}
65
+ from Euroscarf
66
+ </>
67
+ );
68
+ }
69
+
70
+ function WeKwikGeneMessage({ source }) {
71
+ const { repository_id: repositoryId } = source;
72
+ return (
73
+ <>
74
+ Plasmid
75
+ {' '}
76
+ <strong>
77
+ <a href={`https://wekwikgene.wllsb.edu.cn/plasmids/${repositoryId}`} target="_blank" rel="noopener noreferrer">
78
+ {repositoryId}
79
+ </a>
80
+ </strong>
81
+ {' '}
82
+ from WeKwikGene
83
+ </>
84
+ );
85
+ }
86
+
87
+ function BenchlingMessage({ source }) {
88
+ const { repository_id: repositoryId } = source;
89
+ const editUrl = repositoryId.replace(/\.gb$/, '/edit');
90
+ return (
91
+ <>
92
+ Request to Benchling (
93
+ <strong>
94
+ <a href={editUrl} target="_blank" rel="noopener noreferrer">
95
+ link
96
+ </a>
97
+ </strong>
98
+ )
99
+ </>
100
+ );
101
+ }
102
+
103
+ function SnapGenePlasmidMessage({ source }) {
104
+ const { repository_id: repositoryId } = source;
105
+ const [plasmidSet, plasmidName] = repositoryId.split('/');
106
+ return (
107
+ <>
108
+ Plasmid
109
+ {' '}
110
+ <strong>
111
+ <a href={`https://www.snapgene.com/plasmids/${plasmidSet}/${plasmidName}`} target="_blank" rel="noopener noreferrer">
112
+ {plasmidName}
113
+ </a>
114
+ </strong>
115
+ {' '}
116
+ from SnapGene
117
+ </>
118
+ );
119
+ }
120
+
121
+ function OpenDNACollectionsMessage({ source }) {
122
+ const { repository_id: repositoryId, sequence_file_url: sequenceFileUrl } = source;
123
+ return (
124
+ <>
125
+ Plasmid
126
+ {' '}
127
+
128
+ <a href={sequenceFileUrl.replace('https://assets.opencloning.org/open-dna-collections', 'https://github.com/Reclone-org/open-dna-collections/tree/main')} target="_blank" rel="noopener noreferrer">
129
+ {repositoryId.split('/')[1]}
130
+ </a>
131
+ {' '} from collection {' '}
132
+ <a href={`https://github.com/Reclone-org/open-dna-collections/tree/main/${repositoryId.split('/')[0]}`} target="_blank" rel="noopener noreferrer">
133
+ {repositoryId.split('/')[0]}
134
+ </a>
135
+
136
+ {' '}
137
+ from <a href="https://github.com/Reclone-org/open-dna-collections" target="_blank" rel="noopener noreferrer">Open DNA Collections</a>
138
+ </>
139
+ );
140
+ }
141
+
142
+ function RepositoryIdMessage({ source }) {
143
+ const repositorySlug = repositoryMetadata[source.type].slug;
144
+ let url = '';
145
+ if (repositorySlug === 'genbank') {
146
+ url = `https://www.ncbi.nlm.nih.gov/nuccore/${source.repository_id}`;
147
+ } else if (repositorySlug === 'addgene') {
148
+ url = `https://www.addgene.org/${source.repository_id}/sequences/`;
149
+ }
150
+ return (
151
+ <>
152
+ {`Request to ${repositorySlug || source.type} with ID `}
153
+ <strong>
154
+ <a href={url} target="_blank" rel="noopener noreferrer">
155
+ {source.repository_id}
156
+ </a>
157
+ </strong>
158
+ </>
159
+ );
160
+ }
161
+
162
+ function GatewayMessage({ source }) {
163
+ return `Gateway ${source.reaction_type} reaction`;
164
+ }
165
+
166
+ function PlannotateAnnotationMessage({ source }) {
167
+ const [dialogOpen, setDialogOpen] = React.useState(false);
168
+ return (
169
+ <>
170
+ <div>
171
+ {'Annotation from '}
172
+ <a href="https://github.com/mmcguffi/pLannotate" target="_blank" rel="noopener noreferrer">pLannotate</a>
173
+ </div>
174
+ <Button onClick={() => setDialogOpen(true)}>
175
+ See report
176
+ </Button>
177
+ <PlannotateAnnotationReport dialogOpen={dialogOpen} setDialogOpen={setDialogOpen} report={source.annotation_report} />
178
+ </>
179
+ );
180
+ }
181
+
182
+ function IGEMMessage({ source }) {
183
+ // Split repository_id by the first -, the first part is the part name, the rest is the backbone,
184
+ // but there may be more than one -
185
+ const indexOfDash = source.repository_id.indexOf('-');
186
+ if (indexOfDash === -1) {
187
+ return (
188
+ <>
189
+ {`iGEM plasmid ${source.repository_id} from `}
190
+ <a href="https://airtable.com/appgWgf6EPX5gpnNU/shrb0c8oYTgpZDRgH/tblNqHsHbNNQP2HCX" target="_blank" rel="noopener noreferrer">2024 iGEM Distribution</a>
191
+ </>
192
+ );
193
+ }
194
+ const partName = source.repository_id.substring(0, indexOfDash);
195
+ const backbone = source.repository_id.substring(indexOfDash + 1);
196
+ const indexInCollection = source.sequence_file_url.match(/(\d+)\.gb$/)[1];
197
+ return (
198
+ <>
199
+ <div>
200
+ {'iGEM '}
201
+ <a href={source.sequence_file_url} target="_blank" rel="noopener noreferrer">
202
+ plasmid
203
+ </a>
204
+ {' containing part '}
205
+ <a href={`https://parts.igem.org/Part:${partName}`} target="_blank" rel="noopener noreferrer">{partName}</a>
206
+ {` in backbone ${backbone} from `}
207
+ <a href="https://airtable.com/appgWgf6EPX5gpnNU/shrb0c8oYTgpZDRgH/tblNqHsHbNNQP2HCX" target="_blank" rel="noopener noreferrer">2024 iGEM Distribution</a>
208
+ </div>
209
+ <div style={{ marginTop: '10px' }}>
210
+ {'Annotated with '}
211
+ <a href="https://github.com/mmcguffi/pLannotate" target="_blank" rel="noopener noreferrer">
212
+ pLannotate
213
+ </a>
214
+ {', see report '}
215
+ <a href={`https://github.com/manulera/annotated-igem-distribution/blob/master/results/reports/${indexInCollection}.csv`} target="_blank" rel="noopener noreferrer">here</a>
216
+ </div>
217
+ </>
218
+ );
219
+ }
220
+
221
+ function SEVAPlasmidMessage({ source }) {
222
+ return (
223
+ <div>
224
+ {'SEVA plasmid '}
225
+ <a href={source.sequence_file_url} target="_blank" rel="noopener noreferrer">
226
+ {source.repository_id}
227
+ </a>
228
+ </div>
229
+ );
230
+ }
231
+
232
+ function PCRMessage({ source }) {
233
+ const [dialogOpen, setDialogOpen] = React.useState(false);
234
+ const primers = useSelector((state) => state.cloning.primers, isEqual);
235
+ const { pcrDetails, retryGetPCRDetails, requestStatus } = usePCRDetails([source.id]);
236
+ const [fwdPrimer, rvsPrimer] = [source.input[0].sequence, source.input[2].sequence];
237
+ return (
238
+ <div>
239
+ <div>{`PCR with primers ${primers.find((p) => fwdPrimer === p.id).name} and ${primers.find((p) => rvsPrimer === p.id).name}`}</div>
240
+ <RequestStatusWrapper requestStatus={requestStatus} retry={retryGetPCRDetails}>
241
+ <Button onClick={() => setDialogOpen(true)}>
242
+ See PCR details
243
+ </Button>
244
+ </RequestStatusWrapper>
245
+ {pcrDetails.length > 0 && dialogOpen && (
246
+ <Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="md" fullWidth>
247
+ <DialogContent>
248
+ <PCRTable pcrDetail={pcrDetails[0]} />
249
+ </DialogContent>
250
+ </Dialog>
251
+ )}
252
+ </div>
253
+ );
254
+ }
255
+
256
+ function FileMessage({ source }) {
257
+ const common = `Read from file ${source.file_name}`;
258
+ if (source.coordinates) {
259
+ const coordinates = `then extracted subsequence ${source.coordinates}`;
260
+ return (
261
+ <>
262
+ <div>
263
+ {`${common},`}
264
+ </div>
265
+ <div style={{ marginTop: '5px' }}>
266
+ {coordinates}
267
+ </div>
268
+ </>
269
+ );
270
+ }
271
+ return common;
272
+ }
273
+
274
+ function HomologousRecombinationMessage({ source }) {
275
+ if (source.input.length === 1) {
276
+ return (
277
+ <>
278
+ Excission by homologous recombination.
279
+ </>
280
+ );
281
+ }
282
+ return (
283
+ <>
284
+ Homologous recombination with {source.input[0].sequence} as template and {source.input[1].sequence} as insert.
285
+ </>
286
+ );
287
+ }
288
+
289
+ function GenomeCoordinatesMessage({ source }) {
290
+ const strand = source.coordinates.includes('complement') ? -1 : 1;
291
+ const coordinateString = source.coordinates.replace('complement(','').replace(')','');
292
+ return (
293
+ <>
294
+ <h4 style={{ marginBottom: '5px' }}>Genome region</h4>
295
+ {source.assembly_accession && (
296
+ <div>
297
+ <strong>Assembly:</strong>
298
+ {' '}
299
+ <a href={`https://www.ncbi.nlm.nih.gov/datasets/genome/${source.assembly_accession}`} target="_blank" rel="noopener noreferrer">{source.assembly_accession}</a>
300
+ </div>
301
+ )}
302
+ <div>
303
+ <strong>Coords:</strong>
304
+ {' '}
305
+ <a href={`https://www.ncbi.nlm.nih.gov/nuccore/${source.repository_id}`} target="_blank" rel="noopener noreferrer">{source.repository_id}</a>
306
+ {` (${coordinateString}, ${strand})`}
307
+ </div>
308
+ {source.locus_tag && (
309
+ <div>
310
+ <strong>Locus tag:</strong>
311
+ {' '}
312
+ {source.locus_tag}
313
+ </div>
314
+ )}
315
+ {source.gene_id && (
316
+ <div>
317
+ <strong>Gene ID:</strong>
318
+ {' '}
319
+ <a href={`https://www.ncbi.nlm.nih.gov/gene/${source.gene_id}`} target="_blank" rel="noopener noreferrer">
320
+ {source.gene_id}
321
+ </a>
322
+ </div>
323
+ )}
324
+
325
+ </>
326
+ );
327
+ }
328
+
329
+ function FinishedSource({ sourceId }) {
330
+ const source = useSelector((state) => state.cloning.sources.find((s) => s.id === sourceId), isEqual);
331
+ const primers = useSelector((state) => state.cloning.primers, isEqual);
332
+ let message = '';
333
+ switch (source.type) {
334
+ case 'UploadedFileSource': message = <FileMessage source={source} />; break;
335
+ case 'ManuallyTypedSource': message = 'Manually typed sequence'; break;
336
+ case 'LigationSource': message = (source.input.length === 1) ? 'Circularization of fragment' : 'Ligation of fragments'; break;
337
+ case 'GibsonAssemblySource': message = 'Gibson assembly of fragments'; break;
338
+ case 'OverlapExtensionPCRLigationSource': message = 'Overlap extension PCR ligation'; break;
339
+ case 'InFusionSource': message = 'In-Fusion assembly of fragments'; break;
340
+ case 'CreLoxRecombinationSource': message = 'Cre/Lox recombination'; break;
341
+ case 'InVivoAssemblySource': message = 'In vivo assembly of fragments'; break;
342
+ case 'RestrictionEnzymeDigestionSource': {
343
+ const uniqueEnzymes = enzymesInRestrictionEnzymeDigestionSource(source);
344
+ message = `Restriction with ${uniqueEnzymes.join(' and ')}`;
345
+ }
346
+ break;
347
+ case 'RestrictionAndLigationSource': {
348
+ const uniqueEnzymes = [...new Set(source.restriction_enzymes)];
349
+ uniqueEnzymes.sort();
350
+ message = `Restriction with ${uniqueEnzymes.join(' and ')}, then ligation`;
351
+ }
352
+ break;
353
+ case 'PCRSource': message = <PCRMessage source={source} />; break;
354
+ case 'OligoHybridizationSource':
355
+ message = `Hybridization of primers ${primers.find((p) => source.input[0].sequence === p.id).name} and ${primers.find((p) => source.input[1].sequence === p.id).name}`;
356
+ break;
357
+ case 'HomologousRecombinationSource': message = <HomologousRecombinationMessage source={source} />; break;
358
+ case 'CRISPRSource': {
359
+ const guidesString = source.input.filter(({ type }) => type === 'SourceInput').map(({ sequence }) => primers.find((p) => sequence === p.id).name).join(', ');
360
+ message = `CRISPR HDR with ${source.input[0].sequence} as template, ${source.input[1].sequence} as insert and ${guidesString} as a guide${source.input.length > 3 ? 's' : ''}`;
361
+ }
362
+ break;
363
+ case 'NCBISequenceSource': message = <RepositoryIdMessage source={source} />;
364
+ break;
365
+ case 'AddgeneIdSource': message = <RepositoryIdMessage source={source} />;
366
+ break;
367
+ case 'BenchlingUrlSource': message = <BenchlingMessage source={source} />;
368
+ break;
369
+ case 'EuroscarfSource': message = <EuroscarfMessage source={source} />;
370
+ break;
371
+ case 'SnapGenePlasmidSource': message = <SnapGenePlasmidMessage source={source} />;
372
+ break;
373
+ case 'WekWikGeneIdSource': message = <WeKwikGeneMessage source={source} />;
374
+ break;
375
+ case 'GatewaySource': message = <GatewayMessage source={source} />;
376
+ break;
377
+ case 'OpenDNACollectionsSource': message = <OpenDNACollectionsMessage source={source} />;
378
+ break;
379
+ case 'GenomeCoordinatesSource': message = <GenomeCoordinatesMessage source={source} />;
380
+ break;
381
+ case 'PolymeraseExtensionSource': message = 'Polymerase extension'; break;
382
+ case 'AnnotationSource': message = <PlannotateAnnotationMessage source={source} />; break;
383
+ case 'IGEMSource': message = <IGEMMessage source={source} />; break;
384
+ case 'ReverseComplementSource': message = 'Reverse complement'; break;
385
+ case 'SEVASource': message = <SEVAPlasmidMessage source={source} />; break;
386
+ case 'DatabaseSource': message = <DatabaseMessage source={source} />; break;
387
+ default: message = '';
388
+ }
389
+ return (
390
+ <div className="finished-source">
391
+ <div />
392
+ {message}
393
+ </div>
394
+ );
395
+ }
396
+
397
+ export default React.memo(FinishedSource);
@@ -0,0 +1,50 @@
1
+ import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
2
+ import React from 'react';
3
+
4
+ function KnownSourceErrors({ errors }) {
5
+ const [dialogOpen, setDialogOpen] = React.useState(false);
6
+ return (
7
+ <>
8
+ <Alert
9
+ severity="error"
10
+ action={(
11
+ <Button color="inherit" size="small" onClick={() => setDialogOpen(true)}>
12
+ See how
13
+ </Button>
14
+ )}
15
+ sx={{ alignItems: 'center', mb: 1 }}
16
+ >
17
+ Affected by external errors
18
+ </Alert>
19
+ <Dialog
20
+ open={dialogOpen}
21
+ onClose={() => setDialogOpen(false)}
22
+ >
23
+ <DialogTitle>Known external errors</DialogTitle>
24
+ <DialogContent>
25
+ <ul>
26
+ {errors.map((error, i) => (
27
+ <li style={{ marginBottom: '1em' }} key={i} component="li">
28
+ {error}
29
+ </li>
30
+ ))}
31
+ </ul>
32
+
33
+ </DialogContent>
34
+ <DialogActions>
35
+ <Button
36
+ onClick={() => {
37
+ setDialogOpen(false);
38
+ }}
39
+ >
40
+ Close
41
+ </Button>
42
+
43
+ </DialogActions>
44
+ </Dialog>
45
+ </>
46
+
47
+ );
48
+ }
49
+
50
+ export default KnownSourceErrors;
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import { Box, Chip, InputLabel, MenuItem, Select } from '@mui/material';
4
+ import { isEqual } from 'lodash-es';
5
+ import { getIdsOfSequencesWithoutChildSource } from '@opencloning/store/cloning_utils';
6
+
7
+ function MultipleInputsSelector({ inputSequenceIds, onChange, label }) {
8
+ const sequenceNotChildSourceIds = useSelector(({ cloning }) => getIdsOfSequencesWithoutChildSource(cloning.sources, cloning.sequences), isEqual);
9
+
10
+ // The possible options should include the already selected ones + the one without children
11
+ // we eliminate duplicates (can happen if the change of input does not update the source)
12
+ const options = [...new Set(inputSequenceIds.concat(sequenceNotChildSourceIds))].sort((a, b) => (a - b));
13
+ const sequenceNames = useSelector(({ cloning }) => options.map((id) => ({ id, name: cloning.teselaJsonCache[id]?.name || 'template' })), isEqual);
14
+
15
+ const onInputChange = (event) => {
16
+ const selectedIds = event.target.value;
17
+ if (selectedIds.includes('all')) {
18
+ onChange(options);
19
+ } else {
20
+ onChange(selectedIds);
21
+ }
22
+ };
23
+
24
+ return (
25
+ <>
26
+ <InputLabel id="demo-multiple-chip-label">{label}</InputLabel>
27
+ <Select
28
+ labelId="demo-multiple-chip-label"
29
+ id="demo-multiple-chip"
30
+ multiple
31
+ value={inputSequenceIds}
32
+ onChange={onInputChange}
33
+ label={label}
34
+ // input={<OutlinedInput id="select-multiple-chip" label="Select input sequences" />}
35
+ renderValue={(selected) => (
36
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
37
+ {selected.map((value) => (
38
+ <Chip key={value} label={`${value} - ${sequenceNames.find(({ id }) => id === value).name}`} />
39
+ ))}
40
+ </Box>
41
+ )}
42
+ >
43
+ <MenuItem
44
+ key="all"
45
+ value="all"
46
+ >
47
+ <em>Select all</em>
48
+ </MenuItem>
49
+ {options.map((id, index) => (
50
+ <MenuItem
51
+ key={id}
52
+ value={id}
53
+ >
54
+ {`${id} - ${sequenceNames[index].name}`}
55
+ </MenuItem>
56
+ ))}
57
+
58
+ </Select>
59
+ </>
60
+ );
61
+ }
62
+
63
+ export default MultipleInputsSelector;
@@ -0,0 +1,63 @@
1
+ import { SimpleCircularOrLinearView } from '@teselagen/ove';
2
+ import React from 'react';
3
+ import ArrowForward from '@mui/icons-material/ArrowForward';
4
+ import ArrowBack from '@mui/icons-material/ArrowBack';
5
+ import { Button, IconButton } from '@mui/material';
6
+ import { convertToTeselaJson } from '@opencloning/utils/readNwrite';
7
+ import OverhangsDisplay from '../OverhangsDisplay';
8
+ import SubSequenceDisplayer from './SubSequenceDisplayer';
9
+ import AssemblyPlanDisplayer from './AssemblyPlanDisplayer';
10
+
11
+ function MultipleOutputsSelector({ sources, sequences, sourceId, onFragmentChosen }) {
12
+ // If the output is already set or the list of outputs is empty, do not show this element
13
+ if (sources.length === 0) { return null; }
14
+
15
+ // selectedOutput is a local property, until you commit the step by clicking
16
+ const [selectedOutput, setSelectedOutput] = React.useState(0);
17
+
18
+ // Functions called to move between outputs of a restriction reaction
19
+ const incrementSelectedOutput = () => setSelectedOutput(
20
+ (selectedOutput + 1) % sources.length,
21
+ );
22
+ const decreaseSelectedOutput = () => setSelectedOutput((selectedOutput !== 0) ? (selectedOutput - 1) : sources.length - 1);
23
+
24
+ // The function to pick the fragment as the output, and execute the step
25
+ const chooseFragment = (e) => {
26
+ e.preventDefault();
27
+ onFragmentChosen(selectedOutput);
28
+ };
29
+
30
+ const editorName = `source_editor_${sourceId}`;
31
+
32
+ const seq = convertToTeselaJson(sequences[selectedOutput]);
33
+
34
+ return (
35
+ <div className="multiple-output-selector">
36
+ <div className="multiple-output-selector-navigate">
37
+ <IconButton onClick={decreaseSelectedOutput} type="button" sx={{ height: 'fit-content' }}>
38
+ <ArrowBack />
39
+ </IconButton>
40
+ {selectedOutput + 1}
41
+ {' '}
42
+ /
43
+ {' '}
44
+ {sources.length}
45
+ <IconButton onClick={incrementSelectedOutput} type="button" sx={{ height: 'fit-content' }}>
46
+ <ArrowForward />
47
+ </IconButton>
48
+ </div>
49
+
50
+ <div className="fragment-picker">
51
+ <SubSequenceDisplayer {...{ source: sources[selectedOutput], sourceId }} />
52
+ <AssemblyPlanDisplayer {...{ source: sources[selectedOutput] }} />
53
+ <SimpleCircularOrLinearView {...{ sequenceData: seq, editorName, height: 'auto' }} />
54
+ <OverhangsDisplay {...{ sequenceData: seq, sequence: sequences[selectedOutput] }} />
55
+ </div>
56
+ <form onSubmit={chooseFragment}>
57
+ <Button fullWidth type="submit" variant="contained">Choose product</Button>
58
+ </form>
59
+ </div>
60
+ );
61
+ }
62
+
63
+ export default React.memo(MultipleOutputsSelector);
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import AddCircleIcon from '@mui/icons-material/AddCircle';
3
+ import Tooltip from '@mui/material/Tooltip';
4
+ import { useDispatch } from 'react-redux';
5
+ import { IconButton } from '@mui/material';
6
+ import { cloningActions } from '@opencloning/store/cloning';
7
+
8
+ // A component that is rendered on the side of the tree to add a new source
9
+ function NewSourceBox({ inputSequencesIds = [] }) {
10
+ const dispatch = useDispatch();
11
+ const { addEmptySource } = cloningActions;
12
+ const onClick = () => {
13
+ dispatch(addEmptySource(inputSequencesIds));
14
+ // Scroll to the right to see new source
15
+ if (inputSequencesIds.length === 0) {
16
+ setTimeout(() => {
17
+ const tabPanelsContainer = document.querySelector('.tab-panels-container');
18
+ if (tabPanelsContainer) {
19
+ tabPanelsContainer.scrollTo({
20
+ left: tabPanelsContainer.scrollWidth,
21
+ behavior: 'instant',
22
+ });
23
+ }
24
+ }, 100);
25
+ }
26
+ };
27
+ const tooltipText = <div className="tooltip-text">Add source</div>;
28
+ return (
29
+ <IconButton type="submit" sx={{ height: 'fit-content' }} onClick={onClick}>
30
+ <Tooltip title={tooltipText} arrow placement="bottom">
31
+ <AddCircleIcon sx={{ fontSize: '1.8em' }} className="node-corner-icon" color="success" />
32
+ </Tooltip>
33
+ </IconButton>
34
+ );
35
+ }
36
+
37
+ export default NewSourceBox;