Skip to content

Commit eba9bde

Browse files
AmirMohammad CheraghaliAmirMohammad Cheraghali
authored andcommitted
fix: Ensure Contact Map sidebar visibility on mobile with Higher Z-Index
1 parent 87a55fa commit eba9bde

4 files changed

Lines changed: 102 additions & 4 deletions

File tree

src/App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ function App() {
7272
viewerRef.current?.setCameraOrientation((window as any).__pendingOrientation);
7373
delete (window as any).__pendingOrientation;
7474
}
75+
if (initialUrlState.measurements) {
76+
viewerRef.current?.restoreMeasurements(initialUrlState.measurements);
77+
}
7578
setHasRestoredState(false);
7679
}, 500);
7780
} catch (e) { console.warn("App: Failed to restore state", e); }

src/components/ContactMap.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -851,12 +851,41 @@ export const ContactMap: React.FC<ContactMapProps> = ({
851851
)}
852852

853853
<div className={`
854-
absolute inset-y-0 right-0 z-50 w-80 shadow-2xl transition-transform duration-300 ease-in-out md:shadow-none
854+
absolute inset-y-0 right-0 z-[100] w-full max-w-[320px] md:w-80 shadow-2xl transition-transform duration-300 ease-in-out md:shadow-none
855855
${showMobileSidebar ? 'translate-x-0' : 'translate-x-full'} md:translate-x-0 md:relative md:flex md:flex-col md:border-l md:overflow-y-auto
856856
flex flex-col border-l overflow-y-auto
857857
${isLightMode ? 'bg-white border-neutral-100' : 'bg-neutral-900 border-neutral-800'}
858858
`}>
859859

860+
861+
{/* Mobile Actions (Hidden on Desktop) */}
862+
<div className="md:hidden p-3 border-b border-neutral-100 dark:border-neutral-800 space-y-2">
863+
<h4 className="text-xs font-bold uppercase opacity-50 tracking-wider">Actions</h4>
864+
<div className="grid grid-cols-2 gap-2">
865+
<button
866+
onClick={handleDownloadClick}
867+
className={`flex items-center justify-center gap-2 px-3 py-2 rounded-lg text-xs font-medium transition-colors ${isLightMode ? 'bg-blue-600 text-white' : 'bg-blue-600 text-white'}`}
868+
>
869+
<BookOpen className="w-4 h-4" />
870+
<span>PDF Report</span>
871+
</button>
872+
<button
873+
onClick={handleDownload}
874+
className={`flex items-center justify-center gap-2 px-3 py-2 rounded-lg text-xs font-medium transition-colors ${isLightMode ? 'bg-neutral-100 text-neutral-600' : 'bg-neutral-800 text-neutral-300'}`}
875+
>
876+
<Download className="w-4 h-4" />
877+
<span>Image</span>
878+
</button>
879+
<button
880+
onClick={handleDownloadCSV}
881+
className={`col-span-2 flex items-center justify-center gap-2 px-3 py-2 rounded-lg text-xs font-medium transition-colors ${isLightMode ? 'bg-neutral-100 text-neutral-600' : 'bg-neutral-800 text-neutral-300'}`}
882+
>
883+
<FileText className="w-4 h-4" />
884+
<span>Export CSV</span>
885+
</button>
886+
</div>
887+
</div>
888+
860889
{/* Section: View & Zoom */}
861890
<div className="p-3 border-b border-neutral-100 dark:border-neutral-800 space-y-3">
862891
<h4 className="text-xs font-bold uppercase opacity-50 tracking-wider">View Options</h4>

src/components/ProteinViewer.tsx

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ export interface ProteinViewerRef {
6060
recordTurntable: (duration?: number) => Promise<Blob>;
6161
clearMeasurements: () => void;
6262
visualizeContact: (chainA: string, resA: number, chainB: string, resB: number) => void;
63+
getMeasurements: () => MeasurementData[];
64+
restoreMeasurements: (measurements: { atom1: any, atom2: any }[]) => void;
6365
}
6466

6567

@@ -827,6 +829,45 @@ export const ProteinViewer = forwardRef<ProteinViewerRef, ProteinViewerProps>(({
827829
}
828830
});
829831
}
832+
},
833+
getMeasurements: () => measurementsRef.current,
834+
restoreMeasurements: (list: { atom1: any, atom2: any }[]) => {
835+
if (!componentRef.current || !list || list.length === 0) return;
836+
// Clear existing first
837+
measurementsRef.current = [];
838+
839+
// Helper to find atom index
840+
const findAtom = (info: any) => {
841+
let found: any = null;
842+
if (!componentRef.current) return null;
843+
// Try precise match first
844+
const sel = new window.NGL.Selection(`${info.r}:${info.c} and .${info.a}`);
845+
componentRef.current.structure.eachAtom((atom: any) => {
846+
found = atom;
847+
}, sel);
848+
return found;
849+
};
850+
851+
list.forEach(m => {
852+
const a1 = findAtom({ c: m.atom1.chain, r: m.atom1.resNo, a: m.atom1.atomName });
853+
const a2 = findAtom({ c: m.atom2.chain, r: m.atom2.resNo, a: m.atom2.atomName });
854+
855+
if (a1 && a2) {
856+
const dx = a1.x - a2.x;
857+
const dy = a1.y - a2.y;
858+
const dz = a1.z - a2.z;
859+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
860+
861+
const mData: MeasurementData = {
862+
atom1: { chain: a1.chainname, resNo: a1.resno, atomName: a1.atomname, x: a1.x, y: a1.y, z: a1.z, index: a1.index },
863+
atom2: { chain: a2.chainname, resNo: a2.resno, atomName: a2.atomname, x: a2.x, y: a2.y, z: a2.z, index: a2.index },
864+
distance: dist,
865+
shapeId: `measure-${Date.now()}-${Math.random()}`
866+
};
867+
measurementsRef.current.push(mData);
868+
drawMeasurement(mData);
869+
}
870+
});
830871
}
831872
}));
832873

src/utils/urlManager.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export interface AppState {
1010
showLigands: boolean;
1111
showSurface: boolean;
1212
customColors?: CustomColorRule[];
13-
13+
measurements?: { atom1: any, atom2: any, distance: number }[];
1414
}
1515

1616
/**
@@ -50,7 +50,19 @@ export const getShareableURL = (state: AppState): string => {
5050
} catch (e) { console.warn("Failed to serialize custom colors", e); }
5151
}
5252

53-
53+
// 5. Measurements (Encoded JSON)
54+
if (state.measurements && state.measurements.length > 0) {
55+
try {
56+
// Simplify measurement data to essential IDs to save space
57+
const minimalData = state.measurements.map(m => ({
58+
a1: { c: m.atom1.chain, r: m.atom1.resNo, a: m.atom1.atomName },
59+
a2: { c: m.atom2.chain, r: m.atom2.resNo, a: m.atom2.atomName }
60+
}));
61+
const json = JSON.stringify(minimalData);
62+
const b64 = btoa(json);
63+
params.set('meas', b64);
64+
} catch (e) { console.warn("Failed to serialize measurements", e); }
65+
}
5466

5567
const url = new URL(window.location.href);
5668
url.search = params.toString();
@@ -99,7 +111,20 @@ export const parseURLState = (): Partial<AppState> => {
99111
} catch (e) { console.warn("Failed to parse custom colors", e); }
100112
}
101113

102-
114+
// 5. Measurements
115+
const meas = params.get('meas');
116+
if (meas) {
117+
try {
118+
const json = atob(meas);
119+
const minimalData = JSON.parse(json);
120+
// Rehydrate to expected format (partial)
121+
state.measurements = minimalData.map((m: any) => ({
122+
atom1: { chain: m.a1.c, resNo: m.a1.r, atomName: m.a1.a },
123+
atom2: { chain: m.a2.c, resNo: m.a2.r, atomName: m.a2.a },
124+
distance: 0 // Will be recalculated by viewer
125+
}));
126+
} catch (e) { console.warn("Failed to parse measurements", e); }
127+
}
103128

104129
return state;
105130
};

0 commit comments

Comments
 (0)