@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,102 @@
|
|
|
1
|
+
import { Accordion, AccordionDetails, AccordionSummary, FormControl, InputLabel, ListItemText, MenuItem, Select, Tooltip } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useDispatch } from 'react-redux';
|
|
4
|
+
import AddCircleIcon from '@mui/icons-material/AddCircle';
|
|
5
|
+
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
6
|
+
import CancelIcon from '@mui/icons-material/Cancel';
|
|
7
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
8
|
+
import SingleInputSelector from './SingleInputSelector';
|
|
9
|
+
import SelectPrimerForm from '../primers/SelectPrimerForm';
|
|
10
|
+
|
|
11
|
+
function PCRUnitWrapper({ index, children, onDelete }) {
|
|
12
|
+
if (index === null) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="pcr-unit">
|
|
15
|
+
{children}
|
|
16
|
+
</div>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Accordion className="pcr-unit" defaultExpanded>
|
|
22
|
+
<AccordionSummary
|
|
23
|
+
expandIcon={<ExpandMoreIcon />}
|
|
24
|
+
aria-controls={`${index}-content`}
|
|
25
|
+
id={`${index}-header`}
|
|
26
|
+
sx={{ backgroundColor: 'lightgray' }}
|
|
27
|
+
>
|
|
28
|
+
{(index !== 0) && (
|
|
29
|
+
<Tooltip onClick={onDelete} title="Delete primer pair" arrow placement="left">
|
|
30
|
+
<CancelIcon color="gray" />
|
|
31
|
+
</Tooltip>
|
|
32
|
+
)}
|
|
33
|
+
<ListItemText sx={{ my: 0 }}>
|
|
34
|
+
Primer pair
|
|
35
|
+
{' '}
|
|
36
|
+
{index + 1}
|
|
37
|
+
</ListItemText>
|
|
38
|
+
</AccordionSummary>
|
|
39
|
+
<AccordionDetails sx={{ py: 0, my: 1 }}>
|
|
40
|
+
{children}
|
|
41
|
+
</AccordionDetails>
|
|
42
|
+
</Accordion>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function PCRUnitForm({ primers, forwardPrimerId, reversePrimerId, onChangeForward, onChangeReverse, sourceId, sourceInput = [], index = null, deletePrimerPair = null }) {
|
|
47
|
+
const { setCurrentTab, updateSource } = cloningActions;
|
|
48
|
+
const dispatch = useDispatch();
|
|
49
|
+
const goToPrimerTab = () => {
|
|
50
|
+
dispatch(setCurrentTab(1));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const updateInput = (value) => {
|
|
54
|
+
if (index !== null) {
|
|
55
|
+
const newInput = [...sourceInput];
|
|
56
|
+
newInput[index] = value;
|
|
57
|
+
dispatch(updateSource({ id: sourceId, input: newInput }));
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const onDelete = () => {
|
|
62
|
+
if (index !== null) {
|
|
63
|
+
const newInput = [...sourceInput];
|
|
64
|
+
newInput.splice(index, 1);
|
|
65
|
+
deletePrimerPair();
|
|
66
|
+
dispatch(updateSource({ id: sourceId, input: newInput }));
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<PCRUnitWrapper index={index} key={index && `pcr-unit-${index}`} onDelete={onDelete}>
|
|
72
|
+
{(index !== null) ? (
|
|
73
|
+
<FormControl fullWidth>
|
|
74
|
+
<SingleInputSelector
|
|
75
|
+
label="Target sequence"
|
|
76
|
+
selectedId={sourceInput[index]?.sequence || ''}
|
|
77
|
+
onChange={(e) => updateInput({ sequence: e.target.value })}
|
|
78
|
+
inputSequenceIds={sourceInput.map(({sequence}) => sequence)}
|
|
79
|
+
disabled={index === 0}
|
|
80
|
+
/>
|
|
81
|
+
</FormControl>
|
|
82
|
+
) : null}
|
|
83
|
+
|
|
84
|
+
<SelectPrimerForm
|
|
85
|
+
primers={primers}
|
|
86
|
+
selected={forwardPrimerId}
|
|
87
|
+
onChange={onChangeForward}
|
|
88
|
+
goToPrimerTab={goToPrimerTab}
|
|
89
|
+
label="Forward primer"
|
|
90
|
+
/>
|
|
91
|
+
<SelectPrimerForm
|
|
92
|
+
primers={primers}
|
|
93
|
+
selected={reversePrimerId}
|
|
94
|
+
onChange={onChangeReverse}
|
|
95
|
+
goToPrimerTab={goToPrimerTab}
|
|
96
|
+
label="Reverse primer"
|
|
97
|
+
/>
|
|
98
|
+
</PCRUnitWrapper>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default PCRUnitForm;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { shallowEqual, useSelector } from 'react-redux';
|
|
3
|
+
import { FormHelperText, InputLabel, MenuItem, Select } from '@mui/material';
|
|
4
|
+
import { isEqual } from 'lodash-es';
|
|
5
|
+
import { getIdsOfSequencesWithoutChildSource } from '@opencloning/store/cloning_utils';
|
|
6
|
+
|
|
7
|
+
function SingleInputSelector({ selectedId, onChange, label, inputSequenceIds, allowUnset = false, helperText = '', disabled = false }) {
|
|
8
|
+
const idsWithoutChild = useSelector(({ cloning }) => getIdsOfSequencesWithoutChildSource(cloning.sources, cloning.sequences), shallowEqual);
|
|
9
|
+
const options = [...new Set([...idsWithoutChild, ...inputSequenceIds])];
|
|
10
|
+
const sequenceNames = useSelector(({ cloning }) => options.map((id) => ({ id, name: cloning.teselaJsonCache[id]?.name || 'template' })), isEqual);
|
|
11
|
+
const renderedOptions = options.sort((a, b) => (a - b)).map((id) => (
|
|
12
|
+
<MenuItem key={id} value={id}>
|
|
13
|
+
{`${id} - ${sequenceNames.find(({ id: id2 }) => id2 === id).name}`}
|
|
14
|
+
</MenuItem>
|
|
15
|
+
));
|
|
16
|
+
if (allowUnset) {
|
|
17
|
+
renderedOptions.unshift(<MenuItem key="unset" value=""><em>None</em></MenuItem>);
|
|
18
|
+
}
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<InputLabel id="select-single-inputs">{label}</InputLabel>
|
|
22
|
+
<Select
|
|
23
|
+
value={selectedId !== null ? selectedId : ''}
|
|
24
|
+
onChange={onChange}
|
|
25
|
+
labelId="select-single-inputs"
|
|
26
|
+
label={label}
|
|
27
|
+
disabled={disabled}
|
|
28
|
+
>
|
|
29
|
+
{renderedOptions}
|
|
30
|
+
</Select>
|
|
31
|
+
<FormHelperText>{helperText}</FormHelperText>
|
|
32
|
+
</>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default SingleInputSelector;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
3
|
+
import { isEqual } from 'lodash-es';
|
|
4
|
+
import SourceFile from './SourceFile';
|
|
5
|
+
import SourceRepositoryId from './SourceRepositoryId';
|
|
6
|
+
import SourceRestriction from './SourceRestriction';
|
|
7
|
+
import SourceAssembly from './SourceAssembly';
|
|
8
|
+
import SourceTypeSelector from './SourceTypeSelector';
|
|
9
|
+
import SourcePCRorHybridization from './SourcePCRorHybridization';
|
|
10
|
+
import SourceHomologousRecombination from './SourceHomologousRecombination';
|
|
11
|
+
import { SourceGenomeRegion } from './SourceGenomeRegion';
|
|
12
|
+
import SourceManuallyTyped from './SourceManuallyTyped';
|
|
13
|
+
import SourceAnnotation from './SourceAnnotation';
|
|
14
|
+
import SourceDatabase from './SourceDatabase';
|
|
15
|
+
import SourcePolymeraseExtension from './SourcePolymeraseExtension';
|
|
16
|
+
import CollectionSource from './CollectionSource';
|
|
17
|
+
import KnownSourceErrors from './KnownSourceErrors';
|
|
18
|
+
import useBackendAPI from '../../hooks/useBackendAPI';
|
|
19
|
+
import MultipleOutputsSelector from './MultipleOutputsSelector';
|
|
20
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
21
|
+
import SourceCopySequence from './SourceCopySequence';
|
|
22
|
+
import SourceReverseComplement from './SourceReverseComplement';
|
|
23
|
+
import SourceKnownGenomeRegion from './SourceKnownGenomeRegion';
|
|
24
|
+
import { doesSourceHaveOutput } from '@opencloning/store/cloning_utils';
|
|
25
|
+
|
|
26
|
+
// There are several types of source, this components holds the common part,
|
|
27
|
+
// which for now is a select element to pick which kind of source is created
|
|
28
|
+
function Source({ sourceId }) {
|
|
29
|
+
const source = useSelector((state) => state.cloning.sources.find((s) => s.id === sourceId), isEqual);
|
|
30
|
+
const hasOutput = useSelector((state) => doesSourceHaveOutput(state.cloning, sourceId));
|
|
31
|
+
const { type: sourceType } = source;
|
|
32
|
+
let specificSource = null;
|
|
33
|
+
const templateOnlySources = ['CollectionSource', 'KnownGenomeCoordinatesSource'];
|
|
34
|
+
const knownErrors = useSelector((state) => state.cloning.knownErrors, isEqual);
|
|
35
|
+
const { requestStatus, sendPostRequest, sources, sequences } = useBackendAPI();
|
|
36
|
+
const { addSequenceAndUpdateItsSource, updateSequenceAndItsSource } = cloningActions;
|
|
37
|
+
const [chosenFragment, setChosenFragment] = React.useState(null);
|
|
38
|
+
const dispatch = useDispatch();
|
|
39
|
+
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
const dispatchedAction = hasOutput ? updateSequenceAndItsSource : addSequenceAndUpdateItsSource;
|
|
42
|
+
// If there is only a single product, commit the result, else allow choosing via MultipleOutputsSelector
|
|
43
|
+
if (sources.length === 1) {
|
|
44
|
+
dispatch(dispatchedAction({ newSource: { ...sources[0], id: sourceId }, newSequence: sequences[0] }));
|
|
45
|
+
} else if (chosenFragment !== null) {
|
|
46
|
+
dispatch(dispatchedAction({ newSource: { ...sources[chosenFragment], id: sourceId }, newSequence: sequences[chosenFragment] }));
|
|
47
|
+
}
|
|
48
|
+
}, [sources, sequences, chosenFragment]);
|
|
49
|
+
|
|
50
|
+
switch (sourceType) {
|
|
51
|
+
/* eslint-disable */
|
|
52
|
+
case 'UploadedFileSource':
|
|
53
|
+
specificSource = <SourceFile {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
54
|
+
case 'RestrictionEnzymeDigestionSource':
|
|
55
|
+
specificSource = <SourceRestriction {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
56
|
+
case 'RepositoryIdSource':
|
|
57
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
58
|
+
case 'AddgeneIdSource':
|
|
59
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
60
|
+
case 'SnapGenePlasmidSource':
|
|
61
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
62
|
+
case 'EuroscarfSource':
|
|
63
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
64
|
+
case 'WekWikGeneIdSource':
|
|
65
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
66
|
+
case 'OpenDNACollectionsSource':
|
|
67
|
+
specificSource = <SourceRepositoryId {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
68
|
+
case 'LigationSource':
|
|
69
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
70
|
+
case 'GibsonAssemblySource':
|
|
71
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
72
|
+
case 'OverlapExtensionPCRLigationSource':
|
|
73
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
74
|
+
case 'InFusionSource':
|
|
75
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
76
|
+
case 'InVivoAssemblySource':
|
|
77
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
78
|
+
case 'GatewaySource':
|
|
79
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
80
|
+
case 'CreLoxRecombinationSource':
|
|
81
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
82
|
+
case 'HomologousRecombinationSource':
|
|
83
|
+
specificSource = <SourceHomologousRecombination {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
84
|
+
case 'PCRSource':
|
|
85
|
+
specificSource = <SourcePCRorHybridization {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
86
|
+
case 'RestrictionAndLigationSource':
|
|
87
|
+
specificSource = <SourceAssembly {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
88
|
+
case 'GenomeCoordinatesSource':
|
|
89
|
+
specificSource = <SourceGenomeRegion {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
90
|
+
case 'KnownGenomeCoordinatesSource':
|
|
91
|
+
specificSource = <SourceKnownGenomeRegion {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
92
|
+
case 'ManuallyTypedSource':
|
|
93
|
+
specificSource = <SourceManuallyTyped {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
94
|
+
case 'CRISPRSource':
|
|
95
|
+
specificSource = <SourceHomologousRecombination {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
96
|
+
case 'OligoHybridizationSource':
|
|
97
|
+
specificSource = <SourcePCRorHybridization {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
98
|
+
case 'PolymeraseExtensionSource':
|
|
99
|
+
specificSource = <SourcePolymeraseExtension {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
100
|
+
case 'DatabaseSource':
|
|
101
|
+
specificSource = <SourceDatabase {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
102
|
+
case 'CollectionSource':
|
|
103
|
+
specificSource = <CollectionSource {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
104
|
+
case 'CopySequence':
|
|
105
|
+
specificSource = <SourceCopySequence {...{ source }} />; break;
|
|
106
|
+
case 'AnnotationSource':
|
|
107
|
+
specificSource = <SourceAnnotation {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
108
|
+
case 'ReverseComplementSource':
|
|
109
|
+
specificSource = <SourceReverseComplement {...{ source, requestStatus, sendPostRequest }} />; break;
|
|
110
|
+
default:
|
|
111
|
+
break;
|
|
112
|
+
/* eslint-enable */
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<>
|
|
117
|
+
{!templateOnlySources.includes(sourceType) && (<SourceTypeSelector {...{ source }} />)}
|
|
118
|
+
{sourceType && knownErrors[sourceType] && <KnownSourceErrors errors={knownErrors[sourceType]} />}
|
|
119
|
+
{specificSource}
|
|
120
|
+
{sources.length > 1 && (<MultipleOutputsSelector {...{ sources, sequences, sourceId, onFragmentChosen: setChosenFragment }} />)}
|
|
121
|
+
</>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default React.memo(Source);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FormControl, InputLabel, MenuItem, Select } from '@mui/material';
|
|
3
|
+
import { useSelector } from 'react-redux';
|
|
4
|
+
import { isEqual } from 'lodash-es';
|
|
5
|
+
import { getInputSequencesFromSourceId } from '@opencloning/store/cloning_utils';
|
|
6
|
+
import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
|
|
7
|
+
|
|
8
|
+
function SourceAnnotation({ source, requestStatus, sendPostRequest }) {
|
|
9
|
+
const [annotationTool, setAnnotationTool] = React.useState('plannotate');
|
|
10
|
+
|
|
11
|
+
const inputSequences = useSelector((state) => getInputSequencesFromSourceId(state, source.id), isEqual);
|
|
12
|
+
const onSubmit = (event) => {
|
|
13
|
+
event.preventDefault();
|
|
14
|
+
|
|
15
|
+
const requestData = {
|
|
16
|
+
sequence: inputSequences[0],
|
|
17
|
+
source: { id: source.id, input: inputSequences.map((e) => ({ sequence: e.id })), annotation_tool: annotationTool },
|
|
18
|
+
};
|
|
19
|
+
sendPostRequest({ endpoint: 'annotate/plannotate', requestData, source });
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<form onSubmit={onSubmit}>
|
|
24
|
+
<FormControl fullWidth>
|
|
25
|
+
<InputLabel id="annotation-tool-label">Annotation Tool</InputLabel>
|
|
26
|
+
<Select
|
|
27
|
+
labelId="annotation-tool-label"
|
|
28
|
+
id="annotation-tool"
|
|
29
|
+
value={annotationTool}
|
|
30
|
+
label="Annotation Tool"
|
|
31
|
+
onChange={(e) => setAnnotationTool(e.target.value)}
|
|
32
|
+
>
|
|
33
|
+
<MenuItem value="plannotate">pLannotate</MenuItem>
|
|
34
|
+
</Select>
|
|
35
|
+
</FormControl>
|
|
36
|
+
<SubmitButtonBackendAPI
|
|
37
|
+
requestStatus={requestStatus}
|
|
38
|
+
{...(import.meta.env.VITE_UMAMI_WEBSITE_ID && { "data-umami-event": "submit-annotation" })}
|
|
39
|
+
>Annotate</SubmitButtonBackendAPI>
|
|
40
|
+
</form>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default React.memo(SourceAnnotation);
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSelector, shallowEqual, useDispatch } from 'react-redux';
|
|
3
|
+
import { Checkbox, FormControlLabel, InputLabel, MenuItem, Select, TextField, FormControl, InputAdornment } from '@mui/material';
|
|
4
|
+
import MultipleInputsSelector from './MultipleInputsSelector';
|
|
5
|
+
import { getInputSequencesFromSourceId } from '@opencloning/store/cloning_utils';
|
|
6
|
+
import EnzymeMultiSelect from '../form/EnzymeMultiSelect';
|
|
7
|
+
import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
|
|
8
|
+
import { classNameToEndPointMap } from '@opencloning/utils/sourceFunctions';
|
|
9
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
10
|
+
import LabelWithTooltip from '../form/LabelWithTooltip';
|
|
11
|
+
|
|
12
|
+
const helpSingleSite = 'Even if input sequences contain multiple att sites '
|
|
13
|
+
+ '(typically 2), a product could be generated where only one site recombines. '
|
|
14
|
+
+ 'Select this option to get those products.';
|
|
15
|
+
|
|
16
|
+
// A component representing the ligation or gibson assembly of several fragments
|
|
17
|
+
function SourceAssembly({ source, requestStatus, sendPostRequest }) {
|
|
18
|
+
const assemblyType = source.type;
|
|
19
|
+
const { id: sourceId, input: sourceInput } = source;
|
|
20
|
+
const inputSequences = useSelector((state) => getInputSequencesFromSourceId(state, sourceId), shallowEqual);
|
|
21
|
+
const inputContainsTemplates = inputSequences.some((sequence) => sequence.type === 'TemplateSequence');
|
|
22
|
+
const [minimalHomology, setMinimalHomology] = React.useState(20);
|
|
23
|
+
const [allowPartialOverlap, setAllowPartialOverlap] = React.useState(false);
|
|
24
|
+
const [circularOnly, setCircularOnly] = React.useState(false);
|
|
25
|
+
const [bluntLigation, setBluntLigation] = React.useState(false);
|
|
26
|
+
const [gatewaySettings, setGatewaySettings] = React.useState({ greedy: false, reactionType: null, onlyMultiSite: true });
|
|
27
|
+
const [enzymes, setEnzymes] = React.useState([]);
|
|
28
|
+
|
|
29
|
+
const dispatch = useDispatch();
|
|
30
|
+
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
if (assemblyType === 'GatewaySource') {
|
|
33
|
+
setCircularOnly(true);
|
|
34
|
+
}
|
|
35
|
+
}, [assemblyType]);
|
|
36
|
+
|
|
37
|
+
React.useEffect(() => {
|
|
38
|
+
if (assemblyType === 'GatewaySource' && source.reaction_type) {
|
|
39
|
+
setGatewaySettings({ ...gatewaySettings, reactionType: source.reaction_type });
|
|
40
|
+
}
|
|
41
|
+
}, [source]);
|
|
42
|
+
|
|
43
|
+
const { updateSource } = cloningActions;
|
|
44
|
+
|
|
45
|
+
const preventSubmit = (
|
|
46
|
+
(assemblyType === 'RestrictionAndLigationSource' && enzymes.length === 0)
|
|
47
|
+
|| (assemblyType === 'GatewaySource' && gatewaySettings.reactionType === null)
|
|
48
|
+
|| inputContainsTemplates
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const flipAllowPartialOverlap = () => {
|
|
52
|
+
setAllowPartialOverlap(!allowPartialOverlap);
|
|
53
|
+
if (!allowPartialOverlap) {
|
|
54
|
+
setBluntLigation(false);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const flipBluntLigation = () => {
|
|
59
|
+
setBluntLigation(!bluntLigation);
|
|
60
|
+
if (!bluntLigation) {
|
|
61
|
+
setAllowPartialOverlap(false);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const onSubmit = (event) => {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
const requestData = {
|
|
68
|
+
source: { id: sourceId, input: inputSequences.map((e) => ({ sequence: e.id })), output_name: source.output_name },
|
|
69
|
+
sequences: inputSequences,
|
|
70
|
+
};
|
|
71
|
+
if (['GibsonAssemblySource', 'OverlapExtensionPCRLigationSource', 'InFusionSource', 'InVivoAssemblySource'].includes(assemblyType)) {
|
|
72
|
+
const config = { params: {
|
|
73
|
+
minimal_homology: minimalHomology,
|
|
74
|
+
circular_only: circularOnly,
|
|
75
|
+
} };
|
|
76
|
+
// To instantiate the correct class on the backend
|
|
77
|
+
requestData.source.type = assemblyType;
|
|
78
|
+
sendPostRequest({ endpoint: 'gibson_assembly', requestData, config, source });
|
|
79
|
+
} else if (assemblyType === 'RestrictionAndLigationSource') {
|
|
80
|
+
if (enzymes.length === 0) { return; }
|
|
81
|
+
requestData.source.restriction_enzymes = enzymes;
|
|
82
|
+
const config = { params: {
|
|
83
|
+
allow_partial_overlap: allowPartialOverlap,
|
|
84
|
+
circular_only: circularOnly,
|
|
85
|
+
} };
|
|
86
|
+
sendPostRequest({ endpoint: 'restriction_and_ligation', requestData, config, source });
|
|
87
|
+
} else if (assemblyType === 'GatewaySource') {
|
|
88
|
+
requestData.source.greedy = gatewaySettings.greedy;
|
|
89
|
+
requestData.source.reaction_type = gatewaySettings.reactionType;
|
|
90
|
+
const config = { params: { circular_only: circularOnly, only_multi_site: gatewaySettings.onlyMultiSite } };
|
|
91
|
+
sendPostRequest({ endpoint: 'gateway', requestData, config, source });
|
|
92
|
+
} else if (assemblyType === 'CreLoxRecombinationSource') {
|
|
93
|
+
sendPostRequest({ endpoint: 'cre_lox_recombination', requestData, source });
|
|
94
|
+
} else {
|
|
95
|
+
const config = { params: {
|
|
96
|
+
allow_partial_overlap: allowPartialOverlap,
|
|
97
|
+
circular_only: circularOnly,
|
|
98
|
+
blunt: bluntLigation,
|
|
99
|
+
} };
|
|
100
|
+
sendPostRequest({ endpoint: classNameToEndPointMap[assemblyType], requestData, config, source });
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const onChangeInput = (newInputSequenceIds) => {
|
|
105
|
+
const newInput = newInputSequenceIds.map((id) => ({ sequence: id }));
|
|
106
|
+
// We prevent setting empty input
|
|
107
|
+
if (newInput.length === 0) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
dispatch(updateSource({ id: sourceId, input: newInput, type: assemblyType }));
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<div className="assembly">
|
|
115
|
+
<form onSubmit={onSubmit}>
|
|
116
|
+
<FormControl fullWidth>
|
|
117
|
+
<MultipleInputsSelector {...{
|
|
118
|
+
inputSequenceIds: sourceInput.map(({sequence}) => sequence), onChange: onChangeInput, label: 'Assembly inputs',
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
</FormControl>
|
|
122
|
+
{ ['GibsonAssemblySource', 'OverlapExtensionPCRLigationSource', 'InFusionSource', 'InVivoAssemblySource'].includes(assemblyType) && (
|
|
123
|
+
// I don't really understand why fullWidth is required here
|
|
124
|
+
<FormControl fullWidth>
|
|
125
|
+
<TextField
|
|
126
|
+
label="Minimal homology length"
|
|
127
|
+
value={minimalHomology}
|
|
128
|
+
onChange={(e) => { setMinimalHomology(e.target.value); }}
|
|
129
|
+
type="number"
|
|
130
|
+
defaultValue={20}
|
|
131
|
+
InputProps={{
|
|
132
|
+
endAdornment: <InputAdornment position="end">bp</InputAdornment>,
|
|
133
|
+
sx: { '& input': { textAlign: 'center' } },
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
</FormControl>
|
|
137
|
+
)}
|
|
138
|
+
{ (assemblyType === 'RestrictionAndLigationSource') && (
|
|
139
|
+
<EnzymeMultiSelect setEnzymes={setEnzymes} />
|
|
140
|
+
)}
|
|
141
|
+
{ (assemblyType === 'GatewaySource') && (
|
|
142
|
+
<>
|
|
143
|
+
<FormControl fullWidth>
|
|
144
|
+
<InputLabel id="gateway-reaction-type-label">Reaction type</InputLabel>
|
|
145
|
+
<Select
|
|
146
|
+
labelId="gateway-reaction-type-label"
|
|
147
|
+
id="gateway-reaction-type"
|
|
148
|
+
value={gatewaySettings.reactionType || ''}
|
|
149
|
+
onChange={(e) => setGatewaySettings({ ...gatewaySettings, reactionType: e.target.value })}
|
|
150
|
+
label="Reaction type"
|
|
151
|
+
>
|
|
152
|
+
<MenuItem value="BP">BP</MenuItem>
|
|
153
|
+
<MenuItem value="LR">LR</MenuItem>
|
|
154
|
+
</Select>
|
|
155
|
+
</FormControl>
|
|
156
|
+
<FormControl fullWidth>
|
|
157
|
+
<FormControlLabel
|
|
158
|
+
control={<Checkbox checked={gatewaySettings.greedy} onChange={() => setGatewaySettings({ ...gatewaySettings, greedy: !gatewaySettings.greedy })} />}
|
|
159
|
+
label={(
|
|
160
|
+
<LabelWithTooltip label="Greedy attP finder" tooltip="Use a more greedy consensus site to find attP sites (might give false positives)" />
|
|
161
|
+
)}
|
|
162
|
+
/>
|
|
163
|
+
</FormControl>
|
|
164
|
+
<FormControl fullWidth>
|
|
165
|
+
<FormControlLabel
|
|
166
|
+
control={<Checkbox checked={!gatewaySettings.onlyMultiSite} onChange={() => setGatewaySettings({ ...gatewaySettings, onlyMultiSite: !gatewaySettings.onlyMultiSite })} />}
|
|
167
|
+
label={(
|
|
168
|
+
<LabelWithTooltip label="Single-site recombination" tooltip={helpSingleSite} />
|
|
169
|
+
)}
|
|
170
|
+
/>
|
|
171
|
+
</FormControl>
|
|
172
|
+
</>
|
|
173
|
+
)}
|
|
174
|
+
{ ['RestrictionAndLigationSource', 'GibsonAssemblySource', 'LigationSource', 'OverlapExtensionPCRLigationSource', 'GatewaySource', 'InFusionSource', 'InVivoAssemblySource'].includes(assemblyType) && (
|
|
175
|
+
<FormControl fullWidth style={{ textAlign: 'left' }}>
|
|
176
|
+
<FormControlLabel control={<Checkbox checked={circularOnly} onChange={() => setCircularOnly(!circularOnly)} />} label="Circular assemblies only" />
|
|
177
|
+
</FormControl>
|
|
178
|
+
)}
|
|
179
|
+
{ ['RestrictionAndLigationSource', 'LigationSource'].includes(assemblyType) && (
|
|
180
|
+
<FormControl fullWidth style={{ textAlign: 'left' }}>
|
|
181
|
+
<FormControlLabel control={<Checkbox checked={allowPartialOverlap} onChange={flipAllowPartialOverlap} />} label="Allow partial overlaps" />
|
|
182
|
+
</FormControl>
|
|
183
|
+
)}
|
|
184
|
+
{ (assemblyType === 'LigationSource') && (
|
|
185
|
+
<FormControl fullWidth style={{ textAlign: 'left' }}>
|
|
186
|
+
<FormControlLabel control={<Checkbox checked={bluntLigation} onChange={flipBluntLigation} />} label="Blunt ligation" />
|
|
187
|
+
</FormControl>
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
{!preventSubmit && (
|
|
191
|
+
<SubmitButtonBackendAPI
|
|
192
|
+
requestStatus={requestStatus}
|
|
193
|
+
{...(import.meta.env.VITE_UMAMI_WEBSITE_ID && { "data-umami-event": `submit-assembly-${assemblyType}` })}
|
|
194
|
+
>Submit</SubmitButtonBackendAPI>
|
|
195
|
+
)}
|
|
196
|
+
</form>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export default SourceAssembly;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
div.icon-corner {
|
|
2
|
+
position: absolute;
|
|
3
|
+
right: 0px;
|
|
4
|
+
top: 0px;
|
|
5
|
+
margin-left: .3em;
|
|
6
|
+
margin-bottom: .3em;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
div.icon-corner button{
|
|
10
|
+
padding: 0;
|
|
11
|
+
border: none;
|
|
12
|
+
background: none;
|
|
13
|
+
color:#d32f2f;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
div.icon-corner button:hover {
|
|
17
|
+
filter: brightness(70%);
|
|
18
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useDispatch, useStore } from 'react-redux';
|
|
3
|
+
import Tooltip from '@mui/material/Tooltip';
|
|
4
|
+
import DeleteIcon from '@mui/icons-material/Delete';
|
|
5
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
6
|
+
import './SourceBox.css';
|
|
7
|
+
import VerifyDeleteDialog from './VerifyDeleteDialog';
|
|
8
|
+
import useStoreEditor from '../../hooks/useStoreEditor';
|
|
9
|
+
import { isSequenceInputOfAnySource } from '@opencloning/store/cloning_utils';
|
|
10
|
+
|
|
11
|
+
const { deleteSourceAndItsChildren, setMainSequenceId } = cloningActions;
|
|
12
|
+
|
|
13
|
+
function SourceBox({ children, sourceId }) {
|
|
14
|
+
const dispatch = useDispatch();
|
|
15
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
16
|
+
const store = useStore();
|
|
17
|
+
const { updateStoreEditor } = useStoreEditor();
|
|
18
|
+
|
|
19
|
+
const tooltipText = <div className="tooltip-text">Delete source and children</div>;
|
|
20
|
+
|
|
21
|
+
const deleteSource = () => {
|
|
22
|
+
const { mainSequenceId, sources } = store.getState().cloning;
|
|
23
|
+
const source = sources.find((s) => s.id === sourceId);
|
|
24
|
+
dispatch(deleteSourceAndItsChildren(sourceId));
|
|
25
|
+
if (mainSequenceId && mainSequenceId === sourceId) {
|
|
26
|
+
updateStoreEditor('mainEditor', null);
|
|
27
|
+
dispatch(setMainSequenceId(null));
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const onClickDeleteSource = () => {
|
|
31
|
+
const state = store.getState().cloning;
|
|
32
|
+
if (isSequenceInputOfAnySource(sourceId, state.sources)) {
|
|
33
|
+
setDialogOpen(true);
|
|
34
|
+
} else {
|
|
35
|
+
deleteSource();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return (
|
|
39
|
+
<div className="select-source">
|
|
40
|
+
<div className="icon-corner">
|
|
41
|
+
<Tooltip title={tooltipText} arrow placement="top">
|
|
42
|
+
<button type="submit" onClick={onClickDeleteSource}>
|
|
43
|
+
<DeleteIcon sx={{ fontSize: '2em' }} />
|
|
44
|
+
</button>
|
|
45
|
+
</Tooltip>
|
|
46
|
+
</div>
|
|
47
|
+
<VerifyDeleteDialog
|
|
48
|
+
dialogOpen={dialogOpen}
|
|
49
|
+
setDialogOpen={setDialogOpen}
|
|
50
|
+
onClickDelete={() => {
|
|
51
|
+
deleteSource();
|
|
52
|
+
setDialogOpen(false);
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
{children}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default React.memo(SourceBox);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Button, FormControl } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { batch, shallowEqual, useDispatch, useSelector } from 'react-redux';
|
|
4
|
+
import SingleInputSelector from './SingleInputSelector';
|
|
5
|
+
import { CopySequenceThunk } from '@opencloning/utils/thunks';
|
|
6
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
7
|
+
|
|
8
|
+
const { deleteSourceAndItsChildren } = cloningActions;
|
|
9
|
+
|
|
10
|
+
function SourceCopySequence({ source }) {
|
|
11
|
+
const [id, setId] = React.useState(null);
|
|
12
|
+
const allSequenceIds = useSelector((state) => state.cloning.sequences.map((sequence) => sequence.id), shallowEqual);
|
|
13
|
+
const dispatch = useDispatch();
|
|
14
|
+
|
|
15
|
+
const onSubmit = (e) => {
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
batch(() => {
|
|
18
|
+
dispatch(deleteSourceAndItsChildren(source.id));
|
|
19
|
+
dispatch(CopySequenceThunk(id, source.id));
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<form onSubmit={onSubmit}>
|
|
25
|
+
<FormControl fullWidth>
|
|
26
|
+
<SingleInputSelector
|
|
27
|
+
label="Sequence to copy"
|
|
28
|
+
selectedId={id}
|
|
29
|
+
onChange={(e) => setId(e.target.value)}
|
|
30
|
+
inputSequenceIds={allSequenceIds}
|
|
31
|
+
/>
|
|
32
|
+
</FormControl>
|
|
33
|
+
<Button type="submit" variant="contained" style={{ marginTop: 15 }}>Copy sequence</Button>
|
|
34
|
+
</form>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default SourceCopySequence;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Alert } from '@mui/material';
|
|
3
|
+
import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
|
|
4
|
+
import useDatabase from '../../hooks/useDatabase';
|
|
5
|
+
import useLoadDatabaseFile from '../../hooks/useLoadDatabaseFile';
|
|
6
|
+
|
|
7
|
+
function SourceDatabase({ source, requestStatus, sendPostRequest }) {
|
|
8
|
+
const [file, setFile] = React.useState(null);
|
|
9
|
+
const [databaseId, setDatabaseId] = React.useState(null);
|
|
10
|
+
const database = useDatabase();
|
|
11
|
+
const [historyFileError, setHistoryFileError] = React.useState(null);
|
|
12
|
+
const { loadDatabaseFile } = useLoadDatabaseFile({ source, sendPostRequest, setHistoryFileError });
|
|
13
|
+
|
|
14
|
+
const onSubmit = async (e) => {
|
|
15
|
+
e.preventDefault();
|
|
16
|
+
loadDatabaseFile(file, databaseId);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<form onSubmit={onSubmit}>
|
|
21
|
+
{database && <database.GetSequenceFileAndDatabaseIdComponent setFile={setFile} setDatabaseId={setDatabaseId} />}
|
|
22
|
+
{historyFileError && <Alert severity="error">{historyFileError}</Alert>}
|
|
23
|
+
{file && databaseId && <SubmitButtonBackendAPI requestStatus={requestStatus}>Submit </SubmitButtonBackendAPI>}
|
|
24
|
+
</form>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default SourceDatabase;
|