@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.
- package/CHANGELOG.md +18 -0
- package/package.json +37 -0
- package/src/components/AppAlerts.jsx +19 -0
- package/src/components/CloningHistory.jsx +40 -0
- package/src/components/DataModelDisplayer.jsx +30 -0
- package/src/components/DescriptionEditor.jsx +68 -0
- package/src/components/DownloadCloningStrategyDialog.jsx +84 -0
- package/src/components/DownloadSequenceFileDialog.jsx +90 -0
- package/src/components/DragAndDropCloningHistoryWrapper.jsx +39 -0
- package/src/components/DraggableDialogPaper.jsx +16 -0
- package/src/components/EditSequenceNameDialog.jsx +90 -0
- package/src/components/ExternalServicesStatusCheck.jsx +92 -0
- package/src/components/HistoryLoadedDialog.jsx +49 -0
- package/src/components/LoadCloningHistoryWrapper.jsx +166 -0
- package/src/components/MainSequenceCheckBox.jsx +83 -0
- package/src/components/MainSequenceEditor.jsx +165 -0
- package/src/components/NetworkNode.jsx +159 -0
- package/src/components/NetworkTree.css +127 -0
- package/src/components/ObjectTable.jsx +24 -0
- package/src/components/OpenCloning.jsx +102 -0
- package/src/components/OverhangsDisplay.jsx +25 -0
- package/src/components/SequenceEditor.jsx +120 -0
- package/src/components/SequenceTab.jsx +14 -0
- package/src/components/TemplateSequence.jsx +38 -0
- package/src/components/annotation/PlannotateAnnotationReport.jsx +33 -0
- package/src/components/annotation/useUpdateAnnotationInMainSequence.js +39 -0
- package/src/components/assembler/AssemblePartWidget.jsx +252 -0
- package/src/components/assembler/Assembler.jsx +273 -0
- package/src/components/assembler/AssemblerPart.jsx +99 -0
- package/src/components/assembler/StopIcon.jsx +34 -0
- package/src/components/assembler/assembler_data2.json +50 -0
- package/src/components/assembler/assembly_component.module.css +81 -0
- package/src/components/assembler/moclo.json +110 -0
- package/src/components/assembler/sbol_visual_glyphs/LICENSE.html +21 -0
- package/src/components/assembler/sbol_visual_glyphs/assembly-scar.svg +63 -0
- package/src/components/assembler/sbol_visual_glyphs/cds-stop.svg +85 -0
- package/src/components/assembler/sbol_visual_glyphs/cds.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/chromosomal-locus.svg +78 -0
- package/src/components/assembler/sbol_visual_glyphs/engineered-region.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/five-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/origin-of-replication.svg +57 -0
- package/src/components/assembler/sbol_visual_glyphs/primer-binding-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/promoter.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/ribosome-entry-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/specific-recombination-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/terminator.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/three-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs.js +36 -0
- package/src/components/assembler/useAssembler.js +71 -0
- package/src/components/dummy/DummyInterface.js +41 -0
- package/src/components/dummy/GetSequenceFileAndDatabaseIdComponent.jsx +59 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.cy.jsx +86 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.cy.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.jsx +29 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.cy.jsx +107 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.jsx +23 -0
- package/src/components/eLabFTW/GetPrimerComponent.cy.jsx +261 -0
- package/src/components/eLabFTW/GetPrimerComponent.jsx +55 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.cy.jsx +184 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.jsx +62 -0
- package/src/components/eLabFTW/LoadHistoryComponent.cy.jsx +235 -0
- package/src/components/eLabFTW/LoadHistoryComponent.jsx +51 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.cy.jsx +159 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.jsx +54 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.cy.jsx +185 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.jsx +51 -0
- package/src/components/eLabFTW/common.js +26 -0
- package/src/components/eLabFTW/eLabFTWInterface.js +294 -0
- package/src/components/eLabFTW/eLabFTWInterface.test.js +839 -0
- package/src/components/eLabFTW/envValues.js +7 -0
- package/src/components/eLabFTW/utils.js +39 -0
- package/src/components/form/CustomFormHelperText.jsx +10 -0
- package/src/components/form/EnzymeMultiSelect.cy.jsx +61 -0
- package/src/components/form/EnzymeMultiSelect.jsx +34 -0
- package/src/components/form/GetRequestMultiSelect.jsx +107 -0
- package/src/components/form/LabelWithTooltip.jsx +16 -0
- package/src/components/form/PostRequestSelect.cy.jsx +70 -0
- package/src/components/form/PostRequestSelect.jsx +86 -0
- package/src/components/form/RequestStatusWrapper.jsx +17 -0
- package/src/components/form/RetryAlert.jsx +20 -0
- package/src/components/form/ServerErrorMessage.jsx +10 -0
- package/src/components/form/SubmitButtonBackendAPI.jsx +15 -0
- package/src/components/form/SubmitToDatabaseDialog.jsx +133 -0
- package/src/components/form/TextFieldValidate.jsx +67 -0
- package/src/components/form/ValidatedTextField.jsx +33 -0
- package/src/components/form/intermediates_disclaimer.svg +181 -0
- package/src/components/navigation/ButtonWithMenu.jsx +43 -0
- package/src/components/navigation/CustomTab.jsx +14 -0
- package/src/components/navigation/FeedbackDialog.jsx +34 -0
- package/src/components/navigation/GithubCornerRight.jsx +29 -0
- package/src/components/navigation/MainAppBar.css +26 -0
- package/src/components/navigation/MainAppBar.jsx +205 -0
- package/src/components/navigation/SelectExampleDialog.jsx +69 -0
- package/src/components/navigation/SelectTemplateDialog.jsx +107 -0
- package/src/components/navigation/TabPanel.jsx +28 -0
- package/src/components/navigation/VersionDialog.jsx +33 -0
- package/src/components/primers/CreatePrimerFromSequenceForm.jsx +42 -0
- package/src/components/primers/DownloadPrimersButton.jsx +104 -0
- package/src/components/primers/PrimerForm.css +14 -0
- package/src/components/primers/PrimerForm.jsx +107 -0
- package/src/components/primers/PrimerList.css +46 -0
- package/src/components/primers/PrimerList.cy.jsx +95 -0
- package/src/components/primers/PrimerList.jsx +126 -0
- package/src/components/primers/PrimerTableRow.cy.jsx +57 -0
- package/src/components/primers/PrimerTableRow.jsx +84 -0
- package/src/components/primers/SelectPrimerForm.jsx +66 -0
- package/src/components/primers/import_primers/ImportPrimersButton.jsx +101 -0
- package/src/components/primers/import_primers/ImportPrimersTable.jsx +51 -0
- package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +107 -0
- package/src/components/primers/import_primers/styles.css +60 -0
- package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/GatewayRoiSelect.jsx +164 -0
- package/src/components/primers/primer_design/SequenceTabComponents/OrientationPicker.jsx +37 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignContext.jsx +369 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignEBIC.jsx +24 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +29 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGatewayBP.jsx +36 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx +26 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignHomologousRecombination.jsx +32 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignRestriction.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignSimplePair.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignStepper.jsx +53 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesigner.jsx +80 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerResultForm.jsx +49 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSettingsForm.jsx +68 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSpacerForm.jsx +84 -0
- package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +85 -0
- package/src/components/primers/primer_design/SequenceTabComponents/StepNavigation.jsx +48 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelEBICSettings.jsx +216 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelResults.jsx +42 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelSelectRoi.jsx +61 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPannelSettings.jsx +59 -0
- package/src/components/primers/primer_design/SequenceTabComponents/primerDesignMinimalValues.json +5 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEBICPrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEnzymePrimerDesignSettings.js +57 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useGatewayPrimerDesignSettings.js +18 -0
- package/src/components/primers/primer_design/SequenceTabComponents/usePrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGatewayBP.jsx +88 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.jsx +65 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignHomologousRecombination.jsx +84 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignSourceForm.jsx +74 -0
- package/src/components/primers/primer_design/common/NoAttPSitesError.jsx +31 -0
- package/src/components/primers/primer_details/PCRTable.cy.jsx +51 -0
- package/src/components/primers/primer_details/PCRTable.jsx +35 -0
- package/src/components/primers/primer_details/Primer3Figure.jsx +25 -0
- package/src/components/primers/primer_details/PrimerDetailsTds.jsx +39 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.cy.jsx +137 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.jsx +132 -0
- package/src/components/primers/primer_details/TableSection.jsx +17 -0
- package/src/components/primers/primer_details/primerDetailsFormatting.js +3 -0
- package/src/components/primers/primer_details/useMultiplePrimerDetails.js +29 -0
- package/src/components/primers/primer_details/usePCRDetails.js +47 -0
- package/src/components/primers/primer_details/usePrimerDetailsEndpoints.js +49 -0
- package/src/components/primers/primer_details/useSinglePrimerSequenceDetails.js +25 -0
- package/src/components/primers/primersToTabularFile.js +49 -0
- package/src/components/primers/primersToTabularFile.test.js +108 -0
- package/src/components/settings/SettingsTab.cy.jsx +267 -0
- package/src/components/settings/SettingsTab.jsx +170 -0
- package/src/components/sources/AssemblyPlanDisplayer.cy.jsx +22 -0
- package/src/components/sources/AssemblyPlanDisplayer.jsx +27 -0
- package/src/components/sources/CollectionSource.jsx +97 -0
- package/src/components/sources/FinishedSource.jsx +397 -0
- package/src/components/sources/KnownSourceErrors.jsx +50 -0
- package/src/components/sources/MultipleInputsSelector.jsx +63 -0
- package/src/components/sources/MultipleOutputsSelector.jsx +63 -0
- package/src/components/sources/NewSourceBox.jsx +37 -0
- package/src/components/sources/PCRUnitForm.jsx +102 -0
- package/src/components/sources/SingleInputSelector.jsx +36 -0
- package/src/components/sources/Source.jsx +125 -0
- package/src/components/sources/SourceAnnotation.jsx +44 -0
- package/src/components/sources/SourceAssembly.jsx +201 -0
- package/src/components/sources/SourceBox.css +18 -0
- package/src/components/sources/SourceBox.jsx +60 -0
- package/src/components/sources/SourceCopySequence.jsx +38 -0
- package/src/components/sources/SourceDatabase.jsx +28 -0
- package/src/components/sources/SourceFile.jsx +188 -0
- package/src/components/sources/SourceGenomeRegion.cy.jsx +131 -0
- package/src/components/sources/SourceGenomeRegion.jsx +486 -0
- package/src/components/sources/SourceHomologousRecombination.jsx +125 -0
- package/src/components/sources/SourceKnownGenomeRegion.jsx +60 -0
- package/src/components/sources/SourceManuallyTyped.jsx +116 -0
- package/src/components/sources/SourcePCRorHybridization.jsx +165 -0
- package/src/components/sources/SourcePolymeraseExtension.jsx +44 -0
- package/src/components/sources/SourceRepositoryId.jsx +409 -0
- package/src/components/sources/SourceRestriction.jsx +41 -0
- package/src/components/sources/SourceReverseComplement.jsx +33 -0
- package/src/components/sources/SourceTypeSelector.jsx +94 -0
- package/src/components/sources/SubSequenceDisplayer.jsx +70 -0
- package/src/components/sources/VerifyDeleteDialog.jsx +23 -0
- package/src/components/sources/repositoryMetadata.js +14 -0
- package/src/components/verification/LoadFromDatabaseButton.jsx +90 -0
- package/src/components/verification/SequencingFileRow.jsx +34 -0
- package/src/components/verification/VerificationFileDialog.cy.jsx +176 -0
- package/src/components/verification/VerificationFileDialog.jsx +248 -0
- package/src/config/defaultMainEditorProps.js +44 -0
- package/src/hooks/useAlerts.js +16 -0
- package/src/hooks/useBackendAPI.js +51 -0
- package/src/hooks/useBackendRoute.js +22 -0
- package/src/hooks/useDatabase.js +18 -0
- package/src/hooks/useDragAndDropFile.js +31 -0
- package/src/hooks/useGatewaySites.js +40 -0
- package/src/hooks/useHttpClient.js +12 -0
- package/src/hooks/useLoadDatabaseFile.js +108 -0
- package/src/hooks/useStoreEditor.js +101 -0
- package/src/hooks/useValidateState.js +43 -0
- 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
|
+
|