From 13231a1ec1005d007d99046ae51995660cce6fdb Mon Sep 17 00:00:00 2001 From: Kerem Gurkan Date: Mon, 2 Feb 2026 15:10:02 -0700 Subject: [PATCH] GUI of new transformation workflow --- frontend/src/activities.js | 1 - .../panels/assembly-editor/AssemblyPanel.jsx | 48 -- .../panels/assembly-editor/AssemblyWizard.jsx | 452 ------------------ .../transformations/TransformationWizard.jsx | 134 +++++- frontend/src/objectTypes.js | 10 - frontend/src/panels.js | 22 - 6 files changed, 120 insertions(+), 547 deletions(-) delete mode 100644 frontend/src/components/panels/assembly-editor/AssemblyPanel.jsx delete mode 100644 frontend/src/components/panels/assembly-editor/AssemblyWizard.jsx diff --git a/frontend/src/activities.js b/frontend/src/activities.js index caf5193..84cdd6e 100644 --- a/frontend/src/activities.js +++ b/frontend/src/activities.js @@ -61,7 +61,6 @@ export const Activities = { objectTypesToList: [ ObjectTypes.Plasmids.id, ObjectTypes.Strains.id, - ObjectTypes.Assembly.id, ObjectTypes.Transformations.id, ] }, diff --git a/frontend/src/components/panels/assembly-editor/AssemblyPanel.jsx b/frontend/src/components/panels/assembly-editor/AssemblyPanel.jsx deleted file mode 100644 index 60414dc..0000000 --- a/frontend/src/components/panels/assembly-editor/AssemblyPanel.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Badge, ScrollArea, Space, Tabs } from '@mantine/core' -import { createContext } from 'react' -import PanelSaver from "../PanelSaver" -import { useSelector } from 'react-redux' -import { panelsSelectors } from '../../../redux/hooks/panelsHooks' -import { createSelector } from '@reduxjs/toolkit' -import { useState } from 'react' -import AssemblyWizard from "./AssemblyWizard" - -//contains info that is written to json -export const PanelContext = createContext() - -const TabValues = { - SETUP: "setup", - RESULTS: "results" -} - -export default function AssemblyPanel({ id }) { - const [activeTab, setActiveTab] = useState(TabValues.SETUP); - - return ( - - - - Setup - - - - - - - - - - - ) -} - -const tabStyles = theme => ({ - tab: { - width: 120, - textTransform: 'uppercase', - fontWeight: 600 - }, - tabsList: { - // backgroundColor: theme.colors.dark[6] - } -}) \ No newline at end of file diff --git a/frontend/src/components/panels/assembly-editor/AssemblyWizard.jsx b/frontend/src/components/panels/assembly-editor/AssemblyWizard.jsx deleted file mode 100644 index 9ea432b..0000000 --- a/frontend/src/components/panels/assembly-editor/AssemblyWizard.jsx +++ /dev/null @@ -1,452 +0,0 @@ -import { useEffect } from 'react' -import { Container, Stepper, Group, Button, Tabs, Space, Select, Table, Text } from "@mantine/core" -import { useContext } from 'react' -import { PanelContext } from './AssemblyPanel' -import { usePanelProperty } from '../../../redux/hooks/panelsHooks' -import PanelSaver from '../PanelSaver' -import { useUnifiedModal } from '../../../redux/hooks/useUnifiedModal'; -import { showNotification } from '@mantine/notifications' - -const tableContainerStyle = { - position: 'relative', - overflowY: 'scroll', - overflowX: 'auto', - scrollbarWidth: 'thin', - scrollbarColor: '#ccc #f5f5f5', -} - -const cellStyle = { - whiteSpace: 'nowrap', -} - -export const Machines = { - PUDU: { - OT2: 'OT2' - }, -} - -export const AssemblyMethods = { - MOCLO: 'Modular Cloning', - LOOP: 'Loop (Coming Soon)', - GIBSON: 'Gibson (Coming Soon)' -} - -export const Compiler = { - MANUAL: 'Manual (Coming Soon)', - PUDU: 'Automated (PUDU)', - PYLAB: 'Automated (PyLab Robot)', - CLOUD: 'Cloud (Coming Soon)' -} - -export const RestrictionEnzymes = { - MOCLO: { - BSAI: 'BSAI' - } -} - -export default function AssemblyWizard() { - const panelId = useContext(PanelContext) - const { workflows } = useUnifiedModal(); - PanelSaver(panelId) - - // stepper states - const numSteps = 3 - const [activeStep, setActiveStep] = usePanelProperty(panelId, "activeStep", false, 0) - const nextStep = () => setActiveStep((current) => (current < numSteps ? current + 1 : current)) - const prevStep = () => setActiveStep((current) => (current > 0 ? current - 1 : current)) - - // stepper 1 - const [SBHinstance, setSBHinstance] = usePanelProperty(panelId, "SBHinstance", false, '') - const [SBHemail, setSBHemail] = usePanelProperty(panelId, "SBHemail", false, '') - const [depositCollection, setDepositCollection] = usePanelProperty(panelId, "finalCollection", false, '') - const [collectionInfo, setCollectionInfo] = usePanelProperty(panelId, "collectionInfo", false, null); - const [userInfo, setUserInfo] = usePanelProperty(panelId, "userInfo", false, null); - - const [abstractDesign, setAbstractDesign] = usePanelProperty(panelId, "abstractDesign", false, ''); - const [abstractDesignInfo, setAbstractDesignInfo] = usePanelProperty(panelId, "abstractDesignInfo", false, null); - - const [selectedPlasmid, setSelectedPlasmid] = usePanelProperty(panelId, "selectedPlasmid", false, null); - const [selectedPlasmidInfo, setSelectedPlasmidInfo] = usePanelProperty(panelId, "selectedPlasmidInfo", false, null); - - // stepper 2 - const [assemblyMethod, setAssemblyMethod] = usePanelProperty(panelId, "assemblyMethod", false, AssemblyMethods.MOCLO) - const [restrictionEnzyme, setRestrictionEnzyme] = usePanelProperty(panelId, "restrictionEnzyme", false, RestrictionEnzymes.MOCLO.BSAI) - const [compiler, setCompiler] = usePanelProperty(panelId, "compiler", false, Compiler.PUDU) - const [machine, setMachine] = usePanelProperty(panelId, "machine", false, Machines.PUDU.OT2) - - const [selectedBackbone, setSelectedBackbone] = usePanelProperty(panelId, "selectedBackbone", false, null); - const [selectedBackboneInfo, setSelectedBackboneInfo] = usePanelProperty(panelId, "selectedBackboneInfo", false, null); - - useEffect(() => { - if (assemblyMethod === AssemblyMethods.MOCLO && restrictionEnzyme !== RestrictionEnzymes.MOCLO.BSAI) { - setRestrictionEnzyme(RestrictionEnzymes.MOCLO.BSAI); - } - }, [assemblyMethod, setRestrictionEnzyme, restrictionEnzyme]); - - useEffect(() => { - if (!SBHinstance || !SBHemail) { - setDepositCollection(''); - setCollectionInfo(null); - setUserInfo(null); - setAbstractDesign(''); - setAbstractDesignInfo(null); - setSelectedPlasmid(null); - setSelectedPlasmidInfo(null); - setSelectedBackbone(null); - setSelectedBackboneInfo(null); - } - }, [SBHinstance, SBHemail]); - - // workflow calls - const handleBrowseCollections = () => { - workflows.browseCollections((data) => { - if (data && data.completed) { - try { - if ( - !data.selectedRepo || - !data.userInfo || - !data.userInfo.email || - !data.collections || - !data.collections[0] || - !data.collections[0].uri - ) { - throw new Error("Missing required data."); - } - setSBHinstance(data.selectedRepo); - setSBHemail(data.userInfo.email); - setDepositCollection(data.collections[0].uri); - setUserInfo(data.userInfo); - setCollectionInfo(data.collections[0]); - } catch (error) { - showNotification({ - title: 'Failed to Select Collection', - message: error.message, - color: 'red', - }); - } - } - }); - }; - - const handleInsertAbstractDesign = () => { - // Use the new workflow that validates against the deposit collection's email - if (!SBHinstance || !SBHemail) { - showNotification({ - title: 'No Repository Selected', - message: 'Please select a deposit collection first.', - color: 'red', - }); - return; - } - - workflows.browseCollectionsForResource(SBHinstance, SBHemail, (data) => { - if (data && data.aborted) { - const expectedEmail = data.expectedEmail || SBHemail || 'unknown'; - const actualEmail = data.actualEmail || 'unknown'; - showNotification({ - title: data.error === 'Email mismatch' ? 'Email Mismatch' : 'Selection Aborted', - message: data.error === 'Email mismatch' - ? `Expected ${expectedEmail}, but you are logged in as ${actualEmail}.` - : data.error || 'The operation was aborted.', - color: 'red', - }); - return; - } - - if (data && data.completed) { - try { - setAbstractDesign(data.collections[0].uri); - setAbstractDesignInfo(data.collections[0]); - } catch (error) { - showNotification({ - title: 'Failed to Insert Abstract Design', - message: error.message, - color: 'red', - }); - } - } - }, { multiSelect: false }); // Single selection mode - }; - - // Handler for Select Plasmids - const handleSelectPlasmid = () => { - // Use the new workflow that validates against the deposit collection's email - if (!SBHinstance || !SBHemail) { - showNotification({ - title: 'No Repository Selected', - message: 'Please select a deposit collection first.', - color: 'red', - }); - return; - } - - workflows.browseCollectionsForResource(SBHinstance, SBHemail, (data) => { - if (data && data.aborted) { - showNotification({ - title: 'Email Mismatch', - message: `Expected ${data.expectedEmail}, but you are logged in as ${data.actualEmail}.`, - color: 'red', - }); - return; - } - - if (data && data.completed) { - try { - setSelectedPlasmid(data.collections[0].uri); - setSelectedPlasmidInfo(data.collections[0]); - } catch (error) { - showNotification({ - title: 'Failed to Select Plasmid', - message: error.message, - color: 'red', - }); - } - } - }, { multiSelect: false }); // Single selection mode - }; - - // Handler for Select Backbone - const handleSelectBackbone = () => { - // Use the new workflow that validates against the deposit collection's email - if (!SBHinstance || !SBHemail) { - showNotification({ - title: 'No Repository Selected', - message: 'Please select a deposit collection first.', - color: 'red', - }); - return; - } - - workflows.browseCollectionsForResource(SBHinstance, SBHemail, (data) => { - if (data && data.aborted) { - showNotification({ - title: 'Email Mismatch', - message: `Expected ${data.expectedEmail}, but you are logged in as ${data.actualEmail}.`, - color: 'red', - }); - return; - } - - if (data && data.completed) { - try { - setSelectedBackbone(data.collections[0].uri); - setSelectedBackboneInfo(data.collections[0]); - } catch (error) { - showNotification({ - title: 'Failed to Select Backbone', - message: error.message, - color: 'red', - }); - } - } - }, { multiSelect: false }); // Single selection mode - }; - - return ( - - - 0} - label="Designs" - > - - {SBHinstance && depositCollection && collectionInfo && userInfo && ( -
- Selected SynBioHub Instance: {SBHinstance}
- Username: {userInfo.username || '-'}
- Email: {userInfo.email || '-'}
- Collection Name: {collectionInfo.name || collectionInfo.displayId || '-'}
- Collection URI: {depositCollection}
- Description: {collectionInfo.description || '-'}
-
- )} - {SBHinstance && depositCollection && collectionInfo && userInfo && ( - - )} - {SBHinstance && depositCollection && collectionInfo && userInfo && abstractDesign && abstractDesignInfo && ( -
- Design Name: {abstractDesignInfo.name || abstractDesignInfo.displayId || '-'}
- Design URI: {abstractDesign}
- Description: {abstractDesignInfo.description || '-'}
-
- )} - {SBHinstance && depositCollection && collectionInfo && userInfo && abstractDesign && abstractDesignInfo && ( - - )} - {SBHinstance && depositCollection && collectionInfo && userInfo && abstractDesign && abstractDesignInfo && selectedPlasmid && selectedPlasmidInfo && ( -
- Plasmid Name: {selectedPlasmidInfo.name || selectedPlasmidInfo.displayId || '-'}
- Plasmid URI: {selectedPlasmid}
- Description: {selectedPlasmidInfo.description || '-'}
-
- )} -
- 2} - label="Assembly" - > - - - - {AssemblyMethods.GIBSON} - - - {AssemblyMethods.LOOP} - - - {AssemblyMethods.MOCLO} - - - - - ({ - value, - label - }))} - value={machine} - onChange={setMachine} - /> - - - - 2} - label="Execute" - > - -
- - - - - - - - - {Object.entries({ - SBHinstance, - SBHemail, - depositCollection, - collectionInfo, - userInfo, - abstractDesign, - abstractDesignInfo, - selectedPlasmid, - selectedPlasmidInfo, - assemblyMethod, - restrictionEnzyme, - compiler, - machine, - selectedBackbone, - selectedBackboneInfo - }).map(([key, value]) => ( - - - - - ))} - -
{key} - - - {typeof value === 'object' && value !== null - ? JSON.stringify(value, null, 2) - : String(value)} - - -
-
-
-
-
- - {activeStep == 0 ? <> : - - } - {activeStep === numSteps - 1 ? - - : - - } - -
- ) -} \ No newline at end of file diff --git a/frontend/src/components/panels/transformations/TransformationWizard.jsx b/frontend/src/components/panels/transformations/TransformationWizard.jsx index a271bcd..82c58bd 100644 --- a/frontend/src/components/panels/transformations/TransformationWizard.jsx +++ b/frontend/src/components/panels/transformations/TransformationWizard.jsx @@ -1,9 +1,33 @@ import { Container } from "@mantine/core" import { usePanelProperty } from '../../../redux/hooks/panelsHooks' -import { useContext } from 'react' +import { useContext, useEffect } from 'react' import { PanelContext } from './TransformationPanel' -import { Button, Group, Stepper } from '@mantine/core' +import { Button, Group, Stepper, Tabs, Space, Select } from '@mantine/core' +export const Machines = { + PUDU: { + OT2: 'OT2' + }, +} + +export const AssemblyMethods = { + MOCLO: 'Modular Cloning', + LOOP: 'Loop (Coming Soon)', + GIBSON: 'Gibson (Coming Soon)' +} + +export const Compiler = { + MANUAL: 'Manual (Coming Soon)', + PUDU: 'Automated (PUDU)', + PYLAB: 'Automated (PyLab Robot)', + CLOUD: 'Cloud (Coming Soon)' +} + +export const RestrictionEnzymes = { + MOCLO: { + BSAI: 'BSAI' + } +} export default function TransformationWizard() { const panelId = useContext(PanelContext) @@ -13,44 +37,126 @@ export default function TransformationWizard() { const nextStep = () => setActiveStep((current) => (current < numSteps ? current + 1 : current)) const prevStep = () => setActiveStep((current) => (current > 0 ? current - 1 : current)) +const [assemblyMethod, setAssemblyMethod] = usePanelProperty(panelId, "assemblyMethod", false, AssemblyMethods.MOCLO) + const [restrictionEnzyme, setRestrictionEnzyme] = usePanelProperty(panelId, "restrictionEnzyme", false, RestrictionEnzymes.MOCLO.BSAI) + const [compiler, setCompiler] = usePanelProperty(panelId, "compiler", false, Compiler.PUDU) + const [machine, setMachine] = usePanelProperty(panelId, "machine", false, Machines.PUDU.OT2) + + const [selectedBackbone, setSelectedBackbone] = usePanelProperty(panelId, "selectedBackbone", false, null); + const [selectedBackboneInfo, setSelectedBackboneInfo] = usePanelProperty(panelId, "selectedBackboneInfo", false, null); + + useEffect(() => { + if (assemblyMethod === AssemblyMethods.MOCLO && restrictionEnzyme !== RestrictionEnzymes.MOCLO.BSAI) { + setRestrictionEnzyme(RestrictionEnzymes.MOCLO.BSAI); + } + }, [assemblyMethod, setRestrictionEnzyme, restrictionEnzyme]); + return ( 0} - label="Plasmids" + label="Design" > + - - 1} - label="Collections" - > + + + 1} + label="Build" + > + + + + {AssemblyMethods.GIBSON} + + + {AssemblyMethods.LOOP} + + + {AssemblyMethods.MOCLO} + + + + + ({ + value, + label + }))} + value={machine} + onChange={setMachine} + /> + + 2} - label="Transform" + label="Collection" > - Machine protocol form alert("Exporting call placeholder\n(Not implemented yet)")} color='green' > - Export + Run Workflow :