@opencloning/ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/package.json +37 -0
- package/src/components/AppAlerts.jsx +19 -0
- package/src/components/CloningHistory.jsx +40 -0
- package/src/components/DataModelDisplayer.jsx +30 -0
- package/src/components/DescriptionEditor.jsx +68 -0
- package/src/components/DownloadCloningStrategyDialog.jsx +84 -0
- package/src/components/DownloadSequenceFileDialog.jsx +90 -0
- package/src/components/DragAndDropCloningHistoryWrapper.jsx +39 -0
- package/src/components/DraggableDialogPaper.jsx +16 -0
- package/src/components/EditSequenceNameDialog.jsx +90 -0
- package/src/components/ExternalServicesStatusCheck.jsx +92 -0
- package/src/components/HistoryLoadedDialog.jsx +49 -0
- package/src/components/LoadCloningHistoryWrapper.jsx +166 -0
- package/src/components/MainSequenceCheckBox.jsx +83 -0
- package/src/components/MainSequenceEditor.jsx +165 -0
- package/src/components/NetworkNode.jsx +159 -0
- package/src/components/NetworkTree.css +127 -0
- package/src/components/ObjectTable.jsx +24 -0
- package/src/components/OpenCloning.jsx +102 -0
- package/src/components/OverhangsDisplay.jsx +25 -0
- package/src/components/SequenceEditor.jsx +120 -0
- package/src/components/SequenceTab.jsx +14 -0
- package/src/components/TemplateSequence.jsx +38 -0
- package/src/components/annotation/PlannotateAnnotationReport.jsx +33 -0
- package/src/components/annotation/useUpdateAnnotationInMainSequence.js +39 -0
- package/src/components/assembler/AssemblePartWidget.jsx +252 -0
- package/src/components/assembler/Assembler.jsx +273 -0
- package/src/components/assembler/AssemblerPart.jsx +99 -0
- package/src/components/assembler/StopIcon.jsx +34 -0
- package/src/components/assembler/assembler_data2.json +50 -0
- package/src/components/assembler/assembly_component.module.css +81 -0
- package/src/components/assembler/moclo.json +110 -0
- package/src/components/assembler/sbol_visual_glyphs/LICENSE.html +21 -0
- package/src/components/assembler/sbol_visual_glyphs/assembly-scar.svg +63 -0
- package/src/components/assembler/sbol_visual_glyphs/cds-stop.svg +85 -0
- package/src/components/assembler/sbol_visual_glyphs/cds.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/chromosomal-locus.svg +78 -0
- package/src/components/assembler/sbol_visual_glyphs/engineered-region.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/five-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/origin-of-replication.svg +57 -0
- package/src/components/assembler/sbol_visual_glyphs/primer-binding-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/promoter.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/ribosome-entry-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs/specific-recombination-site.svg +59 -0
- package/src/components/assembler/sbol_visual_glyphs/terminator.svg +60 -0
- package/src/components/assembler/sbol_visual_glyphs/three-prime-sticky-restriction-site.svg +56 -0
- package/src/components/assembler/sbol_visual_glyphs.js +36 -0
- package/src/components/assembler/useAssembler.js +71 -0
- package/src/components/dummy/DummyInterface.js +41 -0
- package/src/components/dummy/GetSequenceFileAndDatabaseIdComponent.jsx +59 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.cy.jsx +86 -0
- package/src/components/eLabFTW/ELabFTWCategorySelect.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.cy.jsx +43 -0
- package/src/components/eLabFTW/ELabFTWFileSelect.jsx +29 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.cy.jsx +107 -0
- package/src/components/eLabFTW/ELabFTWResourceSelect.jsx +23 -0
- package/src/components/eLabFTW/GetPrimerComponent.cy.jsx +261 -0
- package/src/components/eLabFTW/GetPrimerComponent.jsx +55 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.cy.jsx +184 -0
- package/src/components/eLabFTW/GetSequenceFileAndDatabaseIdComponent.jsx +62 -0
- package/src/components/eLabFTW/LoadHistoryComponent.cy.jsx +235 -0
- package/src/components/eLabFTW/LoadHistoryComponent.jsx +51 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.cy.jsx +159 -0
- package/src/components/eLabFTW/PrimersNotInDatabaseComponent.jsx +54 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.cy.jsx +185 -0
- package/src/components/eLabFTW/SubmitToDatabaseComponent.jsx +51 -0
- package/src/components/eLabFTW/common.js +26 -0
- package/src/components/eLabFTW/eLabFTWInterface.js +294 -0
- package/src/components/eLabFTW/eLabFTWInterface.test.js +839 -0
- package/src/components/eLabFTW/envValues.js +7 -0
- package/src/components/eLabFTW/utils.js +39 -0
- package/src/components/form/CustomFormHelperText.jsx +10 -0
- package/src/components/form/EnzymeMultiSelect.cy.jsx +61 -0
- package/src/components/form/EnzymeMultiSelect.jsx +34 -0
- package/src/components/form/GetRequestMultiSelect.jsx +107 -0
- package/src/components/form/LabelWithTooltip.jsx +16 -0
- package/src/components/form/PostRequestSelect.cy.jsx +70 -0
- package/src/components/form/PostRequestSelect.jsx +86 -0
- package/src/components/form/RequestStatusWrapper.jsx +17 -0
- package/src/components/form/RetryAlert.jsx +20 -0
- package/src/components/form/ServerErrorMessage.jsx +10 -0
- package/src/components/form/SubmitButtonBackendAPI.jsx +15 -0
- package/src/components/form/SubmitToDatabaseDialog.jsx +133 -0
- package/src/components/form/TextFieldValidate.jsx +67 -0
- package/src/components/form/ValidatedTextField.jsx +33 -0
- package/src/components/form/intermediates_disclaimer.svg +181 -0
- package/src/components/navigation/ButtonWithMenu.jsx +43 -0
- package/src/components/navigation/CustomTab.jsx +14 -0
- package/src/components/navigation/FeedbackDialog.jsx +34 -0
- package/src/components/navigation/GithubCornerRight.jsx +29 -0
- package/src/components/navigation/MainAppBar.css +26 -0
- package/src/components/navigation/MainAppBar.jsx +205 -0
- package/src/components/navigation/SelectExampleDialog.jsx +69 -0
- package/src/components/navigation/SelectTemplateDialog.jsx +107 -0
- package/src/components/navigation/TabPanel.jsx +28 -0
- package/src/components/navigation/VersionDialog.jsx +33 -0
- package/src/components/primers/CreatePrimerFromSequenceForm.jsx +42 -0
- package/src/components/primers/DownloadPrimersButton.jsx +104 -0
- package/src/components/primers/PrimerForm.css +14 -0
- package/src/components/primers/PrimerForm.jsx +107 -0
- package/src/components/primers/PrimerList.css +46 -0
- package/src/components/primers/PrimerList.cy.jsx +95 -0
- package/src/components/primers/PrimerList.jsx +126 -0
- package/src/components/primers/PrimerTableRow.cy.jsx +57 -0
- package/src/components/primers/PrimerTableRow.jsx +84 -0
- package/src/components/primers/SelectPrimerForm.jsx +66 -0
- package/src/components/primers/import_primers/ImportPrimersButton.jsx +101 -0
- package/src/components/primers/import_primers/ImportPrimersTable.jsx +51 -0
- package/src/components/primers/import_primers/PrimerDatabaseImportForm.jsx +107 -0
- package/src/components/primers/import_primers/styles.css +60 -0
- package/src/components/primers/primer_design/SequenceTabComponents/CollapsableLabel.jsx +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/GatewayRoiSelect.jsx +164 -0
- package/src/components/primers/primer_design/SequenceTabComponents/OrientationPicker.jsx +37 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignContext.jsx +369 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignEBIC.jsx +24 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignForm.jsx +29 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGatewayBP.jsx +36 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignGibsonAssembly.jsx +26 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignHomologousRecombination.jsx +32 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignRestriction.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignSimplePair.jsx +25 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesignStepper.jsx +53 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerDesigner.jsx +80 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerResultForm.jsx +49 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSettingsForm.jsx +68 -0
- package/src/components/primers/primer_design/SequenceTabComponents/PrimerSpacerForm.jsx +84 -0
- package/src/components/primers/primer_design/SequenceTabComponents/RestrictionSpacerForm.jsx +85 -0
- package/src/components/primers/primer_design/SequenceTabComponents/StepNavigation.jsx +48 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelEBICSettings.jsx +216 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelResults.jsx +42 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPanelSelectRoi.jsx +61 -0
- package/src/components/primers/primer_design/SequenceTabComponents/TabPannelSettings.jsx +59 -0
- package/src/components/primers/primer_design/SequenceTabComponents/primerDesignMinimalValues.json +5 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEBICPrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useEnzymePrimerDesignSettings.js +57 -0
- package/src/components/primers/primer_design/SequenceTabComponents/useGatewayPrimerDesignSettings.js +18 -0
- package/src/components/primers/primer_design/SequenceTabComponents/usePrimerDesignSettings.js +31 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGatewayBP.jsx +88 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignGibsonAssembly.jsx +65 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignHomologousRecombination.jsx +84 -0
- package/src/components/primers/primer_design/SourceComponents/PrimerDesignSourceForm.jsx +74 -0
- package/src/components/primers/primer_design/common/NoAttPSitesError.jsx +31 -0
- package/src/components/primers/primer_details/PCRTable.cy.jsx +51 -0
- package/src/components/primers/primer_details/PCRTable.jsx +35 -0
- package/src/components/primers/primer_details/Primer3Figure.jsx +25 -0
- package/src/components/primers/primer_details/PrimerDetailsTds.jsx +39 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.cy.jsx +137 -0
- package/src/components/primers/primer_details/PrimerInfoIcon.jsx +132 -0
- package/src/components/primers/primer_details/TableSection.jsx +17 -0
- package/src/components/primers/primer_details/primerDetailsFormatting.js +3 -0
- package/src/components/primers/primer_details/useMultiplePrimerDetails.js +29 -0
- package/src/components/primers/primer_details/usePCRDetails.js +47 -0
- package/src/components/primers/primer_details/usePrimerDetailsEndpoints.js +49 -0
- package/src/components/primers/primer_details/useSinglePrimerSequenceDetails.js +25 -0
- package/src/components/primers/primersToTabularFile.js +49 -0
- package/src/components/primers/primersToTabularFile.test.js +108 -0
- package/src/components/settings/SettingsTab.cy.jsx +267 -0
- package/src/components/settings/SettingsTab.jsx +170 -0
- package/src/components/sources/AssemblyPlanDisplayer.cy.jsx +22 -0
- package/src/components/sources/AssemblyPlanDisplayer.jsx +27 -0
- package/src/components/sources/CollectionSource.jsx +97 -0
- package/src/components/sources/FinishedSource.jsx +397 -0
- package/src/components/sources/KnownSourceErrors.jsx +50 -0
- package/src/components/sources/MultipleInputsSelector.jsx +63 -0
- package/src/components/sources/MultipleOutputsSelector.jsx +63 -0
- package/src/components/sources/NewSourceBox.jsx +37 -0
- package/src/components/sources/PCRUnitForm.jsx +102 -0
- package/src/components/sources/SingleInputSelector.jsx +36 -0
- package/src/components/sources/Source.jsx +125 -0
- package/src/components/sources/SourceAnnotation.jsx +44 -0
- package/src/components/sources/SourceAssembly.jsx +201 -0
- package/src/components/sources/SourceBox.css +18 -0
- package/src/components/sources/SourceBox.jsx +60 -0
- package/src/components/sources/SourceCopySequence.jsx +38 -0
- package/src/components/sources/SourceDatabase.jsx +28 -0
- package/src/components/sources/SourceFile.jsx +188 -0
- package/src/components/sources/SourceGenomeRegion.cy.jsx +131 -0
- package/src/components/sources/SourceGenomeRegion.jsx +486 -0
- package/src/components/sources/SourceHomologousRecombination.jsx +125 -0
- package/src/components/sources/SourceKnownGenomeRegion.jsx +60 -0
- package/src/components/sources/SourceManuallyTyped.jsx +116 -0
- package/src/components/sources/SourcePCRorHybridization.jsx +165 -0
- package/src/components/sources/SourcePolymeraseExtension.jsx +44 -0
- package/src/components/sources/SourceRepositoryId.jsx +409 -0
- package/src/components/sources/SourceRestriction.jsx +41 -0
- package/src/components/sources/SourceReverseComplement.jsx +33 -0
- package/src/components/sources/SourceTypeSelector.jsx +94 -0
- package/src/components/sources/SubSequenceDisplayer.jsx +70 -0
- package/src/components/sources/VerifyDeleteDialog.jsx +23 -0
- package/src/components/sources/repositoryMetadata.js +14 -0
- package/src/components/verification/LoadFromDatabaseButton.jsx +90 -0
- package/src/components/verification/SequencingFileRow.jsx +34 -0
- package/src/components/verification/VerificationFileDialog.cy.jsx +176 -0
- package/src/components/verification/VerificationFileDialog.jsx +248 -0
- package/src/config/defaultMainEditorProps.js +44 -0
- package/src/hooks/useAlerts.js +16 -0
- package/src/hooks/useBackendAPI.js +51 -0
- package/src/hooks/useBackendRoute.js +22 -0
- package/src/hooks/useDatabase.js +18 -0
- package/src/hooks/useDragAndDropFile.js +31 -0
- package/src/hooks/useGatewaySites.js +40 -0
- package/src/hooks/useHttpClient.js +12 -0
- package/src/hooks/useLoadDatabaseFile.js +108 -0
- package/src/hooks/useStoreEditor.js +101 -0
- package/src/hooks/useValidateState.js +43 -0
- package/vitest.config.js +18 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useSelector } from 'react-redux';
|
|
2
|
+
|
|
3
|
+
export default function useBackendRoute() {
|
|
4
|
+
const configBackendUrl = useSelector((state) => state.cloning.config.backendUrl);
|
|
5
|
+
if (!configBackendUrl) {
|
|
6
|
+
return () => {};
|
|
7
|
+
}
|
|
8
|
+
const backendUrl = configBackendUrl.endsWith('/') ? configBackendUrl : `${configBackendUrl}/`;
|
|
9
|
+
|
|
10
|
+
return function backendRoute(path) {
|
|
11
|
+
if (!backendUrl) {
|
|
12
|
+
throw new Error('Backend URL not set');
|
|
13
|
+
}
|
|
14
|
+
// console.log(new URL('/api', window.location.origin).href);
|
|
15
|
+
// console.log(new URL(import.meta.env.VITE_REACT_APP_BACKEND_URL, window.location.origin).href);
|
|
16
|
+
// This handles both the case where the backend url is absolute and the case where it is relative
|
|
17
|
+
const backendRoot = new URL(backendUrl, window.location.origin).href;
|
|
18
|
+
// Remove trailing slash from path
|
|
19
|
+
const sanitizedPath = path.endsWith('/') ? path.slice(0, -1) : path;
|
|
20
|
+
return new URL(sanitizedPath, backendRoot).href;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { useSelector } from 'react-redux';
|
|
3
|
+
import eLabFTWInterface from '../components/eLabFTW/eLabFTWInterface';
|
|
4
|
+
import dummyInterface from '../components/dummy/DummyInterface';
|
|
5
|
+
|
|
6
|
+
export default function useDatabase() {
|
|
7
|
+
const databaseName = useSelector((state) => state.cloning.config.database);
|
|
8
|
+
|
|
9
|
+
return React.useMemo(() => {
|
|
10
|
+
if (databaseName === 'elabftw') {
|
|
11
|
+
return eLabFTWInterface;
|
|
12
|
+
}
|
|
13
|
+
if (databaseName === 'dummy') {
|
|
14
|
+
return dummyInterface;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}, [databaseName]);
|
|
18
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function useDragAndDropFile() {
|
|
4
|
+
const [isDragging, setIsDragging] = React.useState(false);
|
|
5
|
+
const [files, setFiles] = React.useState([]);
|
|
6
|
+
|
|
7
|
+
const handleDragOver = React.useCallback((e) => {
|
|
8
|
+
e.preventDefault();
|
|
9
|
+
e.stopPropagation();
|
|
10
|
+
setIsDragging(true);
|
|
11
|
+
}, []);
|
|
12
|
+
|
|
13
|
+
const handleDragLeave = React.useCallback((e) => {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
e.stopPropagation();
|
|
16
|
+
setIsDragging(false);
|
|
17
|
+
}, []);
|
|
18
|
+
|
|
19
|
+
const handleDrop = React.useCallback(async (e) => {
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
e.stopPropagation();
|
|
22
|
+
setIsDragging(false);
|
|
23
|
+
setFiles(e.dataTransfer.files);
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
const clearFiles = React.useCallback(() => {
|
|
27
|
+
setFiles([]);
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
return { handleDragLeave, handleDragOver, handleDrop, isDragging, files, clearFiles };
|
|
31
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react';
|
|
2
|
+
import { useStore } from 'react-redux';
|
|
3
|
+
import error2String from '@opencloning/utils/error2String';
|
|
4
|
+
import { formatGatewaySites } from '@opencloning/store/cloning_utils';
|
|
5
|
+
import useBackendRoute from './useBackendRoute';
|
|
6
|
+
import useHttpClient from './useHttpClient';
|
|
7
|
+
|
|
8
|
+
export default function useGatewaySites({ target, greedy }) {
|
|
9
|
+
const store = useStore();
|
|
10
|
+
const [requestStatus, setRequestStatus] = useState({ status: null, message: '' });
|
|
11
|
+
const [connectAttempt, setConnectAttempt] = useState(0);
|
|
12
|
+
const [sites, setSites] = useState([]);
|
|
13
|
+
const backendRoute = useBackendRoute();
|
|
14
|
+
const httpClient = useHttpClient();
|
|
15
|
+
|
|
16
|
+
const attemptAgain = useCallback(() => {
|
|
17
|
+
setConnectAttempt((p) => p + 1);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
async function getSitesInTarget() {
|
|
22
|
+
setRequestStatus({ status: 'loading', message: 'loading' });
|
|
23
|
+
const url = backendRoute('annotation/get_gateway_sites');
|
|
24
|
+
const state = store.getState();
|
|
25
|
+
const sequence = state.cloning.sequences.find((seq) => seq.id === target);
|
|
26
|
+
try {
|
|
27
|
+
const { data: donorSites } = await httpClient.post(url, sequence, { params: { greedy } });
|
|
28
|
+
setRequestStatus({ status: 'success', message: '' });
|
|
29
|
+
setSites(formatGatewaySites(donorSites, 'att'));
|
|
30
|
+
} catch (error) {
|
|
31
|
+
setRequestStatus({ status: 'error', message: error2String(error) });
|
|
32
|
+
setSites([]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (target) {
|
|
36
|
+
getSitesInTarget();
|
|
37
|
+
}
|
|
38
|
+
}, [store, connectAttempt, target, greedy]);
|
|
39
|
+
return { requestStatus, attemptAgain, sites };
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { useSelector } from 'react-redux';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import getHttpClient from '@opencloning/utils/getHttpClient';
|
|
4
|
+
|
|
5
|
+
export default function useHttpClient() {
|
|
6
|
+
const backendUrl = useSelector((state) => state.cloning.config.backendUrl);
|
|
7
|
+
|
|
8
|
+
// Memoize the client creation and interceptor setup
|
|
9
|
+
const apiClient = React.useMemo(() => getHttpClient([backendUrl]), [backendUrl]);
|
|
10
|
+
|
|
11
|
+
return apiClient;
|
|
12
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { batch, useDispatch, useStore } from 'react-redux';
|
|
2
|
+
import { jsonToGenbank } from '@teselagen/bio-parsers';
|
|
3
|
+
import useValidateState from './useValidateState';
|
|
4
|
+
import { convertToTeselaJson, loadHistoryFile } from '@opencloning/utils/readNwrite';
|
|
5
|
+
import { getIdsOfSequencesWithoutChildSource } from '@opencloning/store/cloning_utils';
|
|
6
|
+
import { mergeStates, graftState } from '@opencloning/utils/network';
|
|
7
|
+
import { cloningActions } from '@opencloning/store/cloning';
|
|
8
|
+
import useDatabase from './useDatabase';
|
|
9
|
+
|
|
10
|
+
const { deleteSourceAndItsChildren, setState: setCloningState } = cloningActions;
|
|
11
|
+
|
|
12
|
+
export default function useLoadDatabaseFile({ source, sendPostRequest, setHistoryFileError }) {
|
|
13
|
+
const dispatch = useDispatch();
|
|
14
|
+
const validateState = useValidateState();
|
|
15
|
+
const store = useStore();
|
|
16
|
+
const database = useDatabase();
|
|
17
|
+
|
|
18
|
+
const loadDatabaseFile = async (file, databaseId, ancestors = false) => {
|
|
19
|
+
if (file.name.endsWith('.json')) {
|
|
20
|
+
let cloningStrategy;
|
|
21
|
+
try {
|
|
22
|
+
({ cloningStrategy } = await loadHistoryFile(file));
|
|
23
|
+
// If the cloning strategy should end on a single sequence, set the databaseId for the right source
|
|
24
|
+
const terminalSequences = getIdsOfSequencesWithoutChildSource(cloningStrategy.sources, cloningStrategy.sequences);
|
|
25
|
+
if (terminalSequences.length === 1) {
|
|
26
|
+
const lastSource = cloningStrategy.sources.find((s) => s.id === terminalSequences[0]);
|
|
27
|
+
lastSource.database_id = databaseId;
|
|
28
|
+
}
|
|
29
|
+
// When importing sources that had inputs that we don't want to load, we turn them into database sources
|
|
30
|
+
const allSequenceIds = [...cloningStrategy.sequences.map((e) => e.id), ...cloningStrategy.primers.map((e) => e.id)];
|
|
31
|
+
cloningStrategy.sources = cloningStrategy.sources.map((s) => {
|
|
32
|
+
if (s.input.some(({sequence}) => !allSequenceIds.includes(sequence))) {
|
|
33
|
+
return { id: s.id, type: 'DatabaseSource', input: [], database_id: s.database_id };
|
|
34
|
+
}
|
|
35
|
+
return s;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Get primer names (in case they have changed with respect to what was in the file)
|
|
39
|
+
// and verify that the sequence of the primer in the database is the same as the sequence in the cloning strategy
|
|
40
|
+
const primerDatabaseIds = cloningStrategy.primers.filter((p) => p.database_id).map((p) => p.database_id);
|
|
41
|
+
const databasePrimers = await Promise.all(primerDatabaseIds.map(database.getPrimer));
|
|
42
|
+
databasePrimers.forEach((databasePrimer, index) => {
|
|
43
|
+
const primerInCloningStrategy = cloningStrategy.primers.find((p) => p.database_id === databasePrimer.database_id);
|
|
44
|
+
primerInCloningStrategy.name = databasePrimer.name;
|
|
45
|
+
if (primerInCloningStrategy.sequence !== databasePrimer.sequence) {
|
|
46
|
+
throw new Error(`The sequence of primer ${primerInCloningStrategy.name} (${primerInCloningStrategy.database_id}) conflicts with the sequence in the database`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Get the sequence name from the database and update the cloning strategy with it if it is different
|
|
51
|
+
await Promise.all(cloningStrategy.sources.filter((s) => s.database_id).map(async (cloningSource) => {
|
|
52
|
+
const seqDatabaseId = cloningSource.database_id;
|
|
53
|
+
const sequence = cloningStrategy.sequences.find((e) => e.id === cloningSource.id);
|
|
54
|
+
const seq = convertToTeselaJson(sequence);
|
|
55
|
+
const databaseName = await database.getSequenceName(seqDatabaseId);
|
|
56
|
+
if (seq.name !== databaseName) {
|
|
57
|
+
seq.name = databaseName;
|
|
58
|
+
const genbank = jsonToGenbank(seq);
|
|
59
|
+
sequence.file_content = genbank;
|
|
60
|
+
// Maybe this is unnecessary
|
|
61
|
+
cloningSource.output_name = databaseName;
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
} catch (e) {
|
|
65
|
+
console.error(e);
|
|
66
|
+
setHistoryFileError(e.message);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// This one won't have the source.id deleted
|
|
70
|
+
const prevState = store.getState().cloning;
|
|
71
|
+
cloningStrategy = await validateState(cloningStrategy);
|
|
72
|
+
|
|
73
|
+
batch(() => {
|
|
74
|
+
// Replace the source with the new one if called from a source
|
|
75
|
+
if (!ancestors) {
|
|
76
|
+
dispatch(deleteSourceAndItsChildren(source.id));
|
|
77
|
+
}
|
|
78
|
+
const cloningState = store.getState().cloning;
|
|
79
|
+
try {
|
|
80
|
+
let mergedState;
|
|
81
|
+
if (ancestors) {
|
|
82
|
+
({ mergedState } = graftState(cloningStrategy, cloningState, source.id));
|
|
83
|
+
} else {
|
|
84
|
+
({ mergedState } = mergeStates(cloningStrategy, cloningState));
|
|
85
|
+
}
|
|
86
|
+
dispatch(setCloningState(mergedState));
|
|
87
|
+
} catch (e) {
|
|
88
|
+
setHistoryFileError(e.message);
|
|
89
|
+
console.error(e);
|
|
90
|
+
if (!ancestors) {
|
|
91
|
+
dispatch(setCloningState(prevState));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
const requestData = new FormData();
|
|
97
|
+
requestData.append('file', file);
|
|
98
|
+
const config = {
|
|
99
|
+
headers: {
|
|
100
|
+
'content-type': 'multipart/form-data',
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const modifySource = (s) => ({ ...s, database_id: databaseId });
|
|
104
|
+
sendPostRequest({ endpoint: 'read_from_file', requestData, config, source, modifySource });
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
return { loadDatabaseFile };
|
|
108
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { useStore } from 'react-redux';
|
|
2
|
+
import { updateEditor, addAlignment } from '@teselagen/ove';
|
|
3
|
+
import { getPCRPrimers } from '@opencloning/store/cloning_utils';
|
|
4
|
+
import { getTeselaJsonFromBase64 } from '@opencloning/utils/readNwrite';
|
|
5
|
+
import { findRotation, syncChromatogramDataWithAlignment } from '@opencloning/utils/sequenceManipulation';
|
|
6
|
+
import { getReverseComplementSequenceAndAnnotations, getReverseComplementSequenceString, rotateSequenceDataToPosition } from '@teselagen/sequence-utils';
|
|
7
|
+
|
|
8
|
+
export default function useStoreEditor() {
|
|
9
|
+
const store = useStore();
|
|
10
|
+
|
|
11
|
+
const updateStoreEditor = async (editorName, id, selectionLayer = {}) => {
|
|
12
|
+
if (id === null) {
|
|
13
|
+
// if id is null and selectionLayer is empty, clear the sequenceData
|
|
14
|
+
if (Object.keys(selectionLayer).length === 0) {
|
|
15
|
+
updateEditor(store, editorName, { sequenceData: {}, selectionLayer, sequenceDataHistory: {} });
|
|
16
|
+
} else {
|
|
17
|
+
updateEditor(store, editorName, { selectionLayer, sequenceDataHistory: {} });
|
|
18
|
+
}
|
|
19
|
+
} else {
|
|
20
|
+
// otherwise, update the sequenceData with the new id
|
|
21
|
+
const { cloning } = store.getState();
|
|
22
|
+
const { teselaJsonCache } = cloning;
|
|
23
|
+
const sequenceData = { ...teselaJsonCache[id] };
|
|
24
|
+
const sequence = cloning.sequences.find((e) => e.id === id);
|
|
25
|
+
const sequenceWithoutSequencingField = { ...sequence };
|
|
26
|
+
delete sequenceWithoutSequencingField.sequencing;
|
|
27
|
+
const pcrPrimers = getPCRPrimers(cloning, id);
|
|
28
|
+
const alignmentFiles = cloning.files.filter((e) => e.sequence_id === id && e.file_type === 'Sequencing file');
|
|
29
|
+
let { panelsShown } = store.getState().VectorEditor.mainEditor;
|
|
30
|
+
if (alignmentFiles.length > 0) {
|
|
31
|
+
addAlignment(store, {
|
|
32
|
+
id: 'simpleAlignment',
|
|
33
|
+
alignmentType: 'Sequencing alignment',
|
|
34
|
+
name: `Seq. ${id}`,
|
|
35
|
+
// set the visibilities of the annotations you'd like to see
|
|
36
|
+
alignmentAnnotationVisibility: {
|
|
37
|
+
features: true,
|
|
38
|
+
parts: true,
|
|
39
|
+
translations: true,
|
|
40
|
+
},
|
|
41
|
+
alignmentTracks: [
|
|
42
|
+
{
|
|
43
|
+
sequenceData,
|
|
44
|
+
alignmentData: {
|
|
45
|
+
// the alignmentData just needs the sequence < TODO this has to be changed to be the largest ---
|
|
46
|
+
sequence: alignmentFiles[0].alignment[0],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
...await Promise.all(alignmentFiles.map(async (aln) => {
|
|
50
|
+
const fileContent = await getTeselaJsonFromBase64(sessionStorage.getItem(`verification-${id}-${aln.file_name}`), aln.file_name);
|
|
51
|
+
let chromatogramData = fileContent.chromatogramData;
|
|
52
|
+
let alignmentSequenceData = {
|
|
53
|
+
name: aln.file_name,
|
|
54
|
+
sequence: aln.alignment[1].replaceAll('-', ''),
|
|
55
|
+
};
|
|
56
|
+
if (chromatogramData) {
|
|
57
|
+
chromatogramData = syncChromatogramDataWithAlignment(chromatogramData, aln.alignment[1]);
|
|
58
|
+
}
|
|
59
|
+
if (fileContent.features && fileContent.features.length > 0) {
|
|
60
|
+
alignmentSequenceData = fileContent;
|
|
61
|
+
const alignmentSequence = aln.alignment[1].replaceAll('-', '');
|
|
62
|
+
let rotation = findRotation(fileContent.sequence, alignmentSequence);
|
|
63
|
+
// If the rotation is -1, it may be reverse complemented
|
|
64
|
+
const reverseComplemented = rotation === -1;
|
|
65
|
+
if (reverseComplemented) {
|
|
66
|
+
rotation = findRotation(fileContent.sequence, getReverseComplementSequenceString(alignmentSequence));
|
|
67
|
+
alignmentSequenceData = getReverseComplementSequenceAndAnnotations(fileContent);
|
|
68
|
+
}
|
|
69
|
+
if (rotation !== -1 && rotation !== 0) {
|
|
70
|
+
rotation = reverseComplemented ? fileContent.sequence.length - rotation : rotation;
|
|
71
|
+
alignmentSequenceData = rotateSequenceDataToPosition(alignmentSequenceData, rotation);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
sequenceData: {...alignmentSequenceData, name: aln.file_name},
|
|
76
|
+
alignmentData: {
|
|
77
|
+
sequence: aln.alignment[1],
|
|
78
|
+
},
|
|
79
|
+
chromatogramData,
|
|
80
|
+
};
|
|
81
|
+
})),
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
panelsShown = [[
|
|
85
|
+
...panelsShown[0].filter((p) => p.id !== 'simpleAlignment'),
|
|
86
|
+
{
|
|
87
|
+
id: 'simpleAlignment',
|
|
88
|
+
type: 'alignment',
|
|
89
|
+
name: 'Alignments',
|
|
90
|
+
active: true,
|
|
91
|
+
isFullscreen: false,
|
|
92
|
+
},
|
|
93
|
+
]];
|
|
94
|
+
}
|
|
95
|
+
sequenceData.primers = sequenceData.primers.concat([...pcrPrimers]);
|
|
96
|
+
updateEditor(store, editorName, { sequenceData, selectionLayer, panelsShown, sequenceDataHistory: {} });
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return { updateStoreEditor };
|
|
101
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import useAlerts from './useAlerts';
|
|
3
|
+
import useBackendRoute from './useBackendRoute';
|
|
4
|
+
import useHttpClient from './useHttpClient';
|
|
5
|
+
|
|
6
|
+
export default function useValidateState() {
|
|
7
|
+
const backendRoute = useBackendRoute();
|
|
8
|
+
const { addAlert } = useAlerts();
|
|
9
|
+
const httpClient = useHttpClient();
|
|
10
|
+
|
|
11
|
+
const validateState = React.useCallback(async (newState) => {
|
|
12
|
+
// Returns null if the state is valid, otherwise the new updated state
|
|
13
|
+
// It also adds alerts
|
|
14
|
+
try {
|
|
15
|
+
const response = await httpClient.post(backendRoute('validate'), newState);
|
|
16
|
+
if (response.data !== null) {
|
|
17
|
+
response.headers['x-warning'].split(';').forEach((warning) => {
|
|
18
|
+
addAlert({
|
|
19
|
+
message: warning,
|
|
20
|
+
severity: 'warning',
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// Either return the updated state or the original state if it was valid
|
|
25
|
+
return response.data || newState;
|
|
26
|
+
} catch (e) {
|
|
27
|
+
if (e.code === 'ERR_NETWORK') {
|
|
28
|
+
addAlert({
|
|
29
|
+
message: 'Cannot connect to backend server to validate the JSON file',
|
|
30
|
+
severity: 'error',
|
|
31
|
+
});
|
|
32
|
+
} else {
|
|
33
|
+
addAlert({
|
|
34
|
+
message: 'Cloning strategy could be loaded, but it is not valid',
|
|
35
|
+
severity: 'warning',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return newState;
|
|
39
|
+
}
|
|
40
|
+
}, [addAlert, backendRoute]);
|
|
41
|
+
|
|
42
|
+
return validateState;
|
|
43
|
+
}
|
package/vitest.config.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
globals: true,
|
|
7
|
+
environment: 'jsdom',
|
|
8
|
+
setupFiles: '../../tests/setup.js',
|
|
9
|
+
include: ['src/**/*.{test,spec}.{js,jsx}'],
|
|
10
|
+
},
|
|
11
|
+
resolve: {
|
|
12
|
+
alias: {
|
|
13
|
+
'@opencloning/ui': resolve(__dirname, './src'),
|
|
14
|
+
'@opencloning/store': resolve(__dirname, '../store/src'),
|
|
15
|
+
'@opencloning/utils': resolve(__dirname, '../utils/src/utils'),
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|