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
2 changes: 1 addition & 1 deletion web-conexs-client/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ dist-ssr
*.sln
*.sw?

.pnpm-store/
.pnpm-store/*
5 changes: 3 additions & 2 deletions web-conexs-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"tsc": "tsc"
},
"dependencies": {
"3dmol": "^2.5.2",
"@diamondlightsource/sci-react-ui": "^0.2.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
Expand All @@ -25,6 +24,7 @@
"@react-three/fiber": "8",
"@tanstack/react-query": "^5.75.1",
"axios": "^1.7.9",
"molstar": "^5.0.0",
"ndarray": "^1.0.19",
"react": "^18.3.1",
"react-dom": "^18.3.1",
Expand All @@ -45,9 +45,10 @@
"globals": "^15.14.0",
"jsdom": "^26.1.0",
"msw": "^2.7.3",
"sass-embedded": "^1.93.2",
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",
"vite": "^6.0.5",
"vite": "^7.1.9",
"vitest": "^3.1.3"
},
"msw": {
Expand Down
3,539 changes: 3,276 additions & 263 deletions web-conexs-client/pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions web-conexs-client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,9 @@ import SimulationView from "./components/SimulationView";

const queryClient = new QueryClient();

const theme = responsiveFontSizes(DiamondTheme);

function App() {
return (
<ThemeProvider theme={theme}>
<ThemeProvider theme={DiamondTheme}>
<CssBaseline />
<QueryClientProvider client={queryClient}>
<UserProvider>
Expand Down
115 changes: 115 additions & 0 deletions web-conexs-client/src/components/MolstarCrystalViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { useEffect, useRef } from "react";
import { Box } from "@mui/material";
import { DefaultPluginSpec, PluginSpec } from "molstar/lib/mol-plugin/spec";
import { PluginContext } from "molstar/lib/mol-plugin/context";
import { PluginConfig } from "molstar/lib/mol-plugin/config";
import { StructureRepresentation3D } from "molstar/lib/mol-plugin-state/transforms/representation";

import {
QueryContext,
StructureSelection,
} from "molstar/lib/mol-model/structure";
import { atoms } from "molstar/lib/mol-model/structure/query/queries/generators";

const Default3DSpec: PluginSpec = {
...DefaultPluginSpec(),
config: [[PluginConfig.VolumeStreaming.Enabled, false]],
};

let molstar: PluginContext | null = null;

export function MolStarCrystalWrapper(props: {
cif: string | null;
labelledAtomIndex: number | undefined;
}) {
const viewerDiv = useRef<HTMLDivElement>(null);
console.log("Mount");
useEffect(() => {
console.log("GO");
const init = async (rawData: string) => {
molstar = new PluginContext(Default3DSpec);

await molstar.init();
await molstar.mountAsync(viewerDiv.current!);

if (rawData == null) {
return;
}

const data = await molstar.builders.data.rawData(
{ data: props.cif! },
{ state: { isGhost: true } }
);

const trajectory = await molstar.builders.structure.parseTrajectory(
data,
"cifCore"
);

const model = await molstar.builders.structure.createModel(trajectory);

const s = await molstar.builders.structure.createStructure(model);

console.log(model);

console.log(s);
molstar
.build()
.to(s)
.apply(StructureRepresentation3D, {
type: {
name: "ball-and-stick",
params: {
size: "physical",
},
},
colorTheme: {
name: "element-symbol",
params: {
carbonColor: { name: "element-symbol", params: {} },
},
},
sizeTheme: { name: "physical", params: {} },
})
.commit();

await molstar.builders.structure.tryCreateUnitcell(model);

const hierarchy = await molstar.builders.structure.hierarchy.applyPreset(
trajectory,
"default"
);

const struct = hierarchy!.structure.data!;

if (props.labelledAtomIndex != null) {
const query = atoms({
atomTest: (ctx) => {
return ctx.element.element == props.labelledAtomIndex;
},
});
const selection = query(new QueryContext(struct));
const loci = StructureSelection.toLociWithCurrentUnits(selection);
molstar.managers.interactivity.lociSelects.select({ loci });
}
};

if (props.cif) {
init(props.cif);
}

return () => {
molstar?.dispose();
molstar = null;
};
}, [viewerDiv, props.cif, props.labelledAtomIndex]);

return (
<Box position="relative" display="flex" flexGrow={5} h="100%" w="100%">
<Box
style={{ width: 640, height: 300, position: "relative" }}
ref={viewerDiv}
></Box>
</Box>
);
}
102 changes: 102 additions & 0 deletions web-conexs-client/src/components/MolstarMoleculeViewer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { useEffect, useRef } from "react";
import { Box } from "@mui/material";
import { DefaultPluginSpec, PluginSpec } from "molstar/lib/mol-plugin/spec";
import { PluginContext } from "molstar/lib/mol-plugin/context";
import { PluginConfig } from "molstar/lib/mol-plugin/config";
import { StructureRepresentation3D } from "molstar/lib/mol-plugin-state/transforms/representation";

const Default3DSpec: PluginSpec = {
...DefaultPluginSpec(),
config: [[PluginConfig.VolumeStreaming.Enabled, false]],
};

let molstar: PluginContext | null = null;

export function MolStarMoleculeWrapper(props: { xyz: string | null }) {
const viewerDiv = useRef<HTMLDivElement>(null);
useEffect(() => {
const init = async (rawData: string) => {
molstar = new PluginContext(Default3DSpec);

await molstar.init();
await molstar.mountAsync(viewerDiv.current!);

if (rawData == null) {
return;
}

const data = await molstar.builders.data.rawData(
{ data: rawData! },
{ state: { isGhost: true } }
);

const trajectory = await molstar.builders.structure.parseTrajectory(
data,
"xyz"
);

const model = await molstar.builders.structure.createModel(trajectory);

const s = await molstar.builders.structure.createStructure(model);

molstar
.build()
.to(s)
.apply(StructureRepresentation3D, {
type: {
name: "ball-and-stick",
params: {
size: "physical",
},
},
colorTheme: {
name: "element-symbol",
params: {
carbonColor: { name: "element-symbol", params: {} },
},
},
sizeTheme: { name: "physical", params: {} },
})
.commit();

await molstar.builders.structure.hierarchy.applyPreset(
trajectory,
"default"
);
};

if (props.xyz) {
init(props.xyz);
}

return () => {
molstar?.dispose();
molstar = null;
};
}, [viewerDiv, props.xyz]);

return (
<div
ref={viewerDiv}
style={{
height: "100%",
width: "100%",
position: "relative",
minHeight: "100px",
minWidth: "100px",
}}
/>
// <Box position="relative" display="flex" flexGrow={5} h="100%" w="100%">
// <Box
// style={{
// width: "300px",
// height: "300px",
// position: "relative",
// minHeight: "100px",
// minWidth: "100px",
// }}
// ref={viewerDiv}
// ></Box>
// </Box>
);
}
Loading