diff --git a/frontend/react/.vscode/launch.json b/frontend/react/.vscode/launch.json
new file mode 100644
index 00000000..d0e64675
--- /dev/null
+++ b/frontend/react/.vscode/launch.json
@@ -0,0 +1,16 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "chrome",
+ "request": "launch",
+ "name": "Launch Chrome against localhost",
+ "url": "http://localhost:8080",
+ "webRoot": "${workspaceFolder}",
+ "preLaunchTask": "npm: dev"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/frontend/react/.vscode/tasks.json b/frontend/react/.vscode/tasks.json
new file mode 100644
index 00000000..f3be330a
--- /dev/null
+++ b/frontend/react/.vscode/tasks.json
@@ -0,0 +1,13 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "type": "npm",
+ "script": "dev",
+ "problemMatcher": [],
+ "label": "npm: dev",
+ "detail": "vite --port=8080",
+ "isBackground": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/frontend/react/src/olympusapp.ts b/frontend/react/src/olympusapp.ts
index 4019df51..b17010ac 100644
--- a/frontend/react/src/olympusapp.ts
+++ b/frontend/react/src/olympusapp.ts
@@ -35,6 +35,7 @@ import { aircraftDatabase } from "./unit/databases/aircraftdatabase";
import { helicopterDatabase } from "./unit/databases/helicopterdatabase";
import { groundUnitDatabase } from "./unit/databases/groundunitdatabase";
import { navyUnitDatabase } from "./unit/databases/navyunitdatabase";
+import { initFlowbite } from "flowbite";
//import { UnitListPanel } from "./panels/unitlistpanel";
//import { ContextManager } from "./context/contextmanager";
//import { Context } from "./context/context";
diff --git a/frontend/react/src/other/utils.ts b/frontend/react/src/other/utils.ts
index ba660c1c..952c2e0e 100644
--- a/frontend/react/src/other/utils.ts
+++ b/frontend/react/src/other/utils.ts
@@ -473,4 +473,19 @@ export function getGroundElevation(latlng: LatLng, callback: CallableFunction) {
}
};
xhr.send();
-}
\ No newline at end of file
+}
+
+export function getWikipediaEntry(search: string, callback: CallableFunction) {
+ /* Get the ground elevation from the server endpoint */
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', `https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts|pageimages&exintro&explaintext&generator=search&gsrsearch=intitle:${search}&gsrlimit=1&redirects=1&origin=*`, true);
+ xhr.timeout = 500; // ms
+ xhr.responseType = 'json';
+ xhr.onload = () => {
+ var status = xhr?.status;
+ if (status === 200) {
+ callback(xhr.response)
+ }
+ };
+ xhr.send();
+ }
\ No newline at end of file
diff --git a/frontend/react/src/ui.css b/frontend/react/src/ui.css
index e50bf918..bd60d577 100644
--- a/frontend/react/src/ui.css
+++ b/frontend/react/src/ui.css
@@ -1,3 +1,19 @@
+/* width */
+::-webkit-scrollbar {
+ width: 10px;
+}
+
+/* Track */
+::-webkit-scrollbar-track {
+
+}
+
+/* Handle */
+::-webkit-scrollbar-thumb {
+ background: #FFFFFFAA;
+ border-radius: 10px;
+ cursor: pointer;
+}
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
@@ -6,6 +22,8 @@
/* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar {
- -ms-overflow-style: none; /* IE and Edge */
- scrollbar-width: none; /* Firefox */
+ -ms-overflow-style: none;
+ /* IE and Edge */
+ scrollbar-width: none;
+ /* Firefox */
}
\ No newline at end of file
diff --git a/frontend/react/src/ui.tsx b/frontend/react/src/ui.tsx
index f0b1da6b..813e52d1 100644
--- a/frontend/react/src/ui.tsx
+++ b/frontend/react/src/ui.tsx
@@ -1,5 +1,6 @@
-import React, { useState } from 'react'
+import React, { useState, useEffect } from 'react'
import './ui.css'
+import { initFlowbite } from "flowbite";
import { Header } from './ui/panels/header'
import { EventsProvider } from './eventscontext'
@@ -17,6 +18,7 @@ export type OlympusState = {
}
export function UI(props) {
+ var [flowbiteInited, setFlowbiteInited] = useState(false);
var [mainMenuVisible, setMainMenuVisible] = useState(false);
var [spawnMenuVisible, setSpawnMenuVisible] = useState(false);
var [unitControlMenuVisible, setUnitControlMenuVisible] = useState(false);
diff --git a/frontend/react/src/ui/components/olaccordion.tsx b/frontend/react/src/ui/components/olaccordion.tsx
index 2a976a31..0c23e185 100644
--- a/frontend/react/src/ui/components/olaccordion.tsx
+++ b/frontend/react/src/ui/components/olaccordion.tsx
@@ -3,6 +3,7 @@ import React, { useId, useEffect, useRef, useState } from "react"
import 'flowbite';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowCircleDown, faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";
+import { initFlowbite } from "flowbite";
export function OlAccordion(props) {
var [scrolledUp, setScrolledUp] = useState(true);
@@ -21,23 +22,25 @@ export function OlAccordion(props) {
setScrolledUp(e.target.scrollTop === 0);
}
})
+
+ initFlowbite();
})
- return
+ return
-
-
{!scrolledUp && }
+
+ {props.showArrows &&
{!scrolledUp && }
}
{props.children}
-
{!scrolledDown && }
+ {props.showArrows &&
{!scrolledDown && }
}
}
\ No newline at end of file
diff --git a/frontend/react/src/ui/components/oldropdown.tsx b/frontend/react/src/ui/components/oldropdown.tsx
index bc7de864..710a1f9c 100644
--- a/frontend/react/src/ui/components/oldropdown.tsx
+++ b/frontend/react/src/ui/components/oldropdown.tsx
@@ -10,14 +10,14 @@ export function OlDropdown(props) {
var [value, setValue] = useState(props.items[0] ?? "N/A" )
const buttonId = useId();
const dropdownId = useId()
-
+
return
-
+
{props.items.map((item) => {
return -
diff --git a/frontend/react/src/ui/components/olunitlistentry.tsx b/frontend/react/src/ui/components/olunitlistentry.tsx
new file mode 100644
index 00000000..e6e3b81c
--- /dev/null
+++ b/frontend/react/src/ui/components/olunitlistentry.tsx
@@ -0,0 +1,15 @@
+import React, { useId, useState, useRef } from "react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
+
+import 'flowbite';
+
+export function OlUnitEntryList(props) {
+ return
+
+
{props.blueprint.label}
+
{props.blueprint.era === "WW2" ? "WW2" : props.blueprint.era.split(" ").map((word) => {
+ return word.charAt(0).toLocaleUpperCase();
+ })}
+
+}
diff --git a/frontend/react/src/ui/components/olunitsummary.tsx b/frontend/react/src/ui/components/olunitsummary.tsx
new file mode 100644
index 00000000..2bababe6
--- /dev/null
+++ b/frontend/react/src/ui/components/olunitsummary.tsx
@@ -0,0 +1,27 @@
+import React from "react";
+import { UnitBlueprint } from "../../interfaces";
+
+export function OlUnitSummary(props: {blueprint: UnitBlueprint}) {
+ console.log(props.blueprint)
+ return
+
+

+
{props.blueprint.label}
+
+
+
{props.blueprint.description}
+
+
+ {props.blueprint.abilities?.split(" ").map((tag) => {
+ return
+ {tag}
+
+ })}
+
+
{props.blueprint.era === "WW2" ? "WW2" : props.blueprint.era.split(" ").map((word) => {
+ return word.charAt(0).toLocaleUpperCase();
+ })}
+
+
+
+}
\ No newline at end of file
diff --git a/frontend/react/src/ui/panels/components/menu.tsx b/frontend/react/src/ui/panels/components/menu.tsx
index d383ce8c..983b1d20 100644
--- a/frontend/react/src/ui/panels/components/menu.tsx
+++ b/frontend/react/src/ui/panels/components/menu.tsx
@@ -1,4 +1,3 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Drawer, DrawerInterface } from "flowbite";
import React, { useEffect, useId, useRef } from "react";
@@ -11,13 +10,13 @@ export function Menu(props) {
props.open ? drawer.show() : drawer.hide();
})
- return
+ return
{props.title}
diff --git a/frontend/react/src/ui/panels/spawnmenu.tsx b/frontend/react/src/ui/panels/spawnmenu.tsx
index 7cefb56d..0dd666aa 100644
--- a/frontend/react/src/ui/panels/spawnmenu.tsx
+++ b/frontend/react/src/ui/panels/spawnmenu.tsx
@@ -1,25 +1,81 @@
-import React from "react";
+import React, { useState } from "react";
import { Menu } from "./components/menu";
-import { faJetFighter, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { faHelicopter, faJetFighter, faPlus, faShieldAlt, faShip, faTruck } from '@fortawesome/free-solid-svg-icons';
import { library } from '@fortawesome/fontawesome-svg-core'
import { OlSearchBar } from "../components/olsearchbar";
import { OlAccordion } from "../components/olaccordion";
import { getApp } from "../../olympusapp";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { OlUnitEntryList } from "../components/olunitlistentry";
+import { UnitBlueprint } from "../../interfaces";
+import { UnitSpawnMenu } from "./unitspawnmenu";
library.add(faPlus);
export function SpawnMenu(props) {
- return
+ var [blueprint, setBlueprint] = useState(null);
+
+ const filteredAircraft = getApp()?.getAircraftDatabase()?.blueprints ?? {};
+ const filteredHelicopters = getApp()?.getHelicopterDatabase()?.blueprints ?? {};
+ const filteredNavyUnits = getApp()?.getNavyUnitDatabase()?.blueprints ?? {};
+
+ var filteredAirDefense = {};
+ var filteredGroundUnits = {};
+ Object.keys(getApp()?.getGroundUnitDatabase()?.blueprints ?? {}).forEach((key) => {
+ var blueprint = getApp()?.getGroundUnitDatabase()?.blueprints[key];
+ var type = blueprint.label;
+ if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type)) {
+ filteredAirDefense[key] = blueprint;
+ } else {
+ filteredGroundUnits[key] = blueprint;
+ }
+ });
+
+ return
}
\ No newline at end of file
diff --git a/frontend/react/src/ui/panels/unitspawnmenu.tsx b/frontend/react/src/ui/panels/unitspawnmenu.tsx
new file mode 100644
index 00000000..b3068633
--- /dev/null
+++ b/frontend/react/src/ui/panels/unitspawnmenu.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+import { OlUnitSummary } from "../components/olunitsummary";
+
+export function UnitSpawnMenu(props) {
+ return
+}