@@ -36,7 +36,7 @@ import {
3636 Wrench ,
3737 X
3838} from 'lucide-react' ;
39- import type { RepresentationType , ColoringType , ChainInfo , Snapshot , Movie , ColorPalette , PDBMetadata , CustomColorRule , CustomStyleRule , Measurement } from '../types' ;
39+ import type { RepresentationType , ColoringType , ChainInfo , Snapshot , Movie , ColorPalette , PDBMetadata , CustomColorRule , CustomStyleRule , CustomTransparencyRule , Measurement } from '../types' ;
4040import type { DataSource } from '../utils/pdbUtils' ;
4141import type { HistoryItem } from '../hooks/useHistory' ;
4242import { formatChemicalId } from '../utils/pdbUtils' ;
@@ -400,10 +400,14 @@ interface ControlsProps {
400400 onResetCamera ?: ( ) => void ;
401401
402402 // Custom Residue Styles
403- customStyles ? : CustomStyleRule [ ] ;
404- setCustomStyles ? : ( styles : CustomStyleRule [ ] | ( ( prev : CustomStyleRule [ ] ) => CustomStyleRule [ ] ) ) => void ;
403+ customStyles : CustomStyleRule [ ] ;
404+ setCustomStyles : ( styles : CustomStyleRule [ ] | ( ( prev : CustomStyleRule [ ] ) => CustomStyleRule [ ] ) ) => void ;
405405
406- smoothSheetEnabled ?: boolean ;
406+ // Transparency
407+ customTransparency : CustomTransparencyRule [ ] ;
408+ setCustomTransparency : ( rules : CustomTransparencyRule [ ] | ( ( prev : CustomTransparencyRule [ ] ) => CustomTransparencyRule [ ] ) ) => void ;
409+
410+ smoothSheetEnabled : boolean ;
407411 setSmoothSheetEnabled ?: ( enabled : boolean | ( ( prev : boolean ) => boolean ) ) => void ;
408412}
409413
@@ -426,7 +430,9 @@ export const Controls: React.FC<ControlsProps> = ({
426430 setChainStyle, // Destructure
427431 customStyles,
428432 setCustomStyles,
429-
433+ customTransparency,
434+ setCustomTransparency,
435+ smoothSheetEnabled,
430436 chains,
431437 ligands,
432438
@@ -449,7 +455,6 @@ export const Controls: React.FC<ControlsProps> = ({
449455 setShowLigands,
450456 showIons,
451457 setShowIons,
452- smoothSheetEnabled,
453458 setSmoothSheetEnabled,
454459 onRecordMovie,
455460 isRecording,
@@ -577,6 +582,36 @@ export const Controls: React.FC<ControlsProps> = ({
577582 const [ customColorValue , setCustomColorValue ] = useState < string > ( "#ff0000" ) ;
578583 const [ customColorMode , setCustomColorMode ] = useState < 'chain' | 'residue' > ( 'chain' ) ;
579584
585+ // Transparency State
586+ const [ transparencyMode , setTransparencyMode ] = useState < 'chain' | 'residue' > ( 'chain' ) ;
587+ const [ transparencyChain , setTransparencyChain ] = useState < string > ( '' ) ;
588+ const [ transparencyResidues , setTransparencyResidues ] = useState ( '' ) ;
589+ const [ transparencyValue , setTransparencyValue ] = useState ( 50 ) ; // 0-100
590+
591+ // Handlers
592+ const handleAddTransparencyRule = ( ) => {
593+ if ( ! transparencyChain && transparencyMode === 'chain' ) return ;
594+ if ( transparencyChain === 'Select' && transparencyMode === 'chain' ) return ;
595+
596+ const newRule : CustomTransparencyRule = {
597+ id : crypto . randomUUID ( ) ,
598+ chain : transparencyChain || 'All' ,
599+ residues : transparencyMode === 'residue' ? transparencyResidues : undefined ,
600+ opacity : transparencyValue / 100
601+ } ;
602+
603+ if ( setCustomTransparency ) {
604+ setCustomTransparency ( prev => [ ...prev , newRule ] ) ;
605+ setTransparencyResidues ( '' ) ;
606+ }
607+ } ;
608+
609+ const handleRemoveTransparencyRule = ( id : string ) => {
610+ if ( setCustomTransparency ) {
611+ setCustomTransparency ( prev => prev . filter ( r => r . id !== id ) ) ;
612+ }
613+ } ;
614+
580615 const handleAddCustomColor = ( ) => {
581616 let selectionString = '' ;
582617 if ( customChain ) {
@@ -710,7 +745,7 @@ export const Controls: React.FC<ControlsProps> = ({
710745
711746 < div className = { `
712747 absolute top-0 left-0 z-50
713- h-[100dvh] w-full sm:w-[340px]
748+ h-[100dvh] w-full sm:w-[340px]
714749 backdrop-blur-xl border-r shadow-2xl
715750 transition-transform duration-300 ease-in-out
716751 flex flex-col
@@ -1783,6 +1818,134 @@ export const Controls: React.FC<ControlsProps> = ({
17831818
17841819 </ div >
17851820
1821+ { /* Transparency Style */ }
1822+ < div className = { `p-2 rounded-lg border ${ cardBg } mt-2` } >
1823+ < div className = "flex items-center justify-between mb-2" >
1824+ < h4 className = { `text-[10px] font-bold uppercase tracking-wider ${ subtleText } ` } > Transparency</ h4 >
1825+ </ div >
1826+
1827+ < div className = "flex bg-black/10 dark:bg-white/5 rounded-lg p-0.5 mb-2" >
1828+ < button
1829+ onClick = { ( ) => setTransparencyMode ( 'chain' ) }
1830+ className = { `flex-1 py-1 text-[10px] font-medium rounded-md transition-all ${ transparencyMode === 'chain' ? 'bg-blue-500 text-white shadow-sm' : 'text-neutral-500 hover:text-neutral-300' } ` }
1831+ >
1832+ Chain
1833+ </ button >
1834+ < button
1835+ onClick = { ( ) => setTransparencyMode ( 'residue' ) }
1836+ className = { `flex-1 py-1 text-[10px] font-medium rounded-md transition-all ${ transparencyMode === 'residue' ? 'bg-blue-500 text-white shadow-sm' : 'text-neutral-500 hover:text-neutral-300' } ` }
1837+ >
1838+ Residue
1839+ </ button >
1840+ </ div >
1841+
1842+ { /* Chain Selection */ }
1843+ { transparencyMode === 'chain' && (
1844+ < div className = "space-y-2" >
1845+ < label className = { `text-[9px] font-bold uppercase tracking-wider block ${ subtleText } opacity-70` } > Chain</ label >
1846+ < div className = { `relative flex items-center rounded-lg border transition-all ${ inputBg } ` } >
1847+ < select
1848+ value = { transparencyChain }
1849+ onChange = { ( e ) => setTransparencyChain ( e . target . value ) }
1850+ className = "w-full appearance-none bg-transparent py-1.5 pl-2 pr-4 text-xs font-mono outline-none"
1851+ >
1852+ < option value = "" > Select Chain</ option >
1853+ < option value = "All" > All Chains</ option >
1854+ { chains . map ( c => (
1855+ < option key = { c . name } value = { c . name } > Chain { c . name } </ option >
1856+ ) ) }
1857+ </ select >
1858+ < ChevronDown className = "absolute right-2 top-1/2 -translate-y-1/2 w-3 h-3 opacity-50 pointer-events-none" />
1859+ </ div >
1860+ </ div >
1861+ ) }
1862+
1863+ { /* Residue Range Selection */ }
1864+ { transparencyMode === 'residue' && (
1865+ < div className = "flex gap-2" >
1866+ < div className = "w-[80px]" >
1867+ < label className = { `text-[9px] font-bold uppercase tracking-wider mb-1 block ${ subtleText } opacity-70` } > Chain</ label >
1868+ < div className = { `relative flex items-center rounded-lg border transition-all ${ inputBg } ` } >
1869+ < select
1870+ value = { transparencyChain }
1871+ onChange = { ( e ) => setTransparencyChain ( e . target . value ) }
1872+ className = "w-full appearance-none bg-transparent py-1.5 pl-2 pr-4 text-xs font-mono outline-none"
1873+ >
1874+ < option value = "All" > All</ option >
1875+ { chains . map ( c => (
1876+ < option key = { c . name } value = { c . name } > :{ c . name } </ option >
1877+ ) ) }
1878+ </ select >
1879+ < ChevronDown className = "absolute right-1 top-1/2 -translate-y-1/2 w-3 h-3 opacity-50 pointer-events-none" />
1880+ </ div >
1881+ </ div >
1882+ < div className = "flex-1" >
1883+ < label className = { `text-[9px] font-bold uppercase tracking-wider mb-1 block ${ subtleText } opacity-70` } > Residues</ label >
1884+ < input
1885+ type = "text"
1886+ value = { transparencyResidues }
1887+ onChange = { ( e ) => setTransparencyResidues ( e . target . value ) }
1888+ placeholder = "e.g. 50-60"
1889+ className = { `w-full py-1.5 px-2 rounded-lg border text-xs outline-none transition-all ${ inputBg } ` }
1890+ />
1891+ </ div >
1892+ </ div >
1893+ ) }
1894+
1895+ { /* Opacity Slider */ }
1896+ < div className = "mt-2 space-y-1" >
1897+ < div className = "flex justify-between" >
1898+ < label className = { `text-[9px] font-bold uppercase tracking-wider block ${ subtleText } opacity-70` } > Opacity</ label >
1899+ < span className = "text-[9px] font-mono" > { transparencyValue } %</ span >
1900+ </ div >
1901+ < input
1902+ type = "range"
1903+ min = "0"
1904+ max = "100"
1905+ value = { transparencyValue }
1906+ onChange = { ( e ) => setTransparencyValue ( Number ( e . target . value ) ) }
1907+ className = "w-full h-1.5 bg-neutral-200 dark:bg-neutral-700 rounded-lg appearance-none cursor-pointer"
1908+ />
1909+ </ div >
1910+
1911+ < button
1912+ onClick = { handleAddTransparencyRule }
1913+ disabled = { transparencyMode === 'chain' && ! transparencyChain }
1914+ className = "w-full mt-2 py-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-50 disabled:cursor-not-allowed text-white rounded-lg text-xs font-bold transition-all shadow-sm active:scale-95 flex items-center justify-center"
1915+ >
1916+ < Plus size = { 14 } className = "mr-1" />
1917+ Add Rule
1918+ </ button >
1919+
1920+ { /* Active Rules */ }
1921+ { customTransparency && customTransparency . length > 0 && (
1922+ < div className = "space-y-1 pt-2 mt-2 border-t border-dashed border-gray-200 dark:border-gray-700" >
1923+ < div className = { `text-[9px] font-bold uppercase tracking-wider ${ subtleText } opacity-70` } > Active Rules</ div >
1924+ < div className = "space-y-1 max-h-[100px] overflow-y-auto custom-scrollbar" >
1925+ { customTransparency . map ( ( rule ) => (
1926+ < div key = { rule . id } className = { `flex items-center justify-between p-2 rounded border ${ isLightMode ? 'bg-white border-neutral-200' : 'bg-neutral-800 border-white/5' } ` } >
1927+ < div className = "flex flex-col gap-0.5" >
1928+ < div className = "flex items-center gap-2" >
1929+ < span className = { `px-1.5 py-0.5 rounded text-[9px] font-mono font-bold ${ isLightMode ? 'bg-neutral-100 text-neutral-700' : 'bg-neutral-700 text-neutral-300' } ` } >
1930+ { rule . chain === 'All' ? '*' : `:${ rule . chain } ` }
1931+ </ span >
1932+ { rule . residues && < span className = "text-[10px] opacity-70 font-mono" > { rule . residues } </ span > }
1933+ </ div >
1934+ < span className = "text-[9px] text-blue-500 font-medium" > Opacity: { ( rule . opacity * 100 ) . toFixed ( 0 ) } %</ span >
1935+ </ div >
1936+ < button
1937+ onClick = { ( ) => handleRemoveTransparencyRule ( rule . id ) }
1938+ className = "p-1 hover:bg-red-500/20 text-neutral-400 hover:text-red-500 rounded transition-colors"
1939+ >
1940+ < Trash2 size = { 12 } />
1941+ </ button >
1942+ </ div >
1943+ ) ) }
1944+ </ div >
1945+ </ div >
1946+ ) }
1947+ </ div >
1948+
17861949 { /* Custom Rules */ }
17871950 </ div >
17881951 </ SidebarSection >
0 commit comments