@opencloning/ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/package.json +37 -0
  3. package/src/components/AppAlerts.jsx +19 -0
  4. package/src/components/CloningHistory.jsx +40 -0
  5. package/src/components/DataModelDisplayer.jsx +30 -0
  6. package/src/components/DescriptionEditor.jsx +68 -0
  7. package/src/components/DownloadCloningStrategyDialog.jsx +84 -0
  8. package/src/components/DownloadSequenceFileDialog.jsx +90 -0
  9. package/src/components/DragAndDropCloningHistoryWrapper.jsx +39 -0
  10. package/src/components/DraggableDialogPaper.jsx +16 -0
  11. package/src/components/EditSequenceNameDialog.jsx +90 -0
  12. package/src/components/ExternalServicesStatusCheck.jsx +92 -0
  13. package/src/components/HistoryLoadedDialog.jsx +49 -0
  14. package/src/components/LoadCloningHistoryWrapper.jsx +166 -0
  15. package/src/components/MainSequenceCheckBox.jsx +83 -0
  16. package/src/components/MainSequenceEditor.jsx +165 -0
  17. package/src/components/NetworkNode.jsx +159 -0
  18. package/src/components/NetworkTree.css +127 -0
  19. package/src/components/ObjectTable.jsx +24 -0
  20. package/src/components/OpenCloning.jsx +102 -0
  21. package/src/components/OverhangsDisplay.jsx +25 -0
  22. package/src/components/SequenceEditor.jsx +120 -0
  23. package/src/components/SequenceTab.jsx +14 -0
  24. package/src/components/TemplateSequence.jsx +38 -0
  25. package/src/components/annotation/PlannotateAnnotationReport.jsx +33 -0
  26. package/src/components/annotation/useUpdateAnnotationInMainSequence.js +39 -0
  27. package/src/components/assembler/AssemblePartWidget.jsx +252 -0
  28. package/src/components/assembler/Assembler.jsx +273 -0
  29. package/src/components/assembler/AssemblerPart.jsx +99 -0
  30. package/src/components/assembler/StopIcon.jsx +34 -0
  31. package/src/components/assembler/assembler_data2.json +50 -0
  32. package/src/components/assembler/assembly_component.module.css +81 -0
  33. package/src/components/assembler/moclo.json +110 -0
  34. package/src/components/assembler/sbol_visual_glyphs/LICENSE.html +21 -0
  35. package/src/components/assembler/sbol_visual_glyphs/assembly-scar.svg +63 -0
  36. package/src/components/assembler/sbol_visual_glyphs/cds-stop.svg +85 -0
  37. package/src/components/assembler/sbol_visual_glyphs/cds.svg +60 -0
  38. package/src/components/assembler/sbol_visual_glyphs/chromosomal-locus.svg +78 -0
  39. package/src/components/assembler/sbol_visual_glyphs/engineered-region.svg +56 -0
  40. package/src/components/assembler/sbol_visual_glyphs/five-prime-sticky-restriction-site.svg +56 -0
  41. package/src/components/assembler/sbol_visual_glyphs/origin-of-replication.svg +57 -0
  42. package/src/components/assembler/sbol_visual_glyphs/primer-binding-site.svg +59 -0
  43. package/src/components/assembler/sbol_visual_glyphs/promoter.svg +60 -0
  44. package/src/components/assembler/sbol_visual_glyphs/ribosome-entry-site.svg +56 -0
  45. package/src/components/assembler/sbol_visual_glyphs/specific-recombination-site.svg +59 -0
  46. package/src/components/assembler/sbol_visual_glyphs/terminator.svg +60 -0
  47. package/src/components/assembler/sbol_visual_glyphs/three-prime-sticky-restriction-site.svg +56 -0
  48. package/src/components/assembler/sbol_visual_glyphs.js +36 -0
  49. package/src/components/assembler/useAssembler.js +71 -0
  50. package/src/components/dummy/DummyInterface.js +41 -0
  51. package/src/components/dummy/GetSequenceFileAndDatabaseIdComponent.jsx +59 -0
  52. package/src/components/eLabFTW/ELabFTWCategorySelect.cy.jsx +86 -0
  53. package/src/components/eLabFTW/ELabFTWCategorySelect.jsx +43 -0
  54. package/src/components/eLabFTW/ELabFTWFileSelect.cy.jsx +43 -0
  55. package/src/components/eLabFTW/ELabFTWFileSelect.jsx +29 -0
  56. package/src/components/eLabFTW/ELabFTWResourceSelect.cy.jsx +107 -0
  57. package/src/components/eLabFTW/ELabFTWResourceSelect.jsx +23 -0
  58. package/src/components/eLabFTW/GetPrimerComponent.cy.jsx +261 -0
  59. package/src/components/eLabFTW/GetPrimerComponent.jsx +55 -0
  60. package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.cy.jsx +184 -0
  61. package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.jsx +62 -0
  62. package/src/components/eLabFTW/LoadHistoryComponent.cy.jsx +235 -0
  63. package/src/components/eLabFTW/LoadHistoryComponent.jsx +51 -0
  64. package/src/components/eLabFTW/PrimersNotInDatabaseComponent.cy.jsx +159 -0
  65. package/src/components/eLabFTW/PrimersNotInDatabaseComponent.jsx +54 -0
  66. package/src/components/eLabFTW/SubmitToDatabaseComponent.cy.jsx +185 -0
  67. package/src/components/eLabFTW/SubmitToDatabaseComponent.jsx +51 -0
  68. package/src/components/eLabFTW/common.js +26 -0
  69. package/src/components/eLabFTW/eLabFTWInterface.js +294 -0
  70. package/src/components/eLabFTW/eLabFTWInterface.test.js +839 -0
  71. package/src/components/eLabFTW/envValues.js +7 -0
  72. package/src/components/eLabFTW/utils.js +39 -0
  73. package/src/components/form/CustomFormHelperText.jsx +10 -0
  74. package/src/components/form/EnzymeMultiSelect.cy.jsx +61 -0
  75. package/src/components/form/EnzymeMultiSelect.jsx +34 -0
  76. package/src/components/form/GetRequestMultiSelect.jsx +107 -0
  77. package/src/components/form/LabelWithTooltip.jsx +16 -0
  78. package/src/components/form/PostRequestSelect.cy.jsx +70 -0
  79. package/src/components/form/PostRequestSelect.jsx +86 -0
  80. package/src/components/form/RequestStatusWrapper.jsx +17 -0
  81. package/src/components/form/RetryAlert.jsx +20 -0
  82. package/src/components/form/ServerErrorMessage.jsx +10 -0
  83. package/src/components/form/SubmitButtonBackendAPI.jsx +15 -0
  84. package/src/components/form/SubmitToDatabaseDialog.jsx +133 -0
  85. package/src/components/form/TextFieldValidate.jsx +67 -0
  86. package/src/components/form/ValidatedTextField.jsx +33 -0
  87. package/src/components/form/intermediates_disclaimer.svg +181 -0
  88. package/src/components/navigation/ButtonWithMenu.jsx +43 -0
  89. package/src/components/navigation/CustomTab.jsx +14 -0
  90. package/src/components/navigation/FeedbackDialog.jsx +34 -0
  91. package/src/components/navigation/GithubCornerRight.jsx +29 -0
  92. package/src/components/navigation/MainAppBar.css +26 -0
  93. package/src/components/navigation/MainAppBar.jsx +205 -0
  94. package/src/components/navigation/SelectExampleDialog.jsx +69 -0
  95. package/src/components/navigation/SelectTemplateDialog.jsx +107 -0
  96. package/src/components/navigation/TabPanel.jsx +28 -0
  97. package/src/components/navigation/VersionDialog.jsx +33 -0
  98. package/src/components/primers/CreatePrimerFromSequenceForm.jsx +42 -0
  99. package/src/components/primers/DownloadPrimersButton.jsx +104 -0
  100. package/src/components/primers/PrimerForm.css +14 -0
  101. package/src/components/primers/PrimerForm.jsx +107 -0
  102. package/src/components/primers/PrimerList.css +46 -0
  103. package/src/components/primers/PrimerList.cy.jsx +95 -0
  104. package/src/components/primers/PrimerList.jsx +126 -0
  105. package/src/components/primers/PrimerTableRow.cy.jsx +57 -0
  106. package/src/components/primers/PrimerTableRow.jsx +84 -0
  107. package/src/components/primers/SelectPrimerForm.jsx +66 -0
  108. package/src/components/primers/import_primers/ImportPrimersButton.jsx +101 -0
  109. package/src/components/primers/import_primers/ImportPrimersTable.jsx +51 -0
  110. package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +107 -0
  111. package/src/components/primers/import_primers/styles.css +60 -0
  112. package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +31 -0
  113. package/src/components/primers/primer_design/SequenceTabComponents/GatewayRoiSelect.jsx +164 -0
  114. package/src/components/primers/primer_design/SequenceTabComponents/OrientationPicker.jsx +37 -0
  115. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignContext.jsx +369 -0
  116. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignEBIC.jsx +24 -0
  117. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +29 -0
  118. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGatewayBP.jsx +36 -0
  119. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx +26 -0
  120. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignHomologousRecombination.jsx +32 -0
  121. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignRestriction.jsx +25 -0
  122. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignSimplePair.jsx +25 -0
  123. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignStepper.jsx +53 -0
  124. package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesigner.jsx +80 -0
  125. package/src/components/primers/primer_design/SequenceTabComponents/PrimerResultForm.jsx +49 -0
  126. package/src/components/primers/primer_design/SequenceTabComponents/PrimerSettingsForm.jsx +68 -0
  127. package/src/components/primers/primer_design/SequenceTabComponents/PrimerSpacerForm.jsx +84 -0
  128. package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +85 -0
  129. package/src/components/primers/primer_design/SequenceTabComponents/StepNavigation.jsx +48 -0
  130. package/src/components/primers/primer_design/SequenceTabComponents/TabPanelEBICSettings.jsx +216 -0
  131. package/src/components/primers/primer_design/SequenceTabComponents/TabPanelResults.jsx +42 -0
  132. package/src/components/primers/primer_design/SequenceTabComponents/TabPanelSelectRoi.jsx +61 -0
  133. package/src/components/primers/primer_design/SequenceTabComponents/TabPannelSettings.jsx +59 -0
  134. package/src/components/primers/primer_design/SequenceTabComponents/primerDesignMinimalValues.json +5 -0
  135. package/src/components/primers/primer_design/SequenceTabComponents/useEBICPrimerDesignSettings.js +31 -0
  136. package/src/components/primers/primer_design/SequenceTabComponents/useEnzymePrimerDesignSettings.js +57 -0
  137. package/src/components/primers/primer_design/SequenceTabComponents/useGatewayPrimerDesignSettings.js +18 -0
  138. package/src/components/primers/primer_design/SequenceTabComponents/usePrimerDesignSettings.js +31 -0
  139. package/src/components/primers/primer_design/SourceComponents/PrimerDesignGatewayBP.jsx +88 -0
  140. package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.jsx +65 -0
  141. package/src/components/primers/primer_design/SourceComponents/PrimerDesignHomologousRecombination.jsx +84 -0
  142. package/src/components/primers/primer_design/SourceComponents/PrimerDesignSourceForm.jsx +74 -0
  143. package/src/components/primers/primer_design/common/NoAttPSitesError.jsx +31 -0
  144. package/src/components/primers/primer_details/PCRTable.cy.jsx +51 -0
  145. package/src/components/primers/primer_details/PCRTable.jsx +35 -0
  146. package/src/components/primers/primer_details/Primer3Figure.jsx +25 -0
  147. package/src/components/primers/primer_details/PrimerDetailsTds.jsx +39 -0
  148. package/src/components/primers/primer_details/PrimerInfoIcon.cy.jsx +137 -0
  149. package/src/components/primers/primer_details/PrimerInfoIcon.jsx +132 -0
  150. package/src/components/primers/primer_details/TableSection.jsx +17 -0
  151. package/src/components/primers/primer_details/primerDetailsFormatting.js +3 -0
  152. package/src/components/primers/primer_details/useMultiplePrimerDetails.js +29 -0
  153. package/src/components/primers/primer_details/usePCRDetails.js +47 -0
  154. package/src/components/primers/primer_details/usePrimerDetailsEndpoints.js +49 -0
  155. package/src/components/primers/primer_details/useSinglePrimerSequenceDetails.js +25 -0
  156. package/src/components/primers/primersToTabularFile.js +49 -0
  157. package/src/components/primers/primersToTabularFile.test.js +108 -0
  158. package/src/components/settings/SettingsTab.cy.jsx +267 -0
  159. package/src/components/settings/SettingsTab.jsx +170 -0
  160. package/src/components/sources/AssemblyPlanDisplayer.cy.jsx +22 -0
  161. package/src/components/sources/AssemblyPlanDisplayer.jsx +27 -0
  162. package/src/components/sources/CollectionSource.jsx +97 -0
  163. package/src/components/sources/FinishedSource.jsx +397 -0
  164. package/src/components/sources/KnownSourceErrors.jsx +50 -0
  165. package/src/components/sources/MultipleInputsSelector.jsx +63 -0
  166. package/src/components/sources/MultipleOutputsSelector.jsx +63 -0
  167. package/src/components/sources/NewSourceBox.jsx +37 -0
  168. package/src/components/sources/PCRUnitForm.jsx +102 -0
  169. package/src/components/sources/SingleInputSelector.jsx +36 -0
  170. package/src/components/sources/Source.jsx +125 -0
  171. package/src/components/sources/SourceAnnotation.jsx +44 -0
  172. package/src/components/sources/SourceAssembly.jsx +201 -0
  173. package/src/components/sources/SourceBox.css +18 -0
  174. package/src/components/sources/SourceBox.jsx +60 -0
  175. package/src/components/sources/SourceCopySequence.jsx +38 -0
  176. package/src/components/sources/SourceDatabase.jsx +28 -0
  177. package/src/components/sources/SourceFile.jsx +188 -0
  178. package/src/components/sources/SourceGenomeRegion.cy.jsx +131 -0
  179. package/src/components/sources/SourceGenomeRegion.jsx +486 -0
  180. package/src/components/sources/SourceHomologousRecombination.jsx +125 -0
  181. package/src/components/sources/SourceKnownGenomeRegion.jsx +60 -0
  182. package/src/components/sources/SourceManuallyTyped.jsx +116 -0
  183. package/src/components/sources/SourcePCRorHybridization.jsx +165 -0
  184. package/src/components/sources/SourcePolymeraseExtension.jsx +44 -0
  185. package/src/components/sources/SourceRepositoryId.jsx +409 -0
  186. package/src/components/sources/SourceRestriction.jsx +41 -0
  187. package/src/components/sources/SourceReverseComplement.jsx +33 -0
  188. package/src/components/sources/SourceTypeSelector.jsx +94 -0
  189. package/src/components/sources/SubSequenceDisplayer.jsx +70 -0
  190. package/src/components/sources/VerifyDeleteDialog.jsx +23 -0
  191. package/src/components/sources/repositoryMetadata.js +14 -0
  192. package/src/components/verification/LoadFromDatabaseButton.jsx +90 -0
  193. package/src/components/verification/SequencingFileRow.jsx +34 -0
  194. package/src/components/verification/VerificationFileDialog.cy.jsx +176 -0
  195. package/src/components/verification/VerificationFileDialog.jsx +248 -0
  196. package/src/config/defaultMainEditorProps.js +44 -0
  197. package/src/hooks/useAlerts.js +16 -0
  198. package/src/hooks/useBackendAPI.js +51 -0
  199. package/src/hooks/useBackendRoute.js +22 -0
  200. package/src/hooks/useDatabase.js +18 -0
  201. package/src/hooks/useDragAndDropFile.js +31 -0
  202. package/src/hooks/useGatewaySites.js +40 -0
  203. package/src/hooks/useHttpClient.js +12 -0
  204. package/src/hooks/useLoadDatabaseFile.js +108 -0
  205. package/src/hooks/useStoreEditor.js +101 -0
  206. package/src/hooks/useValidateState.js +43 -0
  207. package/vitest.config.js +18 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # @opencloning/ui
2
+
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - babe2f9: Switch to monorepo structure and use changesets
8
+
9
+ ### Minor Changes
10
+
11
+ - 8cd33bb: Rearrange dependencies
12
+
13
+ ### Patch Changes
14
+
15
+ - Updated dependencies [8cd33bb]
16
+ - Updated dependencies [babe2f9]
17
+ - @opencloning/store@1.0.0
18
+ - @opencloning/utils@1.0.0
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@opencloning/ui",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./src/index.js",
6
+ "exports": {
7
+ ".": "./src/index.js"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/manulera/OpenCloning_frontend.git",
12
+ "directory": "packages/ui"
13
+ },
14
+ "dependencies": {
15
+ "@emotion/react": "^11.14.0",
16
+ "@emotion/styled": "^11.14.0",
17
+ "@mui/icons-material": "^5.15.17",
18
+ "@mui/material": "^5.15.17",
19
+ "@opencloning/store": "workspace:*",
20
+ "@opencloning/utils": "workspace:*",
21
+ "@teselagen/bio-parsers": "^0.4.32",
22
+ "@teselagen/ove": "^0.8.18",
23
+ "@teselagen/range-utils": "^0.3.13",
24
+ "@teselagen/sequence-utils": "^0.3.35",
25
+ "@zip.js/zip.js": "^2.7.62",
26
+ "axios": "^1.12.2",
27
+ "lodash-es": "^4.17.21",
28
+ "react": "^18.3.1",
29
+ "react-draggable": "^4.4.6",
30
+ "react-redux": "^8.1.3"
31
+ },
32
+ "peerDependencies": {
33
+ "react": "^18.3.1",
34
+ "react-dom": "^18.3.1",
35
+ "react-redux": "^8.1.3"
36
+ }
37
+ }
@@ -0,0 +1,19 @@
1
+ import { Alert } from '@mui/material';
2
+ import { isEqual } from 'lodash-es';
3
+ import React from 'react';
4
+ import { useSelector } from 'react-redux';
5
+ import useAlerts from '../hooks/useAlerts';
6
+ import ExternalServicesStatusCheck from './ExternalServicesStatusCheck';
7
+
8
+ function AppAlerts() {
9
+ const { alerts } = useSelector((state) => state.cloning, isEqual);
10
+ const { removeAlert } = useAlerts();
11
+ return (
12
+ <div id="global-error-message-wrapper">
13
+ {alerts.map((alert, index) => (<Alert key={index} severity={alert.severity} onClose={() => { removeAlert(alert.message); }}>{alert.message}</Alert>))}
14
+ <ExternalServicesStatusCheck />
15
+ </div>
16
+ );
17
+ }
18
+
19
+ export default AppAlerts;
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import isEqual from 'lodash-es/isEqual';
4
+ import { getSortedSourceIds } from '@opencloning/utils/network';
5
+ import NetWorkNode from './NetworkNode';
6
+ import NewSourceBox from './sources/NewSourceBox';
7
+ import DragAndDropCloningHistoryWrapper from './DragAndDropCloningHistoryWrapper';
8
+
9
+ function CloningHistory() {
10
+ const startingSourceIds = useSelector(
11
+ (state) => {
12
+ const sequenceIds = state.cloning.sequences.map((sequence) => sequence.id);
13
+ const sequenceIdsOfInputs = state.cloning.sources.flatMap((source) => source.input.map(({ sequence }) => sequence));
14
+ const terminalSequences = sequenceIds.filter((sequenceId) => !sequenceIdsOfInputs.includes(sequenceId));
15
+ const terminalSources = state.cloning.sources.filter((source) => terminalSequences.includes(source.id) || !sequenceIds.includes(source.id));
16
+ return getSortedSourceIds(terminalSources, state.cloning.sources);
17
+ },
18
+ isEqual,
19
+ );
20
+ return (
21
+ <DragAndDropCloningHistoryWrapper>
22
+ <div className="tf-tree tf-ancestor-tree">
23
+ <div>
24
+ <ul>
25
+ {startingSourceIds.map((sourceId) => (
26
+ <NetWorkNode key={sourceId} {...{ sourceId }} />
27
+ ))}
28
+ {/* There is always a box on the right side to add a source */}
29
+ <li key="new_source_box" className="new_source_box">
30
+ <span className="tf-nc"><span className="node-text"><NewSourceBox /></span></span>
31
+ </li>
32
+ </ul>
33
+ </div>
34
+ </div>
35
+ </DragAndDropCloningHistoryWrapper>
36
+
37
+ );
38
+ }
39
+
40
+ export default React.memo(CloningHistory);
@@ -0,0 +1,30 @@
1
+ import { isEqual } from 'lodash-es';
2
+ import React from 'react';
3
+ import { useSelector } from 'react-redux';
4
+
5
+ function DataModelDisplayer() {
6
+ const { sources, sequences, primers } = useSelector((state) => state.cloning, isEqual);
7
+ const trimmedSequences = sequences.map((s) => {
8
+ const seqOut = { ...s };
9
+ seqOut.file_content = '[...]';
10
+ return seqOut;
11
+ });
12
+
13
+ // TODO: proper json syntax highlighting here
14
+ return (
15
+ <div className="data-model-displayer">
16
+ <p>
17
+ Visit the
18
+ {' '}
19
+ <a href="https://opencloning.github.io/OpenCloning_LinkML" target="_blank" rel="noopener noreferrer">data model documentation</a>
20
+ .
21
+ </p>
22
+
23
+ <code>
24
+ {JSON.stringify({ sources, sequences: trimmedSequences, primers }, null, 4)}
25
+ </code>
26
+ </div>
27
+ );
28
+ }
29
+
30
+ export default DataModelDisplayer;
@@ -0,0 +1,68 @@
1
+ import React, { useEffect } from 'react';
2
+ import { useDispatch, useSelector, shallowEqual } from 'react-redux';
3
+ import TextField from '@mui/material/TextField';
4
+ import { Button, FormControl } from '@mui/material';
5
+ import { cloningActions } from '@opencloning/store/cloning';
6
+
7
+ function DescriptionEditor() {
8
+ const description = useSelector((state) => state.cloning.description, shallowEqual);
9
+ const [text, setText] = React.useState('');
10
+ const [typing, setTyping] = React.useState('');
11
+
12
+ useEffect(
13
+ () => {
14
+ setText(description);
15
+ setTyping(description === '');
16
+ },
17
+ [description],
18
+ );
19
+ const { setDescription: setDescriptionAction } = cloningActions;
20
+ const dispatch = useDispatch();
21
+
22
+ const onChange = (e) => {
23
+ setText(e.target.value);
24
+ };
25
+ const onSubmit = (e) => {
26
+ e.preventDefault();
27
+ if (text === '') {
28
+ return;
29
+ }
30
+ dispatch(setDescriptionAction(text));
31
+ setTyping(false);
32
+ };
33
+ const onClickEditButton = () => {
34
+ setTyping(true);
35
+ };
36
+ let textShown = (
37
+ <>
38
+ <p>{text}</p>
39
+ <Button onClick={onClickEditButton} variant="contained" color="success" style={{ marginTop: 15 }}>Edit description</Button>
40
+
41
+ </>
42
+ );
43
+ if (typing) {
44
+ textShown = (
45
+ <form onSubmit={onSubmit}>
46
+ <FormControl fullWidth>
47
+ <TextField
48
+ id="outlined-multiline-flexible"
49
+ multiline
50
+ fullWidth
51
+ onChange={onChange}
52
+ value={text}
53
+ label="Add a brief description"
54
+ />
55
+ </FormControl>
56
+ <Button type="submit" variant="contained" style={{ marginTop: 15 }}>Save description</Button>
57
+ </form>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <div className="description-container">
63
+ {textShown}
64
+ </div>
65
+ );
66
+ }
67
+
68
+ export default DescriptionEditor;
@@ -0,0 +1,84 @@
1
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel, FormLabel, Radio, RadioGroup, TextField } from '@mui/material';
2
+ import React from 'react';
3
+ import { useSelector, useStore } from 'react-redux';
4
+ import { downloadStateAsJson, downloadStateAsZip } from '@opencloning/utils/readNwrite';
5
+ import useAlerts from '../hooks/useAlerts';
6
+
7
+
8
+ function DownloadCloningStrategyDialog({ open, setOpen }) {
9
+ const [fileName, setFileName] = React.useState('cloning_strategy');
10
+ const [extension, setExtension] = React.useState('.json');
11
+ const hasVerificationFiles = useSelector(({ cloning }) => cloning.files.length > 0);
12
+
13
+ const store = useStore();
14
+ const { addAlert } = useAlerts();
15
+
16
+ return (
17
+ <Dialog
18
+ open={open}
19
+ className="download-cloning-strategy-dialog"
20
+ onClose={() => setOpen(false)}
21
+ PaperProps={{
22
+ component: 'form',
23
+ onSubmit: async (event) => {
24
+ event.preventDefault();
25
+ setOpen(false);
26
+ const cloningState = store.getState().cloning;
27
+ if (extension === '.zip') {
28
+ try {
29
+ await downloadStateAsZip(cloningState, fileName + extension);
30
+ } catch (error) {
31
+ console.error(error);
32
+ addAlert({ message: error.message, severity: 'error' });
33
+ }
34
+ } else {
35
+ downloadStateAsJson(cloningState, fileName + extension);
36
+ }
37
+ },
38
+ }}
39
+ >
40
+ <DialogTitle>Save cloning strategy to file</DialogTitle>
41
+ <DialogContent>
42
+ <FormControl fullWidth>
43
+ <TextField
44
+ autoFocus
45
+ required
46
+ id="file_name"
47
+ label="File name"
48
+ variant="standard"
49
+ value={fileName}
50
+ onChange={(e) => setFileName(e.target.value)}
51
+ sx={{ mb: 2 }}
52
+ />
53
+ {hasVerificationFiles && (
54
+ <>
55
+ <FormLabel id="save-file-radio-group-label">File format</FormLabel>
56
+ <RadioGroup
57
+ aria-labelledby="save-file-radio-group-label"
58
+ value={extension}
59
+ variant="standard"
60
+ onChange={(e) => setExtension(e.target.value)}
61
+ >
62
+ <FormControlLabel value=".json" control={<Radio />} label="json (cloning strategy)" />
63
+ <FormControlLabel value=".zip" control={<Radio />} label="zip (cloning strategy + verification files)" />
64
+ </RadioGroup>
65
+ </>
66
+ )}
67
+ </FormControl>
68
+
69
+ </DialogContent>
70
+ <DialogActions>
71
+ <Button
72
+ onClick={() => {
73
+ setOpen(false);
74
+ }}
75
+ >
76
+ Cancel
77
+ </Button>
78
+ <Button type="submit">Save file</Button>
79
+ </DialogActions>
80
+ </Dialog>
81
+ );
82
+ }
83
+
84
+ export default DownloadCloningStrategyDialog;
@@ -0,0 +1,90 @@
1
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel, FormLabel, Radio, RadioGroup, TextField } from '@mui/material';
2
+ import React from 'react';
3
+ import { useDispatch, useSelector, useStore } from 'react-redux';
4
+ import { isEqual } from 'lodash-es';
5
+ import { downloadSequence } from '@opencloning/utils/readNwrite';
6
+ import { exportSubStateThunk } from '@opencloning/utils/thunks';
7
+ import { getPCRPrimers} from '@opencloning/store/cloning_utils';
8
+ import { substateHasFiles } from '@opencloning/utils/network';
9
+
10
+ // You can override the downloadSequence function by passing a downloadCallback that takes the fileName and sequence as arguments
11
+ function DownloadSequenceFileDialog({ id, dialogOpen, setDialogOpen, downloadCallback }) {
12
+ const [fileName, setFileName] = React.useState('');
13
+ const [extension, setExtension] = React.useState('.gb');
14
+ const hasFiles = useSelector(({ cloning }) => substateHasFiles(cloning, id), isEqual);
15
+ const sequenceName = useSelector(({ cloning }) => cloning.teselaJsonCache[id]?.name || '', isEqual);
16
+ const store = useStore();
17
+ const dispatch = useDispatch();
18
+
19
+ React.useEffect(() => {
20
+ setFileName(sequenceName);
21
+ }, [sequenceName]);
22
+
23
+ return (
24
+ <Dialog
25
+ open={dialogOpen}
26
+ onClose={() => setDialogOpen(false)}
27
+ PaperProps={{
28
+ component: 'form',
29
+ onSubmit: (event) => {
30
+ event.preventDefault();
31
+ setDialogOpen(false);
32
+ if (extension === '.json' || extension === '.zip') {
33
+ dispatch(exportSubStateThunk(fileName + extension, id, extension.slice(1)));
34
+ return;
35
+ }
36
+ const {cloning} = store.getState();
37
+ const seqCopy = structuredClone(cloning.teselaJsonCache[id]);
38
+ const pcrPrimers = getPCRPrimers(cloning, id);
39
+ seqCopy.primers = [...seqCopy.primers, ...pcrPrimers];
40
+ if (downloadCallback) {
41
+ downloadCallback(fileName + extension, seqCopy);
42
+ } else {
43
+ downloadSequence(fileName + extension, seqCopy);
44
+ }
45
+ },
46
+ }}
47
+ >
48
+ <DialogTitle>Save sequence to file</DialogTitle>
49
+ <DialogContent>
50
+ <FormControl fullWidth>
51
+ <TextField
52
+ autoFocus
53
+ required
54
+ id="file_name"
55
+ label="File name"
56
+ variant="standard"
57
+ value={fileName}
58
+ onChange={(e) => setFileName(e.target.value)}
59
+ sx={{ mb: 2 }}
60
+ />
61
+ <FormLabel id="save-file-radio-group-label">File format</FormLabel>
62
+ <RadioGroup
63
+ aria-labelledby="save-file-radio-group-label"
64
+ value={extension}
65
+ variant="standard"
66
+ onChange={(e) => setExtension(e.target.value)}
67
+ >
68
+ <FormControlLabel value=".gb" control={<Radio />} label="genbank" />
69
+ <FormControlLabel value=".fasta" control={<Radio />} label="fasta" />
70
+ <FormControlLabel value=".json" control={<Radio />} label="json (sequence + history)" />
71
+ {hasFiles && <FormControlLabel value=".zip" control={<Radio />} label="zip (sequence + history + verification files)" />}
72
+ </RadioGroup>
73
+ </FormControl>
74
+
75
+ </DialogContent>
76
+ <DialogActions>
77
+ <Button
78
+ onClick={() => {
79
+ setDialogOpen(false);
80
+ }}
81
+ >
82
+ Cancel
83
+ </Button>
84
+ <Button type="submit">Save file</Button>
85
+ </DialogActions>
86
+ </Dialog>
87
+ );
88
+ }
89
+
90
+ export default DownloadSequenceFileDialog;
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import { Tooltip } from '@mui/material';
3
+ import { UploadFile } from '@mui/icons-material';
4
+ import CancelIcon from '@mui/icons-material/Cancel';
5
+ import useDragAndDropFile from '../hooks/useDragAndDropFile';
6
+ import LoadCloningHistoryWrapper from './LoadCloningHistoryWrapper';
7
+
8
+ function DragAndDropCloningHistoryWrapper({ children }) {
9
+ const { isDragging, handleDragLeave, handleDragOver, handleDrop, files: fileList, clearFiles } = useDragAndDropFile();
10
+
11
+ return (
12
+ <div
13
+ onDragOver={handleDragOver}
14
+ onDragLeave={handleDragLeave}
15
+ onDrop={handleDrop}
16
+ className={`${isDragging ? 'dragging-file' : ''} cloning-history`}
17
+ >
18
+ <LoadCloningHistoryWrapper fileList={fileList} clearFiles={clearFiles}>
19
+ {isDragging ? (
20
+ <div className="drag-file-wrapper">
21
+ <div className="drag-file-container">
22
+ <div className="drag-file-close">
23
+ <Tooltip arrow title="Close (back to cloning)" placement="top">
24
+ <CancelIcon type="button" onClick={handleDragLeave} className="cancel-icon" />
25
+ </Tooltip>
26
+ </div>
27
+ <h2>Drop multiple sequence files or a single history file</h2>
28
+ <UploadFile color="primary" sx={{ fontSize: 200 }} />
29
+ </div>
30
+ </div>
31
+ ) : (
32
+ children
33
+ )}
34
+ </LoadCloningHistoryWrapper>
35
+ </div>
36
+ );
37
+ }
38
+
39
+ export default DragAndDropCloningHistoryWrapper;
@@ -0,0 +1,16 @@
1
+ import { Paper } from '@mui/material';
2
+ import React from 'react';
3
+ import Draggable from 'react-draggable';
4
+
5
+ function DraggableDialogPaper(props) {
6
+ return (
7
+ <Draggable
8
+ handle="#draggable-dialog-title"
9
+ cancel={'[class*="MuiDialogContent-root"]'}
10
+ >
11
+ <Paper {...props} />
12
+ </Draggable>
13
+ );
14
+ }
15
+
16
+ export default DraggableDialogPaper;
@@ -0,0 +1,90 @@
1
+ import { Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, TextField } from '@mui/material';
2
+ import React from 'react';
3
+ import { useDispatch, useSelector, useStore } from 'react-redux';
4
+ import { isEqual } from 'lodash-es';
5
+ import { cloningActions } from '@opencloning/store/cloning';
6
+ import error2String from '@opencloning/utils/error2String';
7
+ import useBackendRoute from '../hooks/useBackendRoute';
8
+ import useHttpClient from '../hooks/useHttpClient';
9
+
10
+ function EditSequenceNameDialog({ id, dialogOpen, setDialogOpen }) {
11
+ const [name, setName] = React.useState('');
12
+ const [originalName, setOriginalName] = React.useState('');
13
+ const [error, setError] = React.useState('');
14
+ const store = useStore();
15
+ const backendRoute = useBackendRoute();
16
+ const httpClient = useHttpClient();
17
+
18
+ const { updateSequenceAndItsSource } = cloningActions;
19
+ const dispatch = useDispatch();
20
+
21
+ const changeName = async (newName) => {
22
+ const {sources, sequences} = store.getState().cloning;
23
+ const source = sources.find((s) => s.id === id);
24
+ const sequence = sequences.find((s) => s.id === id);
25
+ setError('');
26
+ const url = backendRoute('rename_sequence');
27
+ try {
28
+ const { data: newSequence } = await httpClient.post(url, sequence, { params: { name } });
29
+ const newSource = { ...source, output_name: newName };
30
+ dispatch(updateSequenceAndItsSource({ newSequence, newSource }));
31
+ setDialogOpen(false);
32
+ } catch (e) {
33
+ setError(error2String(e));
34
+ }
35
+ };
36
+
37
+ React.useEffect(() => {
38
+ const seq = store.getState().cloning.teselaJsonCache[id];
39
+ setName(seq.name);
40
+ setOriginalName(seq.name);
41
+ }, [id]);
42
+
43
+ const nameIsNotValid = /\s/.test(name);
44
+ const submissionAllowed = name && name !== originalName && !nameIsNotValid;
45
+ return (
46
+ <Dialog
47
+ open={dialogOpen}
48
+ onClose={() => setDialogOpen(false)}
49
+ PaperProps={{
50
+ component: 'form',
51
+ onSubmit: async (event) => {
52
+ event.preventDefault();
53
+ changeName(name);
54
+ },
55
+ }}
56
+ >
57
+ <DialogTitle>Rename sequence</DialogTitle>
58
+ <DialogContent>
59
+ <FormControl fullWidth>
60
+ <TextField
61
+ autoFocus
62
+ required
63
+ id="sequence_rename"
64
+ label="New name"
65
+ variant="standard"
66
+ value={name}
67
+ onChange={(e) => setName(e.target.value)}
68
+ sx={{ mb: 2 }}
69
+ error={nameIsNotValid}
70
+ helperText={nameIsNotValid && 'Name cannot contain spaces'}
71
+ />
72
+ </FormControl>
73
+
74
+ {error && <Alert severity="error" sx={{ mt: 2 }}>{error}</Alert>}
75
+ </DialogContent>
76
+ <DialogActions>
77
+ <Button
78
+ onClick={() => {
79
+ setDialogOpen(false);
80
+ }}
81
+ >
82
+ Cancel
83
+ </Button>
84
+ {submissionAllowed && <Button type="submit">Rename</Button>}
85
+ </DialogActions>
86
+ </Dialog>
87
+ );
88
+ }
89
+
90
+ export default EditSequenceNameDialog;
@@ -0,0 +1,92 @@
1
+ import { Alert, Button, CircularProgress } from '@mui/material';
2
+ import React from 'react';
3
+ import { useDispatch } from 'react-redux';
4
+ import useBackendRoute from '../hooks/useBackendRoute';
5
+ import useHttpClient from '../hooks/useHttpClient';
6
+ import { cloningActions } from '@opencloning/store/cloning';
7
+
8
+ const { updateAppInfo } = cloningActions;
9
+
10
+ function ExternalServicesStatusCheck() {
11
+ const [servicesDown, setServicesDown] = React.useState([]);
12
+ const [connectAttempt, setConnectAttemp] = React.useState(0);
13
+ const [loading, setLoading] = React.useState(false);
14
+ const [successMessage, setSuccessMessage] = React.useState('');
15
+ const dispatch = useDispatch();
16
+ const backendRoute = useBackendRoute();
17
+ const httpClient = useHttpClient();
18
+ React.useEffect(() => {
19
+ dispatch(updateAppInfo({ frontendVersion: __APP_VERSION__ }));
20
+ setLoading(true);
21
+ const checkServices = async () => {
22
+ const services = [
23
+ {
24
+ message: 'Backend server is down',
25
+ url: backendRoute('version'),
26
+ check: (resp) => {
27
+ if (resp.status === 200) {
28
+ const { backend_version: backendVersion, schema_version: schemaVersion } = resp.data;
29
+ dispatch(updateAppInfo({ backendVersion, schemaVersion }));
30
+ return true;
31
+ }
32
+ return false;
33
+ },
34
+ },
35
+ ];
36
+ const downServices = [];
37
+ await Promise.all(
38
+ services.map(async (service) => {
39
+ try {
40
+ const resp = await httpClient.get(service.url);
41
+ if (!service.check(resp)) {
42
+ downServices.push(service);
43
+ }
44
+ } catch (error) {
45
+ downServices.push(service);
46
+ }
47
+ }),
48
+ );
49
+ setServicesDown(downServices);
50
+ setLoading(false);
51
+ if (connectAttempt > 0 && downServices.length === 0) {
52
+ setSuccessMessage('All services are up and running!');
53
+ }
54
+ };
55
+ checkServices();
56
+ }, [connectAttempt]);
57
+ if (successMessage) {
58
+ return (
59
+ <Alert severity="success" className="service-status-check-alert" onClose={() => setSuccessMessage('')}>
60
+ {successMessage}
61
+ </Alert>
62
+ );
63
+ }
64
+ if (servicesDown.length > 0) {
65
+ return (
66
+ <Alert
67
+ severity="error"
68
+ className="service-status-check-alert"
69
+ action={(
70
+ <div>
71
+ {loading ? (
72
+ <CircularProgress color="inherit" size="2em" />
73
+ ) : (
74
+ <Button color="inherit" size="small" onClick={() => setConnectAttemp((prev) => prev + 1)}>
75
+ RE-CHECK
76
+ </Button>
77
+ )}
78
+ </div>
79
+ )}
80
+ >
81
+ <div>
82
+ {servicesDown.map((service) => (
83
+ <div key={service.message}>{service.message}</div>
84
+ ))}
85
+ </div>
86
+ </Alert>
87
+ );
88
+ }
89
+ return null;
90
+ }
91
+
92
+ export default ExternalServicesStatusCheck;
@@ -0,0 +1,49 @@
1
+ import { Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel, Radio, RadioGroup } from '@mui/material';
2
+ import React from 'react';
3
+
4
+ function HistoryLoadedDialog({ fileLoaderFunctions }) {
5
+ const [selectedOption, setSelectedOption] = React.useState('replace');
6
+ const { addState, replaceState, clear } = fileLoaderFunctions;
7
+ return (
8
+ <Dialog
9
+ open
10
+ onClose={clear}
11
+ PaperProps={{
12
+ component: 'form',
13
+ onSubmit: async (event) => {
14
+ event.preventDefault();
15
+ if (selectedOption === 'replace') {
16
+ replaceState();
17
+ } else {
18
+ addState();
19
+ }
20
+ clear();
21
+ },
22
+ }}
23
+ className="history-loaded-dialog"
24
+ >
25
+ <DialogTitle>History loaded</DialogTitle>
26
+ <DialogContent>
27
+ <FormControl fullWidth>
28
+ <RadioGroup
29
+ value={selectedOption}
30
+ variant="standard"
31
+ onChange={(e) => setSelectedOption(e.target.value)}
32
+ >
33
+ <FormControlLabel value="replace" control={<Radio />} label="Replace existing" />
34
+ <FormControlLabel value="add" control={<Radio />} label="Add to existing" />
35
+ </RadioGroup>
36
+ </FormControl>
37
+
38
+ </DialogContent>
39
+ <DialogActions>
40
+ <Button onClick={clear}>
41
+ Cancel
42
+ </Button>
43
+ <Button type="submit">Select</Button>
44
+ </DialogActions>
45
+ </Dialog>
46
+ );
47
+ }
48
+
49
+ export default HistoryLoadedDialog;