@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
@@ -0,0 +1,67 @@
1
+ import React from 'react';
2
+ import { Alert, Button, FormControl, TextField } from '@mui/material';
3
+
4
+ export default function TextFieldValidate({ getterFunction, onChange, label, defaultHelperText = '' }) {
5
+ const [helperText, setHelperText] = React.useState('');
6
+ const [userInput, setUserInput] = React.useState('');
7
+ const [error, setError] = React.useState(false);
8
+ const [connectionError, setConnectionError] = React.useState(false);
9
+ const [connectionAttempt, setConnectionAttempt] = React.useState(0);
10
+ React.useEffect(() => {
11
+ // Send an empty response to clear the form
12
+ onChange('', null);
13
+ // Validate assemblyId with a 500ms delay
14
+ if ((userInput !== '')) {
15
+ setHelperText(`Validating ${label}...`);
16
+ const timeOutId = setTimeout(async () => {
17
+ try {
18
+ const resp = await getterFunction(userInput);
19
+ if (resp === null) {
20
+ setHelperText(`${label} does not exist`);
21
+ setError(true);
22
+ } else {
23
+ setHelperText('');
24
+ setError(false);
25
+ }
26
+ onChange(userInput, resp);
27
+ } catch (e) {
28
+ console.error(e);
29
+ setConnectionError(true);
30
+ }
31
+ }, 500);
32
+ return () => clearTimeout(timeOutId);
33
+ }
34
+ // Also set to null if assemblyId is empty
35
+ setHelperText(defaultHelperText);
36
+ setError(false);
37
+ return () => {};
38
+ }, [userInput, connectionAttempt]);
39
+
40
+ if (connectionError) {
41
+ return (
42
+ <Alert
43
+ sx={{ alignItems: 'center' }}
44
+ severity="error"
45
+ action={(
46
+ <Button color="inherit" size="small" onClick={() => { setConnectionError(false); setConnectionAttempt(connectionAttempt + 1); }}>
47
+ Retry
48
+ </Button>
49
+ )}
50
+ >
51
+ Could not connect to server for validation.
52
+ </Alert>
53
+ );
54
+ }
55
+ return (
56
+ <FormControl fullWidth>
57
+ <TextField
58
+ label={label}
59
+ value={userInput}
60
+ error={error}
61
+ helperText={helperText}
62
+ onChange={(event) => { setUserInput(event.target.value); }}
63
+ />
64
+ </FormControl>
65
+
66
+ );
67
+ }
@@ -0,0 +1,33 @@
1
+ import { TextField } from '@mui/material';
2
+ import React from 'react';
3
+ import CustomFormHelperText from './CustomFormHelperText';
4
+
5
+ function ValidatedTextField({ id, value, onInputChange, errorChecker, updateValidationStatus, required, submissionAttempted, floatingHelperText, initialHelperText = '', ...rest }) {
6
+ const defaultPars = { inputProps: { style: { fontSize: 14 } }, FormHelperTextProps: { component: 'div' } };
7
+ const [error, setError] = React.useState(false);
8
+ const [helperText, setHelperText] = React.useState(initialHelperText);
9
+ const [touched, setTouched] = React.useState(false);
10
+
11
+ const handleChange = (event) => {
12
+ onInputChange(event);
13
+ setTouched(true);
14
+ };
15
+
16
+ React.useEffect(() => {
17
+ if (!submissionAttempted && !touched) return;
18
+ if (required && value.length === 0) {
19
+ setError(true);
20
+ setHelperText('Field required');
21
+ updateValidationStatus(id, true);
22
+ return;
23
+ }
24
+ const { error: newError, helperText: newHelperText } = errorChecker(value);
25
+ setError(newError);
26
+ setHelperText(newHelperText);
27
+ updateValidationStatus(id, newError);
28
+ }, [value, touched, submissionAttempted]);
29
+ const renderedHelperText = floatingHelperText ? (<CustomFormHelperText>{helperText}</CustomFormHelperText>) : helperText;
30
+ return (<TextField id={id} value={value} {...defaultPars} {...rest} onChange={handleChange} error={error} helperText={renderedHelperText} />);
31
+ }
32
+
33
+ export default ValidatedTextField;
@@ -0,0 +1,181 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ width="66.574898mm"
6
+ height="68.316658mm"
7
+ viewBox="0 0 66.574898 68.316658"
8
+ version="1.1"
9
+ id="svg1"
10
+ inkscape:version="1.3.2 (091e20e, 2023-11-25)"
11
+ sodipodi:docname="intermediates_disclaimer.svg"
12
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
13
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ xmlns:svg="http://www.w3.org/2000/svg">
16
+ <sodipodi:namedview
17
+ id="namedview1"
18
+ pagecolor="#ffffff"
19
+ bordercolor="#666666"
20
+ borderopacity="1.0"
21
+ inkscape:showpageshadow="2"
22
+ inkscape:pageopacity="0.0"
23
+ inkscape:pagecheckerboard="0"
24
+ inkscape:deskcolor="#d1d1d1"
25
+ inkscape:document-units="mm"
26
+ inkscape:zoom="1.1615876"
27
+ inkscape:cx="-41.753202"
28
+ inkscape:cy="89.102194"
29
+ inkscape:window-width="1400"
30
+ inkscape:window-height="774"
31
+ inkscape:window-x="353"
32
+ inkscape:window-y="96"
33
+ inkscape:window-maximized="0"
34
+ inkscape:current-layer="layer1" />
35
+ <defs
36
+ id="defs1" />
37
+ <g
38
+ inkscape:label="Layer 1"
39
+ inkscape:groupmode="layer"
40
+ id="layer1"
41
+ transform="translate(-41.665391,-77.454446)">
42
+ <path
43
+ style="fill:none;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
44
+ d="M 55.372552,95.314967 V 103.046"
45
+ id="path2" />
46
+ <path
47
+ style="fill:none;stroke:#000000;stroke-width:0.865;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
48
+ d="m 55.372552,119.87934 v 7.73102"
49
+ id="path2-1" />
50
+ <rect
51
+ style="fill:#ffffff;fill-opacity:1;stroke:#2e7d32;stroke-width:0.92259;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
52
+ id="rect2"
53
+ width="26.491732"
54
+ height="17.76988"
55
+ x="42.126686"
56
+ y="77.915741"
57
+ ry="1.8777486" />
58
+ <rect
59
+ style="fill:#ffffff;fill-opacity:1;stroke:#106ba3;stroke-width:0.92259;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
60
+ id="rect2-5"
61
+ width="26.491732"
62
+ height="17.76988"
63
+ x="42.126686"
64
+ y="102.39127"
65
+ ry="1.8777486" />
66
+ <rect
67
+ style="fill:#ffffff;fill-opacity:1;stroke:#106ba3;stroke-width:0.92259;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
68
+ id="rect2-3"
69
+ width="26.491732"
70
+ height="17.76988"
71
+ x="42.126686"
72
+ y="127.53993"
73
+ ry="1.8777486" />
74
+ <text
75
+ xml:space="preserve"
76
+ style="font-size:4.23333px;line-height:1;font-family:arial;-inkscape-font-specification:arial;text-align:center;text-anchor:middle;stroke-width:0.264583"
77
+ x="55.319843"
78
+ y="85.781624"
79
+ id="text2"><tspan
80
+ sodipodi:role="line"
81
+ id="tspan2"
82
+ style="line-height:1;stroke-width:0.264583"
83
+ x="55.319843"
84
+ y="85.781624">sequence</tspan><tspan
85
+ sodipodi:role="line"
86
+ style="line-height:1;stroke-width:0.264583"
87
+ x="55.319843"
88
+ y="90.014954"
89
+ id="tspan3">in database</tspan></text>
90
+ <text
91
+ xml:space="preserve"
92
+ style="font-size:4.23333px;line-height:1;font-family:arial;-inkscape-font-specification:arial;text-align:center;text-anchor:middle;stroke-width:0.264583"
93
+ x="55.319843"
94
+ y="110.25405"
95
+ id="text2-3"><tspan
96
+ sodipodi:role="line"
97
+ id="tspan2-5"
98
+ style="line-height:1;stroke-width:0.264583"
99
+ x="55.319843"
100
+ y="110.25405">intermediate</tspan><tspan
101
+ sodipodi:role="line"
102
+ style="line-height:1;stroke-width:0.264583"
103
+ x="55.319843"
104
+ y="114.48738"
105
+ id="tspan4">sequence</tspan></text>
106
+ <text
107
+ xml:space="preserve"
108
+ style="font-size:4.23333px;line-height:1;font-family:arial;-inkscape-font-specification:arial;text-align:center;text-anchor:middle;stroke-width:0.264583"
109
+ x="55.38702"
110
+ y="134.98517"
111
+ id="text2-3-2"><tspan
112
+ sodipodi:role="line"
113
+ id="tspan2-5-8"
114
+ style="line-height:1;stroke-width:0.264583"
115
+ x="55.38702"
116
+ y="134.98517">sequence</tspan><tspan
117
+ sodipodi:role="line"
118
+ style="line-height:1;stroke-width:0.264583"
119
+ x="55.38702"
120
+ y="139.21851"
121
+ id="tspan4-5">being saved</tspan></text>
122
+ <text
123
+ xml:space="preserve"
124
+ style="font-size:4.23333px;line-height:1;font-family:arial;-inkscape-font-specification:arial;text-align:center;text-anchor:middle;stroke-width:0.264583"
125
+ x="92.760109"
126
+ y="133.68188"
127
+ id="text2-3-2-4"><tspan
128
+ sodipodi:role="line"
129
+ style="line-height:1;stroke-width:0.264583"
130
+ x="92.760109"
131
+ y="133.68188"
132
+ id="tspan4-5-5">will get a new</tspan><tspan
133
+ sodipodi:role="line"
134
+ style="line-height:1;stroke-width:0.264583"
135
+ x="92.760109"
136
+ y="137.91522"
137
+ id="tspan5">entry in the</tspan><tspan
138
+ sodipodi:role="line"
139
+ style="line-height:1;stroke-width:0.264583"
140
+ x="92.760109"
141
+ y="142.14854"
142
+ id="tspan6">database</tspan></text>
143
+ <text
144
+ xml:space="preserve"
145
+ style="font-size:4.23333px;line-height:1;font-family:arial;-inkscape-font-specification:arial;text-align:center;text-anchor:middle;stroke-width:0.264583"
146
+ x="92.896538"
147
+ y="105.99592"
148
+ id="text2-3-2-4-7"><tspan
149
+ sodipodi:role="line"
150
+ style="line-height:1;stroke-width:0.264583"
151
+ x="92.896538"
152
+ y="105.99592"
153
+ id="tspan8">will be contained</tspan><tspan
154
+ sodipodi:role="line"
155
+ style="line-height:1;stroke-width:0.264583"
156
+ x="92.896538"
157
+ y="110.22925"
158
+ id="tspan10">within the entry </tspan><tspan
159
+ sodipodi:role="line"
160
+ style="line-height:1;stroke-width:0.264583"
161
+ x="92.896538"
162
+ y="114.46258"
163
+ id="tspan13">of the sequence</tspan><tspan
164
+ sodipodi:role="line"
165
+ style="line-height:1;stroke-width:0.264583"
166
+ x="92.896538"
167
+ y="118.69591"
168
+ id="tspan14">being saved</tspan></text>
169
+ <text
170
+ xml:space="preserve"
171
+ style="font-size:4.23333px;line-height:1;font-family:arial;-inkscape-font-specification:arial;text-align:center;text-anchor:middle;stroke-width:0.264583"
172
+ x="92.772514"
173
+ y="87.870384"
174
+ id="text2-3-2-4-7-8"><tspan
175
+ sodipodi:role="line"
176
+ style="line-height:1;stroke-width:0.264583"
177
+ x="92.772514"
178
+ y="87.870384"
179
+ id="tspan14-5">stays the same</tspan></text>
180
+ </g>
181
+ </svg>
@@ -0,0 +1,43 @@
1
+ import * as React from 'react';
2
+ import Button from '@mui/material/Button';
3
+ import Menu from '@mui/material/Menu';
4
+ import MenuItem from '@mui/material/MenuItem';
5
+
6
+ export default function ButtonWithMenu({ children, menuItems }) {
7
+ const [anchorEl, setAnchorEl] = React.useState(null);
8
+ const open = Boolean(anchorEl);
9
+ const handleClick = (event) => {
10
+ setAnchorEl(event.currentTarget);
11
+ };
12
+ const handleClose = () => {
13
+ setAnchorEl(null);
14
+ };
15
+
16
+ return (
17
+ <>
18
+ <Button
19
+ id="basic-button"
20
+ aria-controls={open ? 'basic-menu' : undefined}
21
+ aria-haspopup="true"
22
+ aria-expanded={open ? 'true' : undefined}
23
+ onClick={handleClick}
24
+ sx={{ color: 'white' }}
25
+ >
26
+ {children}
27
+ </Button>
28
+ <Menu
29
+ id="basic-menu"
30
+ anchorEl={anchorEl}
31
+ open={open}
32
+ onClose={handleClose}
33
+ MenuListProps={{
34
+ 'aria-labelledby': 'basic-button',
35
+ }}
36
+ >
37
+ {menuItems.map((item) => (
38
+ <MenuItem key={item.display} onClick={() => { item.onClick(); handleClose(); }}>{item.display}</MenuItem>
39
+ ))}
40
+ </Menu>
41
+ </>
42
+ );
43
+ }
@@ -0,0 +1,14 @@
1
+ import { Tab } from '@mui/material';
2
+ import React from 'react';
3
+
4
+ const CustomTab = React.forwardRef(({ label, index, ...other }, ref) => (
5
+ <Tab
6
+ label={label}
7
+ id={`tab-${index}`}
8
+ aria-controls={`tab-panel-${index}`}
9
+ ref={ref}
10
+ {...other}
11
+ />
12
+ ));
13
+
14
+ export default CustomTab;
@@ -0,0 +1,34 @@
1
+ import { Dialog, DialogContent, DialogTitle } from '@mui/material';
2
+ import React from 'react';
3
+
4
+ function FeedbackDialog({ open, setOpen }) {
5
+ return (
6
+ <Dialog open={open} onClose={() => setOpen(false)} className="load-example-dialog">
7
+ <DialogTitle sx={{ textAlign: 'center', fontSize: 'x-large' }}> 😊 Give feedback 😭 </DialogTitle>
8
+ <DialogContent sx={{ fontSize: 'large' }}>
9
+ <p>
10
+ 🙏 Thanks for using OpenCloning!
11
+ </p>
12
+ <p>
13
+ {' '}
14
+ Your feedback is really appreciated:
15
+ <ul>
16
+ <li style={{ marginBottom: 10 }}>
17
+ If you use GitHub,
18
+ {' '}
19
+ <a href="https://github.com/manulera/OpenCloning/issues/new" target="_blank" rel="noopener noreferrer">create an issue</a>
20
+ </li>
21
+ <li>
22
+ Otherwise, send an email to
23
+ {' '}
24
+ <a href="mailto:manuel.lera-ramirez@ucl.ac.uk">manuel.lera-ramirez@ucl.ac.uk</a>
25
+ </li>
26
+ </ul>
27
+
28
+ </p>
29
+ </DialogContent>
30
+ </Dialog>
31
+ );
32
+ }
33
+
34
+ export default React.memo(FeedbackDialog);
@@ -0,0 +1,29 @@
1
+ // From https://github.com/tholman/github-corners under MIT license
2
+ // The MIT License (MIT)
3
+ // Copyright (c) 2025 Tim Holman
4
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
5
+ // associated documentation files (the "Software"), to deal in the Software without restriction, including
6
+ // without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ // sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject
8
+ // to the following conditions:
9
+ // The above copyright notice and this permission notice shall be included in all copies or substantial
10
+ // portions of the Software.
11
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
12
+ // LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
13
+ // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
14
+ // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
15
+ // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
16
+ import React from 'react';
17
+
18
+ function GithubCornerRight() {
19
+ return (
20
+ <svg xmlns="http://www.w3.org/2000/svg" width="80" height="80" viewBox="0 0 250 250" fill="#1976d2">
21
+ <path d="M0 0l115 115h15l12 27 108 108V0z" fill="#fff" />
22
+ <path className="octo-arm" d="M128 109c-15-9-9-19-9-19 3-7 2-11 2-11-1-7 3-2 3-2 4 5 2 11 2 11-3 10 5 15 9 16" style={{ '-webkit-transform-origin': '130px 106px', transformOrigin: '130px 106px' }} />
23
+ <path className="octo-body" d="M115 115s4 2 5 0l14-14c3-2 6-3 8-3-8-11-15-24 2-41 5-5 10-7 16-7 1-2 3-7 12-11 0 0 5 3 7 16 4 2 8 5 12 9s7 8 9 12c14 3 17 7 17 7-4 8-9 11-11 11 0 6-2 11-7 16-16 16-30 10-41 2 0 3-1 7-5 11l-12 11c-1 1 1 5 1 5z" />
24
+ </svg>
25
+
26
+ );
27
+ }
28
+
29
+ export default GithubCornerRight;
@@ -0,0 +1,26 @@
1
+ .app-bar {
2
+ position: relative;
3
+ }
4
+
5
+ .github-corner {
6
+ position: fixed !important;
7
+ top: 0px !important;
8
+ right: 0px !important;
9
+ padding: 0px !important;
10
+ }
11
+
12
+
13
+ .app-bar-container button[type="button"] {
14
+ font-size: large;
15
+ margin-left: 20px;
16
+ margin-right: 20px;
17
+ color: white;
18
+ }
19
+
20
+ .app-name {
21
+ font-size: 4em;
22
+ font-weight: 200;
23
+ text-align: center;
24
+ }
25
+
26
+
@@ -0,0 +1,205 @@
1
+ import React from 'react';
2
+ import AppBar from '@mui/material/AppBar';
3
+ import Box from '@mui/material/Box';
4
+ import Toolbar from '@mui/material/Toolbar';
5
+ import Container from '@mui/material/Container';
6
+ import { Button, Tooltip } from '@mui/material';
7
+ import './MainAppBar.css';
8
+ import { useDispatch } from 'react-redux';
9
+ import IconButton from '@mui/material/IconButton';
10
+ import Menu from '@mui/material/Menu';
11
+ import MenuItem from '@mui/material/MenuItem';
12
+ import MenuIcon from '@mui/icons-material/Menu';
13
+ import ButtonWithMenu from './ButtonWithMenu';
14
+ import { downloadCloningStrategyAsSvg, formatTemplate, loadHistoryFile, loadFilesToSessionStorage } from '@opencloning/utils/readNwrite';
15
+ import SelectExampleDialog from './SelectExampleDialog';
16
+ import SelectTemplateDialog from './SelectTemplateDialog';
17
+ import FeedbackDialog from './FeedbackDialog';
18
+ import { cloningActions } from '@opencloning/store/cloning';
19
+ import VersionDialog from './VersionDialog';
20
+ import useAlerts from '../../hooks/useAlerts';
21
+ import DownloadCloningStrategyDialog from '../DownloadCloningStrategyDialog';
22
+ import LoadCloningHistoryWrapper from '../LoadCloningHistoryWrapper';
23
+ import useValidateState from '../../hooks/useValidateState';
24
+ import GithubCornerRight from './GithubCornerRight';
25
+ import useHttpClient from '../../hooks/useHttpClient';
26
+
27
+ const { setCurrentTab, setState: setCloningState } = cloningActions;
28
+
29
+ function MainAppBar() {
30
+ const [openExampleDialog, setOpenExampleDialog] = React.useState(false);
31
+ const [openTemplateDialog, setOpenTemplateDialog] = React.useState(false);
32
+ const [openFeedbackDialog, setOpenFeedbackDialog] = React.useState(false);
33
+ const [openCloningStrategyDialog, setOpenCloningStrategyDialog] = React.useState(false);
34
+ const [fileList, setFileList] = React.useState([]);
35
+ const [openVersionDialog, setOpenVersionDialog] = React.useState(false);
36
+ const [anchorEl, setAnchorEl] = React.useState(null);
37
+ const openMenu = Boolean(anchorEl);
38
+ const httpClient = useHttpClient();
39
+
40
+ const dispatch = useDispatch();
41
+ const { addAlert } = useAlerts();
42
+ const validateState = useValidateState();
43
+
44
+ // Hidden input field, used to load files.
45
+ const fileInputRef = React.useRef(null);
46
+
47
+ const fileMenu = [
48
+ { display: 'Save cloning history to file', onClick: () => setOpenCloningStrategyDialog(true) },
49
+ { display: 'Load cloning history from file', onClick: () => { fileInputRef.current.click(); fileInputRef.current.value = ''; } },
50
+ { display: 'Print cloning history to svg',
51
+ onClick: async () => {
52
+ await dispatch(setCurrentTab(0));
53
+ downloadCloningStrategyAsSvg('history.svg');
54
+ } },
55
+ ];
56
+
57
+
58
+ const handleCloseDialog = async (url, isTemplate) => {
59
+ setOpenExampleDialog(false);
60
+ setOpenTemplateDialog(false);
61
+ if (url) {
62
+ let data;
63
+ if (url.endsWith('.zip')) {
64
+ // For zip files, get as blob and process with loadHistoryFile
65
+ const { data: blob } = await httpClient.get(url, { responseType: 'blob' });
66
+ const fileName = url.split('/').pop();
67
+ // eslint-disable-next-line no-undef
68
+ const file = new File([blob], fileName);
69
+ const { cloningStrategy, verificationFiles } = await loadHistoryFile(file);
70
+ data = await validateState(cloningStrategy);
71
+ await loadFilesToSessionStorage(verificationFiles, 0);
72
+ } else {
73
+ // For JSON files, get as JSON
74
+ let { data: jsonData } = await httpClient.get(url);
75
+ data = await validateState(jsonData);
76
+ }
77
+ if (isTemplate) {
78
+ data = formatTemplate(data, url);
79
+ }
80
+ dispatch(setCloningState(data));
81
+ dispatch(setCurrentTab(0))
82
+ }
83
+ };
84
+
85
+ // TODO: turn these into <a> elements.
86
+ const helpMenu = [
87
+ { display: 'Newsletter', onClick: () => window.open('https://eepurl.com/h9-n71') },
88
+ { display: 'About the project', onClick: () => window.open('https://github.com/manulera/OpenCloning') },
89
+ { display: 'GitHub repository', onClick: () => window.open('https://github.com/manulera/OpenCloning_frontend') },
90
+ { display: 'Demo videos', onClick: () => window.open('https://www.youtube.com/watch?v=n0hedzvpW88&list=PLpv3x-ensLZkJToD2E6ejefADmHcUPYSJ&index=1') },
91
+ { display: 'App version', onClick: () => setOpenVersionDialog(true) },
92
+ ];
93
+
94
+ const onFileChange = async (event) => {
95
+ const { files } = event.target;
96
+ if (files[0].name.endsWith('.json') || files[0].name.endsWith('.zip')) {
97
+ setFileList([...files]);
98
+ } else {
99
+ setFileList([]);
100
+ addAlert({ message: 'Only JSON and zip files are accepted', severity: 'error' });
101
+ }
102
+ event.target.value = '';
103
+ };
104
+
105
+ // If you want to do something on page load, you can do it here.
106
+ // React.useEffect(() => {
107
+ // const fetchExample = async () => {
108
+ // dispatch(setCurrentTab(3));
109
+ // // Wait for the primer designer to be rendered
110
+ // setTimeout(() => {
111
+ // // Click on button that says Open primer designer
112
+ // const primerDesignerButton = document.querySelector('.main-sequence-editor button');
113
+ // if (primerDesignerButton) {
114
+ // primerDesignerButton.click();
115
+ // }
116
+ // }, 300);
117
+ // };
118
+ // fetchExample();
119
+ // }, []);
120
+
121
+ const handleMenuOpen = (event) => {
122
+ setAnchorEl(event.currentTarget);
123
+ };
124
+
125
+ const handleMenuClose = () => {
126
+ setAnchorEl(null);
127
+ };
128
+
129
+ return (
130
+ <AppBar position="static" className="app-bar">
131
+ <div className="app-name">OpenCloning</div>
132
+ <Tooltip title="View source code on GitHub" placement="left" componentsProps={{ tooltip: { sx: { fontSize: '1rem' } } }}>
133
+ <Box sx={{ display: { xs: 'none', md: 'flex' } }}>
134
+ <Button
135
+ className="github-corner"
136
+ onClick={() => window.open('https://github.com/manulera/OpenCloning')}
137
+ aria-label="GitHub repository"
138
+ >
139
+ <GithubCornerRight />
140
+ </Button>
141
+ </Box>
142
+ </Tooltip>
143
+ <Container className="app-bar-container">
144
+ <Toolbar variant="dense" sx={{ minHeight: 50, justifyContent: 'center' }}>
145
+ {/* Mobile menu button and dropdown */}
146
+ <Box sx={{ display: { xs: 'flex', md: 'none' } }}>
147
+ <IconButton
148
+ size="large"
149
+ edge="start"
150
+ color="inherit"
151
+ aria-label="menu"
152
+ onClick={handleMenuOpen}
153
+ >
154
+ <MenuIcon />
155
+ </IconButton>
156
+ <Menu
157
+ anchorEl={anchorEl}
158
+ open={openMenu}
159
+ onClose={handleMenuClose}
160
+ sx={{ display: { xs: 'block', md: 'none' } }}
161
+ >
162
+ {/* File menu items */}
163
+ {fileMenu.map((item) => (
164
+ <MenuItem key={item.display} onClick={() => { item.onClick(); handleMenuClose(); }}>
165
+ {item.display}
166
+ </MenuItem>
167
+ ))}
168
+ {/* Help menu items */}
169
+ {helpMenu.map((item) => (
170
+ <MenuItem key={item.display} onClick={() => { item.onClick(); handleMenuClose(); }}>
171
+ {item.display}
172
+ </MenuItem>
173
+ ))}
174
+ <MenuItem><a style={{ color: 'inherit', textDecoration: 'none' }} target="_blank" href="https://docs.opencloning.org">Documentation</a></MenuItem>
175
+ <MenuItem onClick={() => { setOpenExampleDialog(true); handleMenuClose(); }}>Examples</MenuItem>
176
+ <MenuItem onClick={() => { setOpenTemplateDialog(true); handleMenuClose(); }}>Templates</MenuItem>
177
+ <MenuItem onClick={() => { setOpenFeedbackDialog(true); handleMenuClose(); }}>Feedback</MenuItem>
178
+ </Menu>
179
+ </Box>
180
+
181
+ {/* Desktop buttons - hidden on mobile */}
182
+ <Box sx={{ display: { xs: 'none', md: 'flex' }, gap: 1 }}>
183
+ <ButtonWithMenu menuItems={fileMenu}>File</ButtonWithMenu>
184
+ <input type="file" ref={fileInputRef} style={{ display: 'none' }} onChange={onFileChange} accept=".json,.zip" />
185
+ {fileList && <LoadCloningHistoryWrapper fileList={fileList} clearFiles={() => setFileList([])} />}
186
+ <ButtonWithMenu menuItems={helpMenu}>About</ButtonWithMenu>
187
+ <Button><a style={{ color: 'inherit', textDecoration: 'none' }} target="_blank" href="https://docs.opencloning.org">Documentation</a></Button>
188
+ <Button onClick={() => setOpenExampleDialog(true)}>Examples</Button>
189
+ <Button onClick={() => setOpenTemplateDialog(true)}>Templates</Button>
190
+ <Button onClick={() => setOpenFeedbackDialog(true)}>Feedback</Button>
191
+ </Box>
192
+
193
+ </Toolbar>
194
+ </Container>
195
+ <SelectExampleDialog onClose={handleCloseDialog} open={openExampleDialog} />
196
+ {/* Conditional, since we only want to make request to github if templates want to be used */}
197
+ {openTemplateDialog && <SelectTemplateDialog onClose={handleCloseDialog} open={openTemplateDialog} />}
198
+ <FeedbackDialog open={openFeedbackDialog} setOpen={setOpenFeedbackDialog} />
199
+ {openCloningStrategyDialog && <DownloadCloningStrategyDialog open={openCloningStrategyDialog} setOpen={setOpenCloningStrategyDialog} />}
200
+ <VersionDialog open={openVersionDialog} setOpen={setOpenVersionDialog} />
201
+ </AppBar>
202
+ );
203
+ }
204
+
205
+ export default MainAppBar;