Skip to content

Commit cc99365

Browse files
committed
Move html and runcontrol into react
1 parent 7500bed commit cc99365

File tree

15 files changed

+18758
-146
lines changed

15 files changed

+18758
-146
lines changed

mesa/visualization/ModularVisualization.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,11 @@ def check_origin(self, origin):
212212

213213
@property
214214
def viz_state_message(self):
215-
return {"type": "viz_state", "data": self.application.render_model()}
215+
return {
216+
"type": "viz_state",
217+
"data": self.application.render_model(),
218+
"step": self.application.model.schedule.steps,
219+
}
216220

217221
def on_message(self, message):
218222
"""Receiving a message from the websocket, parse, and act accordingly."""

mesa/visualization/frontend/package-lock.json

Lines changed: 107 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mesa/visualization/frontend/package.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,23 @@
44
"description": "Frontend for mesa ABM",
55
"main": "main.ts",
66
"scripts": {
7-
"build": "esbuild src/main.tsx --bundle --outfile=../templates/js/main.js --minify",
8-
"dev": "esbuild src/main.tsx --bundle --outfile=../templates/js/main.js --watch"
7+
"build": "esbuild src/main.tsx --bundle --outfile=../templates/main.js --minify",
8+
"dev": "esbuild src/main.tsx --bundle --outfile=../templates/main.js --sourcemap --watch"
99
},
1010
"author": "Project Mesa Team",
1111
"repository": {
1212
"url": "https://github.com/projectmesa/mesa"
1313
},
1414
"license": "Apache-2.0",
1515
"dependencies": {
16+
"bootstrap": "^5.1.3",
17+
"bootstrap-slider": "^11.0.2",
18+
"jquery": "^3.6.0",
1619
"react": "^18.1.0",
1720
"react-dom": "^18.1.0"
1821
},
1922
"devDependencies": {
23+
"@types/bootstrap-slider": "^11.0.2",
2024
"@types/react": "^18.0.9",
2125
"@types/react-dom": "^18.0.4",
2226
"esbuild": "^0.14.39",
Lines changed: 176 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,179 @@
1-
import * as React from "react";
1+
import React, { useState, useEffect } from "react";
2+
import { useWebSocket } from "./useWebsocket";
3+
import Slider from "bootstrap-slider";
4+
import "bootstrap-slider/dist/css/bootstrap-slider.min.css";
5+
import { Sidebar } from "./Sidebar";
26

37
export const App = () => {
4-
return <div style={{ display: "none" }}>Hello Mesa!</div>;
8+
const model_name = "Model Name";
9+
const description = "Model Description";
10+
11+
const [fps, setFps] = useState(3);
12+
const [running, setRunning] = useState(false);
13+
14+
const { sendJSON, currentStep, done, vizState, modelParams } = useWebSocket();
15+
16+
useEffect(() => {
17+
window.control = { tick: 0 };
18+
window.elements = [];
19+
window.initElements();
20+
const fpsControl = new Slider(document.querySelector("#fps"), {
21+
max: 20,
22+
min: 0,
23+
value: fps,
24+
ticks: [0, 20],
25+
ticks_labels: [0, 20],
26+
ticks_position: [0, 100],
27+
});
28+
29+
fpsControl.on("change", () => setFps(fpsControl.getValue()));
30+
31+
// cleanup function only relevant for react development mode
32+
return () => {
33+
const elementsTopbar = document.getElementById("elements-topbar");
34+
const elements = document.getElementById("elements");
35+
36+
if (elements) {
37+
elements.innerHTML = "";
38+
elements.appendChild(elementsTopbar);
39+
window.elements = [];
40+
}
41+
};
42+
}, []);
43+
44+
useEffect(() => {
45+
window.control.tick = currentStep;
46+
}, [currentStep]);
47+
48+
useEffect(() => {
49+
if (!vizState) return;
50+
window.elements.forEach((element, index) => {
51+
element.render(vizState[index]);
52+
});
53+
}, [vizState]);
54+
55+
useEffect(() => {
56+
if (running && !done) {
57+
const timeout = setTimeout(() => {
58+
sendJSON({
59+
type: "get_step",
60+
data: { step: currentStep },
61+
});
62+
}, 1000 / fps);
63+
64+
return () => clearTimeout(timeout);
65+
}
66+
}, [running, done, currentStep, fps]);
67+
68+
const handleClickStartStop = () => {
69+
setRunning(!running);
70+
};
71+
72+
const handleClickStep = () => {
73+
sendJSON({ type: "get_step", data: { step: currentStep } });
74+
};
75+
76+
const handleClickReset = () => {
77+
setRunning(false);
78+
sendJSON({ type: "reset" });
79+
};
80+
81+
return (
82+
// Navbar
83+
<>
84+
<nav className="navbar navbar-dark bg-dark navbar-static-top navbar-expand-md mb-3">
85+
<div className="container">
86+
<button
87+
type="button"
88+
className="navbar-toggler collapsed"
89+
data-toggle="collapse"
90+
data-target="#navbar"
91+
aria-expanded="false"
92+
aria-controls="navbar"
93+
>
94+
<span className="sr-only">Toggle navigation</span>
95+
&#x2630;
96+
</button>
97+
<a className="navbar-brand" href="#">
98+
{model_name}
99+
</a>
100+
<div id="navbar" className="navbar-collapse collapse">
101+
<ul className="nav navbar-nav">
102+
<li className="nav-item">
103+
<a
104+
href="#"
105+
data-toggle="modal"
106+
data-target="#about"
107+
data-title="About"
108+
data-content="#about-content"
109+
className="nav-link"
110+
>
111+
About
112+
</a>
113+
</li>
114+
</ul>
115+
<ul className="nav navbar-nav ml-auto">
116+
<li id="play-pause" className="nav-item">
117+
<a href="#" className="nav-link" onClick={handleClickStartStop}>
118+
{running ? "Stop" : "Start"}
119+
</a>
120+
</li>
121+
<li id="step" className="nav-item">
122+
<a href="#" className="nav-link" onClick={handleClickStep}>
123+
Step
124+
</a>
125+
</li>
126+
<li id="reset" className="nav-item">
127+
<a href="#" className="nav-link" onClick={handleClickReset}>
128+
Reset
129+
</a>
130+
</li>
131+
</ul>
132+
</div>
133+
</div>
134+
</nav>
135+
<div className="container d-flex flex-row">
136+
<Sidebar modelParams={modelParams} send={sendJSON} />
137+
<div className="col-xl-8 col-lg-8 col-md-8 col-9" id="elements">
138+
<div id="elements-topbar">
139+
<div>
140+
<label
141+
className="badge badge-primary"
142+
htmlFor="fps"
143+
style={{ marginRight: 15 }}
144+
>
145+
Frames Per Second
146+
</label>
147+
<input id="fps" data-slider-id="fps" type="text" />
148+
</div>
149+
<p>
150+
Current Step: <span id="currentStep">{currentStep}</span>
151+
</p>
152+
</div>
153+
</div>
154+
</div>
155+
<div id="about" className="modal fade" tabIndex={-1} role="dialog">
156+
<div className="modal-dialog modal-lg">
157+
<div className="modal-content">
158+
<div className="modal-header">
159+
<h4 className="modal-title">About {model_name}</h4>
160+
<button
161+
type="button"
162+
className="close"
163+
data-dismiss="modal"
164+
aria-label="Close"
165+
>
166+
<span aria-hidden="true">&#xD7;</span>
167+
</button>
168+
</div>
169+
<div className="modal-body">
170+
<div>{description}</div>
171+
<div>&#xA0;</div>
172+
<div style={{ clear: "both" }}></div>
173+
</div>
174+
</div>
175+
</div>
176+
</div>
177+
</>
178+
);
5179
};

0 commit comments

Comments
 (0)