@@ -3,10 +3,91 @@ import { Alert, Box, FormControl, FormLabel, InputAdornment, TextField } from '@
33import TabPanel from '../../../navigation/TabPanel' ;
44import { usePrimerDesign } from './PrimerDesignContext' ;
55import StepNavigation from './StepNavigation' ;
6+ import { useSelector } from 'react-redux' ;
7+ import EnzymeMultiSelect from '../../../form/EnzymeMultiSelect' ;
8+ import { isEqual } from 'lodash-es' ;
9+ import { getSequenceWithinRange } from '@teselagen/range-utils' ;
10+ import { aliasedEnzymesByName , cutSequenceByRestrictionEnzyme } from '@teselagen/sequence-utils' ;
11+
12+ function trimPadding ( { templateSequence, padding_left, padding_right, restrictionSitesToAvoid, roi, max_inside, max_outside } ) {
13+ const { start, end } = roi . selectionLayer ;
14+ const leftAnnotationRange = { start : start - padding_left , end : start - 1 } ;
15+ const leftArm = getSequenceWithinRange ( leftAnnotationRange , templateSequence . sequence ) ;
16+ const rightAnnotationRange = { start : end + 1 , end : end + padding_right } ;
17+ const rightArm = getSequenceWithinRange ( rightAnnotationRange , templateSequence . sequence ) ;
18+
19+ const leftMargin = { start : start - max_outside , end : start + max_inside - 1 } ;
20+ const rightMargin = { start : end - max_inside , end : end + max_outside - 1 } ;
21+ const leftMarginArm = getSequenceWithinRange ( leftMargin , templateSequence . sequence ) ;
22+ const rightMarginArm = getSequenceWithinRange ( rightMargin , templateSequence . sequence ) ;
23+
24+ const enzymes = restrictionSitesToAvoid . map ( ( enzyme ) => aliasedEnzymesByName [ enzyme . toLowerCase ( ) ] ) ;
25+ if ( enzymes . length === 0 ) {
26+ return { padding_left, padding_right, cutsitesInMargins : false } ;
27+ }
28+
29+ const cutsInLeftMargin = enzymes . flatMap ( ( enzyme ) => cutSequenceByRestrictionEnzyme (
30+ leftMarginArm ,
31+ true ,
32+ enzyme
33+ ) ) ;
34+ const cutsInRightMargin = enzymes . flatMap ( ( enzyme ) => cutSequenceByRestrictionEnzyme (
35+ rightMarginArm ,
36+ false ,
37+ enzyme
38+ ) ) ;
39+
40+ const cutsitesInMargins = cutsInLeftMargin . length > 0 || cutsInRightMargin . length > 0 ;
41+
42+ const leftCutsites = enzymes . flatMap ( ( enzyme ) => cutSequenceByRestrictionEnzyme (
43+ leftArm ,
44+ true ,
45+ enzyme
46+ ) ) ;
47+ const rightCutsites = enzymes . flatMap ( ( enzyme ) => cutSequenceByRestrictionEnzyme (
48+ rightArm ,
49+ false ,
50+ enzyme
51+ ) ) ;
52+
53+ let paddingLeft = padding_left ;
54+ let paddingRight = padding_right ;
55+ if ( leftCutsites . length > 0 ) {
56+ paddingLeft = leftArm . length - 1 - Math . max ( ...leftCutsites . map ( ( cutsite ) => cutsite . recognitionSiteRange . end ) ) ;
57+ }
58+ if ( rightCutsites . length > 0 ) {
59+ paddingRight = Math . min ( ...rightCutsites . map ( ( cutsite ) => cutsite . recognitionSiteRange . start ) ) ;
60+ }
61+ return {
62+ padding_left : paddingLeft ,
63+ padding_right : paddingRight ,
64+ cutsitesInMargins,
65+ } ;
66+
67+ }
668
769function TabPanelEBICSettings ( ) {
8- const { error, selectedTab, sequenceIds, primers, submissionPreventedMessage, designPrimers, primerDesignSettings } = usePrimerDesign ( ) ;
9- const { max_inside, max_outside, target_tm, target_tm_tolerance, updateSettings } = primerDesignSettings ;
70+ const { error, selectedTab, sequenceIds, primers, submissionPreventedMessage, designPrimers, primerDesignSettings, rois } = usePrimerDesign ( ) ;
71+ const { max_inside, max_outside, target_tm, target_tm_tolerance, updateSettings, restrictionSitesToAvoid, padding_left, padding_right } = primerDesignSettings ;
72+ const [ cutsitesInMarginsError , setCutsitesInMarginsError ] = React . useState ( false ) ;
73+
74+ const templateSequence = useSelector ( ( state ) => state . cloning . teselaJsonCache [ sequenceIds [ 0 ] ] , isEqual ) ;
75+
76+ React . useEffect ( ( ) => {
77+ if ( rois . length > 0 && rois [ 0 ] !== null ) {
78+ const { padding_left : newPaddingLeft , padding_right : newPaddingRight , cutsitesInMargins } = trimPadding ( {
79+ templateSequence,
80+ padding_left,
81+ padding_right,
82+ restrictionSitesToAvoid,
83+ roi : rois [ 0 ] ,
84+ max_inside,
85+ max_outside,
86+ } ) ;
87+ updateSettings ( { padding_left : newPaddingLeft , padding_right : newPaddingRight } ) ;
88+ setCutsitesInMarginsError ( cutsitesInMargins ) ;
89+ }
90+ } , [ templateSequence , restrictionSitesToAvoid , rois , max_inside , max_outside , padding_left , padding_right ] ) ;
1091
1192 return (
1293 < TabPanel value = { selectedTab } index = { sequenceIds . length } >
@@ -73,11 +154,54 @@ function TabPanelEBICSettings() {
73154 } }
74155 />
75156 </ FormControl >
157+
158+ </ Box >
159+ < Box sx = { { mt : 2 } } >
160+ < FormControl sx = { { mr : 2 } } >
161+ < TextField
162+ label = "Padding left"
163+ value = { padding_left }
164+ onChange = { ( e ) => { updateSettings ( { padding_left : Number ( e . target . value ) } ) ; } }
165+ type = "number"
166+ InputProps = { {
167+ endAdornment : < InputAdornment position = "end" > bp</ InputAdornment > ,
168+ sx : { width : '10em' } ,
169+ } }
170+ />
171+ </ FormControl >
172+ < FormControl sx = { { mr : 2 } } >
173+ < TextField
174+ label = "Padding right"
175+ value = { padding_right }
176+ onChange = { ( e ) => { updateSettings ( { padding_right : Number ( e . target . value ) } ) ; } }
177+ type = "number"
178+ InputProps = { {
179+ endAdornment : < InputAdornment position = "end" > bp</ InputAdornment > ,
180+ sx : { width : '10em' } ,
181+ } }
182+ />
183+ </ FormControl >
184+ </ Box >
185+ < Box sx = { { mt : 2 } } >
186+ < FormControl sx = { { mr : 2 , width : '15em' } } >
187+ < EnzymeMultiSelect
188+ value = { restrictionSitesToAvoid }
189+ setEnzymes = { ( v ) => updateSettings ( { restrictionSitesToAvoid : v } ) }
190+ label = "Sites to avoid"
191+ multiple = { true }
192+ />
193+ </ FormControl >
194+
76195 </ Box >
77196 </ Box >
78197 </ Box >
79198 </ Box >
80199 { error && < Alert severity = "error" sx = { { width : 'fit-content' , margin : 'auto' , mt : 2 } } > { error } </ Alert > }
200+ { cutsitesInMarginsError && (
201+ < Alert severity = "error" sx = { { width : 'fit-content' , margin : 'auto' , mt : 2 } } >
202+ Restriction enzyme cut sites were detected in the margin regions. Please adjust the margin size or select different restriction sites to avoid this issue.
203+ </ Alert >
204+ ) }
81205 < StepNavigation
82206 onStepCompletion = { designPrimers }
83207 stepCompletionText = "Design primers"
0 commit comments