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