diff --git a/frontend/react/public/resources/theme/images/splash/1.png b/frontend/react/public/resources/theme/images/splash/1.png deleted file mode 100644 index 1ae92cce..00000000 Binary files a/frontend/react/public/resources/theme/images/splash/1.png and /dev/null differ diff --git a/frontend/react/src/interfaces.ts b/frontend/react/src/interfaces.ts index 1784f4f9..2ea1e135 100644 --- a/frontend/react/src/interfaces.ts +++ b/frontend/react/src/interfaces.ts @@ -1,4 +1,5 @@ import { LatLng } from "leaflet"; +import { Coalition } from "./types/types"; class Airbase { @@ -63,13 +64,19 @@ export interface ServerRequestOptions { commandHash?: string; } +export interface SpawnRequestTable { + category: string, + coalition: string, + unit: UnitSpawnTable +} + export interface UnitSpawnTable { unitType: string, location: LatLng, - altitude?: number, - loadout?: string, skill: string, - liveryID: string + liveryID: string, + altitude: number, + loadout: string } export interface ObjectIconOptions { @@ -225,20 +232,6 @@ export interface UnitBlueprint { unitWhenGrouped?: string; } -export interface UnitSpawnOptions { - roleType: string; - name: string; - latlng: LatLng; - coalition: string; - count: number; - country: string; - skill: string; - loadout: LoadoutBlueprint | undefined; - airbase: Airbase | undefined; - liveryID: string | undefined; - altitude: number | undefined; -} - export interface AirbaseOptions { name: string, position: L.LatLng diff --git a/frontend/react/src/main.tsx b/frontend/react/src/main.tsx index 69f57f37..22124f14 100644 --- a/frontend/react/src/main.tsx +++ b/frontend/react/src/main.tsx @@ -11,5 +11,3 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( , ) - -//window.onload = setupApp; diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts index 50a6e7d2..329a3cf0 100644 --- a/frontend/react/src/map/map.ts +++ b/frontend/react/src/map/map.ts @@ -7,7 +7,7 @@ import { BoxSelect } from "./boxselect"; //import { Dropdown } from "../controls/dropdown"; import { Airbase } from "../mission/airbase"; import { Unit } from "../unit/unit"; -import { bearing, /*createCheckboxOption, createSliderInputOption, createTextInputOption,*/ deg2rad, getGroundElevation, polyContains } from "../other/utils"; +import { bearing, /*createCheckboxOption, createSliderInputOption, createTextInputOption,*/ deg2rad, getGroundElevation, getUnitCategoryByBlueprint, polyContains } from "../other/utils"; import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker"; import { TemporaryUnitMarker } from "./markers/temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; @@ -30,7 +30,7 @@ import './markers/stylesheets/units.css' // Temporary import './theme.css' import { Coalition, MapHiddenTypes, MapOptions } from "../types/types"; -import { UnitBlueprint } from "../interfaces"; +import { SpawnRequestTable, UnitBlueprint, UnitSpawnTable } from "../interfaces"; var hasTouchScreen = false; //if ("maxTouchPoints" in navigator) @@ -56,7 +56,7 @@ export class Map extends L.Map { #state: string; #layer: L.TileLayer | L.LayerGroup | null = null; - #spawningBlueprint: UnitBlueprint | null = null; + #spawnRequestTable: SpawnRequestTable | null = null; #preventLeftClick: boolean = false; #leftClickTimer: number = 0; @@ -292,7 +292,7 @@ export class Map extends L.Map { } /* State machine */ - setState(state: string, options?: { blueprint: UnitBlueprint, coalition: Coalition }) { + setState(state: string, options?: { spawnRequestTable: SpawnRequestTable }) { this.#state = state; /* Operations to perform if you are NOT in a state */ @@ -302,9 +302,9 @@ export class Map extends L.Map { /* Operations to perform if you ARE in a state */ if (this.#state === SPAWN_UNIT) { - this.#spawningBlueprint = options?.blueprint ?? null; + this.#spawnRequestTable = options?.spawnRequestTable ?? null; this.#spawnCursor?.removeFrom(this); - this.#spawnCursor = new TemporaryUnitMarker(new L.LatLng(0, 0), this.#spawningBlueprint?.name ?? "unknown", options?.coalition ?? 'blue'); + this.#spawnCursor = new TemporaryUnitMarker(new L.LatLng(0, 0), this.#spawnRequestTable?.unit.unitType ?? "unknown", this.#spawnRequestTable?.coalition ?? 'blue'); } else if (this.#state === COALITIONAREA_DRAW_POLYGON) { this.#coalitionAreas.push(new CoalitionArea([])); @@ -621,12 +621,20 @@ export class Map extends L.Map { this.deselectAllCoalitionAreas(); } else if (this.#state === SPAWN_UNIT) { - //getApp().getUnitsManager().spawnUnits( - // "asd", - // [ - // {} - // ] - //) + if (this.#spawnRequestTable !== null) { + const location = this.getMouseCoordinates(); + getApp().getUnitsManager().spawnUnits( + this.#spawnRequestTable.category, + [ this.#spawnRequestTable.unit ], + this.#spawnRequestTable.coalition, + false, + undefined, + undefined, + (hash) => { + this.addTemporaryMarker(location, this.#spawnRequestTable?.unit.unitType ?? "unknown", this.#spawnRequestTable?.coalition ?? "blue", hash) + } + ) + } } else if (this.#state === COALITIONAREA_DRAW_POLYGON) { if (this.getSelectedCoalitionArea()?.getEditing()) { diff --git a/frontend/react/src/olympusapp.ts b/frontend/react/src/olympusapp.ts index 2a91048f..a7a6b175 100644 --- a/frontend/react/src/olympusapp.ts +++ b/frontend/react/src/olympusapp.ts @@ -452,52 +452,10 @@ export class OlympusApp { }); }); - // TODO: move from here in dedicated class - document.addEventListener("closeDialog", (ev: CustomEventInit) => { - ev.detail._element.closest(".ol-dialog").classList.add("hide"); - document.getElementById("gray-out")?.classList.toggle("hide", true); - }); - - /* Try and connect with the Olympus REST server */ - const loginForm = document.getElementById("authentication-form"); - if (loginForm instanceof HTMLFormElement) { - loginForm.addEventListener("submit", (ev:SubmitEvent) => { - ev.preventDefault(); - ev.stopPropagation(); - var hash = sha256.create(); - const username = (loginForm.querySelector("#username") as HTMLInputElement).value; - const password = hash.update((loginForm.querySelector("#password") as HTMLInputElement).value).hex(); - - // Update the user credentials - this.getServerManager().setCredentials(username, password); - - // Start periodically requesting updates - this.getServerManager().startUpdate(); - - this.setLoginStatus("connecting"); - }); - } else { - console.error("Unable to find login form."); - } - - /* Temporary */ - this.getServerManager().setCredentials("admin", "4b8823ed9e5c2392ab4a791913bb8ce41956ea32e308b760eefb97536746dd33"); - this.getServerManager().startUpdate(); - /* Reload the page, used to mimic a restart of the app */ document.addEventListener("reloadPage", () => { location.reload(); }) - - ///* Inject the svgs with the corresponding svg code. This allows to dynamically manipulate the svg, like changing colors */ - //document.querySelectorAll("[inject-svg]").forEach((el: Element) => { - // var img = el as HTMLImageElement; - // var isLoaded = img.complete; - // if (isLoaded) - // SVGInjector(img); - // else - // img.addEventListener("load", () => { SVGInjector(img); }); - //}) } getConfig() { diff --git a/frontend/react/src/other/utils.ts b/frontend/react/src/other/utils.ts index 17b8a466..d1691eab 100644 --- a/frontend/react/src/other/utils.ts +++ b/frontend/react/src/other/utils.ts @@ -1,7 +1,7 @@ import { LatLng, Point, Polygon } from "leaflet"; import * as turf from "@turf/turf"; import { UnitDatabase } from "../unit/databases/unitdatabase"; -import { aircraftDatabase } from "../unit/databases/aircraftdatabase"; +import { AircraftDatabase, aircraftDatabase } from "../unit/databases/aircraftdatabase"; import { helicopterDatabase } from "../unit/databases/helicopterdatabase"; import { groundUnitDatabase } from "../unit/databases/groundunitdatabase"; //import { Buffer } from "buffer"; @@ -10,6 +10,7 @@ import { navyUnitDatabase } from "../unit/databases/navyunitdatabase"; import { DateAndTime, UnitBlueprint } from "../interfaces"; import { Converter } from "usng"; import { MGRS } from "../types/types"; +import { getApp } from "../olympusapp"; export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) { @@ -365,20 +366,15 @@ export function getUnitDatabaseByCategory(category: string) { return null; } -export function getCategoryBlueprintIconSVG(category: string, unitName: string) { - - const path = "/resources/theme/images/buttons/visibility/"; - - // We can just send these back okay - if (["Aircraft", "Helicopter", "NavyUnit"].includes(category)) return `${path}${category.toLowerCase()}.svg`; - - // Return if not a ground units as it's therefore something we don't recognise - if (category !== "GroundUnit") return false; - - /** We need to get the unit detail for ground units so we can work out if it's an air defence unit or not **/ - return GROUND_UNIT_AIR_DEFENCE_REGEX.test(unitName) ? `${path}groundunit-sam.svg` : `${path}groundunit.svg`; +export function getUnitCategoryByBlueprint(blueprint: UnitBlueprint) { + for (let database of [getApp()?.getAircraftDatabase(), getApp()?.getHelicopterDatabase(), getApp()?.getGroundUnitDatabase(), getApp()?.getNavyUnitDatabase()]) { + if (blueprint.name in database.blueprints) + return database.getCategory(); + } + return "unknown"; } + export function base64ToBytes(base64: string) { //return Buffer.from(base64, 'base64').buffer; } @@ -477,4 +473,4 @@ export function getWikipediaEntry(search: string, callback: CallableFunction) { } }; xhr.send(); - } \ No newline at end of file +} \ No newline at end of file diff --git a/frontend/react/src/ui/components/olbuttongroup.tsx b/frontend/react/src/ui/components/olbuttongroup.tsx index 9cd3a9ff..100d0d63 100644 --- a/frontend/react/src/ui/components/olbuttongroup.tsx +++ b/frontend/react/src/ui/components/olbuttongroup.tsx @@ -5,8 +5,6 @@ import React from "react"; export function OlButtonGroup(props: { children?: JSX.Element | JSX.Element[] }) { - - return
{props.children}
diff --git a/frontend/react/src/ui/modals/components/modal.tsx b/frontend/react/src/ui/modals/components/modal.tsx new file mode 100644 index 00000000..d3d740a5 --- /dev/null +++ b/frontend/react/src/ui/modals/components/modal.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +export function Modal(props: { + grayout?: boolean, + children?: JSX.Element | JSX.Element[], + className?: string +}) { + return
+ {props.children} +
+} \ No newline at end of file diff --git a/frontend/react/src/ui/modals/login.tsx b/frontend/react/src/ui/modals/login.tsx new file mode 100644 index 00000000..3bec25ff --- /dev/null +++ b/frontend/react/src/ui/modals/login.tsx @@ -0,0 +1,69 @@ +import React, { useState } from 'react' +import { Modal } from './components/modal' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faArrowRight, faCheckCircle, faExternalLink, faLink, faUnlink } from '@fortawesome/free-solid-svg-icons' +import { VERSION, connectedToServer } from '../../olympusapp' + +export function LoginModal(props: { + checkingPassword: boolean, + loginError: boolean, + onLogin: (password: string) => void +}) { + const [password, setPassword] = useState(""); + + return + +
+
+ { + !props.checkingPassword ? + <> +
+
Connect to
+
{window.location.toString()}
+
+
+

DCS Olympus

+
Version {VERSION}
+
+ { + !props.loginError ? + <> +
+ + setPassword(ev.currentTarget.value)} className="w-80 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Enter password" required /> +
+
+ + +
+ + : +
+ There was an issue connecting you dog cunt +
+ } + +
+ DCS Olympus (the "MATERIAL" or "Software") is provided completely free to users subject to the terms of the CC BY-NC-SA 4.0 Licence except where such terms conflict with this disclaimer, in which case, the terms of this disclaimer shall prevail. Any party making use of the Software in any manner agrees to be bound by the terms set out in the disclaimer. THIS MATERIAL IS NOT MADE OR SUPPORTED BY EAGLE DYNAMICS SA. +
+ + : +
+ +
+ } +
+
+ +
+
+
+} \ No newline at end of file diff --git a/frontend/react/src/ui/panels/header.tsx b/frontend/react/src/ui/panels/header.tsx index 86cb2b47..de84266a 100644 --- a/frontend/react/src/ui/panels/header.tsx +++ b/frontend/react/src/ui/panels/header.tsx @@ -1,6 +1,6 @@ import React from 'react' import { OlRoundStateButton, OlStateButton, OlLockStateButton } from '../components/olstatebutton'; -import { faLock, faSkull, faCamera, faFlag, faCircle, faLink, faUnlink } from '@fortawesome/free-solid-svg-icons'; +import { faSkull, faCamera, faFlag, faLink, faUnlink } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { EventsConsumer } from '../../eventscontext'; import { StateConsumer } from '../../statecontext'; diff --git a/frontend/react/src/ui/panels/spawnmenu.tsx b/frontend/react/src/ui/panels/spawnmenu.tsx index e9e67586..0a14d8f1 100644 --- a/frontend/react/src/ui/panels/spawnmenu.tsx +++ b/frontend/react/src/ui/panels/spawnmenu.tsx @@ -1,6 +1,6 @@ -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import { Menu } from "./components/menu"; -import { faHelicopter, faJetFighter, faPlus, faShieldAlt, faShip, faTruck } from '@fortawesome/free-solid-svg-icons'; +import { faPlus } from '@fortawesome/free-solid-svg-icons'; import { library } from '@fortawesome/fontawesome-svg-core' import { OlSearchBar } from "../components/olsearchbar"; import { OlAccordion } from "../components/olaccordion"; @@ -9,13 +9,12 @@ import { OlUnitEntryList } from "../components/olunitlistentry"; import { UnitSpawnMenu } from "./unitspawnmenu"; import { UnitBlueprint } from "../../interfaces"; import { olButtonsVisibilityAircraft, olButtonsVisibilityGroundunit, olButtonsVisibilityGroundunitSam, olButtonsVisibilityHelicopter, olButtonsVisibilityNavyunit } from "../components/olicons"; -import { SPAWN_UNIT } from "../../constants/constants"; library.add(faPlus); -function filterUnits(blueprints: {[key: string]: UnitBlueprint}, filterString: string) { +function filterUnits(blueprints: { [key: string]: UnitBlueprint }, filterString: string) { var filteredUnits = {}; - if (blueprints) { + if (blueprints) { Object.entries(blueprints).forEach(([key, value]) => { if (value.enabled && (filterString === "" || value.label.includes(filterString))) filteredUnits[key] = value; @@ -32,12 +31,6 @@ export function SpawnMenu(props: { var [blueprint, setBlueprint] = useState(null as (null | UnitBlueprint)); var [filterString, setFilterString] = useState(""); - useEffect(() => { - if (!props.open || blueprint !== null) { - //getApp()?.getMap()?.setState(SPAWN_UNIT, {name: blueprint?.name ?? '', coalition: 'blue'}); - } - } ) - /* Filter aircrafts, helicopters, and navyunits */ const filteredAircraft = filterUnits(getApp()?.getAircraftDatabase()?.blueprints, filterString); const filteredHelicopters = filterUnits(getApp()?.getHelicopterDatabase()?.blueprints, filterString); @@ -65,7 +58,7 @@ export function SpawnMenu(props: { > <> {(blueprint === null) &&
- setFilterString(ev.target.value)}/> + setFilterString(ev.target.value)} />
{Object.keys(filteredAircraft).map((key) => { diff --git a/frontend/react/src/ui/panels/unitspawnmenu.tsx b/frontend/react/src/ui/panels/unitspawnmenu.tsx index 4fcab002..f170423d 100644 --- a/frontend/react/src/ui/panels/unitspawnmenu.tsx +++ b/frontend/react/src/ui/panels/unitspawnmenu.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { OlUnitSummary } from "../components/olunitsummary"; import { OlCoalitionToggle } from "../components/olcoalitiontoggle"; import { OlNumberInput } from "../components/olnumberinput"; @@ -7,6 +7,10 @@ import { OlRangeSlider } from "../components/olrangeslider"; import { OlDropdownItem, OlDropdown } from '../components/oldropdown'; import { LoadoutBlueprint, UnitBlueprint } from "../../interfaces"; import { Coalition } from "../../types/types"; +import { getApp } from "../../olympusapp"; +import { IDLE, SPAWN_UNIT } from "../../constants/constants"; +import { ftToM, getUnitCategoryByBlueprint } from "../../other/utils"; +import { LatLng } from "leaflet"; export function UnitSpawnMenu(props: { blueprint: UnitBlueprint @@ -26,6 +30,30 @@ export function UnitSpawnMenu(props: { var [spawnAltitude, setSpawnAltitude] = useState((maxAltitude - minAltitude) / 2); var [spawnAltitudeType, setSpawnAltitudeType] = useState(false); + /* When the menu is opened show the unit preview on the map as a cursor */ + useEffect(() => { + if (props.blueprint !== null) { + getApp()?.getMap()?.setState(SPAWN_UNIT, { + spawnRequestTable: { + category: getUnitCategoryByBlueprint(props.blueprint), + unit: { + unitType: props.blueprint.name, + location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit + skill: "High", + liveryID: "", + altitude: ftToM(spawnAltitude), + loadout: props.blueprint.loadouts?.find((loadout) => { return loadout.name === spawnLoadoutName})?.code ?? "" + }, + coalition: 'blue' + } + }); + } + else { + if (getApp()?.getMap()?.getState() === SPAWN_UNIT) + getApp()?.getMap()?.setState(IDLE); + } + }) + /* Get a list of all the roles */ const roles: string[] = []; (props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => { diff --git a/frontend/react/src/ui/ui.tsx b/frontend/react/src/ui/ui.tsx index b7132b39..3ab52c0c 100644 --- a/frontend/react/src/ui/ui.tsx +++ b/frontend/react/src/ui/ui.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react' +import React, { useState } from 'react' import './ui.css' import { EventsProvider } from '../eventscontext' @@ -11,8 +11,10 @@ import { MainMenu } from './panels/mainmenu' import { SideBar } from './panels/sidebar'; import { Options } from './panels/options'; import { MapHiddenTypes, MapOptions } from '../types/types' -import { MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from '../constants/constants' +import { BLUE_COMMANDER, GAME_MASTER, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, RED_COMMANDER } from '../constants/constants' import { getApp, setupApp } from '../olympusapp' +import { LoginModal } from './modals/login' +import { sha256 } from 'js-sha256' export type OlympusState = { mainMenuVisible: boolean, @@ -34,6 +36,9 @@ export function UI() { var [optionsMenuVisible, setOptionsMenuVisible] = useState(false); var [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS); var [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS); + var [checkingPassword, setCheckingPassword] = useState(false); + var [loginError, setLoginError] = useState(false); + var [commandMode, setCommandMode] = useState(null as null | string); document.addEventListener("hiddenTypesChanged", (ev) => { setMapHiddenTypes({ ...getApp().getMap().getHiddenTypes() }); @@ -52,6 +57,21 @@ export function UI() { setOptionsMenuVisible(false); } + function checkPassword(password: string) { + setCheckingPassword(true); + var hash = sha256.create(); + getApp().getServerManager().setCredentials("no-username", hash.update(password).hex()); + getApp().getServerManager().getMission((response) => { + const commandMode = response.mission.commandModeOptions.commandMode; + try { + [GAME_MASTER, BLUE_COMMANDER, RED_COMMANDER].includes(commandMode) ? setCommandMode(commandMode) : setLoginError(true); + } catch { + setLoginError(true); + } + setCheckingPassword(false); + }) + } + return (
+ { checkPassword(password) }} + checkingPassword={checkingPassword} + loginError={loginError} + /> - setMainMenuVisible(false)} /> - setSpawnMenuVisible(false)} /> - setOptionsMenuVisible(false)} /> + setMainMenuVisible(false)} + /> + setSpawnMenuVisible(false)} + /> + setOptionsMenuVisible(false)} + />
diff --git a/frontend/server/demo.js b/frontend/server/demo.js index 9ae83884..0dfe33a3 100644 --- a/frontend/server/demo.js +++ b/frontend/server/demo.js @@ -500,14 +500,16 @@ module.exports = function (configLocation) { var auth = req.get("Authorization"); if (auth) { var username = Buffer.from(auth.replace("Basic ", ""), 'base64').toString('binary').split(":")[0]; - switch (username) { - case "admin": + var password = Buffer.from(auth.replace("Basic ", ""), 'base64').toString('binary').split(":")[1]; + console.log(password) + switch (password) { + case "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918": ret.mission.commandModeOptions.commandMode = "Game master"; break - case "blue": + case "16477688c0e00699c6cfa4497a3612d7e83c532062b64b250fed8908128ed548": ret.mission.commandModeOptions.commandMode = "Blue commander"; break; - case "red": + case "b1f51a511f1da0cd348b8f8598db32e61cb963e5fc69e2b41485bf99590ed75a": ret.mission.commandModeOptions.commandMode = "Red commander"; break; }