Skip to content
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4753a11
make indicator clickable
meganrm Aug 28, 2025
c5f2deb
switch module and save which have been completed
meganrm Aug 28, 2025
fe0c926
add pop confirm
meganrm Aug 28, 2025
e54b3e1
Update src/components/PageIndicator.tsx
meganrm Sep 2, 2025
ad535ef
type index as number
meganrm Sep 2, 2025
2d25fe0
Merge branch 'feature/navigation' of https://github.com/simularium/bi…
meganrm Sep 2, 2025
9a87d17
add key
meganrm Sep 17, 2025
57a2de7
make sure index is a number
meganrm Nov 19, 2025
59cdf1f
use a set for completed modules
meganrm Nov 19, 2025
21dab42
show cursor
meganrm Nov 19, 2025
13a1254
Merge branch 'main' into feature/navigation
meganrm Nov 19, 2025
16e1b16
fix merge conflict
meganrm Nov 19, 2025
e425c5e
fix merge conflict
meganrm Nov 19, 2025
a25115d
Update src/components/PageIndicator.tsx
meganrm Nov 19, 2025
b9f55f1
clean up css
meganrm Nov 20, 2025
b22c34d
Merge branch 'feature/navigation' of https://github.com/simularium/bi…
meganrm Nov 20, 2025
ee64742
display adjustments
meganrm Nov 21, 2025
a2e517a
add competitive content
meganrm Nov 21, 2025
57dada6
remove unused imports
meganrm Nov 21, 2025
d6b35c8
change data structure
meganrm Nov 21, 2025
e4c3d4a
use type id
meganrm Nov 21, 2025
a85f67b
content adjustments
meganrm Nov 21, 2025
4c17e56
Merge branch 'main' of https://github.com/simularium/binding-sim-edu …
meganrm Nov 21, 2025
8064b34
change vis control
meganrm Nov 21, 2025
b1a4989
remove log
meganrm Nov 22, 2025
060b30e
lift state up
meganrm Nov 22, 2025
b270d35
Update src/content/Competitive.tsx
meganrm Nov 25, 2025
c39141d
Update src/App.tsx
meganrm Dec 5, 2025
52a6650
changes based on comments
meganrm Dec 5, 2025
800f22f
changes based on comments
meganrm Dec 5, 2025
4806e87
write logic consistently
meganrm Dec 5, 2025
1fd19ae
add comment
meganrm Dec 5, 2025
e02a457
fix error
meganrm Dec 5, 2025
7fe65d5
fix ic50
meganrm Dec 5, 2025
59193e7
Update src/simulation/BindingSimulator2D.ts
meganrm Dec 5, 2025
07698c9
Merge branch 'feature/3rd-module' of https://github.com/simularium/bi…
meganrm Dec 5, 2025
abe6556
remove duplicate call
meganrm Dec 5, 2025
3e0de31
add comment and error check
meganrm Dec 5, 2025
142f897
Update src/simulation/BindingSimulator2D.ts
meganrm Dec 5, 2025
ec81d10
more explicit checks
meganrm Dec 5, 2025
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
89 changes: 68 additions & 21 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
ScatterTrace,
Section,
TrajectoryStatus,
ViewType,
} from "./types";
import LeftPanel from "./components/main-layout/LeftPanel";
import RightPanel from "./components/main-layout/RightPanel";
Expand Down Expand Up @@ -56,6 +57,7 @@ import useModule from "./hooks/useModule";
import LandingPage from "./components/LandingPage";

function App() {
const [currentView, setCurrentView] = useState<ViewType>(ViewType.Lab);
const [page, setPage] = useState(FIRST_PAGE[Module.A_B_AB]);
const [time, setTime] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
Expand Down Expand Up @@ -85,9 +87,13 @@ function App() {
const [inputConcentration, setInputConcentration] =
useState<InputConcentration>({
[AgentName.A]:
LiveSimulationData.INITIAL_CONCENTRATIONS[AgentName.A],
LiveSimulationData.INITIAL_CONCENTRATIONS[Module.A_B_AB][
AgentName.A
],
[AgentName.B]:
LiveSimulationData.INITIAL_CONCENTRATIONS[AgentName.B],
LiveSimulationData.INITIAL_CONCENTRATIONS[Module.A_B_AB][
AgentName.B
],
});
const [timeFactor, setTimeFactor] = useState(
LiveSimulationData.INITIAL_TIME_FACTOR
Expand All @@ -108,9 +114,13 @@ function App() {
const [liveConcentration, setLiveConcentration] =
useState<CurrentConcentration>({
[AgentName.A]:
LiveSimulationData.INITIAL_CONCENTRATIONS[AgentName.A],
LiveSimulationData.INITIAL_CONCENTRATIONS[Module.A_B_AB][
AgentName.A
],
[AgentName.B]:
LiveSimulationData.INITIAL_CONCENTRATIONS[AgentName.B],
LiveSimulationData.INITIAL_CONCENTRATIONS[Module.A_B_AB][
AgentName.B
],
[productName]: 0,
});
const [recordedInputConcentration, setRecordedInputConcentration] =
Expand Down Expand Up @@ -182,21 +192,26 @@ function App() {
const clientSimulator = useMemo(() => {
const activeAgents = simulationData.getActiveAgents(currentModule);
setInputConcentration(
simulationData.getInitialConcentrations(activeAgents)
simulationData.getInitialConcentrations(
activeAgents,
currentModule,
sectionType === Section.Experiment
)
);
resetCurrentRunAnalysisState();
const trajectory =
simulationData.createAgentsFromConcentrations(activeAgents);
const trajectory = simulationData.createAgentsFromConcentrations(
activeAgents,
currentModule,
sectionType === Section.Experiment
);
if (!trajectory) {
return null;
}
const longestAxis = Math.max(viewportSize.width, viewportSize.height);
const productColor = simulationData.getAgentColor(productName);
const startMixed = sectionType !== Section.Introduction;
return new BindingSimulator(
trajectory,
longestAxis / 3,
productColor,
startMixed ? InitialCondition.RANDOM : InitialCondition.SORTED
);
}, [
Expand All @@ -205,7 +220,6 @@ function App() {
resetCurrentRunAnalysisState,
viewportSize.width,
viewportSize.height,
productName,
sectionType,
]);

Expand Down Expand Up @@ -304,6 +318,21 @@ function App() {
[currentProductConcentrationArray, productOverTimeTraces]
);

const setExperiment = () => {
setIsPlaying(false);

const activeAgents = simulationData.getActiveAgents(currentModule);
const concentrations = simulationData.getInitialConcentrations(
activeAgents,
currentModule,
true
);
clientSimulator?.mixAgents();
setTimeFactor(LiveSimulationData.INITIAL_TIME_FACTOR);
setInputConcentration(concentrations);
setLiveConcentration(concentrations);
};

const handleMixAgents = useCallback(() => {
if (clientSimulator) {
setIsPlaying(false);
Expand Down Expand Up @@ -350,23 +379,26 @@ function App() {
]
);
const totalReset = useCallback(() => {
setCurrentView(ViewType.Lab);
const activeAgents = [AgentName.A, AgentName.B];
setCurrentModule(Module.A_B_AB);
const concentrations = simulationData.getInitialConcentrations(
activeAgents,
Module.A_B_AB
);
setLiveConcentration({
[AgentName.A]:
LiveSimulationData.INITIAL_CONCENTRATIONS[AgentName.A],
[AgentName.B]:
LiveSimulationData.INITIAL_CONCENTRATIONS[AgentName.B],
[AgentName.A]: concentrations[AgentName.A],
[AgentName.B]: concentrations[AgentName.B],
[productName]: 0,
});
setCurrentModule(Module.A_B_AB);
setInputConcentration({
[AgentName.A]:
LiveSimulationData.INITIAL_CONCENTRATIONS[AgentName.A],
[AgentName.B]:
LiveSimulationData.INITIAL_CONCENTRATIONS[AgentName.B],
[AgentName.A]: concentrations[AgentName.A],
[AgentName.B]: concentrations[AgentName.B],
});
handleNewInputConcentration(
adjustableAgentName,
LiveSimulationData.INITIAL_CONCENTRATIONS[AgentName.B]
concentrations[AgentName.B] ?? 4
);
setIsPlaying(false);
clearAllAnalysisState();
Expand All @@ -376,6 +408,7 @@ function App() {
handleNewInputConcentration,
productName,
adjustableAgentName,
simulationData,
]);
// Special events in page navigation
// usePageNumber takes a page number, a conditional and a callback
Expand Down Expand Up @@ -491,11 +524,17 @@ function App() {
clearAllAnalysisState();
setCurrentModule(module);
setIsPlaying(false);
// the first module is the only one that starts with the lab view
if (module === Module.A_B_AB) {
setCurrentView(ViewType.Lab);
} else {
setCurrentView(ViewType.Simulation);
}
};

const handleStartExperiment = () => {
simulariumController.pause();
totalReset();
clearAllAnalysisState();
setExperiment();
setPage(page + 1);
};

Expand Down Expand Up @@ -588,6 +627,12 @@ function App() {
}, 3000);
};

const handleSwitchView = () => {
setCurrentView((prevView) =>
prevView === ViewType.Lab ? ViewType.Simulation : ViewType.Lab
);
};

const handleRecordEquilibrium = () => {
if (!clientSimulator) {
return false;
Expand Down Expand Up @@ -687,11 +732,13 @@ function App() {
setModule,
setPage,
setViewportSize,
setViewportType: handleSwitchView,
simulariumController,
timeFactor,
timeUnit: simulationData.timeUnit,
trajectoryName,
viewportSize,
viewportType: currentView,
addCompletedModule,
completedModules,
}}
Expand Down
43 changes: 8 additions & 35 deletions src/components/ViewSwitch.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useContext, useState } from "react";
import React, { useContext } from "react";

import Viewer from "./Viewer";
import { SimulariumContext } from "../simulation/context";
Expand All @@ -9,47 +9,19 @@ import LabIcon from "./icons/Lab";
import Molecules from "./icons/Molecules";
import LabView from "./LabView";
import VisibilityControl from "./shared/VisibilityControl";
import { Module, Section } from "../types";
import { Module, ViewType } from "../types";
import { FIRST_PAGE } from "../content";
import useModule from "../hooks/useModule";
import { VIEW_SWITCH_ID } from "../constants";

enum View {
Lab = "lab",
Simulation = "simulation",
}

const ViewSwitch: React.FC = () => {
const [currentView, setCurrentView] = useState<View>(View.Lab);
const [previousModule, setPreviousModule] = useState<Module>(Module.A_B_AB);
const { viewportType, setViewportType } = useContext(SimulariumContext);

const switchView = () => {
setCurrentView((prevView) =>
prevView === View.Lab ? View.Simulation : View.Lab
);
};
const { page, isPlaying, setIsPlaying, handleTimeChange, module } =
useContext(SimulariumContext);

const isFirstPageOfFirstModule =
page === FIRST_PAGE[module] + 1 && module === Module.A_B_AB;

if (isFirstPageOfFirstModule && currentView === View.Simulation) {
setCurrentView(View.Lab);
}

const { contentData } = useModule(module);

// Show the sim view at the beginning of the module
if (module !== previousModule) {
setPreviousModule(module);
if (contentData[page].section === Section.Experiment) {
if (currentView === View.Lab) {
setCurrentView(View.Simulation);
}
}
}

let buttonStyle: React.CSSProperties = {
top: 16,
right: 16,
Expand All @@ -73,22 +45,23 @@ const ViewSwitch: React.FC = () => {
<VisibilityControl notInBonusMaterial>
<ProgressionControl elementId={VIEW_SWITCH_ID}>
<OverlayButton
onClick={switchView}
onClick={setViewportType}
style={buttonStyle}
icon={
currentView === View.Lab ? (
viewportType === ViewType.Lab ? (
<Molecules />
) : (
<LabIcon />
)
}
>
{currentView === View.Lab ? "Molecular" : "Lab"} view
{viewportType === ViewType.Lab ? "Molecular" : "Lab"}{" "}
view
</OverlayButton>
</ProgressionControl>
</VisibilityControl>
<PlayButton />
{currentView === View.Lab ? <LabView /> : null}
{viewportType === ViewType.Lab ? <LabView /> : null}
<Viewer
isPlaying={isPlaying}
setIsPlaying={setIsPlaying}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,11 @@ const ConcentrationSlider: React.FC<SliderProps> = ({
marks[index] = {
label: (
<Mark
index={index}
index={
index < 1 && index > 0
? (index.toFixed(1) as unknown as number)
: index
}
disabledNumbers={disabledNumbers}
onMouseUp={() => onChangeComplete?.(name, index)}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const LiveConcentrationDisplay: React.FC<LiveConcentrationDisplayProps> = ({
const { maxConcentration, getAgentColor } = useContext(SimulariumContext);
// the steps have a 2px gap, so we are adjusting the
// size of the step based on the total number we want
const steps = Math.min(maxConcentration, 10);
const steps = Math.max(maxConcentration, 10);
const size = width / steps - 2;
return (
<div className={styles.container}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/main-layout/LeftPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
const eventsOverTimeExcludedPages = {
[Module.A_B_AB]: [0, 1, 2],
[Module.A_C_AC]: [],
[Module.A_B_D_AB]: [],
};
return (
<>
Expand All @@ -53,6 +52,7 @@ const LeftPanel: React.FC<LeftPanelProps> = ({
</VisibilityControl>
<VisibilityControl
excludedPages={eventsOverTimeExcludedPages}
includedPages={{ [Module.A_B_D_AB]: [] }} // don't show at all in last module
notInBonusMaterial
>
<EventsOverTimePlot
Expand Down
Loading