@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,127 @@
|
|
|
1
|
+
/* Adapted from https://github.com/dumptyd/treeflex */
|
|
2
|
+
|
|
3
|
+
.tf-ancestor-tree {
|
|
4
|
+
position: relative;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.tf-ancestor-tree>div {
|
|
8
|
+
transform-origin: top left;
|
|
9
|
+
/* This is how you could potentially scale for screenshots */
|
|
10
|
+
/* transform: scale(0.5); */
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.tf-ancestor-tree>div>ul {
|
|
14
|
+
transform: rotateX(180deg);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.node-text {
|
|
18
|
+
display: inline-block;
|
|
19
|
+
transform: rotateX(180deg);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.tf-tree {
|
|
23
|
+
font-size: 16px;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.tf-tree * {
|
|
27
|
+
box-sizing: border-box;
|
|
28
|
+
margin: 0;
|
|
29
|
+
padding: 0
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.tf-tree ul {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.tf-tree li {
|
|
37
|
+
align-items: center;
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-direction: column;
|
|
40
|
+
flex-wrap: wrap;
|
|
41
|
+
padding: 0 1em;
|
|
42
|
+
position: relative
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.tf-tree li ul {
|
|
46
|
+
margin-top: 2em
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.tf-tree li li:before {
|
|
50
|
+
border-top: 2px solid #000;
|
|
51
|
+
content: "";
|
|
52
|
+
display: block;
|
|
53
|
+
height: .0625em;
|
|
54
|
+
left: -.03125em;
|
|
55
|
+
position: absolute;
|
|
56
|
+
top: -1.00em;
|
|
57
|
+
width: 100%;
|
|
58
|
+
z-index: -1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.tf-tree li li:first-child:before {
|
|
62
|
+
left: calc(50% - .03125em);
|
|
63
|
+
max-width: calc(50% + .0625em)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.tf-tree li li:last-child:before {
|
|
67
|
+
left: auto;
|
|
68
|
+
max-width: calc(50% + .0625em);
|
|
69
|
+
right: calc(50% - .03125em)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.tf-tree li li:only-child:before {
|
|
73
|
+
display: none
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.tf-tree li li:only-child>.tf-nc:before, .tf-tree li li:only-child>.tf-node-content:before {
|
|
77
|
+
height: 1.0625em;
|
|
78
|
+
top: -1.0625em
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.tf-tree .tf-nc, .tf-tree .tf-node-content {
|
|
82
|
+
padding: .5em 1em;
|
|
83
|
+
position: relative
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Sticking down */
|
|
87
|
+
.tf-tree .tf-nc:before, .tf-tree .tf-node-content:before {
|
|
88
|
+
top: -1.2em
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.tf-tree .tf-nc:after, .tf-tree .tf-nc:before, .tf-tree .tf-node-content:after, .tf-tree .tf-node-content:before {
|
|
92
|
+
border-left: 2px solid #000;
|
|
93
|
+
content: "";
|
|
94
|
+
display: block;
|
|
95
|
+
height: 1.2em;
|
|
96
|
+
left: calc(50% - .03125em);
|
|
97
|
+
position: absolute;
|
|
98
|
+
width: 2px;
|
|
99
|
+
z-index: -1;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.tf-tree .tf-nc:after, .tf-tree .tf-node-content:after {
|
|
103
|
+
top: calc(100% + .12em)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* .tf-tree>div>ul>li>.tf-nc:before removes the things sticking to the bottom */
|
|
107
|
+
.tf-tree .tf-nc:only-child:after, .tf-tree .tf-node-content:only-child:after, .tf-tree>div>ul>li>.tf-nc:before, .tf-tree>div>ul>li>.tf-node-content:before {
|
|
108
|
+
display: none
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.tf-tree.tf-gap-sm li {
|
|
112
|
+
padding: 0 .6em
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.tf-tree.tf-gap-sm li>.tf-nc:before, .tf-tree.tf-gap-sm li>.tf-node-content:before {
|
|
116
|
+
height: .6em;
|
|
117
|
+
top: -.6em
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.tf-tree.tf-gap-sm li>.tf-nc:after, .tf-tree.tf-gap-sm li>.tf-node-content:after {
|
|
121
|
+
height: .6em
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.tf-nc {
|
|
125
|
+
border-radius: 15px;
|
|
126
|
+
text-align: center;
|
|
127
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// A react fucntioncal component that renders a table
|
|
2
|
+
// where each row contains a key-value pair of an object
|
|
3
|
+
// it uses mui
|
|
4
|
+
|
|
5
|
+
import { Table, TableCell, TableRow } from '@mui/material';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
function ObjectTable({ object }) {
|
|
9
|
+
return (
|
|
10
|
+
|
|
11
|
+
<Table sx={{ my: 2 }}>
|
|
12
|
+
|
|
13
|
+
{Object.keys(object).map((key) => (
|
|
14
|
+
<TableRow key={key}>
|
|
15
|
+
<TableCell sx={{ py: 0.5 }}><strong>{key}</strong></TableCell>
|
|
16
|
+
<TableCell sx={{ py: 0.5 }}>{object[key]}</TableCell>
|
|
17
|
+
</TableRow>
|
|
18
|
+
))}
|
|
19
|
+
</Table>
|
|
20
|
+
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default React.memo(ObjectTable);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
3
|
+
import Tabs from '@mui/material/Tabs';
|
|
4
|
+
import { isEqual } from 'lodash-es';
|
|
5
|
+
import DescriptionEditor from './DescriptionEditor';
|
|
6
|
+
import PrimerList from './primers/PrimerList';
|
|
7
|
+
import SettingsTab from './settings/SettingsTab';
|
|
8
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
9
|
+
import TabPanel from './navigation/TabPanel';
|
|
10
|
+
import CustomTab from './navigation/CustomTab';
|
|
11
|
+
import DataModelDisplayer from './DataModelDisplayer';
|
|
12
|
+
import CloningHistory from './CloningHistory';
|
|
13
|
+
import SequenceTab from './SequenceTab';
|
|
14
|
+
import AppAlerts from './AppAlerts';
|
|
15
|
+
import Assembler from './assembler/Assembler';
|
|
16
|
+
|
|
17
|
+
const { setCurrentTab } = cloningActions;
|
|
18
|
+
|
|
19
|
+
function OpenCloning() {
|
|
20
|
+
const dispatch = useDispatch();
|
|
21
|
+
const currentTab = useSelector((state) => state.cloning.currentTab);
|
|
22
|
+
const tabPanelsRef = useRef(null);
|
|
23
|
+
const [smallDevice, setSmallDevice] = useState(window.innerWidth < 600);
|
|
24
|
+
const hasAppBar = useSelector((state) => state.cloning.config.showAppBar, isEqual);
|
|
25
|
+
const enableAssembler = useSelector((state) => state.cloning.config.enableAssembler);
|
|
26
|
+
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
const handleResize = () => {
|
|
29
|
+
setSmallDevice(window.innerWidth < 600);
|
|
30
|
+
};
|
|
31
|
+
window.addEventListener('resize', handleResize);
|
|
32
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
const changeTab = (event, newValue) => {
|
|
36
|
+
dispatch(setCurrentTab(newValue));
|
|
37
|
+
if (tabPanelsRef.current) {
|
|
38
|
+
tabPanelsRef.current.scrollTo({
|
|
39
|
+
top: 0,
|
|
40
|
+
behavior: 'instant',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="app-container" style={{ height: hasAppBar ? 'calc(100vh - 114px - 10px)' : '100vh' }}>
|
|
47
|
+
<AppAlerts />
|
|
48
|
+
<Tabs
|
|
49
|
+
variant={smallDevice ? 'scrollable' : 'standard'}
|
|
50
|
+
scrollButtons={smallDevice ? 'auto' : false}
|
|
51
|
+
allowScrollButtonsMobile
|
|
52
|
+
centered={!smallDevice}
|
|
53
|
+
value={currentTab}
|
|
54
|
+
onChange={changeTab}
|
|
55
|
+
sx={{ pb: 3, pt: 1 }}
|
|
56
|
+
id="opencloning-app-tabs"
|
|
57
|
+
>
|
|
58
|
+
<CustomTab label="Cloning" index={0} />
|
|
59
|
+
<CustomTab label="Primers" index={1} />
|
|
60
|
+
<CustomTab label="Description" index={2} />
|
|
61
|
+
<CustomTab label="Sequence" index={3} />
|
|
62
|
+
<CustomTab label="Data model" index={4} />
|
|
63
|
+
<CustomTab label="Settings" index={5} />
|
|
64
|
+
{enableAssembler && <CustomTab label="Assembler" index={6} />}
|
|
65
|
+
</Tabs>
|
|
66
|
+
<div className="tab-panels-container" ref={tabPanelsRef}>
|
|
67
|
+
<TabPanel index={1} value={currentTab} className="primer-tab-pannel">
|
|
68
|
+
<div className="primer-list-container">
|
|
69
|
+
<PrimerList />
|
|
70
|
+
</div>
|
|
71
|
+
</TabPanel>
|
|
72
|
+
<TabPanel index={2} value={currentTab} className="description-tab-pannel">
|
|
73
|
+
<div className="description-editor">
|
|
74
|
+
<DescriptionEditor />
|
|
75
|
+
</div>
|
|
76
|
+
</TabPanel>
|
|
77
|
+
{/* For some reason, putting this here is required for primer.color to work */}
|
|
78
|
+
<TabPanel index={3} value={currentTab} className="main-editor-tab-pannel">
|
|
79
|
+
<div className="main-sequence-editor">
|
|
80
|
+
<SequenceTab />
|
|
81
|
+
</div>
|
|
82
|
+
</TabPanel>
|
|
83
|
+
<TabPanel index={0} value={currentTab} className="cloning-tab-pannel">
|
|
84
|
+
<div className="open-cloning">
|
|
85
|
+
<CloningHistory />
|
|
86
|
+
</div>
|
|
87
|
+
</TabPanel>
|
|
88
|
+
<TabPanel index={4} value={currentTab} className="data-model-tab-pannel">
|
|
89
|
+
<DataModelDisplayer />
|
|
90
|
+
</TabPanel>
|
|
91
|
+
<TabPanel index={5} value={currentTab} className="settings-tab-pannel">
|
|
92
|
+
<SettingsTab />
|
|
93
|
+
</TabPanel>
|
|
94
|
+
{enableAssembler && <TabPanel index={6} value={currentTab} className="assembler-tab-pannel">
|
|
95
|
+
<Assembler />
|
|
96
|
+
</TabPanel>}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export default OpenCloning;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { formatSequenceForOverhangDisplay } from '@opencloning/utils/sequenceDisplay';
|
|
3
|
+
|
|
4
|
+
function OverhangsDisplay({ sequence, sequenceData }) {
|
|
5
|
+
if (sequence === undefined
|
|
6
|
+
|| (sequence.overhang_crick_3prime === 0 && sequence.overhang_watson_3prime === 0)
|
|
7
|
+
) { return null; }
|
|
8
|
+
const { watson, crick, middle } = formatSequenceForOverhangDisplay(
|
|
9
|
+
sequenceData.sequence,
|
|
10
|
+
sequence.overhang_crick_3prime,
|
|
11
|
+
sequence.overhang_watson_3prime,
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="overhang-representation">
|
|
16
|
+
{watson}
|
|
17
|
+
<br />
|
|
18
|
+
{middle}
|
|
19
|
+
<br />
|
|
20
|
+
{crick}
|
|
21
|
+
</div>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default OverhangsDisplay;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { SimpleCircularOrLinearView } from '@teselagen/ove';
|
|
3
|
+
import { useDispatch, useSelector } from 'react-redux';
|
|
4
|
+
import { isEqual } from 'lodash-es';
|
|
5
|
+
import OverhangsDisplay from './OverhangsDisplay';
|
|
6
|
+
import NewSourceBox from './sources/NewSourceBox';
|
|
7
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
8
|
+
import getTransformCoords from '@opencloning/utils/transformCoords';
|
|
9
|
+
import { getPCRPrimers, isSequenceInputOfAnySource } from '@opencloning/store/cloning_utils';
|
|
10
|
+
|
|
11
|
+
const transformToRegion = (eventOutput) => {
|
|
12
|
+
if (eventOutput.selectionLayer) {
|
|
13
|
+
// When selecting a region
|
|
14
|
+
return { selectionLayer: eventOutput.selectionLayer, caretPosition: -1 };
|
|
15
|
+
}
|
|
16
|
+
// When clicking a feature
|
|
17
|
+
return { selectionLayer: { start: eventOutput.start, end: eventOutput.end }, caretPosition: -1 };
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function AddSourceComponent({ sequenceId }) {
|
|
21
|
+
const isRootNode = useSelector((state) => !isSequenceInputOfAnySource(sequenceId, state.cloning.sources));
|
|
22
|
+
return isRootNode ? (
|
|
23
|
+
<div className="hang-from-node">
|
|
24
|
+
<div>
|
|
25
|
+
<NewSourceBox {...{ inputSequencesIds: [sequenceId] }} />
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
) : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function SequenceEditor({ sequenceId }) {
|
|
32
|
+
const editorName = `editor_${sequenceId}`;
|
|
33
|
+
const sequence = useSelector((state) => state.cloning.sequences.find((e) => e.id === sequenceId), isEqual);
|
|
34
|
+
const pcrPrimers = useSelector(({ cloning }) => getPCRPrimers(cloning, sequenceId), isEqual);
|
|
35
|
+
const unmutableSeq = useSelector((state) => state.cloning.teselaJsonCache[sequenceId], isEqual);
|
|
36
|
+
const seq = { ...unmutableSeq };
|
|
37
|
+
|
|
38
|
+
// Make a copy
|
|
39
|
+
const seqCopy = React.useMemo(() => structuredClone(seq), [seq]);
|
|
40
|
+
// Filter out features of type "source"
|
|
41
|
+
seqCopy.features = seqCopy.features.filter((f) => f.type !== 'source');
|
|
42
|
+
// If the primer has been already added to the template, we don't add it again
|
|
43
|
+
const pcrPrimers2Include = pcrPrimers.filter((p) => !seqCopy.primers.some(
|
|
44
|
+
(p2) => p2.name === p.name && p2.start === p.start && p2.end === p.end,
|
|
45
|
+
));
|
|
46
|
+
seqCopy.primers = [...seqCopy.primers, ...pcrPrimers2Include];
|
|
47
|
+
const parentSource = useSelector((state) => state.cloning.sources.find((source) => source.id === sequenceId), isEqual);
|
|
48
|
+
const stateSelectedRegion = useSelector((state) => state.cloning.selectedRegions.find((r) => r.id === sequenceId)?.selectedRegion, isEqual);
|
|
49
|
+
const parentSequenceData = useSelector((state) => parentSource.input.map(({sequence}) => state.cloning.teselaJsonCache[sequence]), isEqual);
|
|
50
|
+
const [rangeInParent, setRangeInParent] = React.useState(() => null);
|
|
51
|
+
React.useEffect(() => {
|
|
52
|
+
const callBack = getTransformCoords(parentSource, parentSequenceData, seqCopy.size);
|
|
53
|
+
// Here we have to set the state like this, since it's a function
|
|
54
|
+
// otherwise, react calls the function with the previous state
|
|
55
|
+
setRangeInParent(() => callBack);
|
|
56
|
+
}, [parentSource, parentSequenceData]);
|
|
57
|
+
|
|
58
|
+
const { setSelectedRegions } = cloningActions;
|
|
59
|
+
const dispatch = useDispatch();
|
|
60
|
+
const [selectedRegion, setSelectedRegion] = React.useState({ selectionLayer: { start: -1, end: -1 }, caretPosition: -1 });
|
|
61
|
+
const [timeOutId, setTimeOutId] = React.useState(null);
|
|
62
|
+
|
|
63
|
+
const { selectionLayer, caretPosition } = selectedRegion;
|
|
64
|
+
|
|
65
|
+
React.useEffect(() => {
|
|
66
|
+
if (stateSelectedRegion !== undefined) {
|
|
67
|
+
setSelectedRegion(stateSelectedRegion);
|
|
68
|
+
} else {
|
|
69
|
+
setSelectedRegion({ selectionLayer: { start: -1, end: -1 }, caretPosition: -1 });
|
|
70
|
+
}
|
|
71
|
+
}, [stateSelectedRegion]);
|
|
72
|
+
|
|
73
|
+
const updateSelectedRegion = (eventOutput, isCaret) => {
|
|
74
|
+
if (isCaret) {
|
|
75
|
+
// TODO: something here?
|
|
76
|
+
} else {
|
|
77
|
+
const newRegion = transformToRegion(eventOutput);
|
|
78
|
+
const newTimeOutId = setTimeout(() => {
|
|
79
|
+
const parentSequenceIds = parentSource.input.map(({sequence}) => sequence);
|
|
80
|
+
// We add the current sequence to the selectedRegions array
|
|
81
|
+
const selectedRegions = [{ id: sequenceId, selectedRegion: newRegion }];
|
|
82
|
+
// If possible, add the equivalent region in the parent sequence
|
|
83
|
+
parentSequenceIds.forEach((id) => {
|
|
84
|
+
const selectionLayerAssembly = rangeInParent(newRegion.selectionLayer, id);
|
|
85
|
+
if (selectionLayerAssembly !== null) {
|
|
86
|
+
selectedRegions.push({ id, selectedRegion: { selectionLayer: selectionLayerAssembly, caretPosition: -1 } });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
dispatch(setSelectedRegions(selectedRegions));
|
|
90
|
+
}, 500);
|
|
91
|
+
setSelectedRegion(newRegion);
|
|
92
|
+
setTimeOutId((prev) => {
|
|
93
|
+
clearTimeout(prev);
|
|
94
|
+
return newTimeOutId;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<div>
|
|
101
|
+
<SimpleCircularOrLinearView {...{
|
|
102
|
+
sequenceData: seqCopy,
|
|
103
|
+
editorName,
|
|
104
|
+
height: seq.circular ? null : 'auto',
|
|
105
|
+
withCaretEnabled: true,
|
|
106
|
+
withSelectionEnabled: true,
|
|
107
|
+
selectionLayer,
|
|
108
|
+
selectionLayerUpdate: (a) => updateSelectedRegion(a, false),
|
|
109
|
+
// TODO: this does not work
|
|
110
|
+
caretPosition,
|
|
111
|
+
caretPositionUpdate: (a) => updateSelectedRegion(a, true),
|
|
112
|
+
}}
|
|
113
|
+
/>
|
|
114
|
+
<OverhangsDisplay {...{ sequenceData: seq, sequence }} />
|
|
115
|
+
<AddSourceComponent {...{ sequenceId }} />
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export default React.memo(SequenceEditor);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import MainSequenceEditor from './MainSequenceEditor';
|
|
3
|
+
import PrimerDesigner from './primers/primer_design/SequenceTabComponents/PrimerDesigner';
|
|
4
|
+
|
|
5
|
+
function SequenceTab() {
|
|
6
|
+
return (
|
|
7
|
+
<>
|
|
8
|
+
<PrimerDesigner />
|
|
9
|
+
<MainSequenceEditor />
|
|
10
|
+
</>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default SequenceTab;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Tooltip } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { useSelector } from 'react-redux';
|
|
4
|
+
|
|
5
|
+
function TemplateSequence({ sequenceId }) {
|
|
6
|
+
const circular = useSelector((state) => state.cloning.sequences.find((e) => e.id === sequenceId).circular);
|
|
7
|
+
let svgContent = null;
|
|
8
|
+
let tooltipText = null;
|
|
9
|
+
|
|
10
|
+
if (circular === true) {
|
|
11
|
+
tooltipText = 'Circular sequence expected';
|
|
12
|
+
svgContent = <circle cx="60" cy="60" r="40" stroke="lightgrey" strokeWidth="9" fill="none" />;
|
|
13
|
+
} else if (circular === false) {
|
|
14
|
+
svgContent = <line x1="10" y1="60" x2="140" y2="60" stroke="lightgrey" strokeWidth="9" />;
|
|
15
|
+
tooltipText = 'Linear sequence expected';
|
|
16
|
+
} else {
|
|
17
|
+
svgContent = (
|
|
18
|
+
<>
|
|
19
|
+
<circle cx="30" cy="40" r="25" stroke="lightgrey" strokeWidth="5" fill="none" />
|
|
20
|
+
<line x1="10" y1="110" x2="110" y2="20" stroke="grey" strokeWidth="2" />
|
|
21
|
+
<line x1="60" y1="90" x2="110" y2="90" stroke="lightgrey" strokeWidth="5" />
|
|
22
|
+
</>
|
|
23
|
+
);
|
|
24
|
+
tooltipText = 'Linear/circular sequence expected';
|
|
25
|
+
}
|
|
26
|
+
const toolTipElement = <div style={{ fontSize: 'medium' }}>{tooltipText}</div>;
|
|
27
|
+
return (
|
|
28
|
+
<div>
|
|
29
|
+
<Tooltip title={toolTipElement}>
|
|
30
|
+
<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg">
|
|
31
|
+
{svgContent}
|
|
32
|
+
</svg>
|
|
33
|
+
</Tooltip>
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default React.memo(TemplateSequence);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Dialog, DialogContent, Table, TableBody, TableCell, TableHead, TableRow } from '@mui/material';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
function PlannotateAnnotationReport({ dialogOpen, setDialogOpen, report }) {
|
|
5
|
+
return (
|
|
6
|
+
<Dialog fullWidth maxWidth="lg" open={dialogOpen} onClose={() => setDialogOpen(false)}>
|
|
7
|
+
<DialogContent>
|
|
8
|
+
<Table sx={{ textAlign: 'center' }}>
|
|
9
|
+
<TableHead>
|
|
10
|
+
<TableRow>
|
|
11
|
+
<TableCell sx={{ fontWeight: 'bold', textAlign: 'center' }}>Feature</TableCell>
|
|
12
|
+
<TableCell sx={{ fontWeight: 'bold', textAlign: 'center' }}>Percent identity</TableCell>
|
|
13
|
+
<TableCell sx={{ fontWeight: 'bold', textAlign: 'center' }}>Percent match length</TableCell>
|
|
14
|
+
<TableCell sx={{ fontWeight: 'bold', textAlign: 'center' }}>Description</TableCell>
|
|
15
|
+
</TableRow>
|
|
16
|
+
</TableHead>
|
|
17
|
+
<TableBody>
|
|
18
|
+
{report.map((row) => (
|
|
19
|
+
<TableRow key={`${row.Feature}-${row.Description}`}>
|
|
20
|
+
<TableCell>{row.Feature}</TableCell>
|
|
21
|
+
<TableCell sx={{ textAlign: 'center' }}>{Number(row.percent_identity).toFixed(0)}</TableCell>
|
|
22
|
+
<TableCell sx={{ textAlign: 'center' }}>{Number(row.percent_match_length).toFixed(0)}</TableCell>
|
|
23
|
+
<TableCell>{row.Description}</TableCell>
|
|
24
|
+
</TableRow>
|
|
25
|
+
))}
|
|
26
|
+
</TableBody>
|
|
27
|
+
</Table>
|
|
28
|
+
</DialogContent>
|
|
29
|
+
</Dialog>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default PlannotateAnnotationReport;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useDispatch, useStore } from 'react-redux';
|
|
3
|
+
import useAlerts from '../../hooks/useAlerts';
|
|
4
|
+
import { jsonToGenbank } from '@teselagen/bio-parsers';
|
|
5
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
6
|
+
import useStoreEditor from '../../hooks/useStoreEditor';
|
|
7
|
+
|
|
8
|
+
const { updateSequence } = cloningActions;
|
|
9
|
+
|
|
10
|
+
export default function useUpdateAnnotationInMainSequence() {
|
|
11
|
+
const store = useStore();
|
|
12
|
+
const { addAlert } = useAlerts();
|
|
13
|
+
const dispatch = useDispatch();
|
|
14
|
+
const { updateStoreEditor } = useStoreEditor();
|
|
15
|
+
|
|
16
|
+
const updateFunction = React.useCallback(() => {
|
|
17
|
+
const state = store.getState();
|
|
18
|
+
const mainSequenceId = state.cloning.mainSequenceId;
|
|
19
|
+
const currentSequenceData = state.cloning.teselaJsonCache[mainSequenceId];
|
|
20
|
+
const newSequenceData = state.VectorEditor.mainEditor.sequenceData;
|
|
21
|
+
const mainSequence = state.cloning.sequences.find((s) => s.id === mainSequenceId);
|
|
22
|
+
const newSequence = { ...mainSequence, file_content: jsonToGenbank(newSequenceData) };
|
|
23
|
+
if (currentSequenceData.sequence.toUpperCase() === newSequenceData.sequence.toUpperCase()) {
|
|
24
|
+
addAlert({
|
|
25
|
+
message: 'Annotation updated',
|
|
26
|
+
severity: 'success',
|
|
27
|
+
});
|
|
28
|
+
dispatch(updateSequence(newSequence));
|
|
29
|
+
// Clear the sequenceDataHistory
|
|
30
|
+
updateStoreEditor('mainEditor', mainSequenceId, { sequenceDataHistory: {} });
|
|
31
|
+
} else {
|
|
32
|
+
addAlert({
|
|
33
|
+
message: 'Sequences are different!',
|
|
34
|
+
severity: 'error',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}, [store, addAlert, dispatch, updateStoreEditor]);
|
|
38
|
+
return updateFunction;
|
|
39
|
+
};
|