Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions client/src/components/InspectDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ interface Props {
glRef: RefObject<unknown>;
commandCentre: RefObject<moorhen.CommandCentre | null>;
cootInitialized: boolean;
// Deep-link target: open in Site mode focused on this site_num (from a
// dashboard site-bar click). null = normal entry.
initialSite?: number | null;
}

// PanDDA event maps are contoured in ABSOLUTE map units, not σ. They're
Expand Down Expand Up @@ -216,6 +219,7 @@ export function InspectDrawer({
glRef,
commandCentre,
cootInitialized,
initialSite,
}: Props) {
const dispatch = useDispatch();
// Redux is the LIVE source of truth for map contour/visibility/active state —
Expand All @@ -241,6 +245,10 @@ export function InspectDrawer({
);
const [datasets, setDatasets] = useState<Dataset[]>([]);
const [axis, setAxis] = useState<GroupAxis>("dataset");
// Ref mirror so loadEvent picks the model rendition for the CURRENT tab
// (ribbons in Site mode) without being re-created on every axis change.
const axisRef = useRef(axis);
axisRef.current = axis;
const [sort, setSort] = useState<SortKey>("dtag");
const [search, setSearch] = useState("");
const [filter, setFilter] = useState<DatasetFilter>("active");
Expand Down Expand Up @@ -434,13 +442,25 @@ export function InspectDrawer({
// Non-fatal: fall back to bare-atom rendering.
}
}
await mol.addRepresentation("CBs", "/*/*");
// Backbone rendition follows the grouping tab: in SITE mode draw
// RIBBONS (CRs) for spatial context while scanning sites, plus the
// bound ligand as sticks ("ligands") so the site itself stays
// visible; otherwise bonds (CBs) for detailed building. Read the ref
// so it tracks the tab at load time. NB switching tabs re-skins on
// the NEXT model load, not the currently-shown one.
const modelStyles =
axisRef.current === "site" ? ["CRs", "ligands"] : ["CBs"];
for (const style of modelStyles) {
await mol.addRepresentation(style, "/*/*");
}
// addDict does NOT redraw, so the first draw above perceives bonds
// without the dict (all single bonds). Re-perceive WITH the dict so
// aromatic/double orders render — the proven 0.23 dirty+redraw.
if (dictLoaded) {
mol.setAtomsDirty(true);
await mol.fetchIfDirtyAndDraw("CBs");
for (const style of modelStyles) {
await mol.fetchIfDirtyAndDraw(style);
}
}
// Hide H on the freshly-loaded model if the toggle is on (default).
// Read the REF so a fresh model adopts the current preference. No-op
Expand Down Expand Up @@ -799,6 +819,26 @@ export function InspectDrawer({
);
loadEventRef.current = loadEvent;

// Deep-link consume: arriving via ?site=N (a dashboard site-bar click) opens
// the viewer in SITE mode focused on that site. Wait until datasets + Coot are
// ready, then switch the tab and load the site's first event. Consume ONCE (a
// ref guard) so later renders don't re-fire it. A fresh navigation is a fresh
// InspectPage mount, so the guard resets and re-clicking a bar works.
const consumedSiteRef = useRef(false);
useEffect(() => {
if (consumedSiteRef.current) return;
if (initialSite == null || !cootInitialized || datasets.length === 0) {
return;
}
const ev = datasets
.flatMap((d) => d.events)
.find((e) => e.site_num === initialSite);
consumedSiteRef.current = true;
if (!ev) return;
setAxis("site");
loadEvent(ev);
}, [initialSite, cootInitialized, datasets, loadEvent]);

// Contour ONE of the loaded maps (by molNo). The slider value is in that
// map's native unit: event maps ABSOLUTE (pass straight to Coot), model maps
// σ (multiply by RMSD — Coot always contours in absolute). Dispatch — the
Expand Down
17 changes: 17 additions & 0 deletions client/src/components/SummaryCharts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,12 @@ function Histogram({
export function SummaryCharts({
distributions,
sites,
onSiteClick,
}: {
distributions: Distributions;
sites: SiteSummary[];
// Clicking a site bar jumps to that site in the Moorhen viewer (deep-link).
onSiteClick?: (siteNum: number) => void;
}) {
// Events-per-site, STACKED by decision (replaces PanDDA1's
// analyse_events_site_N pies). One bar per site whose total height is the
Expand Down Expand Up @@ -180,6 +183,20 @@ export function SummaryCharts({
ticks: { precision: 0 },
},
},
// Click a bar → open that site in Moorhen (deep-link). The
// clicked element's index maps to sites[index]; pointer cursor
// on hover signals the affordance.
onClick: (_evt, elements) => {
const i = elements?.[0]?.index;
if (i != null && sites[i]) onSiteClick?.(sites[i].site_num);
},
onHover: (evt, elements) => {
const target = evt?.native?.target as HTMLElement | null;
if (target) {
target.style.cursor =
onSiteClick && elements?.length ? "pointer" : "default";
}
},
}}
/>
</Box>
Expand Down
10 changes: 8 additions & 2 deletions client/src/pages/InspectPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useRef, useState } from "react";
import type { RefObject } from "react";
import { useParams } from "react-router-dom";
import { useParams, useSearchParams } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import {
MoorhenContainer,
Expand All @@ -26,6 +26,11 @@ export function InspectPage() {
const id = Number(projectId);
const dispatch = useDispatch();
const [project, setProject] = useState<Project | null>(null);
// Deep-link: `?site=N` (from clicking a site bar on the dashboard) tells the
// drawer to open in Site mode focused on that site.
const [searchParams] = useSearchParams();
const siteParam = searchParams.get("site");
const initialSite = siteParam != null ? Number(siteParam) : null;

const cootInitialized = useSelector(
(s: any) => s.generalStates.cootInitialized
Expand Down Expand Up @@ -56,11 +61,12 @@ export function InspectPage() {
glRef={glRef}
commandCentre={commandCentre}
cootInitialized={!!cootInitialized}
initialSite={initialSite}
/>
),
},
}),
[project?.name, id, cootInitialized]
[project?.name, id, cootInitialized, initialSite]
);

useEffect(() => {
Expand Down
11 changes: 9 additions & 2 deletions client/src/pages/ProjectDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import { Link, useNavigate, useParams } from "react-router-dom";
import {
Box,
Button,
Expand Down Expand Up @@ -33,6 +33,7 @@ function StatCard({ label, value }: { label: string; value: string | number }) {
export function ProjectDashboard() {
const { projectId } = useParams();
const id = Number(projectId);
const navigate = useNavigate();
const [project, setProject] = useState<Project | null>(null);
const [reports, setReports] = useState<Artifact[]>([]);
const [runs, setRuns] = useState<Run[]>([]);
Expand Down Expand Up @@ -147,7 +148,13 @@ export function ProjectDashboard() {
label={`${s.n_refined} crystals refined`}
/>
</Stack>
<SummaryCharts distributions={s.distributions} sites={s.sites} />
<SummaryCharts
distributions={s.distributions}
sites={s.sites}
onSiteClick={(siteNum) =>
navigate(`/projects/${id}/inspect?site=${siteNum}`)
}
/>
</Paper>

{/* PanDDA runs for this project (cloud-triggered). Mirrors the Reports
Expand Down
Loading