Skip to content
This repository was archived by the owner on May 29, 2022. It is now read-only.

Commit 5ac9d63

Browse files
committed
Add a somewhat nice UI 🎊
- Add Grommet for UI components - Fix to work with tfjs-vis (preact JSX type namespace collition) - tensorflow/tfjs#1415 πŸ’•πŸ’•πŸ’•
1 parent 61ca60a commit 5ac9d63

File tree

10 files changed

+422
-142
lines changed

10 files changed

+422
-142
lines changed

β€Žpackage.json

+10-6
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,26 @@
55
"dependencies": {
66
"@tensorflow/tfjs": "^1.0.4",
77
"@tensorflow/tfjs-vis": "^1.0.3",
8-
"@types/jest": "24.0.11",
9-
"@types/node": "11.13.0",
10-
"@types/react": "16.8.12",
11-
"@types/react-dom": "16.8.3",
8+
"@types/jest": "^24.0.11",
9+
"@types/node": "^11.13.4",
10+
"@types/react": "^16.8.13",
11+
"@types/react-dom": "^16.8.4",
12+
"grommet": "^2.6.5",
13+
"grommet-icons": "^4.2.0",
1214
"react": "^16.8.6",
1315
"react-dom": "^16.8.6",
1416
"react-scripts": "2.1.8",
1517
"seedrandom": "~2.4.3",
16-
"typescript": "3.4.2",
18+
"styled-components": "^4.2.0",
19+
"typescript": "^3.4.3",
1720
"vega": "^5.3.5"
1821
},
1922
"scripts": {
2023
"start": "react-scripts start",
2124
"build": "react-scripts build",
2225
"test": "react-scripts test",
23-
"eject": "react-scripts eject"
26+
"eject": "react-scripts eject",
27+
"postinstall": "rimraf node_modules/preact/*/preact.d.ts"
2428
},
2529
"eslintConfig": {
2630
"extends": "react-app"

β€Žpublic/index.html

+9
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,16 @@
2222
work correctly both with client-side routing and a non-root public URL.
2323
Learn how to configure a non-root public URL by running `npm run build`.
2424
-->
25+
<link
26+
rel="stylesheet"
27+
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
28+
/>
2529
<title>React App</title>
30+
<style type="text/css">
31+
body {
32+
margin: 0;
33+
}
34+
</style>
2635
</head>
2736
<body>
2837
<noscript>You need to enable JavaScript to run this app.</noscript>

β€Žsrc/App.tsx

+100-11
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,104 @@
1-
import React from "react";
1+
import React, { Component } from "react";
2+
import {
3+
Grommet,
4+
ResponsiveContext,
5+
Box,
6+
Heading,
7+
Button,
8+
Collapsible,
9+
Layer
10+
} from "grommet";
11+
import { grommet } from "grommet/themes";
12+
import { FormClose, Menu } from "grommet-icons";
213
import MnistTrain from "./components/MnistTrain";
3-
import VisorControl from "./components/VisorControl";
414
import DigitPad from "./components/DigitPad";
15+
import VisorControl from "./components/VisorControl";
516

6-
export default () => (
7-
<div>
8-
<VisorControl />
9-
<div className="center">
10-
<MnistTrain />
11-
<hr />
12-
<DigitPad />
13-
</div>
14-
</div>
17+
const AppBar = (props: any) => (
18+
<Box
19+
tag="header"
20+
direction="row"
21+
align="center"
22+
justify="between"
23+
background="brand"
24+
pad={{ left: "medium", right: "small", vertical: "small" }}
25+
elevation="medium"
26+
style={{ zIndex: "1" }}
27+
{...props}
28+
/>
1529
);
30+
31+
const SideBarContent = () => <VisorControl />;
32+
33+
export default () => {
34+
const [showSidebar, setShowSidebar] = React.useState(false);
35+
36+
return (
37+
<Grommet theme={grommet} full>
38+
<ResponsiveContext.Consumer>
39+
{size => (
40+
<Box fill>
41+
<AppBar>
42+
<Heading level="3" margin="none">
43+
<Button
44+
icon={<Menu />}
45+
onClick={() => setShowSidebar(!showSidebar)}
46+
/>{" "}
47+
MNIST-tfjs
48+
</Heading>
49+
</AppBar>
50+
<Box direction="row" flex overflow={{ horizontal: "hidden" }}>
51+
{!showSidebar || size !== "small" ? (
52+
<Collapsible direction="horizontal" open={showSidebar}>
53+
<Box
54+
flex
55+
width="medium"
56+
background="light-2"
57+
elevation="small"
58+
align="center"
59+
justify="center"
60+
>
61+
<SideBarContent />
62+
</Box>
63+
</Collapsible>
64+
) : (
65+
<Layer>
66+
<Box
67+
background="light-2"
68+
tag="header"
69+
justify="end"
70+
align="center"
71+
direction="row"
72+
>
73+
<Button
74+
icon={<FormClose />}
75+
onClick={() => setShowSidebar(false)}
76+
/>
77+
</Box>
78+
<Box
79+
fill
80+
background="light-2"
81+
align="center"
82+
justify="center"
83+
>
84+
<SideBarContent />
85+
</Box>
86+
</Layer>
87+
)}
88+
<Box
89+
flex
90+
align="center"
91+
justify="center"
92+
height="1200px"
93+
overflow={{ vertical: "scroll" }}
94+
>
95+
<MnistTrain />
96+
<DigitPad />
97+
</Box>
98+
</Box>
99+
</Box>
100+
)}
101+
</ResponsiveContext.Consumer>
102+
</Grommet>
103+
);
104+
};

β€Žsrc/components/DigitPad.tsx

+17-11
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
runWIthCanvasContext,
77
recognizeDigit
88
} from "../helpers/canvas";
9+
import { Box, Text } from "grommet";
910

1011
interface Coordinates {
1112
x: number;
@@ -57,7 +58,7 @@ export default () => {
5758
touchmove(coors: Coordinates) {
5859
if (isDrawing) {
5960
try {
60-
clearTimeout(touchend)
61+
clearTimeout(touchend);
6162
} catch (e) {}
6263
if (isRecognized) {
6364
clearer(context);
@@ -135,11 +136,13 @@ export default () => {
135136
};
136137

137138
return (
138-
<div>
139-
<div>
140-
<span>Draw here</span>
141-
</div>
142-
<div>
139+
<Box direction="column" align="center" pad="medium">
140+
<Text>Draw here</Text>
141+
<Box
142+
border={{ color: "brand", size: "large" }}
143+
height={`${DIGIT_PAD_HEIGHT + 12}px`}
144+
width={`${DIGIT_PAD_WIDTH + 12}px`}
145+
>
143146
<canvas
144147
ref={canvasRef}
145148
width={DIGIT_PAD_WIDTH}
@@ -151,10 +154,13 @@ export default () => {
151154
onMouseMove={draw}
152155
onMouseUp={draw}
153156
/>
154-
</div>
155-
<div>
156-
<span>{prediction}</span>
157-
</div>
158-
</div>
157+
</Box>
158+
<Box>
159+
<Text>Prediction</Text>
160+
<Text size="160px" weight="bold">
161+
{prediction}
162+
</Text>
163+
</Box>
164+
</Box>
159165
);
160166
};

β€Žsrc/components/MnistTrain.tsx

+39-28
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React from "react";
22
import mnistTraining from "../mnist/training";
3+
import { Box, Button, Text, TextInput, Heading, FormField } from "grommet";
4+
5+
const RowBox = (props: any) => <Box pad="medium" direction="row" {...props} />;
36

47
interface IState {
58
isDataLoaded: boolean;
@@ -77,37 +80,45 @@ export default () => {
7780
};
7881

7982
return (
80-
<div>
81-
<div>Type "`" to show/hide the tfjs vis</div>
82-
<hr />
83-
<div>
84-
<button onClick={_loadData} disabled={state.isDataLoaded}>
85-
Load data
86-
</button>
87-
</div>
88-
<hr />
89-
<div>
90-
<button onClick={_createModel} disabled={state.isModelCreated}>
91-
Visualize Model
92-
</button>
93-
</div>
94-
<hr />
95-
<div>
96-
<div>
97-
<label htmlFor="epochs">Epochs:</label>
98-
<input
99-
id="epochs"
83+
<Box direction="column" align="center" pad="medium">
84+
<RowBox>
85+
<Text>Visualization pane will open from the right hand side</Text>
86+
</RowBox>
87+
<RowBox>
88+
<Button
89+
label="Load data"
90+
onClick={_loadData}
91+
disabled={state.isDataLoaded}
92+
/>
93+
</RowBox>
94+
<RowBox>
95+
<Button
96+
label="Model Summary"
97+
onClick={_createModel}
98+
disabled={state.isModelCreated}
99+
/>
100+
</RowBox>
101+
<RowBox>
102+
<Text>Model summary would be displayed in the vis pane</Text>
103+
</RowBox>
104+
<RowBox>
105+
<FormField label="Number of epochs to train">
106+
<TextInput
107+
placeholder="Enter number of epochs"
100108
type="number"
109+
size="small"
101110
value={state.epochs}
102111
onChange={_changeEpochs}
103112
/>
104-
</div>
105-
<button onClick={_startTraining}>Start Training</button>
106-
</div>
107-
<hr />
108-
<div>
109-
<button onClick={_showMatrics}>Show Matrices</button>
110-
</div>
111-
</div>
113+
</FormField>
114+
</RowBox>
115+
<RowBox>
116+
<Button label="Start Training" onClick={_startTraining} />
117+
</RowBox>
118+
<RowBox>
119+
<Button label="Show Matrices" onClick={_showMatrics} />
120+
</RowBox>
121+
<Heading level="5">Type "`" to show/hide the tfjs vis</Heading>
122+
</Box>
112123
);
113124
};

β€Žsrc/components/VisorControl.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import React from "react";
2+
import { Box, Text, Button } from "grommet";
3+
24
import { openVis } from "../mnist/vis";
5+
import { Sidebar } from "grommet-icons";
36

47
export default () => (
5-
<div className="visor-control">
6-
<span>TFVis control</span>
7-
<button onClick={openVis}>Open</button>
8-
</div>
8+
<Box direction="row" align="center" justify="between" pad="medium">
9+
<Text margin={{ right: "10px" }}>TFVis control</Text>
10+
<Button icon={<Sidebar />} label="Open" onClick={openVis} />
11+
</Box>
912
);

β€Žsrc/index.css

-45
This file was deleted.

β€Žsrc/index.tsx

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import React from "react";
2-
import ReactDOM from "react-dom";
3-
import "./index.css";
4-
import App from "./App";
5-
import * as serviceWorker from "./serviceWorker";
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import App from './App';
4+
import * as serviceWorker from './serviceWorker';
65

7-
ReactDOM.render(<App />, document.getElementById("root"));
6+
ReactDOM.render(<App />, document.getElementById('root'));
87

98
// If you want your app to work offline and load faster, you can change
109
// unregister() to register() below. Note this comes with some pitfalls.

0 commit comments

Comments
Β (0)