@opencloning/ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/package.json +37 -0
- package/src/components/AppAlerts.jsx +19 -0
- package/src/components/CloningHistory.jsx +40 -0
- package/src/components/DataModelDisplayer.jsx +30 -0
- package/src/components/DescriptionEditor.jsx +68 -0
- package/src/components/DownloadCloningStrategyDialog.jsx +84 -0
- package/src/components/DownloadSequenceFileDialog.jsx +90 -0
- package/src/components/DragAndDropCloningHistoryWrapper.jsx +39 -0
- package/src/components/DraggableDialogPaper.jsx +16 -0
- package/src/components/EditSequenceNameDialog.jsx +90 -0
- package/src/components/ExternalServicesStatusCheck.jsx +92 -0
- package/src/components/HistoryLoadedDialog.jsx +49 -0
- package/src/components/LoadCloningHistoryWrapper.jsx +166 -0
- package/src/components/MainSequenceCheckBox.jsx +83 -0
- package/src/components/MainSequenceEditor.jsx +165 -0
- package/src/components/NetworkNode.jsx +159 -0
- package/src/components/NetworkTree.css +127 -0
- package/src/components/ObjectTable.jsx +24 -0
- package/src/components/OpenCloning.jsx +102 -0
- package/src/components/OverhangsDisplay.jsx +25 -0
- package/src/components/SequenceEditor.jsx +120 -0
- package/src/components/SequenceTab.jsx +14 -0
- package/src/components/TemplateSequence.jsx +38 -0
- package/src/components/annotation/PlannotateAnnotationReport.jsx +33 -0
- package/src/components/annotation/useUpdateAnnotationInMainSequence.js +39 -0
- package/src/components/assembler/AssemblePartWidget.jsx +252 -0
- package/src/components/assembler/Assembler.jsx +273 -0
- package/src/components/assembler/AssemblerPart.jsx +99 -0
- package/src/components/assembler/StopIcon.jsx +34 -0
- package/src/components/assembler/assembler_data2.json +50 -0
- package/src/components/assembler/assembly_component.module.css +81 -0
- package/src/components/assembler/moclo.json +110 -0
- package/src/components/assembler/sbol_visual_glyphs/LICENSE.html +21 -0
- package/src/components/assembler/sbol_visual_glyphs/assembly-scar.svg +63 -0
- package/src/components/assembler/sbol_visual_glyphs/cds-stop.svg +85 -0
- package/src/components/assembler/sbol_visual_glyphs/cds.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/chromosomal-locus.svg +78 -0
- package/src/components/assembler/sbol_visual_glyphs/engineered-region.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/five-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/origin-of-replication.svg +57 -0
- package/src/components/assembler/sbol_visual_glyphs/primer-binding-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/promoter.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/ribosome-entry-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/specific-recombination-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/terminator.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/three-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs.js +36 -0
- package/src/components/assembler/useAssembler.js +71 -0
- package/src/components/dummy/DummyInterface.js +41 -0
- package/src/components/dummy/GetSequenceFileAndDatabaseIdComponent.jsx +59 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.cy.jsx +86 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.cy.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.jsx +29 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.cy.jsx +107 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.jsx +23 -0
- package/src/components/eLabFTW/GetPrimerComponent.cy.jsx +261 -0
- package/src/components/eLabFTW/GetPrimerComponent.jsx +55 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.cy.jsx +184 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.jsx +62 -0
- package/src/components/eLabFTW/LoadHistoryComponent.cy.jsx +235 -0
- package/src/components/eLabFTW/LoadHistoryComponent.jsx +51 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.cy.jsx +159 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.jsx +54 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.cy.jsx +185 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.jsx +51 -0
- package/src/components/eLabFTW/common.js +26 -0
- package/src/components/eLabFTW/eLabFTWInterface.js +294 -0
- package/src/components/eLabFTW/eLabFTWInterface.test.js +839 -0
- package/src/components/eLabFTW/envValues.js +7 -0
- package/src/components/eLabFTW/utils.js +39 -0
- package/src/components/form/CustomFormHelperText.jsx +10 -0
- package/src/components/form/EnzymeMultiSelect.cy.jsx +61 -0
- package/src/components/form/EnzymeMultiSelect.jsx +34 -0
- package/src/components/form/GetRequestMultiSelect.jsx +107 -0
- package/src/components/form/LabelWithTooltip.jsx +16 -0
- package/src/components/form/PostRequestSelect.cy.jsx +70 -0
- package/src/components/form/PostRequestSelect.jsx +86 -0
- package/src/components/form/RequestStatusWrapper.jsx +17 -0
- package/src/components/form/RetryAlert.jsx +20 -0
- package/src/components/form/ServerErrorMessage.jsx +10 -0
- package/src/components/form/SubmitButtonBackendAPI.jsx +15 -0
- package/src/components/form/SubmitToDatabaseDialog.jsx +133 -0
- package/src/components/form/TextFieldValidate.jsx +67 -0
- package/src/components/form/ValidatedTextField.jsx +33 -0
- package/src/components/form/intermediates_disclaimer.svg +181 -0
- package/src/components/navigation/ButtonWithMenu.jsx +43 -0
- package/src/components/navigation/CustomTab.jsx +14 -0
- package/src/components/navigation/FeedbackDialog.jsx +34 -0
- package/src/components/navigation/GithubCornerRight.jsx +29 -0
- package/src/components/navigation/MainAppBar.css +26 -0
- package/src/components/navigation/MainAppBar.jsx +205 -0
- package/src/components/navigation/SelectExampleDialog.jsx +69 -0
- package/src/components/navigation/SelectTemplateDialog.jsx +107 -0
- package/src/components/navigation/TabPanel.jsx +28 -0
- package/src/components/navigation/VersionDialog.jsx +33 -0
- package/src/components/primers/CreatePrimerFromSequenceForm.jsx +42 -0
- package/src/components/primers/DownloadPrimersButton.jsx +104 -0
- package/src/components/primers/PrimerForm.css +14 -0
- package/src/components/primers/PrimerForm.jsx +107 -0
- package/src/components/primers/PrimerList.css +46 -0
- package/src/components/primers/PrimerList.cy.jsx +95 -0
- package/src/components/primers/PrimerList.jsx +126 -0
- package/src/components/primers/PrimerTableRow.cy.jsx +57 -0
- package/src/components/primers/PrimerTableRow.jsx +84 -0
- package/src/components/primers/SelectPrimerForm.jsx +66 -0
- package/src/components/primers/import_primers/ImportPrimersButton.jsx +101 -0
- package/src/components/primers/import_primers/ImportPrimersTable.jsx +51 -0
- package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +107 -0
- package/src/components/primers/import_primers/styles.css +60 -0
- package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/GatewayRoiSelect.jsx +164 -0
- package/src/components/primers/primer_design/SequenceTabComponents/OrientationPicker.jsx +37 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignContext.jsx +369 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignEBIC.jsx +24 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +29 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGatewayBP.jsx +36 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx +26 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignHomologousRecombination.jsx +32 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignRestriction.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignSimplePair.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignStepper.jsx +53 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesigner.jsx +80 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerResultForm.jsx +49 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSettingsForm.jsx +68 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSpacerForm.jsx +84 -0
- package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +85 -0
- package/src/components/primers/primer_design/SequenceTabComponents/StepNavigation.jsx +48 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelEBICSettings.jsx +216 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelResults.jsx +42 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelSelectRoi.jsx +61 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPannelSettings.jsx +59 -0
- package/src/components/primers/primer_design/SequenceTabComponents/primerDesignMinimalValues.json +5 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEBICPrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEnzymePrimerDesignSettings.js +57 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useGatewayPrimerDesignSettings.js +18 -0
- package/src/components/primers/primer_design/SequenceTabComponents/usePrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGatewayBP.jsx +88 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.jsx +65 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignHomologousRecombination.jsx +84 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignSourceForm.jsx +74 -0
- package/src/components/primers/primer_design/common/NoAttPSitesError.jsx +31 -0
- package/src/components/primers/primer_details/PCRTable.cy.jsx +51 -0
- package/src/components/primers/primer_details/PCRTable.jsx +35 -0
- package/src/components/primers/primer_details/Primer3Figure.jsx +25 -0
- package/src/components/primers/primer_details/PrimerDetailsTds.jsx +39 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.cy.jsx +137 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.jsx +132 -0
- package/src/components/primers/primer_details/TableSection.jsx +17 -0
- package/src/components/primers/primer_details/primerDetailsFormatting.js +3 -0
- package/src/components/primers/primer_details/useMultiplePrimerDetails.js +29 -0
- package/src/components/primers/primer_details/usePCRDetails.js +47 -0
- package/src/components/primers/primer_details/usePrimerDetailsEndpoints.js +49 -0
- package/src/components/primers/primer_details/useSinglePrimerSequenceDetails.js +25 -0
- package/src/components/primers/primersToTabularFile.js +49 -0
- package/src/components/primers/primersToTabularFile.test.js +108 -0
- package/src/components/settings/SettingsTab.cy.jsx +267 -0
- package/src/components/settings/SettingsTab.jsx +170 -0
- package/src/components/sources/AssemblyPlanDisplayer.cy.jsx +22 -0
- package/src/components/sources/AssemblyPlanDisplayer.jsx +27 -0
- package/src/components/sources/CollectionSource.jsx +97 -0
- package/src/components/sources/FinishedSource.jsx +397 -0
- package/src/components/sources/KnownSourceErrors.jsx +50 -0
- package/src/components/sources/MultipleInputsSelector.jsx +63 -0
- package/src/components/sources/MultipleOutputsSelector.jsx +63 -0
- package/src/components/sources/NewSourceBox.jsx +37 -0
- package/src/components/sources/PCRUnitForm.jsx +102 -0
- package/src/components/sources/SingleInputSelector.jsx +36 -0
- package/src/components/sources/Source.jsx +125 -0
- package/src/components/sources/SourceAnnotation.jsx +44 -0
- package/src/components/sources/SourceAssembly.jsx +201 -0
- package/src/components/sources/SourceBox.css +18 -0
- package/src/components/sources/SourceBox.jsx +60 -0
- package/src/components/sources/SourceCopySequence.jsx +38 -0
- package/src/components/sources/SourceDatabase.jsx +28 -0
- package/src/components/sources/SourceFile.jsx +188 -0
- package/src/components/sources/SourceGenomeRegion.cy.jsx +131 -0
- package/src/components/sources/SourceGenomeRegion.jsx +486 -0
- package/src/components/sources/SourceHomologousRecombination.jsx +125 -0
- package/src/components/sources/SourceKnownGenomeRegion.jsx +60 -0
- package/src/components/sources/SourceManuallyTyped.jsx +116 -0
- package/src/components/sources/SourcePCRorHybridization.jsx +165 -0
- package/src/components/sources/SourcePolymeraseExtension.jsx +44 -0
- package/src/components/sources/SourceRepositoryId.jsx +409 -0
- package/src/components/sources/SourceRestriction.jsx +41 -0
- package/src/components/sources/SourceReverseComplement.jsx +33 -0
- package/src/components/sources/SourceTypeSelector.jsx +94 -0
- package/src/components/sources/SubSequenceDisplayer.jsx +70 -0
- package/src/components/sources/VerifyDeleteDialog.jsx +23 -0
- package/src/components/sources/repositoryMetadata.js +14 -0
- package/src/components/verification/LoadFromDatabaseButton.jsx +90 -0
- package/src/components/verification/SequencingFileRow.jsx +34 -0
- package/src/components/verification/VerificationFileDialog.cy.jsx +176 -0
- package/src/components/verification/VerificationFileDialog.jsx +248 -0
- package/src/config/defaultMainEditorProps.js +44 -0
- package/src/hooks/useAlerts.js +16 -0
- package/src/hooks/useBackendAPI.js +51 -0
- package/src/hooks/useBackendRoute.js +22 -0
- package/src/hooks/useDatabase.js +18 -0
- package/src/hooks/useDragAndDropFile.js +31 -0
- package/src/hooks/useGatewaySites.js +40 -0
- package/src/hooks/useHttpClient.js +12 -0
- package/src/hooks/useLoadDatabaseFile.js +108 -0
- package/src/hooks/useStoreEditor.js +101 -0
- package/src/hooks/useValidateState.js +43 -0
- package/vitest.config.js +18 -0
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;
|