@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,252 @@
1
+ import React from 'react'
2
+ import { TextField, FormControl, InputLabel, Select, MenuItem, Box, Grid, Paper, Typography, Table, TableContainer, TableHead, TableBody, TableRow, TableCell, Button } from '@mui/material'
3
+ import ContentCopyIcon from '@mui/icons-material/ContentCopy'
4
+ import AssemblerPart from './AssemblerPart'
5
+
6
+ /* eslint-disable camelcase */
7
+ const defaultData = {
8
+ header: 'Header',
9
+ body: 'helper text / body text',
10
+ glyph: 'cds-stop',
11
+ left_overhang: 'CATG',
12
+ right_overhang: 'TATG',
13
+ left_inside: 'AAAATA',
14
+ right_inside: 'AATG',
15
+ left_codon_start: 2,
16
+ right_codon_start: 1,
17
+ color: 'greenyellow',
18
+ }
19
+ /* eslint-enable camelcase */
20
+
21
+ const glyphOptions = [
22
+ 'assembly-scar',
23
+ 'cds',
24
+ 'cds-stop',
25
+ 'chromosomal-locus',
26
+ 'engineered-region',
27
+ 'five-prime-sticky-restriction-site',
28
+ 'origin-of-replication',
29
+ 'primer-binding-site',
30
+ 'promoter',
31
+ 'ribosome-entry-site',
32
+ 'specific-recombination-site',
33
+ 'terminator',
34
+ 'three-prime-sticky-restriction-site',
35
+ ]
36
+
37
+ function AssemblePartWidget() {
38
+ const [formData, setFormData] = React.useState(defaultData)
39
+
40
+ const handleChange = (field) => (event) => {
41
+ const value = event.target.value
42
+ setFormData((prev) => ({
43
+ ...prev,
44
+ [field]: field === 'left_codon_start' || field === 'right_codon_start'
45
+ ? (value === '' ? '' : parseInt(value, 10) || 0)
46
+ : value,
47
+ }))
48
+ }
49
+
50
+ const handleCopyRow = async () => {
51
+ const keys = Object.keys(formData)
52
+ const headers = keys.join('\t')
53
+ const values = keys.map((key) => String(formData[key])).join('\t')
54
+ const tsvData = `${headers}\n${values}`
55
+
56
+ try {
57
+ if (window.navigator && window.navigator.clipboard) {
58
+ await window.navigator.clipboard.writeText(tsvData)
59
+ }
60
+ } catch (err) {
61
+ // eslint-disable-next-line no-console
62
+ console.error('Failed to copy to clipboard:', err)
63
+ }
64
+ }
65
+
66
+ return (
67
+ <Box sx={{
68
+ p: 1.5,
69
+ maxHeight: '100vh',
70
+ overflowY: 'auto',
71
+ overflowX: 'hidden'
72
+ }}>
73
+ <Grid container spacing={2}>
74
+ <Grid item xs={12} md={6}>
75
+ <Paper sx={{ p: 1.5 }}>
76
+ <Typography variant="h6" gutterBottom sx={{ mb: 1.5 }}>
77
+ Part Configuration
78
+ </Typography>
79
+ <Box component="form" sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
80
+ <TextField
81
+ size="small"
82
+ label="Header"
83
+ value={formData.header}
84
+ onChange={handleChange('header')}
85
+ fullWidth
86
+ />
87
+ <TextField
88
+ size="small"
89
+ label="Body"
90
+ value={formData.body}
91
+ onChange={handleChange('body')}
92
+ fullWidth
93
+ multiline
94
+ rows={2}
95
+ />
96
+ <FormControl fullWidth size="small">
97
+ <InputLabel id="glyph-select-label">Glyph</InputLabel>
98
+ <Select
99
+ labelId="glyph-select-label"
100
+ value={formData.glyph}
101
+ label="Glyph"
102
+ onChange={handleChange('glyph')}
103
+ >
104
+ {glyphOptions.map((option) => (
105
+ <MenuItem key={option} value={option}>
106
+ {option}
107
+ </MenuItem>
108
+ ))}
109
+ </Select>
110
+ </FormControl>
111
+ <TextField
112
+ size="small"
113
+ label="Color"
114
+ value={formData.color}
115
+ onChange={handleChange('color')}
116
+ fullWidth
117
+ helperText="CSS color name or hex code"
118
+ />
119
+ <Typography variant="subtitle2" sx={{ mt: 0.5, mb: 0.5 }}>
120
+ Left Side
121
+ </Typography>
122
+ <TextField
123
+ size="small"
124
+ label="Left Overhang"
125
+ value={formData.left_overhang}
126
+ onChange={handleChange('left_overhang')}
127
+ error={formData.left_overhang.length !== 4}
128
+ helperText={formData.left_overhang.length !== 4 ? 'Must be 4 bases' : ''}
129
+ fullWidth
130
+ />
131
+ <TextField
132
+ size="small"
133
+ label="Left Inside"
134
+ value={formData.left_inside}
135
+ onChange={handleChange('left_inside')}
136
+ fullWidth
137
+ />
138
+ <TextField
139
+ size="small"
140
+ label="Left Codon Start"
141
+ type="number"
142
+ value={formData.left_codon_start}
143
+ onChange={handleChange('left_codon_start')}
144
+ fullWidth
145
+ inputProps={{ min: 0 }}
146
+ helperText="If the left side is translated, where the codon starts"
147
+ />
148
+ <Typography variant="subtitle2" sx={{ mt: 0.5, mb: 0.5 }}>
149
+ Right Side
150
+ </Typography>
151
+ <TextField
152
+ size="small"
153
+ label="Right Overhang"
154
+ value={formData.right_overhang}
155
+ onChange={handleChange('right_overhang')}
156
+ fullWidth
157
+ />
158
+ <TextField
159
+ size="small"
160
+ label="Right Inside"
161
+ value={formData.right_inside}
162
+ onChange={handleChange('right_inside')}
163
+ fullWidth
164
+ />
165
+ <TextField
166
+ size="small"
167
+ label="Right Codon Start"
168
+ type="number"
169
+ value={formData.right_codon_start}
170
+ onChange={handleChange('right_codon_start')}
171
+ fullWidth
172
+ inputProps={{ min: 0 }}
173
+ helperText="If the right side is translated, where the codon starts"
174
+ />
175
+ </Box>
176
+ </Paper>
177
+ </Grid>
178
+ <Grid item xs={12} md={6}>
179
+ <Paper sx={{ p: 1.5 }}>
180
+ <Typography variant="h6" gutterBottom sx={{ mb: 1.5 }}>
181
+ Preview
182
+ </Typography>
183
+ <Box sx={{ mt: 1, display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
184
+ {(formData.header || formData.body) && (
185
+ <Box sx={{
186
+ textAlign: 'center',
187
+ mb: 1.5,
188
+ display: 'flex',
189
+ flexDirection: 'column',
190
+ gap: 0.5
191
+ }}>
192
+ {formData.header && (
193
+ <Typography variant="h6" sx={{ fontWeight: 'bold' }}>
194
+ {formData.header}
195
+ </Typography>
196
+ )}
197
+ {formData.body && (
198
+ <Typography variant="body2" sx={{ color: 'text.secondary' }}>
199
+ {formData.body}
200
+ </Typography>
201
+ )}
202
+ </Box>
203
+ )}
204
+ <AssemblerPart data={formData} />
205
+ </Box>
206
+ </Paper>
207
+ </Grid>
208
+ </Grid>
209
+ <Box sx={{ mt: 2 }}>
210
+ <Paper sx={{ p: 1.5 }}>
211
+ <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1.5 }}>
212
+ <Typography variant="h6">
213
+ JSON Data
214
+ </Typography>
215
+ <Button
216
+ size="small"
217
+ variant="outlined"
218
+ startIcon={<ContentCopyIcon />}
219
+ onClick={handleCopyRow}
220
+ >
221
+ Copy Row
222
+ </Button>
223
+ </Box>
224
+ <TableContainer>
225
+ <Table size="small" sx={{ '& .MuiTableCell-root': { py: 0.5, px: 1 } }}>
226
+ <TableHead>
227
+ <TableRow>
228
+ {Object.keys(formData).map((key) => (
229
+ <TableCell key={key} sx={{ fontWeight: 'bold' }}>
230
+ {key}
231
+ </TableCell>
232
+ ))}
233
+ </TableRow>
234
+ </TableHead>
235
+ <TableBody>
236
+ <TableRow>
237
+ {Object.keys(formData).map((key) => (
238
+ <TableCell key={key}>
239
+ {String(formData[key])}
240
+ </TableCell>
241
+ ))}
242
+ </TableRow>
243
+ </TableBody>
244
+ </Table>
245
+ </TableContainer>
246
+ </Paper>
247
+ </Box>
248
+ </Box>
249
+ )
250
+ }
251
+
252
+ export default AssemblePartWidget
@@ -0,0 +1,273 @@
1
+ import React from 'react'
2
+ import data2 from './assembler_data2.json'
3
+ import { Alert, Autocomplete, Box, Button, CircularProgress, FormControl, IconButton, InputAdornment, InputLabel, MenuItem, Select, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField } from '@mui/material'
4
+ import ClearIcon from '@mui/icons-material/Clear';
5
+ import { useAssembler } from './useAssembler';
6
+ import { arrayCombinations } from '../eLabFTW/utils';
7
+ import VisibilityIcon from '@mui/icons-material/Visibility';
8
+ import { useDispatch } from 'react-redux';
9
+ import { cloningActions } from '@opencloning/store/cloning';
10
+ import RequestStatusWrapper from '../form/RequestStatusWrapper';
11
+ import useHttpClient from '../../hooks/useHttpClient';
12
+ import AssemblerPart from './AssemblerPart';
13
+
14
+
15
+ const { setState: setCloningState, setCurrentTab: setCurrentTabAction } = cloningActions;
16
+
17
+ const categoryFilter = (category, previousCategory) => {
18
+ if (previousCategory === '') {
19
+ return category.startsWith('A_')
20
+ }
21
+ return previousCategory.split('_')[1] === category.split('_')[0]
22
+ }
23
+
24
+ function AssemblerLink({ overhang }) {
25
+ return (
26
+ <Box sx={{ display: 'flex', alignItems: 'center', minWidth: '80px' }}>
27
+ <Box sx={{ flex: 1, height: '2px', bgcolor: 'primary.main' }} />
28
+ <Box sx={{ mx: 1, px: 1, py: 0.5, bgcolor: 'background.paper', border: 1, borderColor: 'primary.main', borderRadius: 1, fontSize: '0.75rem', fontWeight: 'bold' }}>
29
+ {overhang}
30
+ </Box>
31
+ <Box sx={{ flex: 1, height: '2px', bgcolor: 'primary.main' }} />
32
+ </Box>
33
+ )
34
+ }
35
+
36
+ function formatItemName(item) {
37
+ if (item.plasmid_name && item.id !== item.plasmid_name) {
38
+ return `${item.id} (${item.plasmid_name})`
39
+ }
40
+ return item.id
41
+ }
42
+
43
+ function AssemblerComponent({ data, categories }) {
44
+
45
+ const [assembly, setAssembly] = React.useState([{ category: '', id: [] }])
46
+ const { requestSources, requestAssemblies } = useAssembler()
47
+ const [requestedAssemblies, setRequestedAssemblies] = React.useState([])
48
+ const [loadingMessage, setLoadingMessage] = React.useState('')
49
+ const [errorMessage, setErrorMessage] = React.useState('')
50
+ const dispatch = useDispatch()
51
+ const onSubmitAssembly = async () => {
52
+ clearAssembly()
53
+ const sources = assembly.map(({ id }) => id.map((id) => (data.find((item) => item.id === id).source)))
54
+ let errorMessage = 'Error fetching sequences'
55
+ try {
56
+ setLoadingMessage('Requesting sequences...')
57
+ const resp = await requestSources(sources)
58
+ errorMessage = 'Error assembling sequences'
59
+ setLoadingMessage('Assembling...')
60
+ const assemblies = await requestAssemblies(resp)
61
+ setRequestedAssemblies(assemblies)
62
+ } catch (e) {
63
+ setErrorMessage(errorMessage)
64
+ } finally {
65
+ setLoadingMessage(false)
66
+ }
67
+ }
68
+
69
+ const clearAssembly = () => {
70
+ setRequestedAssemblies([])
71
+ setErrorMessage('')
72
+ }
73
+
74
+ const setCategory = (category, index) => {
75
+ clearAssembly()
76
+ if (category === '') {
77
+ const newAssembly = assembly.slice(0, index)
78
+ newAssembly[index] = { category: '', id: [] }
79
+ setAssembly(newAssembly)
80
+ return
81
+ }
82
+ setAssembly(assembly.map((item, i) => i === index ? { category, id: [] } : item))
83
+ }
84
+ const setId = (idArray, index) => {
85
+ clearAssembly()
86
+ // Handle case where user clears all selections (empty array)
87
+ if (!idArray || idArray.length === 0) {
88
+ setAssembly(assembly.map((item, i) => i === index ? { ...item, id: [] } : item))
89
+ return
90
+ }
91
+
92
+ // For multiple selection, we need to determine the category based on the first selected item
93
+ // or maintain the current category if it's already set
94
+ const currentItem = assembly[index]
95
+ const firstOption = data.find((item) => item.id === idArray[0])
96
+ const category = currentItem.category || firstOption?.category || ''
97
+
98
+ setAssembly(assembly.map((item, i) => i === index ? { id: idArray, category } : item))
99
+ }
100
+
101
+ const handleViewAssembly = (index) => {
102
+ const newState = requestedAssemblies[index]
103
+ dispatch(setCloningState(newState))
104
+ dispatch(setCurrentTabAction(0))
105
+ }
106
+
107
+ React.useEffect(() => {
108
+ const lastPosition = assembly.length - 1
109
+ if (assembly[lastPosition].category.endsWith('A')) {
110
+ return
111
+ }
112
+ if (assembly[lastPosition].category !== '') {
113
+ const newAssembly = [...assembly, { category: '', id: [] }]
114
+ setAssembly(newAssembly)
115
+ }
116
+ }, [assembly])
117
+
118
+ const expandedAssemblies = arrayCombinations(assembly.map(({ id }) => id))
119
+ const assemblyComplete = assembly.every((item) => item.category !== '' && item.id.length > 0)
120
+ const currentCategories = assembly.map((item) => item.category)
121
+
122
+ return (
123
+ <Box className="assembler-container" sx={{ width: '80%', margin: 'auto', mb: 4 }}>
124
+ <Alert severity="warning" sx={{ maxWidth: '400px', margin: 'auto', fontSize: '.9rem' }}>
125
+ The Assembler is experimental. Use with caution.
126
+ </Alert>
127
+
128
+ <Stack direction="row" alignItems="center" spacing={1} sx={{ overflowX: 'auto', my: 2 }}>
129
+ {assembly.map((item, index) => {
130
+ const allowedCategories = item.category ? [item.category] : categories.filter((category) => categoryFilter(category, index === 0 ? '' : assembly[index - 1].category))
131
+ const isCompleted = item.category !== '' && item.id.length > 0
132
+ const borderColor = isCompleted ? 'success.main' : 'primary.main'
133
+ const leftOverhang = data.find((d) => d.category === item.category)?.left_overhang
134
+ const rightOverhang = data.find((d) => d.category === item.category)?.right_overhang
135
+
136
+ return (
137
+ <React.Fragment key={index}>
138
+ {/* Link before first box */}
139
+ {index === 0 && item.category !== '' && (
140
+ <AssemblerLink overhang={leftOverhang} />
141
+ )}
142
+ <AssemblerPart />
143
+ <Box sx={{ width: '250px', border: 3, borderColor, borderRadius: 4, p: 2 }}>
144
+ <FormControl fullWidth sx={{ mb: 2 }}>
145
+ <InputLabel>Category</InputLabel>
146
+ <Select
147
+ endAdornment={item.category && (<InputAdornment position="end"><IconButton onClick={() => setCategory('', index)}><ClearIcon /></IconButton></InputAdornment>)}
148
+ value={item.category}
149
+ onChange={(e) => setCategory(e.target.value, index)}
150
+ label="Category"
151
+ disabled={index < assembly.length - 1}
152
+ >
153
+ {allowedCategories.map((category) => (
154
+ <MenuItem key={category} value={category}>{category === 'F_A' ? 'Backbone' : category}</MenuItem>
155
+ ))}
156
+ </Select>
157
+ </FormControl>
158
+ <FormControl fullWidth>
159
+ <Autocomplete
160
+ multiple
161
+ value={item.id}
162
+ onChange={(e, value) => setId(value, index)}
163
+ label="ID"
164
+ options={data.filter((d) => allowedCategories.includes(d.category)).map((item) => item.id)}
165
+ getOptionLabel={(id) => formatItemName(data.find((d) => d.id === id))}
166
+ renderInput={(params) => <TextField {...params} label="ID" />}
167
+ />
168
+ </FormControl>
169
+ {leftOverhang && rightOverhang && (
170
+ <Box sx={{ display: 'flex', justifyContent: 'center' }}>
171
+ <AssemblerPart data={ { left_overhang: leftOverhang, right_overhang: rightOverhang }}/>
172
+ </Box>
173
+ )}
174
+ </Box>
175
+
176
+ {/* Link between boxes */}
177
+ {index < assembly.length - 1 && item.category !== '' && (
178
+ <AssemblerLink overhang={rightOverhang} />
179
+ )}
180
+
181
+ {/* Link after last box */}
182
+ {index === assembly.length - 1 && item.category !== '' && (
183
+ <AssemblerLink overhang={rightOverhang} />
184
+ )}
185
+ </React.Fragment>
186
+ )
187
+ })}
188
+ </Stack>
189
+ {assemblyComplete && <>
190
+ <Button
191
+ sx={{ p: 2, px: 4, my: 2, fontSize: '1.2rem' }}
192
+ variant="contained"
193
+ color="primary"
194
+ onClick={onSubmitAssembly}
195
+ disabled={Boolean(loadingMessage)}>
196
+ {loadingMessage ? <><CircularProgress /> {loadingMessage}</> : 'Submit'}
197
+ </Button>
198
+ </>}
199
+ {errorMessage && <Alert severity="error" sx={{ my: 2, maxWidth: 300, margin: 'auto', fontSize: '1.2rem' }}>{errorMessage}</Alert>}
200
+ {requestedAssemblies.length > 0 &&
201
+ <TableContainer sx={{ '& td': { fontSize: '1.2rem' }, '& th': { fontSize: '1.2rem' } }}>
202
+ <Table size="small">
203
+ <TableHead>
204
+ <TableRow>
205
+ <TableCell padding="checkbox" />
206
+ {currentCategories.map(category => (
207
+ <TableCell key={category} sx={{ fontWeight: 'bold' }}>
208
+ {category === 'F_A' ? 'Backbone' : category}
209
+ </TableCell>
210
+ ))}
211
+ </TableRow>
212
+ </TableHead>
213
+ <TableBody>
214
+ {expandedAssemblies.map((parts, rowIndex) => (
215
+ <TableRow key={rowIndex}>
216
+ <TableCell padding="checkbox">
217
+ <IconButton onClick={() => handleViewAssembly(rowIndex)} size="small">
218
+ <VisibilityIcon />
219
+ </IconButton>
220
+ </TableCell>
221
+ {parts.map((part, colIndex) => (
222
+ <TableCell key={colIndex}>
223
+ {formatItemName(data.find((d) => d.id === part))}
224
+ </TableCell>
225
+ ))}
226
+
227
+
228
+ </TableRow>
229
+ ))}
230
+ </TableBody>
231
+ </Table>
232
+ </TableContainer>
233
+ }
234
+
235
+ </Box >
236
+ )
237
+ }
238
+
239
+ function Assembler() {
240
+ const [requestStatus, setRequestStatus] = React.useState({ status: 'loading' })
241
+ const [retry, setRetry] = React.useState(0)
242
+ const [data, setData] = React.useState([])
243
+ const [categories, setCategories] = React.useState([])
244
+ const httpClient = useHttpClient()
245
+ React.useEffect(() => {
246
+ setRequestStatus({ status: 'loading' })
247
+ const fetchData = async () => {
248
+ try {
249
+ const { data } = await httpClient.get('https://assets.opencloning.org/open-dna-collections/scripts/index_overhangs.json')
250
+ const formattedData = data.map((item) => ({
251
+ ...item,
252
+ category: data2.find((item2) => item2.overhang === item.left_overhang).name + '_' + data2.find((item2) => item2.overhang === item.right_overhang).name
253
+ }))
254
+
255
+ const categories = [...new Set(formattedData.map((item) => item.category))].sort()
256
+ setData(formattedData)
257
+ setCategories(categories)
258
+ setRequestStatus({ status: 'success' })
259
+ } catch (error) {
260
+ setRequestStatus({ status: 'error', message: 'Could not load assembler data' })
261
+ }
262
+ }
263
+ fetchData()
264
+
265
+ }, [retry])
266
+ return (
267
+ <RequestStatusWrapper requestStatus={requestStatus} retry={() => setRetry((prev) => prev + 1)}>
268
+ <AssemblerComponent data={data} categories={categories} />
269
+ </RequestStatusWrapper>
270
+ )
271
+ }
272
+
273
+ export default Assembler
@@ -0,0 +1,99 @@
1
+ import React from 'react'
2
+ import styles from './assembly_component.module.css'
3
+
4
+ import { getAminoAcidFromSequenceTriplet, getComplementSequenceString } from '@teselagen/sequence-utils'
5
+ import { getSvgByGlyph } from './sbol_visual_glyphs'
6
+
7
+
8
+ const defaultData =
9
+ {
10
+ header: 'Promoter',
11
+ body: 'promoter text',
12
+ glyph: 'cds-stop',
13
+ left_overhang: 'CATG',
14
+ right_overhang: 'TATG',
15
+ left_inside:'AAAATA',
16
+ right_inside:'AATG',
17
+ left_codon_start: 2,
18
+ right_codon_start: 1,
19
+ color: 'greenyellow',
20
+ }
21
+
22
+
23
+ function AssemblerPart( { data = defaultData } ) {
24
+ const {
25
+ left_codon_start: leftCodonStart,
26
+ right_codon_start: rightCodonStart,
27
+ left_overhang: leftOverhang,
28
+ right_overhang: rightOverhang,
29
+ left_inside: leftInside,
30
+ right_inside: rightInside,
31
+ glyph
32
+ } = data
33
+ const leftOverhangRc = getComplementSequenceString(leftOverhang)
34
+ const rightOverhangRc = getComplementSequenceString(rightOverhang)
35
+ const leftInsideRc = getComplementSequenceString(leftInside)
36
+ const rightInsideRc = getComplementSequenceString(rightInside)
37
+ let leftTranslationOverhang = ''
38
+ let leftTranslationInside = ''
39
+ if (leftCodonStart) {
40
+ const triplets = (leftOverhang + leftInside).slice(leftCodonStart - 1).match(/.{3}/g)
41
+ const padding = ' '.repeat(leftCodonStart - 1)
42
+ const translationLeft = padding + triplets.map(triplet => getAminoAcidFromSequenceTriplet(triplet).threeLettersName.replace('Stop', '***')).join('')
43
+ leftTranslationOverhang = translationLeft.slice(0, leftOverhang.length)
44
+ leftTranslationInside = translationLeft.slice(leftOverhang.length)
45
+ }
46
+ let rightTranslationOverhang = ''
47
+ let rightTranslationInside = ''
48
+ if (rightCodonStart) {
49
+ const triplets = (rightInside + rightOverhang).slice(rightCodonStart - 1).match(/.{3}/g)
50
+ const padding = ' '.repeat(rightCodonStart - 1)
51
+ const translationRight = padding + triplets.map(triplet => getAminoAcidFromSequenceTriplet(triplet).threeLettersName.replace('Stop', '***')).join('')
52
+ rightTranslationInside = translationRight.slice(0, rightInside.length)
53
+ rightTranslationOverhang = translationRight.slice(rightInside.length)
54
+ }
55
+
56
+ return (
57
+
58
+ <div className={styles.container}>
59
+ <div className={`${styles.dna} ${styles.overhang} ${styles.left}`}>
60
+ <div className={styles.top}>{leftTranslationOverhang}</div>
61
+ <div className={styles.watson}>{leftOverhang}</div>
62
+ <div className={styles.crick}>{leftOverhangRc}</div>
63
+ <div className={styles.bottom}> </div>
64
+ </div>
65
+ {leftInside && (
66
+ <div className={`${styles.dna} ${styles.insideLeft}`}>
67
+ <div className={styles.top}>{leftTranslationInside}</div>
68
+ <div className={styles.watson}>{leftInside}</div>
69
+ <div className={styles.crick}>{leftInsideRc}</div>
70
+ <div className={styles.bottom}> </div>
71
+ </div>
72
+ )}
73
+ <div className={styles.boxContainer}>
74
+ <div className={styles.box}>
75
+ <div className={styles.imageContainer} style={{ backgroundColor: data.color || 'lightgray' }}>
76
+ <img src={getSvgByGlyph(glyph)} alt={`${glyph}.svg`} />
77
+ </div>
78
+ </div>
79
+ </div>
80
+ {rightInside && (
81
+ <div className={`${styles.dna} ${styles.insideRight}`}>
82
+ <div className={styles.top}>{rightTranslationInside}</div>
83
+ <div className={styles.watson}>{rightInside}</div>
84
+ <div className={styles.crick}>{rightInsideRc}</div>
85
+ <div className={styles.bottom}> </div>
86
+ </div>
87
+ )}
88
+ <div className={`${styles.dna} ${styles.overhang} ${styles.right}`}>
89
+ <div className={styles.top}>{rightTranslationOverhang}</div>
90
+ <div className={styles.watson}>{rightOverhang}</div>
91
+ <div className={styles.crick}>{rightOverhangRc}</div>
92
+ <div className={styles.bottom}> </div>
93
+ </div>
94
+ </div>
95
+
96
+ )
97
+ }
98
+
99
+ export default AssemblerPart
@@ -0,0 +1,34 @@
1
+ import React from 'react'
2
+
3
+ function StopIcon({ color = 'currentColor', size = 24, ...props }) {
4
+ return (
5
+ <svg xmlns="http://www.w3.org/2000/svg"
6
+ viewBox="0 0 24 24"
7
+ width={size} height={size}
8
+ role="img"
9
+ aria-label="Stop sign icon"
10
+ {...props}
11
+ >
12
+ <mask id="stop-mask">
13
+ {/* Octagon area (visible part) */}
14
+ <polygon fill="white"
15
+ points="7.07,2 16.93,2 22,7.07 22,16.93 16.93,22 7.07,22 2,16.93 2,7.07" />
16
+ {/* Text area (cut out) */}
17
+ <text x="12" y="12.3"
18
+ fill="black"
19
+ fontFamily="Arial, Helvetica, sans-serif"
20
+ fontWeight="700"
21
+ fontSize="7.2"
22
+ textAnchor="middle"
23
+ dominantBaseline="middle">STOP</text>
24
+ </mask>
25
+
26
+ {/* Octagon using mask */}
27
+ <rect width="24" height="24" fill={color} mask="url(#stop-mask)" />
28
+ </svg>
29
+ )
30
+ }
31
+
32
+ export default StopIcon
33
+
34
+