@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,409 @@
1
+ import React from 'react';
2
+ import IconButton from '@mui/material/IconButton';
3
+ import InputAdornment from '@mui/material/InputAdornment';
4
+ import ClearIcon from '@mui/icons-material/Clear';
5
+ import InputLabel from '@mui/material/InputLabel';
6
+ import MenuItem from '@mui/material/MenuItem';
7
+ import TextField from '@mui/material/TextField';
8
+ import FormControl from '@mui/material/FormControl';
9
+ import Select from '@mui/material/Select';
10
+ import { Alert, Autocomplete, Table, TableBody, TableCell, TableRow } from '@mui/material';
11
+ import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
12
+ import RequestStatusWrapper from '../form/RequestStatusWrapper';
13
+ import getHttpClient from '@opencloning/utils/getHttpClient';
14
+ import repositoryMetadata from './repositoryMetadata';
15
+
16
+ const httpClient = getHttpClient();
17
+
18
+ function validateRepositoryId(repositoryId, repository) {
19
+ switch (repository) {
20
+ case 'AddgeneIdSource':
21
+ if (!repositoryId.match(/^\d+/)) {
22
+ return 'Addgene IDs must be numbers (e.g. 39296)';
23
+ }
24
+ break;
25
+ case 'BenchlingUrlSource':
26
+ if (!repositoryId.match(/^https:\/\/benchling\.com\/.+\/edit$/)) {
27
+ return 'Use a Benchling URL like https://benchling.com/siverson/f/lib_B94YxDHhQh-cidar-moclo-library/seq_dh1FrJTc-b0015_dh/edit';
28
+ }
29
+ break;
30
+ case 'EuroscarfSource':
31
+ if (!repositoryId.match(/^P\d+$/)) {
32
+ return 'Euroscarf IDs must be P followed by numbers (e.g. P30174)';
33
+ }
34
+ break;
35
+ case 'WekWikGeneIdSource':
36
+ if (!repositoryId.match(/^\d+$/)) {
37
+ return 'WeKwikGene IDs must be numbers (e.g. 0000304)';
38
+ }
39
+ break;
40
+ default:
41
+ break;
42
+ }
43
+ return '';
44
+ }
45
+
46
+
47
+
48
+ const snapgeneCheckOption = (option, inputValue) => option.name.toLowerCase().includes(inputValue.toLowerCase());
49
+ const snapgeneFormatOption = (option, plasmidSet, plasmidSetName) => ({ name: option.name, path: `${plasmidSet}/${option.subpath}`, plasmidSetName, plasmidSet });
50
+ const snapgeneGetOptions = (data, inputValue) => Object.entries(data)
51
+ .flatMap(([plasmidSet, category]) => category.plasmids
52
+ .filter((option) => snapgeneCheckOption(option, inputValue))
53
+ .map((option) => snapgeneFormatOption(option, plasmidSet, data[plasmidSet].name)));
54
+ function SnapgeneSuccessComponent({ option }) {
55
+ return (
56
+ <Alert severity="info" sx={{ mb: 1 }}>
57
+ Plasmid
58
+ {' '}
59
+ <a href={`https://www.snapgene.com/plasmids/${option.path}`} target="_blank" rel="noopener noreferrer">{option.name}</a>
60
+ {' '}
61
+ from set
62
+ {' '}
63
+ <a href={`https://www.snapgene.com/plasmids/${option.plasmidSet}`} target="_blank" rel="noopener noreferrer">{option.plasmidSetName}</a>
64
+ </Alert>
65
+ );
66
+ }
67
+
68
+ const iGEMGetOptions = (plasmids, inputValue) => plasmids.map((p) => ({
69
+ name: `${p['Short Desc / Name']} / ${p['Part Name']} / ${p['Plasmid Backbone']}`,
70
+ url: `https://assets.opencloning.org/annotated-igem-distribution/results/plasmids/${p['Index ID']}.gb`,
71
+ table_name: p['Short Desc / Name'],
72
+ part_name: p['Part Name'],
73
+ part_url: p['Part URL'],
74
+ backbone: p['Plasmid Backbone'],
75
+ })).filter((p) => p.name.toLowerCase().includes(inputValue.toLowerCase()));
76
+
77
+ function iGEMSuccessComponent({ option }) {
78
+ return (
79
+ <Alert severity="info" sx={{ mb: 1 }}>
80
+ {'Plasmid '}
81
+ <a href={option.url} target="_blank" rel="noopener noreferrer">{option.table_name}</a>
82
+ {' containing part '}
83
+ <a href={option.part_url} target="_blank" rel="noopener noreferrer">{option.part_name}</a>
84
+ {` in backbone ${option.backbone} from `}
85
+ <a href="https://airtable.com/appgWgf6EPX5gpnNU/shrb0c8oYTgpZDRgH/tblNqHsHbNNQP2HCX" target="_blank" rel="noopener noreferrer">2024 iGEM Distribution</a>
86
+ </Alert>
87
+ );
88
+ }
89
+
90
+ const sevaGetOptions = (data, inputValue) => data.map((p) => {
91
+ const info = [p.Resistance, p.ORI, p.Cargo, p.Gadget, p.FunctionType].filter((i) => i !== '').join('/');
92
+ return {
93
+ name: `${p.Name} (${info})`,
94
+ plasmid_name: p.Name,
95
+ data: p,
96
+ };
97
+ }).filter((p) => p.name.toLowerCase().includes(inputValue.toLowerCase()));
98
+
99
+ function SEVASuccessComponent({ option }) {
100
+ return (
101
+ <Alert severity="info" sx={{ mb: 1 }} icon={false}>
102
+ <Table size="small">
103
+ <TableBody>
104
+ {Object.entries(option.data)
105
+ .filter(([key]) => ['Name', 'Resistance', 'ORI', 'Cargo', 'Gadget', 'FunctionType'].includes(key))
106
+ .map(([key, value]) => (
107
+ <TableRow key={key}>
108
+ <TableCell>{key}</TableCell>
109
+ <TableCell>{value || 'N/A'}</TableCell>
110
+ </TableRow>
111
+ ))}
112
+ </TableBody>
113
+ </Table>
114
+ </Alert>
115
+ );
116
+ }
117
+
118
+ const openDNACollectionsGetOptions = (data, inputValue, groupedBy) => data.map((p) => ({
119
+ name: `${p.id}` + (p.plasmid_name ? ` - ${p.plasmid_name}` : ''),
120
+ plasmid_name: p.plasmid_name,
121
+ plasmid_id: p.id,
122
+ collection: p.collection,
123
+ url: `https://assets.opencloning.org/open-dna-collections/${p.path}`,
124
+ })).filter((p) => p.name.toLowerCase().includes(inputValue.toLowerCase()) && (groupedBy ? p.collection === groupedBy : true));
125
+
126
+ function OpenDNACollectionsSuccessComponent({ option }) {
127
+ return (
128
+ <Alert severity="info" sx={{ mb: 1 }}>
129
+ Plasmid <a href={option.url} target="_blank" rel="noopener noreferrer">{option.name}</a> {' '}
130
+ from collection <a href={`https://github.com/Reclone-org/open-dna-collections/tree/main/${option.collection}`} target="_blank" rel="noopener noreferrer">{option.collection}</a>
131
+ </Alert>
132
+ );
133
+ }
134
+
135
+ function IndexJsonSelector({
136
+ url,
137
+ setInputValue,
138
+ getOptions,
139
+ noOptionsText,
140
+ inputLabel,
141
+ SuccessComponent,
142
+ responseProcessCallback = (resp) => resp.data,
143
+ requiredInput = 3,
144
+ groupField = null, // You can pass the name of the field to group by
145
+ }) {
146
+ const [userInput, setUserInput] = React.useState('');
147
+ const [data, setData] = React.useState(null);
148
+ const [options, setOptions] = React.useState([]);
149
+ const [requestStatus, setRequestStatus] = React.useState({ status: 'loading' });
150
+ const [retry, setRetry] = React.useState(0);
151
+ const [groups, setGroups] = React.useState([]);
152
+ const [groupedBy, setGroupedBy] = React.useState(null);
153
+
154
+ React.useEffect(() => {
155
+ const fetchOptions = async () => {
156
+ setRequestStatus({ status: 'loading' });
157
+ try {
158
+ const resp = await httpClient.get(url);
159
+ setData(responseProcessCallback(resp));
160
+ if (requiredInput === 0) {
161
+ setOptions(getOptions(resp.data, ''));
162
+ }
163
+ if (groupField) {
164
+ setGroups([...new Set(resp.data.map((p) => p[groupField]))].sort());
165
+ }
166
+ setRequestStatus({ status: 'success' });
167
+ } catch (error) {
168
+ setRequestStatus({ status: 'error', message: error.message });
169
+ }
170
+ };
171
+ fetchOptions();
172
+ }, [retry]);
173
+
174
+ const onInputChange = (newInputValue) => {
175
+ if (newInputValue === undefined) {
176
+ // When clearing the input via x button
177
+ setUserInput('');
178
+ if (requiredInput === 0) {
179
+ setOptions(getOptions(data, '', groupedBy));
180
+ } else {
181
+ setOptions([]);
182
+ }
183
+ return;
184
+ }
185
+ setUserInput(newInputValue);
186
+ if (newInputValue.length < requiredInput) {
187
+ setOptions([]);
188
+ return;
189
+ }
190
+
191
+ setOptions(getOptions(data, newInputValue, groupedBy));
192
+ };
193
+
194
+ const onGroupChange = (newGroup) => {
195
+ setUserInput('');
196
+ setInputValue('')
197
+ setGroupedBy(newGroup);
198
+ setOptions(getOptions(data, '', newGroup))
199
+ };
200
+
201
+ const selectedOption = options.find((option) => option.name === userInput);
202
+
203
+ return (
204
+ <RequestStatusWrapper requestStatus={requestStatus} retry={() => setRetry(retry + 1)}>
205
+ {groupField && (
206
+ <FormControl fullWidth>
207
+ <InputLabel>{groupField.charAt(0).toUpperCase() + groupField.slice(1).replace(/([A-Z])/g, ' $1')}</InputLabel>
208
+ <Select
209
+ endAdornment={groupedBy && (<InputAdornment position="end"><IconButton onClick={() => onGroupChange(null)}><ClearIcon /></IconButton></InputAdornment>)}
210
+ value={groupedBy}
211
+ onChange={(e) => onGroupChange(e.target.value)}
212
+ // Capitalize the first letter and add a space before each capital letter
213
+ label={groupField.charAt(0).toUpperCase() + groupField.slice(1).replace(/([A-Z])/g, ' $1')}
214
+ >
215
+ {groups.map((group) => (
216
+ <MenuItem value={group}>{group}</MenuItem>
217
+ ))}
218
+ </Select>
219
+ </FormControl>
220
+ )}
221
+ <FormControl fullWidth>
222
+ <Autocomplete
223
+ onChange={(event, value) => {
224
+ onInputChange(value?.name);
225
+ if (value) {
226
+ setInputValue(value);
227
+ } else {
228
+ setInputValue('');
229
+ }
230
+ }}
231
+ // Change options only when input changes (not when an option is picked)
232
+ onInputChange={(event, newInputValue, reason) => (reason === 'input') && onInputChange(newInputValue)}
233
+ id="tags-standard"
234
+ options={options}
235
+ noOptionsText={userInput.length < requiredInput ? noOptionsText : 'Nothing found'}
236
+ getOptionLabel={(o) => o.name}
237
+ isOptionEqualToValue={(o1, o2) => o1.subpath === o2.subpath}
238
+ inputValue={userInput}
239
+ renderInput={(params) => (
240
+ <TextField
241
+ {...params}
242
+ label={inputLabel}
243
+ />
244
+ )}
245
+ />
246
+ </FormControl>
247
+ {selectedOption && <SuccessComponent option={selectedOption} />}
248
+ </RequestStatusWrapper>
249
+ );
250
+ }
251
+
252
+ // A component providing an interface for the user to type a repository ID
253
+ // and get a sequence
254
+ function SourceRepositoryId({ source, requestStatus, sendPostRequest }) {
255
+ const { id: sourceId } = source;
256
+ const [inputValue, setInputValue] = React.useState('');
257
+ const [repositoryType, setRepositoryType] = React.useState(source.type || '');
258
+ const [error, setError] = React.useState('');
259
+
260
+ const repositoryMeta = repositoryMetadata[repositoryType] || {};
261
+
262
+ React.useEffect(() => {
263
+ setRepositoryType(source.type || '');
264
+ }, [source.type]);
265
+
266
+ React.useEffect(() => {
267
+ setInputValue('');
268
+ setError('');
269
+ }, [repositoryType]);
270
+
271
+ React.useEffect(() => {
272
+ if (inputValue) {
273
+ setError(validateRepositoryId(inputValue, repositoryType));
274
+ } else {
275
+ setError('');
276
+ }
277
+ }, [inputValue, repositoryType]);
278
+
279
+ const onSubmit = (event) => {
280
+ event.preventDefault();
281
+ const extra = { repository_id: inputValue };
282
+ if (repositoryType === 'BenchlingUrlSource') {
283
+ // Remove /edit from the end of the URL and add .gb
284
+ extra.repository_id = inputValue.replace(/\/edit$/, '.gb');
285
+ }
286
+ if (repositoryType === 'SnapGenePlasmidSource') {
287
+ extra.repository_id = inputValue.path;
288
+ }
289
+ if (repositoryType === 'IGEMSource') {
290
+ extra.repository_id = `${inputValue.part_name}-${inputValue.backbone}`;
291
+ extra.sequence_file_url = inputValue.url;
292
+ }
293
+ if (repositoryType === 'SEVASource') {
294
+ extra.repository_id = inputValue.plasmid_name;
295
+ }
296
+ if (repositoryType === 'OpenDNACollectionsSource') {
297
+ extra.repository_id = inputValue.collection + '/' + inputValue.plasmid_id;
298
+ extra.sequence_file_url = encodeURI(inputValue.url);
299
+ }
300
+ const requestData = { id: sourceId, ...extra, type: repositoryType };
301
+ sendPostRequest({ endpoint: `repository_id/${repositoryMeta.slug}`, requestData, source });
302
+ };
303
+ const helperText = error || (repositoryMeta?.example && `Example: ${repositoryMeta.example}`);
304
+ return (
305
+ <>
306
+ <FormControl fullWidth>
307
+ <InputLabel id={`select-repository-${sourceId}-label`}>Select repository</InputLabel>
308
+ <Select
309
+ value={repositoryType}
310
+ onChange={(event) => setRepositoryType(event.target.value)}
311
+ labelId={`select-repository-${sourceId}-label`}
312
+ label="Select repository"
313
+ >
314
+ <MenuItem value="AddgeneIdSource">Addgene</MenuItem>
315
+ <MenuItem value="BenchlingUrlSource">Benchling</MenuItem>
316
+ <MenuItem value="EuroscarfSource">Euroscarf</MenuItem>
317
+ <MenuItem value="NCBISequenceSource">GenBank</MenuItem>
318
+ <MenuItem value="IGEMSource">iGEM</MenuItem>
319
+ <MenuItem value="OpenDNACollectionsSource">Open DNA Collections</MenuItem>
320
+ <MenuItem value="SEVASource">SEVA Plasmids</MenuItem>
321
+ <MenuItem value="SnapGenePlasmidSource">SnapGene</MenuItem>
322
+ <MenuItem value="WekWikGeneIdSource">WeKwikGene</MenuItem>
323
+ </Select>
324
+ </FormControl>
325
+ {repositoryType && repositoryType !== 'RepositoryIdSource' && (
326
+ <form onSubmit={onSubmit}>
327
+ {!['SnapGenePlasmidSource', 'IGEMSource', 'SEVASource', 'OpenDNACollectionsSource'].includes(repositoryType) && (
328
+ <>
329
+ <FormControl fullWidth>
330
+ <TextField
331
+ label={repositoryMeta.inputLabel}
332
+ id={`repository-id-${sourceId}`}
333
+ value={inputValue}
334
+ onChange={(event) => setInputValue(event.target.value)}
335
+ helperText={helperText}
336
+ error={error !== ''}
337
+ />
338
+ </FormControl>
339
+ {/* Extra info for benchling case */}
340
+ {repositoryType === 'BenchlingUrlSource' && (
341
+ <Alert severity="info" sx={{ mb: 1 }}>
342
+ The sequence must be publicly accessible. Use the URL from a sequence editor page (ending in &quot;/edit&quot;), like
343
+ {' '}
344
+ <a target="_blank" rel="noopener noreferrer" href="https://benchling.com/siverson/f/lib_B94YxDHhQh-cidar-moclo-library/seq_dh1FrJTc-b0015_dh/edit">this example</a>
345
+ .
346
+ </Alert>
347
+ )}
348
+ </>
349
+ )}
350
+ {repositoryType === 'SnapGenePlasmidSource'
351
+ && (
352
+ <IndexJsonSelector
353
+ url="https://assets.opencloning.org/SnapGene_crawler/index.json"
354
+ setInputValue={setInputValue}
355
+ getOptions={snapgeneGetOptions}
356
+ noOptionsText="Type at least 3 characters to search, see SnapGene plasmids for options"
357
+ inputLabel="Plasmid name"
358
+ SuccessComponent={SnapgeneSuccessComponent}
359
+ requiredInput={3}
360
+ />
361
+ )}
362
+ {repositoryType === 'IGEMSource' && (
363
+ <IndexJsonSelector
364
+ url="https://assets.opencloning.org/annotated-igem-distribution/results/index.json"
365
+ setInputValue={setInputValue}
366
+ getOptions={iGEMGetOptions}
367
+ noOptionsText=""
368
+ inputLabel="Plasmid name"
369
+ SuccessComponent={iGEMSuccessComponent}
370
+ requiredInput={0}
371
+ />
372
+ )}
373
+ {repositoryType === 'SEVASource' && (
374
+ <IndexJsonSelector
375
+ url="https://assets.opencloning.org/seva_plasmids_index/index.json"
376
+ setInputValue={setInputValue}
377
+ getOptions={sevaGetOptions}
378
+ noOptionsText="Type at least 3 characters to search"
379
+ inputLabel="Plasmid name"
380
+ SuccessComponent={SEVASuccessComponent}
381
+ requiredInput={3}
382
+ />
383
+ )}
384
+ {repositoryType === 'OpenDNACollectionsSource' && (
385
+ <IndexJsonSelector
386
+ url="https://assets.opencloning.org/open-dna-collections/scripts/index.json"
387
+ setInputValue={setInputValue}
388
+ getOptions={openDNACollectionsGetOptions}
389
+ noOptionsText=""
390
+ inputLabel="Plasmid name"
391
+ SuccessComponent={OpenDNACollectionsSuccessComponent}
392
+ requiredInput={0}
393
+ groupField="collection"
394
+ />
395
+ )}
396
+ {inputValue && !error && (
397
+ <SubmitButtonBackendAPI
398
+ requestStatus={requestStatus}
399
+ {...(import.meta.env.VITE_UMAMI_WEBSITE_ID && { "data-umami-event": "submit-repository-id", "data-umami-event-repository": `${repositoryMeta.slug}` })}
400
+ >Submit</SubmitButtonBackendAPI>
401
+ )}
402
+
403
+ </form>
404
+ )}
405
+ </>
406
+ );
407
+ }
408
+
409
+ export default SourceRepositoryId;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { shallowEqual, useSelector } from 'react-redux';
3
+ import { getInputSequencesFromSourceId } from '@opencloning/store/cloning_utils';
4
+ import EnzymeMultiSelect from '../form/EnzymeMultiSelect';
5
+ import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
6
+
7
+ // A component providing an interface for the user to perform a restriction reaction
8
+ // with one or more restriction enzymes, move between output fragments, and eventually
9
+ // select one as an output.
10
+ function SourceRestriction({ source, requestStatus, sendPostRequest }) {
11
+ const { id: sourceId } = source;
12
+ const [enzymes, setEnzymes] = React.useState([]);
13
+ const inputSequences = useSelector((state) => getInputSequencesFromSourceId(state, sourceId), shallowEqual);
14
+
15
+ const onSubmit = (e) => {
16
+ e.preventDefault();
17
+ if (enzymes.length === 0) { return; }
18
+ const requestData = {
19
+ source: { id: sourceId, input: source.input },
20
+ sequences: inputSequences,
21
+ };
22
+ sendPostRequest({ endpoint: 'restriction', requestData, source, config: { params: { restriction_enzymes: enzymes } } });
23
+ };
24
+
25
+ return (
26
+ <div className="restriction">
27
+ <form onSubmit={onSubmit}>
28
+ <EnzymeMultiSelect setEnzymes={setEnzymes} />
29
+ {(enzymes.length > 0) && (
30
+ <SubmitButtonBackendAPI
31
+ requestStatus={requestStatus}
32
+ color="success"
33
+ {...(import.meta.env.VITE_UMAMI_WEBSITE_ID && { "data-umami-event": "submit-restriction" })}
34
+ >Perform restriction</SubmitButtonBackendAPI>
35
+ )}
36
+ </form>
37
+ </div>
38
+ );
39
+ }
40
+
41
+ export default SourceRestriction;
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import { shallowEqual, useSelector } from 'react-redux';
3
+ import { getInputSequencesFromSourceId } from '@opencloning/store/cloning_utils';
4
+ import SubmitButtonBackendAPI from '../form/SubmitButtonBackendAPI';
5
+
6
+ function SourceReverseComplement({ source, requestStatus, sendPostRequest }) {
7
+ const { id: sourceId, input } = source;
8
+ const inputSequences = useSelector((state) => getInputSequencesFromSourceId(state, sourceId), shallowEqual);
9
+ const onSubmit = (event) => {
10
+ event.preventDefault();
11
+
12
+ const requestData = {
13
+ sequences: inputSequences,
14
+ source: { id: sourceId, input },
15
+ };
16
+ sendPostRequest({ endpoint: 'reverse_complement', requestData, source });
17
+ };
18
+ // No need for MultipleOutputsSelector, since there is only one output
19
+ return (
20
+ <div className="ReverseComplementSource">
21
+ <form onSubmit={onSubmit}>
22
+ <SubmitButtonBackendAPI
23
+ requestStatus={requestStatus}
24
+ {...(import.meta.env.VITE_UMAMI_WEBSITE_ID && { "data-umami-event": "submit-reverse-complement" })}
25
+ >
26
+ Reverse complement
27
+ </SubmitButtonBackendAPI>
28
+ </form>
29
+ </div>
30
+ );
31
+ }
32
+
33
+ export default SourceReverseComplement;
@@ -0,0 +1,94 @@
1
+ import React from 'react';
2
+ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
3
+ import InputLabel from '@mui/material/InputLabel';
4
+ import MenuItem from '@mui/material/MenuItem';
5
+ import FormControl from '@mui/material/FormControl';
6
+ import Select from '@mui/material/Select';
7
+ import { getInputSequencesFromSourceId } from '@opencloning/store/cloning_utils';
8
+ import { cloningActions } from '@opencloning/store/cloning';
9
+ import useDatabase from '../../hooks/useDatabase';
10
+
11
+ const { replaceSource } = cloningActions;
12
+
13
+ function SourceTypeSelector({ source }) {
14
+ const { id: sourceId, type: sourceType } = source;
15
+ const dispatch = useDispatch();
16
+ const database = useDatabase();
17
+ const sourceIsPrimerDesign = useSelector((state) => Boolean(state.cloning.sequences.find((e) => e.id === source.id)?.primer_design));
18
+ const noExternalRequests = useSelector((state) => state.cloning.config.noExternalRequests);
19
+ const enablePlannotate = useSelector((state) => state.cloning.config.enablePlannotate);
20
+
21
+ const onChange = (event) => {
22
+ // Clear the source other than these fields
23
+ dispatch(replaceSource({
24
+ id: sourceId,
25
+ type: event.target.value,
26
+ input: source.input
27
+ }));
28
+ };
29
+ const inputSequences = useSelector((state) => getInputSequencesFromSourceId(state, sourceId), shallowEqual);
30
+ const sequencesExist = useSelector((state) => state.cloning.sequences.length > 0, shallowEqual);
31
+ const options = [];
32
+ if (inputSequences.length === 0) {
33
+ options.push(<MenuItem key="UploadedFileSource" value="UploadedFileSource">Submit file</MenuItem>);
34
+ if (!noExternalRequests) {
35
+ options.push(<MenuItem key="RepositoryIdSource" value="RepositoryIdSource">Repository</MenuItem>);
36
+ options.push(<MenuItem key="GenomeCoordinatesSource" value="GenomeCoordinatesSource">Genome region</MenuItem>);
37
+ }
38
+ options.push(<MenuItem key="ManuallyTypedSource" value="ManuallyTypedSource">Enter manually</MenuItem>);
39
+ options.push(<MenuItem key="OligoHybridizationSource" value="OligoHybridizationSource">Oligonucleotide hybridization</MenuItem>);
40
+ if (database) {
41
+ options.push(<MenuItem key="DatabaseSource" value="DatabaseSource">{`Import from ${database.name}`}</MenuItem>);
42
+ }
43
+ if (sequencesExist) {
44
+ options.push(<MenuItem key="CopySequence" value="CopySequence">Use an existing sequence</MenuItem>);
45
+ }
46
+ } else {
47
+ // See https://github.com/manulera/OpenCloning_frontend/issues/101
48
+ if (inputSequences.length < 2) {
49
+ options.push(<MenuItem key="RestrictionEnzymeDigestionSource" value="RestrictionEnzymeDigestionSource">Restriction</MenuItem>);
50
+ options.push(<MenuItem key="PCRSource" value="PCRSource">PCR</MenuItem>);
51
+ options.push(<MenuItem key="PolymeraseExtensionSource" value="PolymeraseExtensionSource">Polymerase extension</MenuItem>);
52
+ options.push(<MenuItem key="ReverseComplementSource" value="ReverseComplementSource">Reverse complement</MenuItem>);
53
+ if (enablePlannotate) {
54
+ options.push(<MenuItem key="AnnotationSource" value="AnnotationSource">Annotate features</MenuItem>);
55
+ }
56
+ }
57
+ options.push(<MenuItem key="LigationSource" value="LigationSource">Ligation (sticky / blunt)</MenuItem>);
58
+ options.push(<MenuItem key="GibsonAssemblySource" value="GibsonAssemblySource">Gibson assembly</MenuItem>);
59
+ options.push(<MenuItem key="HomologousRecombinationSource" value="HomologousRecombinationSource">Homologous recombination</MenuItem>);
60
+ options.push(<MenuItem key="CRISPRSource" value="CRISPRSource">CRISPR</MenuItem>);
61
+ options.push(<MenuItem key="RestrictionAndLigationSource" value="RestrictionAndLigationSource">Restriction + ligation / Golden Gate</MenuItem>);
62
+ options.push(<MenuItem key="OverlapExtensionPCRLigationSource" value="OverlapExtensionPCRLigationSource">Join overlap extension PCR fragments</MenuItem>);
63
+ options.push(<MenuItem key="InFusionSource" value="InFusionSource">In-Fusion</MenuItem>);
64
+ options.push(<MenuItem key="InVivoAssemblySource" value="InVivoAssemblySource">In vivo assembly</MenuItem>);
65
+ options.push(<MenuItem key="GatewaySource" value="GatewaySource">Gateway</MenuItem>);
66
+ options.push(<MenuItem key="CreLoxRecombinationSource" value="CreLoxRecombinationSource">Cre/Lox recombination</MenuItem>);
67
+ }
68
+
69
+ // Sort options by text content
70
+ options.sort((a, b) => a.props.children.localeCompare(b.props.children));
71
+
72
+ return (
73
+ <>
74
+ {!sourceType && (<h2 className="empty-source-title">{inputSequences.length === 0 ? 'Import a sequence' : 'Use this sequence'}</h2>)}
75
+ <FormControl fullWidth>
76
+ <InputLabel id={`select-source-${sourceId}-label`}>Source type</InputLabel>
77
+ <Select
78
+ value={sourceType || ''}
79
+ onChange={onChange}
80
+ labelId={`select-source-${sourceId}-label`}
81
+ // Note how you have to set the label in two places
82
+ // see https://stackoverflow.com/questions/67064682/material-ui-outlined-select-label-is-not-rendering-properly
83
+ label="Source type"
84
+ disabled={sourceIsPrimerDesign}
85
+ >
86
+ {options}
87
+ </Select>
88
+ </FormControl>
89
+ </>
90
+
91
+ );
92
+ }
93
+
94
+ export default SourceTypeSelector;
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import { SimpleCircularOrLinearView } from '@teselagen/ove';
3
+ import { useSelector } from 'react-redux';
4
+ import { reversePositionInRange } from '@teselagen/range-utils';
5
+ import { isEqual } from 'lodash-es';
6
+ import { parseFeatureLocation } from '@teselagen/bio-parsers';
7
+ import { getInputSequencesFromSourceId } from '@opencloning/store/cloning_utils';
8
+
9
+ function getCutParameters(seq, cut, isLeft) {
10
+ if (cut === null) {
11
+ return isLeft ? [0, 0, 0] : [seq.size, seq.size, 0];
12
+ }
13
+ const { cut_watson: watson, overhang: ovhg } = cut;
14
+ const crick = (watson - ovhg) % seq.size;
15
+ return [watson, crick, ovhg];
16
+ }
17
+
18
+ function SubSequenceDisplayer({
19
+ source, sourceId,
20
+ }) {
21
+ if (!['PCRSource', 'RestrictionEnzymeDigestionSource'].includes(source.type)) {
22
+ return null;
23
+ }
24
+ const inputSequenceIds = useSelector((state) => getInputSequencesFromSourceId(state, sourceId).map(({ id }) => id), isEqual);
25
+ const seq = useSelector((state) => state.cloning.teselaJsonCache[inputSequenceIds[0]], isEqual);
26
+
27
+ const editorName = `subsequence_editor_${sourceId}`;
28
+ let selectionLayer = null;
29
+
30
+ if (['PCRSource'].includes(source.type)) {
31
+ const leftLocation = parseFeatureLocation(source.input[1].left_location, 0, 0, 1, seq.length)[0];
32
+ const rightLocation = parseFeatureLocation(source.input[1].right_location, 0, 0, 1, seq.length)[0];
33
+ // Special case for the whole sequence amplification
34
+ if (isEqual(leftLocation, rightLocation)) {
35
+ selectionLayer = {
36
+ start: 0,
37
+ end: seq.size - 1,
38
+ };
39
+ } else if (!source.input[1].reverse_complemented) {
40
+ selectionLayer = {
41
+ start: leftLocation.start,
42
+ end: rightLocation.end,
43
+ };
44
+ } else {
45
+ selectionLayer = {
46
+ end: reversePositionInRange(leftLocation.start, seq.size),
47
+ start: reversePositionInRange(rightLocation.end, seq.size),
48
+ };
49
+ }
50
+ }
51
+ if (['RestrictionEnzymeDigestionSource'].includes(source.type)) {
52
+ // The edges have the form (watson_pos, ovhg)
53
+
54
+ const [leftWatson, leftCrick, leftOvhg] = getCutParameters(seq, source.left_edge, true);
55
+ const [rightWatson, rightCrick, RightOvhg] = getCutParameters(seq, source.right_edge, false);
56
+
57
+ selectionLayer = {
58
+ start: leftOvhg > 0 ? leftCrick : leftWatson,
59
+ end: RightOvhg > 0 ? rightWatson : rightCrick,
60
+ };
61
+ }
62
+
63
+ return (
64
+ <div className="multiple-output-selector">
65
+ <SimpleCircularOrLinearView {...{ sequenceData: seq, editorName, selectionLayer, caretPosition: null, height: 'auto' }} />
66
+ </div>
67
+ );
68
+ }
69
+
70
+ export default SubSequenceDisplayer;
@@ -0,0 +1,23 @@
1
+ import { Button, Dialog, DialogActions, DialogTitle } from '@mui/material';
2
+ import React from 'react';
3
+
4
+ function VerifyDeleteDialog({ dialogOpen, setDialogOpen, onClickDelete }) {
5
+ return (
6
+ <Dialog
7
+ open={dialogOpen}
8
+ onClose={() => setDialogOpen(false)}
9
+ className="verify-delete-dialog"
10
+ >
11
+ <DialogTitle>
12
+ Delete this source and all its children?
13
+
14
+ </DialogTitle>
15
+ <DialogActions>
16
+ <Button color="error" onClick={onClickDelete}>Delete</Button>
17
+ <Button onClick={() => { setDialogOpen(false); }}>Cancel</Button>
18
+ </DialogActions>
19
+ </Dialog>
20
+ );
21
+ }
22
+
23
+ export default VerifyDeleteDialog;