Commit ·
edf650c
1
Parent(s): cb1600f
Put the 3D compare button next to each partner and pin that column
Browse files- backend/space_app.py +4 -2
- frontend/src/components/Transitions.tsx +19 -18
- frontend/src/styles/ui.css +49 -0
backend/space_app.py
CHANGED
|
@@ -120,11 +120,13 @@ async def frontend(path: str):
|
|
| 120 |
if path and requested.is_file():
|
| 121 |
return FileResponse(requested)
|
| 122 |
|
|
|
|
|
|
|
| 123 |
prerendered = requested / "index.html"
|
| 124 |
if path and prerendered.is_file():
|
| 125 |
-
return FileResponse(prerendered)
|
| 126 |
|
| 127 |
index = FRONTEND_DIR / "index.html"
|
| 128 |
if not index.is_file():
|
| 129 |
raise HTTPException(status_code=503, detail="Frontend build is not available")
|
| 130 |
-
return FileResponse(index)
|
|
|
|
| 120 |
if path and requested.is_file():
|
| 121 |
return FileResponse(requested)
|
| 122 |
|
| 123 |
+
# HTML must be revalidated so a new deploy (new hashed bundle) shows on reload
|
| 124 |
+
no_cache = {"Cache-Control": "no-cache"}
|
| 125 |
prerendered = requested / "index.html"
|
| 126 |
if path and prerendered.is_file():
|
| 127 |
+
return FileResponse(prerendered, headers=no_cache)
|
| 128 |
|
| 129 |
index = FRONTEND_DIR / "index.html"
|
| 130 |
if not index.is_file():
|
| 131 |
raise HTTPException(status_code=503, detail="Frontend build is not available")
|
| 132 |
+
return FileResponse(index, headers=no_cache)
|
frontend/src/components/Transitions.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import { useMemo, useState } from 'react';
|
|
|
|
| 2 |
import { ENERGY_TERMS, type EnergyTerm, type OrderChange, type Transition } from '../lib/api';
|
| 3 |
import { FIDELITY_INFO, FIDELITY_ORDER, fidelityColor, fidelityKey } from '../lib/fidelity';
|
| 4 |
import { chainLabel, fmtNum, fmtSigned, methodShort } from '../lib/format';
|
|
@@ -263,7 +264,7 @@ export function TransitionsTable({
|
|
| 263 |
<EmptyState title="No partner observations in this view" />
|
| 264 |
) : (
|
| 265 |
<div className="table-wrap">
|
| 266 |
-
<table className="table table--dense table--rows">
|
| 267 |
<thead>
|
| 268 |
<tr>
|
| 269 |
<th>Partner</th>
|
|
@@ -279,7 +280,6 @@ export function TransitionsTable({
|
|
| 279 |
{th('resolution', 'Res. Å')}
|
| 280 |
<th>Ligand</th>
|
| 281 |
{th('date', 'Released')}
|
| 282 |
-
{onView && <th>Structure</th>}
|
| 283 |
</tr>
|
| 284 |
</thead>
|
| 285 |
<tbody>
|
|
@@ -295,7 +295,22 @@ export function TransitionsTable({
|
|
| 295 |
}}
|
| 296 |
>
|
| 297 |
<td>
|
| 298 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
</td>
|
| 300 |
<td className="num tabular">{t.state_id ?? '—'}</td>
|
| 301 |
<td>
|
|
@@ -322,21 +337,7 @@ export function TransitionsTable({
|
|
| 322 |
<LigandBadge status={t.binding_status} />
|
| 323 |
</td>
|
| 324 |
<td className="num tabular">{t.release_date ?? '—'}</td>
|
| 325 |
-
|
| 326 |
-
<td>
|
| 327 |
-
<button
|
| 328 |
-
type="button"
|
| 329 |
-
className="btn btn--ghost btn--sm"
|
| 330 |
-
onClick={(e) => {
|
| 331 |
-
e.stopPropagation();
|
| 332 |
-
onView(t);
|
| 333 |
-
}}
|
| 334 |
-
title="Superpose with this chain, coloured by per-residue deviation"
|
| 335 |
-
>
|
| 336 |
-
View
|
| 337 |
-
</button>
|
| 338 |
-
</td>
|
| 339 |
-
)}
|
| 340 |
</tr>
|
| 341 |
);
|
| 342 |
})}
|
|
|
|
| 1 |
import { useMemo, useState } from 'react';
|
| 2 |
+
import { Box } from 'lucide-react';
|
| 3 |
import { ENERGY_TERMS, type EnergyTerm, type OrderChange, type Transition } from '../lib/api';
|
| 4 |
import { FIDELITY_INFO, FIDELITY_ORDER, fidelityColor, fidelityKey } from '../lib/fidelity';
|
| 5 |
import { chainLabel, fmtNum, fmtSigned, methodShort } from '../lib/format';
|
|
|
|
| 264 |
<EmptyState title="No partner observations in this view" />
|
| 265 |
) : (
|
| 266 |
<div className="table-wrap">
|
| 267 |
+
<table className="table table--dense table--rows table--sticky-first">
|
| 268 |
<thead>
|
| 269 |
<tr>
|
| 270 |
<th>Partner</th>
|
|
|
|
| 280 |
{th('resolution', 'Res. Å')}
|
| 281 |
<th>Ligand</th>
|
| 282 |
{th('date', 'Released')}
|
|
|
|
| 283 |
</tr>
|
| 284 |
</thead>
|
| 285 |
<tbody>
|
|
|
|
| 295 |
}}
|
| 296 |
>
|
| 297 |
<td>
|
| 298 |
+
<span className="partner-cell">
|
| 299 |
+
<ChainLink pdb={t.pdb_id} chain={t.auth_asym_id} strong />
|
| 300 |
+
{onView && (
|
| 301 |
+
<button
|
| 302 |
+
type="button"
|
| 303 |
+
className="btn-3d"
|
| 304 |
+
onClick={(e) => {
|
| 305 |
+
e.stopPropagation();
|
| 306 |
+
onView(t);
|
| 307 |
+
}}
|
| 308 |
+
title="View both structures superposed, coloured by per-residue deviation"
|
| 309 |
+
>
|
| 310 |
+
<Box aria-hidden /> 3D
|
| 311 |
+
</button>
|
| 312 |
+
)}
|
| 313 |
+
</span>
|
| 314 |
</td>
|
| 315 |
<td className="num tabular">{t.state_id ?? '—'}</td>
|
| 316 |
<td>
|
|
|
|
| 337 |
<LigandBadge status={t.binding_status} />
|
| 338 |
</td>
|
| 339 |
<td className="num tabular">{t.release_date ?? '—'}</td>
|
| 340 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 341 |
</tr>
|
| 342 |
);
|
| 343 |
})}
|
frontend/src/styles/ui.css
CHANGED
|
@@ -1369,3 +1369,52 @@ main {
|
|
| 1369 |
.state__retry {
|
| 1370 |
margin-top: 10px;
|
| 1371 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1369 |
.state__retry {
|
| 1370 |
margin-top: 10px;
|
| 1371 |
}
|
| 1372 |
+
|
| 1373 |
+
/* first column stays in view while a wide table scrolls sideways */
|
| 1374 |
+
.table--sticky-first th:first-child,
|
| 1375 |
+
.table--sticky-first td:first-child {
|
| 1376 |
+
position: sticky;
|
| 1377 |
+
left: 0;
|
| 1378 |
+
z-index: 1;
|
| 1379 |
+
box-shadow: 1px 0 0 var(--line);
|
| 1380 |
+
}
|
| 1381 |
+
|
| 1382 |
+
.table--sticky-first td:first-child {
|
| 1383 |
+
background: var(--surface);
|
| 1384 |
+
}
|
| 1385 |
+
|
| 1386 |
+
.table--sticky-first th:first-child {
|
| 1387 |
+
z-index: 2;
|
| 1388 |
+
}
|
| 1389 |
+
|
| 1390 |
+
.partner-cell {
|
| 1391 |
+
display: inline-flex;
|
| 1392 |
+
align-items: center;
|
| 1393 |
+
gap: 8px;
|
| 1394 |
+
}
|
| 1395 |
+
|
| 1396 |
+
.btn-3d {
|
| 1397 |
+
display: inline-flex;
|
| 1398 |
+
align-items: center;
|
| 1399 |
+
gap: 3px;
|
| 1400 |
+
height: 22px;
|
| 1401 |
+
padding: 0 7px;
|
| 1402 |
+
border-radius: 6px;
|
| 1403 |
+
border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--line));
|
| 1404 |
+
background: var(--accent-soft);
|
| 1405 |
+
color: var(--accent-ink);
|
| 1406 |
+
font-size: 11.5px;
|
| 1407 |
+
font-weight: 650;
|
| 1408 |
+
white-space: nowrap;
|
| 1409 |
+
}
|
| 1410 |
+
|
| 1411 |
+
.btn-3d svg {
|
| 1412 |
+
width: 12px;
|
| 1413 |
+
height: 12px;
|
| 1414 |
+
}
|
| 1415 |
+
|
| 1416 |
+
.btn-3d:hover {
|
| 1417 |
+
background: var(--accent);
|
| 1418 |
+
border-color: var(--accent);
|
| 1419 |
+
color: #fff;
|
| 1420 |
+
}
|