Skip to content

Commit b02de5c

Browse files
AmirMohammad CheraghaliAmirMohammad Cheraghali
authored andcommitted
refactor: Upgrade Dual View layout with clean headers and active indicators
1 parent 70023ed commit b02de5c

1 file changed

Lines changed: 105 additions & 65 deletions

File tree

src/App.tsx

Lines changed: 105 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,97 +1411,137 @@ function App() {
14111411
})()}
14121412

14131413
{/* Dual View Layout */}
1414-
<div className="relative flex-1 flex w-full h-full overflow-hidden">
1414+
<div className="relative flex-1 flex w-full h-full overflow-hidden bg-black">
14151415

14161416
{/* Left / Single View */}
1417-
<div className={`relative h-full transition-all duration-300 ${isComparisonMode ? 'w-1/2 border-r border-white/10' : 'w-full'}`}>
1418-
<ProteinViewer
1419-
ref={leftRef}
1420-
// Spread Left Controller State
1421-
pdbId={left.pdbId}
1422-
dataSource={left.dataSource}
1423-
file={left.file || undefined}
1424-
fileType={left.fileType}
1425-
isLightMode={isLightMode}
1426-
isSpinning={left.isSpinning}
1427-
representation={left.representation}
1428-
showSurface={left.showSurface}
1429-
showLigands={left.showLigands}
1430-
showIons={left.showIons}
1431-
coloring={left.coloring}
1432-
palette={colorPalette} // Global
1433-
backgroundColor={left.customBackgroundColor || (isLightMode ? 'white' : 'black')}
1434-
measurementTextColor={measurementTextColorMode} // Global?
1435-
enableAmbientOcclusion={true}
1436-
1437-
onStructureLoaded={(info) => handleLoad(info, left)}
1438-
onAtomClick={(info) => handleAtomClick(info, left, leftRef)}
1439-
isMeasurementMode={isMeasurementMode} // Global
1440-
measurements={left.measurements}
1441-
onAddMeasurement={(m) => {
1442-
left.setMeasurements([...left.measurements, m]);
1443-
setActiveView('left');
1444-
}}
1445-
onHover={setHoveredResidue} // Global
1446-
1447-
quality={isPublicationMode ? 'high' : 'medium'} // Global
1448-
resetCamera={left.resetKey}
1449-
customColors={left.customColors}
1450-
className="w-full h-full"
1451-
/>
1452-
1453-
{/* Active Indicator for Dual Mode */}
1417+
<div className={`flex flex-col h-full transition-all duration-300 ${isComparisonMode ? 'w-1/2 border-r border-[#333]' : 'w-full'}`}>
1418+
{/* Viewport Header (Dual Mode Only) */}
14541419
{isComparisonMode && (
14551420
<div
14561421
onClick={() => setActiveView('left')}
1457-
className={`absolute top-4 left-4 z-10 px-3 py-1 rounded-full text-xs font-bold cursor-pointer transition-colors backdrop-blur-md border ${activeView === 'left' ? 'bg-indigo-600/90 text-white border-indigo-400 shadow-[0_0_15px_rgba(79,70,229,0.5)]' : 'bg-black/40 text-white/50 border-white/10 hover:bg-black/60 hover:text-white'}`}
1422+
className={`shrink-0 h-9 flex items-center justify-between px-3 border-b transition-colors cursor-pointer select-none
1423+
${activeView === 'left' ? 'bg-[#1a1a1a] border-indigo-500/50' : 'bg-black border-[#222] opacity-60 hover:opacity-100'}
1424+
`}
14581425
>
1459-
Left View
1426+
<div className="flex items-center gap-2">
1427+
<div className={`w-2 h-2 rounded-full shadow-sm transition-all ${activeView === 'left' ? 'bg-indigo-500 shadow-indigo-500/50 scale-110' : 'bg-neutral-700'}`} />
1428+
<span className={`text-[10px] font-bold uppercase tracking-widest ${activeView === 'left' ? 'text-indigo-400' : 'text-neutral-500'}`}>Left View</span>
1429+
</div>
1430+
<div className="flex items-center gap-3">
1431+
<span className="text-[10px] text-neutral-400 font-mono max-w-[120px] truncate">
1432+
{left.proteinTitle || left.pdbId || (left.file ? left.file.name : "No Structure")}
1433+
</span>
1434+
{/* Quick Action: Reset */}
1435+
<button
1436+
onClick={(e) => { e.stopPropagation(); left.handleResetView(); }}
1437+
className="p-1 hover:bg-white/10 rounded text-neutral-500 hover:text-white transition-colors"
1438+
title="Reset Camera"
1439+
>
1440+
<RefreshCw className="w-3 h-3" />
1441+
</button>
1442+
</div>
14601443
</div>
14611444
)}
1462-
</div>
14631445

1464-
{/* Right View */}
1465-
{isComparisonMode && (
1466-
<div className="relative w-1/2 h-full">
1446+
<div className="relative flex-1 w-full h-full">
14671447
<ProteinViewer
1468-
ref={rightRef}
1469-
pdbId={right.pdbId}
1470-
dataSource={right.dataSource}
1471-
file={right.file || undefined}
1472-
fileType={right.fileType}
1448+
ref={leftRef}
1449+
pdbId={left.pdbId}
1450+
dataSource={left.dataSource}
1451+
file={left.file || undefined}
1452+
fileType={left.fileType}
14731453
isLightMode={isLightMode}
1474-
isSpinning={right.isSpinning}
1475-
representation={right.representation}
1476-
showSurface={right.showSurface}
1477-
showLigands={right.showLigands}
1478-
showIons={right.showIons}
1479-
coloring={right.coloring}
1454+
isSpinning={left.isSpinning}
1455+
representation={left.representation}
1456+
showSurface={left.showSurface}
1457+
showLigands={left.showLigands}
1458+
showIons={left.showIons}
1459+
coloring={left.coloring}
14801460
palette={colorPalette}
1481-
backgroundColor={right.customBackgroundColor || (isLightMode ? 'white' : 'black')}
1461+
backgroundColor={left.customBackgroundColor || (isLightMode ? 'white' : 'black')}
14821462
measurementTextColor={measurementTextColorMode}
14831463
enableAmbientOcclusion={true}
14841464

1485-
onStructureLoaded={(info) => handleLoad(info, right)}
1486-
onAtomClick={(info) => handleAtomClick(info, right, rightRef)}
1465+
onStructureLoaded={(info) => handleLoad(info, left)}
1466+
onAtomClick={(info) => handleAtomClick(info, left, leftRef)}
14871467
isMeasurementMode={isMeasurementMode}
1488-
measurements={right.measurements}
1468+
measurements={left.measurements}
14891469
onAddMeasurement={(m) => {
1490-
right.setMeasurements([...right.measurements, m]);
1491-
setActiveView('right');
1470+
left.setMeasurements([...left.measurements, m]);
1471+
setActiveView('left');
14921472
}}
14931473
onHover={setHoveredResidue}
14941474

14951475
quality={isPublicationMode ? 'high' : 'medium'}
1496-
resetCamera={right.resetKey}
1497-
customColors={right.customColors}
1476+
resetCamera={left.resetKey}
1477+
customColors={left.customColors}
14981478
className="w-full h-full"
14991479
/>
1480+
</div>
1481+
</div>
1482+
1483+
{/* Right View */}
1484+
{isComparisonMode && (
1485+
<div className="flex flex-col w-1/2 h-full bg-black">
1486+
{/* Viewport Header */}
15001487
<div
15011488
onClick={() => setActiveView('right')}
1502-
className={`absolute top-4 left-4 z-10 px-3 py-1 rounded-full text-xs font-bold cursor-pointer transition-colors backdrop-blur-md border ${activeView === 'right' ? 'bg-indigo-600/90 text-white border-indigo-400 shadow-[0_0_15px_rgba(79,70,229,0.5)]' : 'bg-black/40 text-white/50 border-white/10 hover:bg-black/60 hover:text-white'}`}
1489+
className={`shrink-0 h-9 flex items-center justify-between px-3 border-b transition-colors cursor-pointer select-none
1490+
${activeView === 'right' ? 'bg-[#1a1a1a] border-indigo-500/50' : 'bg-black border-[#222] opacity-60 hover:opacity-100'}
1491+
`}
15031492
>
1504-
Right View
1493+
<div className="flex items-center gap-2">
1494+
<div className={`w-2 h-2 rounded-full shadow-sm transition-all ${activeView === 'right' ? 'bg-indigo-500 shadow-indigo-500/50 scale-110' : 'bg-neutral-700'}`} />
1495+
<span className={`text-[10px] font-bold uppercase tracking-widest ${activeView === 'right' ? 'text-indigo-400' : 'text-neutral-500'}`}>Right View</span>
1496+
</div>
1497+
<div className="flex items-center gap-3">
1498+
<span className="text-[10px] text-neutral-400 font-mono max-w-[120px] truncate">
1499+
{right.proteinTitle || right.pdbId || (right.file ? right.file.name : "No Structure")}
1500+
</span>
1501+
<button
1502+
onClick={(e) => { e.stopPropagation(); right.handleResetView(); }}
1503+
className="p-1 hover:bg-white/10 rounded text-neutral-500 hover:text-white transition-colors"
1504+
title="Reset Camera"
1505+
>
1506+
<RefreshCw className="w-3 h-3" />
1507+
</button>
1508+
</div>
1509+
</div>
1510+
1511+
<div className="relative flex-1 w-full h-full">
1512+
<ProteinViewer
1513+
ref={rightRef}
1514+
pdbId={right.pdbId}
1515+
dataSource={right.dataSource}
1516+
file={right.file || undefined}
1517+
fileType={right.fileType}
1518+
isLightMode={isLightMode}
1519+
isSpinning={right.isSpinning}
1520+
representation={right.representation}
1521+
showSurface={right.showSurface}
1522+
showLigands={right.showLigands}
1523+
showIons={right.showIons}
1524+
coloring={right.coloring}
1525+
palette={colorPalette}
1526+
backgroundColor={right.customBackgroundColor || (isLightMode ? 'white' : 'black')}
1527+
measurementTextColor={measurementTextColorMode}
1528+
enableAmbientOcclusion={true}
1529+
1530+
onStructureLoaded={(info) => handleLoad(info, right)}
1531+
onAtomClick={(info) => handleAtomClick(info, right, rightRef)}
1532+
isMeasurementMode={isMeasurementMode}
1533+
measurements={right.measurements}
1534+
onAddMeasurement={(m) => {
1535+
right.setMeasurements([...right.measurements, m]);
1536+
setActiveView('right');
1537+
}}
1538+
onHover={setHoveredResidue}
1539+
1540+
quality={isPublicationMode ? 'high' : 'medium'}
1541+
resetCamera={right.resetKey}
1542+
customColors={right.customColors}
1543+
className="w-full h-full"
1544+
/>
15051545
</div>
15061546
</div>
15071547
)}

0 commit comments

Comments
 (0)