Skip to content
Open
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
52 changes: 36 additions & 16 deletions src/components/LabView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import Cuvette from "./icons/Cuvette";
import styles from "./labview.module.css";
import classNames from "classnames";
import ScaleBar from "./ScaleBar";
import VisibilityControl from "./shared/VisibilityControl";
import { Module } from "../types";

const LabView: React.FC = () => {
Expand All @@ -18,29 +17,50 @@ const LabView: React.FC = () => {
getAgentColor,
productName,
} = useSimulariumSimulation();
const { page, module } = useSimulariumUi();
const { module, page, setViewportType } = useSimulariumUi();
const color = getAgentColor(productName);
const colorGradient = useMemo(() => {
const rainbow = new Rainbow();
rainbow.setSpectrum("#FFFFFF", color);
return rainbow;
}, [color]);
const position = (currentProductionConcentration / maxConcentration) * 100;
return (
<div
className={classNames([
styles.container,
{ [styles.top]: page === 1 && module === Module.A_B_AB },
])}
>
<VisibilityControl excludedPages={{ [Module.A_B_AB]: [1] }}>
<ScaleBar productColor={color} />
</VisibilityControl>
<div className={styles.cuvette}>
<Cuvette color={colorGradient.colorAt(position)} />
const isIntroPage = page === 1 && module === Module.A_B_AB;

if (isIntroPage) {
// shows the full screen illustrated cuvette on the first page of the first module
return (
<div className={classNames(styles.container, styles.top)}>
<div className={styles.cuvette}>
<Cuvette color={colorGradient.colorAt(position)} />
</div>
</div>
);
} else {
return (
<div className={styles.inset}>
<div className={styles.insetLabel}>
<span>In the wet lab</span>
<button
className={styles.insetClose}
onClick={setViewportType}
aria-label="Close lab view"
>
×
</button>
</div>
<div className={styles.insetBody}>
<ScaleBar
productColor={color}
className={styles.scaleBarInset}
/>
<div className={styles.insetCuvette}>
<Cuvette color={colorGradient.colorAt(position)} />
</div>
</div>
</div>
</div>
);
);
}
};

export default LabView;
8 changes: 4 additions & 4 deletions src/components/ScaleBar.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import React from "react";
import classNames from "classnames";

import styles from "./scalebar.module.css";
import { MICRO } from "../constants";
import { useSimulariumSimulation } from "../hooks/useSimulationContext";

interface ScaleBarProps {
productColor: string;
className?: string;
}

const ScaleBar: React.FC<ScaleBarProps> = ({ productColor }) => {
const ScaleBar: React.FC<ScaleBarProps> = ({ className, productColor }) => {
const { maxConcentration } = useSimulariumSimulation();
const labelArray = [];
const interval = maxConcentration / 5;
Expand All @@ -17,7 +19,7 @@ const ScaleBar: React.FC<ScaleBarProps> = ({ productColor }) => {
}
return (
<div
className={styles.container}
className={classNames(styles.container, className)}
role="img"
aria-label={`Color key for the cuvette. The lowest concentration (white) is 0 ${MICRO}M and the highest concentration (yellow) is ${maxConcentration} ${MICRO}M.`}
>
Expand All @@ -29,8 +31,6 @@ const ScaleBar: React.FC<ScaleBarProps> = ({ productColor }) => {
))}
</div>
<div
// inlined this style because the agent color is in javascript
// and I want to avoid defining it in both places
style={{
background: `linear-gradient(0deg, #ffffff 0%, ${productColor} 100%)`,
}}
Expand Down
17 changes: 12 additions & 5 deletions src/components/ViewSwitch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const ViewSwitch: React.FC = () => {

const isFirstPageOfFirstModule =
page === FIRST_PAGE[module] + 1 && module === Module.A_B_AB;
const isLabViewOpen = viewportType === ViewType.Lab;

let buttonStyle: React.CSSProperties = {
top: 16,
Expand All @@ -44,26 +45,32 @@ const ViewSwitch: React.FC = () => {

return (
<div style={{ position: "relative", height: "100%" }}>
<VisibilityControl notInBonusMaterial>
<VisibilityControl
notInBonusMaterial
excludedPages={{ [Module.A_B_AB]: [2] }}
>
<ProgressionControl elementId={VIEW_SWITCH_ID}>
<OverlayButton
active={isLabViewOpen && !isFirstPageOfFirstModule}
onClick={setViewportType}
style={buttonStyle}
icon={
viewportType === ViewType.Lab ? (
isFirstPageOfFirstModule && isLabViewOpen ? (
<Molecules />
) : (
<LabIcon />
)
}
>
{viewportType === ViewType.Lab ? "Molecular" : "Lab"}{" "}
view
{/* on the first page we show text, but on other pages we just show the icon */}
{isFirstPageOfFirstModule
? `${isLabViewOpen ? "Simulation" : "Lab"} view`
: null}
</OverlayButton>
</ProgressionControl>
</VisibilityControl>
<PlayButton />
{viewportType === ViewType.Lab ? <LabView /> : null}
{isLabViewOpen ? <LabView /> : null}
<Viewer
isPlaying={isPlaying}
setIsPlaying={setIsPlaying}
Expand Down
78 changes: 78 additions & 0 deletions src/components/labview.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,81 @@
position: absolute;
width: 95px;
}

/* ── Inset panel (all pages except intro) ──────────────── */

.inset {
position: absolute;
top: 58px;
right: 16px;
width: 210px;
border: 1px solid var(--app-border-color);
overflow: hidden;
z-index: var(--bottom);
border-radius: 4px;
}

/* Blurred lab background image behind inset content */
.inset::before {
content: "";
position: absolute;
inset: 0;
background-image: url("../assets/ipub-bg_4.png");
background-size: cover;
background-position: center;
filter: blur(3px);
transform: scale(1.1); /* prevents blurred edges from bleeding to border */
z-index: 0;
}

.insetLabel {
position: relative;
z-index: 1;
padding: 6px 8px 4px;
font-size: 12px;
font-weight: 600;
color: var(--primary-color);
border-bottom: 1px solid var(--app-border-color);
display: flex;
align-items: center;
justify-content: space-between;
}

.insetClose {
background: none;
border: none;
color: var(--primary-color);
cursor: pointer;
font-size: 14px;
line-height: 1;
padding: 0 0 0 8px;
opacity: 0.7;
}

.insetClose:hover {
opacity: 1;
}

.insetBody {
position: relative;
z-index: 1;
display: flex;
flex-direction: row;
align-items: flex-end;
padding: 8px;
gap: 8px;
/* 166px = scale bar height; 24px = top+bottom padding */
height: 190px;
}

/* Override ScaleBar's absolute positioning so it participates in flex layout */
.scaleBarInset {
position: static !important;
bottom: unset !important;
right: unset !important;
}

.insetCuvette {
width: 70px;
flex-shrink: 0;
}
12 changes: 10 additions & 2 deletions src/components/shared/ButtonLibrary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,13 @@ export const IconButton: React.FC<IconButtonProps> = (props) => {
};

interface OverlayButtonProps extends React.ComponentProps<typeof AntdButton> {
active?: boolean;
children?: React.ReactNode;
style?: React.CSSProperties;
}

export const OverlayButton: React.FC<OverlayButtonProps> = (props) => {
const { style } = props;
const { active, style } = props;
let buttonStyle: React.CSSProperties = {
position: "absolute",
zIndex: zStacking.viewerOverlay,
Expand All @@ -89,5 +90,12 @@ export const OverlayButton: React.FC<OverlayButtonProps> = (props) => {
if (style) {
buttonStyle = { ...buttonStyle, ...style };
}
return <TertiaryButton size="large" {...props} style={buttonStyle} />;
return (
<TertiaryButton
size="large"
{...props}
className={classNames(props.className, { [styles.active]: active })}
style={buttonStyle}
/>
);
};
2 changes: 1 addition & 1 deletion src/components/shared/VisibilityControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const VisibilityControl: React.FC<VisibilityControlProps> = ({

let shouldRender = true;

if (includedPages) {
if (includedPages && module in includedPages) {
shouldRender = includedPages[module]?.includes(page) ?? false;
} else if (excludedPages) {
shouldRender = !excludedPages[module]?.includes(page);
Expand Down
11 changes: 11 additions & 0 deletions src/components/shared/button-library.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@
transition: color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
}

/* Filled-in state for a toggle-style overlay button, e.g. to show it's currently active/open */
.tertiary.active {
background: var(--light-purple) !important;
border-color: var(--light-purple) !important;
color: var(--background-color) !important;
}

.tertiary.active :global(.custom-icon) {
color: var(--background-color) !important;
}

.icon {
width: 12px !important;
min-width: 12px !important;
Expand Down
38 changes: 3 additions & 35 deletions src/content/HighAffinity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,23 +104,6 @@ export const highAffinityContentArray: PageContent[] = [
layout: LayoutType.LiveSimulation,
progressionElement: VIEW_SWITCH_ID,
},
{
content: (
<>
The clear liquid is slowly turning yellow as the simulation
progresses, but it is taking a long time to change. Can you
estimate the concentration of <AB /> ?
</>
),
callToAction: (
<>
Click <strong>Molecular view</strong> to switch back.
</>
),
section: Section.Introduction,
layout: LayoutType.LiveSimulation,
progressionElement: VIEW_SWITCH_ID,
},
{
content: (
<>
Expand Down Expand Up @@ -161,21 +144,6 @@ export const highAffinityContentArray: PageContent[] = [
layout: LayoutType.LiveSimulation,
progressionElement: PLAY_BUTTON_ID,
},
{
content: (
<>Randomizing the positions is simulating a well-mixed solution.</>
),
callToAction: (
<>
Click <strong>Lab view</strong> to see what the cuvette looks
like now.
</>
),

section: Section.Introduction,
layout: LayoutType.LiveSimulation,
progressionElement: VIEW_SWITCH_ID,
},
{
content: (
<>
Expand All @@ -186,14 +154,14 @@ export const highAffinityContentArray: PageContent[] = [
),
callToAction: (
<>
Click <strong>Molecular view</strong> to switch back to the
simulation.
When you're done observing the color changes to the solution in
the cuvette, <strong>pause</strong> the simulation to move on.
</>
),

section: Section.Introduction,
layout: LayoutType.LiveSimulation,
progressionElement: VIEW_SWITCH_ID,
progressionElement: PLAY_BUTTON_ID,
},
{
title: "Start the experiment",
Expand Down
Loading