Skip to content

Commit 61f1dd5

Browse files
author
Shawn Toubeau
authored
Merge pull request #80 from path2finding/feature/info-button
Feature/info button
2 parents f323c1e + 42f4f75 commit 61f1dd5

16 files changed

+509
-40
lines changed

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
"@types/redux-mock-store": "^1.0.2",
1818
"@types/yup": "^0.26.33",
1919
"connected-react-router": "^6.6.1",
20+
"fs": "^0.0.1-security",
2021
"history": "^4.10.1",
2122
"lodash": "^4.17.15",
2223
"moment": "^2.24.0",
2324
"node-sass": "^4.13.1",
2425
"react": "^16.12.0",
2526
"react-dom": "^16.12.0",
27+
"react-markdown": "^4.3.1",
2628
"react-moment": "^0.9.7",
2729
"react-redux": "^7.1.3",
2830
"react-router": "^5.1.2",

public/android-chrome-192x192.png

13.5 KB
Loading

public/android-chrome-512x512.png

25.1 KB
Loading

public/apple-touch-icon.png

13.1 KB
Loading

public/browserconfig.xml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<browserconfig>
3+
<msapplication>
4+
<tile>
5+
<square150x150logo src="/mstile-150x150.png"/>
6+
<TileColor>#da532c</TileColor>
7+
</tile>
8+
</msapplication>
9+
</browserconfig>

public/favicon-16x16.png

1.09 KB
Loading

public/favicon-32x32.png

1.65 KB
Loading

public/favicon.ico

11.7 KB
Binary file not shown.

public/index.html

+9-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang="en">
33
<head>
44
<meta charset="utf-8" />
5-
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
5+
<!-- <link rel="icon" href="%PUBLIC_URL%/path2phamming.png" /> -->
66
<meta name="viewport" content="width=device-width, initial-scale=1" />
77
<meta name="theme-color" content="#000000" />
88
<meta
@@ -16,6 +16,13 @@
1616
-->
1717
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
1818

19+
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
20+
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
21+
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
22+
<link rel="manifest" href="/site.webmanifest" />
23+
<meta name="msapplication-TileColor" content="#da532c" />
24+
<meta name="theme-color" content="#ffffff" />
25+
1926
<!-- External Dependencies -->
2027
<link
2128
rel="stylesheet"
@@ -32,7 +39,7 @@
3239
work correctly both with client-side routing and a non-root public URL.
3340
Learn how to configure a non-root public URL by running `npm run build`.
3441
-->
35-
<title>React App</title>
42+
<title>Path2Finding</title>
3643
</head>
3744
<body>
3845
<noscript>You need to enable JavaScript to run this app.</noscript>

public/manifest.json

+11-17
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,19 @@
11
{
2-
"short_name": "React App",
3-
"name": "Create React App Sample",
2+
"name": "Path2Finding",
3+
"short_name": "Path2Finding",
44
"icons": [
55
{
6-
"src": "favicon.ico",
7-
"sizes": "64x64 32x32 24x24 16x16",
8-
"type": "image/x-icon"
6+
"src": "/android-chrome-192x192.png",
7+
"sizes": "192x192",
8+
"type": "image/png"
99
},
1010
{
11-
"src": "logo192.png",
12-
"type": "image/png",
13-
"sizes": "192x192"
14-
},
15-
{
16-
"src": "logo512.png",
17-
"type": "image/png",
18-
"sizes": "512x512"
11+
"src": "/android-chrome-512x512.png",
12+
"sizes": "512x512",
13+
"type": "image/png"
1914
}
2015
],
21-
"start_url": ".",
22-
"display": "standalone",
23-
"theme_color": "#000000",
24-
"background_color": "#ffffff"
16+
"theme_color": "#ffffff",
17+
"background_color": "#ffffff",
18+
"display": "standalone"
2519
}

public/mstile-150x150.png

9.59 KB
Loading

public/path2phamming.png

330 KB
Loading

src/algos.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//used for information pop up
2+
export default {
3+
a: " ### A* Info\
4+
\nA* pronounced A-Star was originally created in an attempt to create a general purpose robot as part of the Shakey\
5+
project in the 1960’s and 70’s. A* is a best-first search algorithm.\
6+
\n### Runtime: \
7+
\nThe time complexity is polynomial when the search space is a tree, there is a single goal state,\
8+
and the heuristic function h meets the following condition:\
9+
\n| h ( x ) − h ∗ ( x ) | = O ( log ⁡ h ∗ ( x ) ) \
10+
\nwhere h* is the optimal heuristic, the exact cost to get from x to the goal.\
11+
\n### Helpful links:\
12+
\n[The Coding Train (video part1)] (https://www.youtube.com/watch?v=aKYlikFAV4k&t=968s) \
13+
\n[Computerphile (video)] (https://www.youtube.com/watch?v=ySN5Wnu88nE )",
14+
15+
BFS: "### BFS Info\
16+
\nBreadth-First Search is a graph or tree traversing algorithm that looks at all nodes at the current level \
17+
before moving onto the next deepest set of nodes. It was created by Konrad Zuse in 1945 and later reinvented by Edward F. Moore in 1959. \
18+
\n### Runtime:\
19+
\nO( | V | + | E |) where V is the number of nodes and E the number of edges.\
20+
\n### Helpful links: \
21+
\n[TheHippieCat (video)] (https://www.youtube.com/watch?v=WvR9voi0y2I)\
22+
\n[Nesbox (README)] (https://github.com/nesbox/TIC-80/wiki/Pathfinding%EA%9E%89-BFS-Algorithm)",
23+
24+
DFS: "### DFS Info\
25+
\nDepth-first search is a pathfinding or tree spanning algorithm that follows one branch to its deepest point before backtracking. \
26+
It was created in the 19th century by French mathematician Charles Pierre Trémaux. \
27+
\n### Runtime:\
28+
\nO( | V | + | E |) where V is the number of nodes and E the number of edges.\
29+
\n### Helpful links:\
30+
\n[Brilliant (articel)] (https://brilliant.org/wiki/depth-first-search-dfs)\
31+
\n[Go GATE IIT (video)](https://www.youtube.com/watch?v=iaBEKo5sM7w)",
32+
33+
Djikstras: "### Djikstra's Algorithm Info\
34+
\nDjikstra's shortest path first Algorithm is a pathfinding algorithm created by Edsger W. Djikstra in 1956. \
35+
It uses a min-priority queue to find the shortest path of a weighted graph. \
36+
\n### Runtime: \
37+
\n(Min-priority queue) O(| V | +| E |log| V |) (where | V | s the number of nodes and | E | is the number of edges)\
38+
\n(Array) O(V^2)\
39+
\n### Helpful links:\
40+
\n[Computerphile (video)](https://www.youtube.com/watch?v=GazC3A4OQTE)\
41+
\n[Clément Mihailescu (video)] (https://www.youtube.com/watch?v=msttfIHHkak&t=2826s)",
42+
};

src/components/Menu/index.tsx

+151-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from "react";
22
import Moment from "react-moment";
33
import { MenuState } from "../../models/menu";
4+
import ReactMarkdown from "react-markdown";
45
import {
56
Menu,
67
DropdownProps,
@@ -11,11 +12,17 @@ import {
1112
Modal,
1213
Form,
1314
TextAreaProps,
15+
Sidebar,
16+
Segment,
1417
Message,
1518
Input,
1619
Label,
1720
} from "semantic-ui-react";
18-
import { MazeInfo, Maze } from "../../models/maze";
21+
import { MazeInfo, Space } from "../../models/maze";
22+
import * as yup from "yup";
23+
import { Maze } from "../../models/maze";
24+
import { SpaceTypes } from "../Space/types";
25+
import algos from "../../algos";
1926
import { getMazeSize } from "../Maze/Maze";
2027

2128
import "./Menu.scss";
@@ -65,37 +72,130 @@ export interface MenuProps extends MenuState {
6572
export interface _MenuState {
6673
value: string;
6774
showModal: boolean;
68-
jsonError: boolean;
75+
hasError: boolean;
76+
errorMessage: string;
77+
sidebar: boolean;
6978
mazeCols: number;
7079
mazeRows: number;
7180
}
7281

82+
export const yupSpaceSchema = yup.object({
83+
type: yup
84+
.mixed()
85+
.oneOf(["wall", "empty", "startpoint", "endpoint"])
86+
.required(),
87+
visited: yup.bool().required(),
88+
path: yup.bool().required(),
89+
});
90+
7391
class MenuBar extends React.Component<MenuProps, _MenuState> {
7492
state = {
7593
value: "",
7694
showModal: false,
77-
jsonError: false,
95+
hasError: false,
96+
errorMessage: "",
97+
sidebar: false,
7898
mazeCols: getMazeSize(this.props.maze.mazeInfo).x,
7999
mazeRows: getMazeSize(this.props.maze.mazeInfo).y,
80100
};
81101

102+
checkDynamicKeys = (keys: any[]): boolean => {
103+
for (let i = 0; i < keys.length; i++) {
104+
const keyIsValid = yup.number().required().isValidSync(keys[i]);
105+
if (!keyIsValid) {
106+
return false;
107+
}
108+
}
109+
return true;
110+
};
111+
112+
checkSpace = (keys: any[], obj: any): boolean => {
113+
// {key: "value"} => [0, 1, 2, 3, 4]
114+
for (let i = 0; i < keys.length; i++) {
115+
const isArray = yup.array().isValidSync(obj[i]);
116+
if (!isArray) return false;
117+
if (obj[i].length === 0) return false;
118+
for (let j = 0; j < obj[i].length; j++) {
119+
console.log(obj[keys[i]][j]);
120+
const spaceIsValid = yupSpaceSchema.isValidSync(obj[keys[i]][j]);
121+
if (!spaceIsValid) {
122+
return false;
123+
}
124+
}
125+
}
126+
return true;
127+
};
128+
129+
checkStartEndPoints = (maze: MazeInfo): boolean => {
130+
let numStart: number = 0;
131+
let numEnd: number = 0;
132+
// eslint-disable-next-line array-callback-return
133+
Object.keys(maze).map((key: string) => {
134+
// eslint-disable-next-line array-callback-return
135+
maze[+key].map((value: Space) => {
136+
numStart += value.type === SpaceTypes.start ? 1 : 0;
137+
numEnd += value.type === SpaceTypes.end ? 1 : 0;
138+
});
139+
});
140+
return numStart <= 1 && numEnd <= 1;
141+
};
142+
82143
//event: React.FormEvent<HTMLTextAreaElement>, data: TextAreaProps) => void
83144
handleChange = (
84145
event: React.FormEvent<HTMLTextAreaElement>,
85146
data: TextAreaProps
86147
): void => {
148+
console.log("here");
87149
try {
88-
var json = JSON.parse(data.value as string);
89-
this.setState({
90-
...this.state,
91-
jsonError: false,
92-
value: data.value as string,
93-
});
150+
const maze: MazeInfo = JSON.parse(data.value as string);
151+
console.log(maze);
152+
const keys = Object.keys(maze);
153+
if (keys.length === 0) {
154+
this.setState({
155+
...this.state,
156+
hasError: true,
157+
errorMessage: "Object should have keys!",
158+
});
159+
} else {
160+
const keysAreValid = this.checkDynamicKeys(keys);
161+
if (!keysAreValid) {
162+
this.setState({
163+
...this.state,
164+
hasError: true,
165+
errorMessage: "Object keys must be numeric!",
166+
});
167+
} else {
168+
const spacesAreValid = this.checkSpace(keys, maze);
169+
if (!spacesAreValid) {
170+
this.setState({
171+
...this.state,
172+
hasError: true,
173+
errorMessage:
174+
"Spaces required parameters type, visited, and path!",
175+
});
176+
} else {
177+
const startEndPointsValid = this.checkStartEndPoints(maze);
178+
if (!startEndPointsValid) {
179+
this.setState({
180+
...this.state,
181+
hasError: true,
182+
errorMessage:
183+
"Maze can only have one start point and one end point",
184+
});
185+
} else {
186+
this.setState({
187+
hasError: false,
188+
value: data.value as string,
189+
});
190+
}
191+
}
192+
}
193+
}
94194
} catch (e) {
95195
this.setState({
96196
...this.state,
97-
jsonError: true,
98-
value: data.value as string,
197+
hasError: true,
198+
errorMessage: "Maze data must be in JSON format!",
99199
});
100200
}
101201
};
@@ -114,6 +214,26 @@ class MenuBar extends React.Component<MenuProps, _MenuState> {
114214
this.setState({ ...this.state, showModal: true });
115215
};
116216

217+
getInfo = () => {
218+
let al = this.props.selectedAlgo;
219+
if (al == null) {
220+
al = "Select an algorithm";
221+
} else if (al == "A*") {
222+
al = algos.a;
223+
} else if (al == "BFS") {
224+
al = algos.BFS;
225+
} else if (al == "DFS") {
226+
al = algos.DFS;
227+
} else if (al == "Djikstras") {
228+
al = algos.Djikstras;
229+
}
230+
return al as string;
231+
};
232+
233+
setVisible = (b: boolean) => {
234+
this.setState({ ...this.state, sidebar: b });
235+
};
236+
117237
render() {
118238
const {
119239
canMoveStart,
@@ -343,14 +463,33 @@ class MenuBar extends React.Component<MenuProps, _MenuState> {
343463
value={this.state.value}
344464
onChange={this.handleChange.bind(this)}
345465
/>
346-
<Message visible={this.state.jsonError}>
466+
<Message visible={this.state.hasError}>
347467
Enter properly formatted json
348468
</Message>
349469
<Form.Button content="Submit" />
350470
</Form>
351471
</Modal.Description>
352472
</Modal.Content>
353473
</Modal>
474+
&nbsp; {/* Essentially just a fancy space */}
475+
<Button color="blue" circular onClick={() => this.setVisible(true)}>
476+
<Icon name="info" />
477+
</Button>
478+
<Sidebar
479+
as={Menu}
480+
animation="push"
481+
overlay
482+
icon="labeled"
483+
direction="right"
484+
onHide={() => this.setVisible(false)}
485+
vertical
486+
visible={this.state.sidebar}
487+
width="very wide"
488+
>
489+
<Segment textAlign="left" padded="very">
490+
<ReactMarkdown source={this.getInfo()} text-color="white" />
491+
</Segment>
492+
</Sidebar>
354493
</Menu.Item>
355494
</Menu>
356495
<Message attached="bottom">

src/components/Menu/menu.spec.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe("<Menu />", () => {
6262
expect(wrapper.find(SemanticMenu.Item)).toBeDefined();
6363
expect(wrapper.find(SemanticMenu.Item)).toHaveLength(5);
6464
expect(wrapper.find(SemanticIcon)).toBeDefined();
65-
expect(wrapper.find(SemanticIcon)).toHaveLength(11);
65+
expect(wrapper.find(SemanticIcon)).toHaveLength(12);
6666
});
6767

6868
it("Dropdown renders", () => {
@@ -71,7 +71,7 @@ describe("<Menu />", () => {
7171

7272
it("Buttons render", () => {
7373
expect(wrapper.find(SemanticButton)).toBeDefined();
74-
expect(wrapper.find(SemanticButton)).toHaveLength(9);
74+
expect(wrapper.find(SemanticButton)).toHaveLength(10);
7575
});
7676

7777
it("Play button calls action", () => {

0 commit comments

Comments
 (0)