diff --git a/frontend/react/package.json b/frontend/react/package.json index 8fbc7492..ab8fc751 100644 --- a/frontend/react/package.json +++ b/frontend/react/package.json @@ -19,8 +19,6 @@ "@types/leaflet": "^1.9.8", "@types/react-leaflet": "^3.0.0", "@types/turf": "^3.5.32", - "flowbite": "^2.3.0", - "flowbite-react-icons": "^1.0.5", "js-sha256": "^0.11.0", "leaflet": "^1.9.4", "leaflet-control-mini-map": "^0.4.0", diff --git a/frontend/react/src/constants/constants.ts b/frontend/react/src/constants/constants.ts index 9eae0ea7..78ad3c42 100644 --- a/frontend/react/src/constants/constants.ts +++ b/frontend/react/src/constants/constants.ts @@ -184,68 +184,11 @@ export const defaultMapLayers = { export const IDLE = "Idle"; export const MOVE_UNIT = "Move unit"; export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; -export const visibilityControls: string[] = ["human", "dcs", "aircraft", "helicopter", "groundunit-sam", "groundunit", "navyunit", "airbase"]; -export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["helicopter"], ["groundunit-sam"], ["groundunit"], ["navyunit"], ["airbase"]]; -export const visibilityControlsTooltips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle helicopter visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; - -//export const MAP_MARKER_CONTROLS: MapMarkerVisibilityControl[] = [{ -// "name": "Human", -// "image": "visibility/human.svg", -// "toggles": ["human"], -// "tooltip": "Toggle human players' visibility" -//}, { -// "image": "visibility/olympus.svg", -// "isProtected": false, -// "name": "Olympus", -// "protectable": false, -// "toggles": ["olympus"], -// "tooltip": "Toggle Olympus-controlled units' visibility" -//}, { -// "image": "visibility/dcs.svg", -// "isProtected": true, -// "name": "DCS", -// "protectable": true, -// "toggles": ["dcs"], -// "tooltip": "Toggle DCS-controlled units' visibility" -//}, { -// "category": "Aircraft", -// "image": "visibility/aircraft.svg", -// "name": "Aircraft", -// "toggles": ["aircraft"], -// "tooltip": "Toggle aircraft's visibility" -//}, { -// "category": "Helicopter", -// "image": "visibility/helicopter.svg", -// "name": "Helicopter", -// "toggles": ["helicopter"], -// "tooltip": "Toggle helicopters' visibility" -//}, { -// "category": "AirDefence", -// "image": "visibility/groundunit-sam.svg", -// "name": "Air defence", -// "toggles": ["groundunit-sam"], -// "tooltip": "Toggle air defence units' visibility" -//}, { -// "image": "visibility/groundunit.svg", -// "name": "Ground units", -// "toggles": ["groundunit"], -// "tooltip": "Toggle ground units' visibility" -//}, { -// "category": "GroundUnit", -// "image": "visibility/navyunit.svg", -// "name": "Naval", -// "toggles": ["navyunit"], -// "tooltip": "Toggle naval units' visibility" -//}, { -// "image": "visibility/airbase.svg", -// "name": "Airbase", -// "toggles": ["airbase"], -// "tooltip": "Toggle airbase' visibility" -//}]; export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"]; export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "SAM Site": 0.1, "Radar (EWR)": 0.05 }; export const GROUND_UNIT_AIR_DEFENCE_REGEX: RegExp = /(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/; + export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out"; export const SHOW_UNIT_LABELS = "Show unit labels (L)"; export const SHOW_UNITS_ENGAGEMENT_RINGS = "Show units threat range rings (Q)"; @@ -258,6 +201,44 @@ export const SHOW_UNIT_TARGETS = "Show selected unit targets"; export const DCS_LINK_PORT = "DCS Camera link port"; export const DCS_LINK_RATIO = "DCS Camera zoom"; +export const MAP_OPTIONS_TOOLTIPS = { + hideGroupMembers: "Hide group members when zoomed out", + hideUnitsShortRangeRings: "Hide short range units threat range rings (R)", + showUnitContacts: "Show selected units contact lines", + showUnitPaths: "Show selected unit paths", + showUnitTargets: "Show selected unit targets", + showUnitLabels: "Show unit labels (L)", + showUnitsEngagementRings: "Show units threat range rings (Q)", + showUnitsAcquisitionRings: "Show units detection range rings (E)" +} + +export const MAP_OPTIONS_DEFAULTS = { + hideGroupMembers: true, + hideUnitsShortRangeRings: true, + showUnitContacts: true, + showUnitPaths: true, + showUnitTargets: false, + showUnitLabels: true, + showUnitsEngagementRings: true, + showUnitsAcquisitionRings: true +} + +export const MAP_HIDDEN_TYPES_DEFAULTS = { + 'human': false, + 'olympus': false, + 'dcs': false, + 'aircraft': false, + 'helicopter': false, + 'groundunit-sam': false, + 'groundunit': false, + 'navyunit': false, + 'airbase': false, + 'dead': false, + 'blue': false, + 'red': false, + 'neutral': false +} + export enum DataIndexes { startOfData = 0, category, diff --git a/frontend/react/src/dom.d.ts b/frontend/react/src/dom.d.ts index 974e40f5..22bfc7d3 100644 --- a/frontend/react/src/dom.d.ts +++ b/frontend/react/src/dom.d.ts @@ -20,6 +20,7 @@ interface CustomEventMap { "groupDeletion": CustomEvent, "mapStateChanged": CustomEvent, "mapContextMenu": CustomEvent, + "mapOptionChanged": CustomEvent, "mapOptionsChanged": CustomEvent, "commandModeOptionsChanged": CustomEvent, "contactsUpdated": CustomEvent, diff --git a/frontend/react/src/index.css b/frontend/react/src/index.css index 4629c07a..081d8dcf 100644 --- a/frontend/react/src/index.css +++ b/frontend/react/src/index.css @@ -4,6 +4,14 @@ @tailwind components; @tailwind utilities; -.z-ui { +.z-ui-0 { z-index: 2000; +} + +.z-ui-1 { + z-index: 2001; +} + +.z-ui-2 { + z-index: 2002; } \ No newline at end of file diff --git a/frontend/react/src/main.tsx b/frontend/react/src/main.tsx index 0fc59e42..5f3827cc 100644 --- a/frontend/react/src/main.tsx +++ b/frontend/react/src/main.tsx @@ -2,12 +2,10 @@ import React from 'react' import ReactDOM from 'react-dom/client' import { setupApp } from './olympusapp.js' -import { UI } from './ui.js'; +import { UI } from './ui/ui.js'; import './index.css' -import 'flowbite'; - ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/frontend/react/src/map/map.ts b/frontend/react/src/map/map.ts index e9b62a6c..bbbace7b 100644 --- a/frontend/react/src/map/map.ts +++ b/frontend/react/src/map/map.ts @@ -12,7 +12,7 @@ import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker"; import { TemporaryUnitMarker } from "./markers/temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; import { SVGInjector } from '@tanem/svg-injector' -import { defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, /*MAP_MARKER_CONTROLS,*/ DCS_LINK_PORT, DCSMapsZoomLevelsByTheatre, DCS_LINK_RATIO } from "../constants/constants"; +import { defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, /*MAP_MARKER_CONTROLS,*/ DCS_LINK_PORT, DCSMapsZoomLevelsByTheatre, DCS_LINK_RATIO, MAP_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS } from "../constants/constants"; import { CoalitionArea } from "./coalitionarea/coalitionarea"; //import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu"; import { DrawingCursor } from "./coalitionarea/drawingcursor"; @@ -29,7 +29,7 @@ import './markers/stylesheets/units.css' // Temporary import './theme.css' - +import { MapHiddenTypes, MapOptions } from "../types/types"; var hasTouchScreen = false; //if ("maxTouchPoints" in navigator) @@ -45,18 +45,12 @@ else // TODO would be nice to convert to ts - yes //require("../../node_modules/leaflet.nauticscale/dist/leaflet.nauticscale.js") //require("../../node_modules/leaflet-path-drag/dist/index.js") - -export type MapMarkerVisibilityControl = { - "category"?: string; - "image": string; - "isProtected"?: boolean, - "name": string, - "protectable"?: boolean, - "toggles": string[], - "tooltip": string -} export class Map extends L.Map { + /* Options */ + #options: MapOptions = MAP_OPTIONS_DEFAULTS; + #hiddenTypes: MapHiddenTypes = MAP_HIDDEN_TYPES_DEFAULTS; + #ID: string; #state: string; #layer: L.TileLayer | L.LayerGroup | null = null; @@ -97,19 +91,7 @@ export class Map extends L.Map { #longPressHandled: boolean = false; #longPressTimer: number = 0; - //#mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu"); - //#unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu"); - //#airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu"); - //#airbaseSpawnMenu: AirbaseSpawnContextMenu = new AirbaseSpawnContextMenu("airbase-spawn-contextmenu"); - //#coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu"); - - //#mapSourceDropdown: Dropdown; #mapLayers: any = defaultMapLayers; - //#mapMarkerVisibilityControls: MapMarkerVisibilityControl[] = MAP_MARKER_CONTROLS; - //#mapVisibilityOptionsDropdown: Dropdown; - //#optionButtons: { [key: string]: HTMLButtonElement[] } = {} - #visibilityOptions: { [key: string]: boolean | string | number } = {} - #hiddenTypes: string[] = []; #layerName: string = ""; #cameraOptionsXmlHttp: XMLHttpRequest | null = null; #bradcastPositionXmlHttp: XMLHttpRequest | null = null; @@ -146,6 +128,7 @@ export class Map extends L.Map { this.#miniMapLayerGroup = new L.LayerGroup([minimapLayer]); this.#miniMapPolyline = new L.Polyline([], { color: '#202831' }); this.#miniMapPolyline.addTo(this.#miniMapLayerGroup); + /* Scale */ //@ts-ignore TODO more hacking because the module is provided as a pure javascript module only @@ -180,27 +163,14 @@ export class Map extends L.Map { this.on('move', (e: any) => { if (this.#slaveDCSCamera) this.#broadcastPosition() }); /* Event listeners */ - document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { - const el = ev.detail._element; - el?.classList.toggle("off"); - this.setHiddenType(ev.detail.coalition, !el?.classList.contains("off")); + document.addEventListener("hiddenTypesChanged", (ev: CustomEventInit) => { Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); - }); - - document.addEventListener("toggleMarkerVisibility", (ev: CustomEventInit) => { - const el = ev.detail._element; - el?.classList.toggle("off"); - ev.detail.types.forEach((type: string) => this.setHiddenType(type, !el?.classList.contains("off"))); - Object.values(getApp().getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); - - if (ev.detail.types.includes("airbase")) { - Object.values(getApp().getMissionManager().getAirbases()).forEach((airbase: Airbase) => { - if (el?.classList.contains("off")) - airbase.removeFrom(this); - else - airbase.addTo(this); - }) - } + Object.values(getApp().getMissionManager().getAirbases()).forEach((airbase: Airbase) => { + if (this.getHiddenTypes().airbase) + airbase.removeFrom(this); + else + airbase.addTo(this); + }) }); document.addEventListener("toggleCoalitionAreaDraw", (ev: CustomEventInit) => { @@ -220,9 +190,9 @@ export class Map extends L.Map { //}); document.addEventListener("mapOptionsChanged", () => { - this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]); - this.#cameraControlPort = this.getVisibilityOptions()[DCS_LINK_PORT] as number; - this.#cameraZoomRatio = 50 / (20 + (this.getVisibilityOptions()[DCS_LINK_RATIO] as number)); + this.getContainer().toggleAttribute("data-hide-labels", !this.getOptions().showUnitLabels); + //this.#cameraControlPort = this.getOptions()[DCS_LINK_PORT] as number; + //this.#cameraZoomRatio = 50 / (20 + (this.getOptions()[DCS_LINK_RATIO] as number)); if (this.#slaveDCSCamera) { this.#broadcastPosition(); @@ -267,39 +237,6 @@ export class Map extends L.Map { this.#cameraControlTimer = window.setInterval(() => { this.#checkCameraPort(); }, 1000) - - /* Option buttons */ - this.#createUnitMarkerControlButtons(); - - /* Create the checkboxes to select the advanced visibility options */ - this.addVisibilityOption(DCS_LINK_PORT, 3003, { min: 1024, max: 65535 }); - this.addVisibilityOption(DCS_LINK_RATIO, 50, { min: 0, max: 100, slider: true }); - - //this.#mapVisibilityOptionsDropdown.addHorizontalDivider(); - - this.addVisibilityOption(SHOW_UNIT_CONTACTS, false); - this.addVisibilityOption(HIDE_GROUP_MEMBERS, true); - this.addVisibilityOption(SHOW_UNIT_PATHS, true); - this.addVisibilityOption(SHOW_UNIT_TARGETS, true); - this.addVisibilityOption(SHOW_UNIT_LABELS, true); - this.addVisibilityOption(SHOW_UNITS_ENGAGEMENT_RINGS, true); - this.addVisibilityOption(SHOW_UNITS_ACQUISITION_RINGS, true); - this.addVisibilityOption(HIDE_UNITS_SHORT_RANGE_RINGS, true); - /* this.addVisibilityOption(FILL_SELECTED_RING, false); Removed since currently broken: TODO fix!*/ - } - - addVisibilityOption(option: string, defaultValue: boolean | number | string, options?: { [key: string]: any }) { - //this.#visibilityOptions[option] = defaultValue; - //if (typeof defaultValue === 'boolean') { - // this.#mapVisibilityOptionsDropdown.addOptionElement(createCheckboxOption(option, option, defaultValue as boolean, (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); - //} else if (typeof defaultValue === 'number') { - // if (options !== undefined && options?.slider === true) - // this.#mapVisibilityOptionsDropdown.addOptionElement(createSliderInputOption(option, option, defaultValue, (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); - // else - // this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue.toString(), 'number', (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); - //} else { - // this.#mapVisibilityOptionsDropdown.addOptionElement(createTextInputOption(option, option, defaultValue, 'text', (ev: any) => { this.#setVisibilityOption(option, ev); }, options)); - //} } setLayer(layerName: string) { @@ -376,13 +313,8 @@ export class Map extends L.Map { } setHiddenType(key: string, value: boolean) { - if (value) { - if (this.#hiddenTypes.includes(key)) - delete this.#hiddenTypes[this.#hiddenTypes.indexOf(key)]; - } - else { - this.#hiddenTypes.push(key); - } + this.#hiddenTypes[key] = value; + document.dispatchEvent(new CustomEvent("hiddenTypesChanged")); } getHiddenTypes() { @@ -583,8 +515,13 @@ export class Map extends L.Map { this.#coalitionAreas.unshift(coalitionArea); } - getVisibilityOptions() { - return this.#visibilityOptions; + setOption(key, value) { + this.#options[key] = value; + document.dispatchEvent(new CustomEvent("mapOptionsChanged")); + } + + getOptions() { + return this.#options; } isZooming() { @@ -877,49 +814,6 @@ export class Map extends L.Map { return minimapBoundaries; } - #createUnitMarkerControlButtons() { - const unitVisibilityControls = document.getElementById("unit-visibility-control"); - const makeTitle = (isProtected: boolean) => { - return (isProtected) ? "Unit type is protected and will ignore orders" : "Unit is NOT protected and will respond to orders"; - } - //this.getMapMarkerVisibilityControls().forEach((control: MapMarkerVisibilityControl) => { - // const toggles = `["${control.toggles.join('","')}"]`; - // const div = document.createElement("div"); - // div.className = control.protectable === true ? "protectable" : ""; -// - // // TODO: for consistency let's avoid using innerHTML. Let's create elements. - // div.innerHTML = ` - // - // `; - // unitVisibilityControls.appendChild(div); -// - // if (control.protectable) { - // div.innerHTML += ` - // `; -// - // const btn = div.querySelector("button.lock"); - // btn.addEventListener("click", (ev: MouseEventInit) => { - // control.isProtected = !control.isProtected; - // btn.toggleAttribute("data-protected", control.isProtected); - // btn.title = makeTitle(control.isProtected); - // document.dispatchEvent(new CustomEvent("toggleMarkerProtection", { - // detail: { - // "_element": btn, - // "control": control - // } - // })); - // }); - // } - //}); - - //unitVisibilityControls.querySelectorAll(`img[src$=".svg"]`).forEach(img => SVGInjector(img)); - } - #deselectSelectedCoalitionArea() { this.getSelectedCoalitionArea()?.setSelected(false); } @@ -1029,16 +923,6 @@ export class Map extends L.Map { this.#drawingCursor.removeFrom(this); } - #setVisibilityOption(option: string, ev: any) { - if (typeof this.#visibilityOptions[option] === 'boolean') - this.#visibilityOptions[option] = ev.currentTarget.checked; - else if (typeof this.#visibilityOptions[option] === 'number') - this.#visibilityOptions[option] = Number(ev.currentTarget.value); - else - this.#visibilityOptions[option] = ev.currentTarget.value; - document.dispatchEvent(new CustomEvent("mapOptionsChanged")); - } - #setSlaveDCSCameraAvailable(newSlaveDCSCameraAvailable: boolean) { this.#slaveDCSCameraAvailable = newSlaveDCSCameraAvailable; let linkButton = document.getElementById("camera-link-control"); diff --git a/frontend/react/src/other/utils.ts b/frontend/react/src/other/utils.ts index 952c2e0e..17b8a466 100644 --- a/frontend/react/src/other/utils.ts +++ b/frontend/react/src/other/utils.ts @@ -9,6 +9,7 @@ import { GROUND_UNIT_AIR_DEFENCE_REGEX, ROEs, emissionsCountermeasures, reaction import { navyUnitDatabase } from "../unit/databases/navyunitdatabase"; import { DateAndTime, UnitBlueprint } from "../interfaces"; import { Converter } from "usng"; +import { MGRS } from "../types/types"; export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) { @@ -167,18 +168,6 @@ export function editDistance(s1: string, s2: string) { return costs[s2.length]; } -export type MGRS = { - bandLetter: string, - columnLetter: string, - easting: string, - groups: string[], - northing: string, - precision: number, - rowLetter: string, - string: string, - zoneNumber: string -} - export function latLngToMGRS(lat: number, lng: number, precision: number = 4): MGRS | false { if (precision < 0 || precision > 6) { diff --git a/frontend/react/src/statecontext.tsx b/frontend/react/src/statecontext.tsx index 0dfc9de5..93f47833 100644 --- a/frontend/react/src/statecontext.tsx +++ b/frontend/react/src/statecontext.tsx @@ -1,12 +1,15 @@ import { createContext } from "react"; -import { OlympusState } from "./ui"; +import { MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from "./constants/constants"; export const StateContext = createContext({ + mainMenuVisible: false, spawnMenuVisible: false, unitControlMenuVisible: false, measureMenuVisible: false, - drawingMenuVisible: false -} as OlympusState) + drawingMenuVisible: false, + mapHiddenTypes: MAP_HIDDEN_TYPES_DEFAULTS, + mapOptions: MAP_OPTIONS_DEFAULTS +}) export const StateProvider = StateContext.Provider; export const StateConsumer = StateContext.Consumer; diff --git a/frontend/react/src/types/types.ts b/frontend/react/src/types/types.ts new file mode 100644 index 00000000..3d4e8dcc --- /dev/null +++ b/frontend/react/src/types/types.ts @@ -0,0 +1,50 @@ + +/* Types definition */ +export type MapMarkerVisibilityControl = { + "category"?: string; + "image": string; + "isProtected"?: boolean, + "name": string, + "protectable"?: boolean, + "toggles": string[], + "tooltip": string +} + +export type MapOptions = { + hideGroupMembers: boolean, + hideUnitsShortRangeRings: boolean, + showUnitContacts: boolean, + showUnitPaths: boolean, + showUnitTargets: boolean, + showUnitLabels: boolean, + showUnitsEngagementRings: boolean, + showUnitsAcquisitionRings: boolean +} + +export type MapHiddenTypes = { + 'human': boolean, + 'olympus': boolean, + 'dcs': boolean, + 'aircraft': boolean, + 'helicopter': boolean, + 'groundunit-sam': boolean, + 'groundunit': boolean, + 'navyunit': boolean, + 'airbase': boolean, + 'dead': boolean, + 'blue': boolean, + 'red': boolean, + 'neutral': boolean +} + +export type MGRS = { + bandLetter: string, + columnLetter: string, + easting: string, + groups: string[], + northing: string, + precision: number, + rowLetter: string, + string: string, + zoneNumber: string +} \ No newline at end of file diff --git a/frontend/react/src/ui.tsx b/frontend/react/src/ui.tsx deleted file mode 100644 index 813e52d1..00000000 --- a/frontend/react/src/ui.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useState, useEffect } from 'react' -import './ui.css' -import { initFlowbite } from "flowbite"; - -import { Header } from './ui/panels/header' -import { EventsProvider } from './eventscontext' -import { StateProvider } from './statecontext' -import { SpawnMenu } from './ui/panels/spawnmenu' -import { UnitControlMenu } from './ui/panels/unitcontrolmenu' -import { MainMenu } from './ui/panels/mainmenu' - -export type OlympusState = { - mainMenuVisible: boolean, - spawnMenuVisible: boolean, - unitControlMenuVisible: boolean, - measureMenuVisible: boolean, - drawingMenuVisible: boolean -} - -export function UI(props) { - var [flowbiteInited, setFlowbiteInited] = useState(false); - var [mainMenuVisible, setMainMenuVisible] = useState(false); - var [spawnMenuVisible, setSpawnMenuVisible] = useState(false); - var [unitControlMenuVisible, setUnitControlMenuVisible] = useState(false); - var [measureMenuVisible, setMeasureMenuVisible] = useState(false); - var [drawingMenuVisible, setDrawingMenuVisible] = useState(false); - - function hideAllMenus() { - setMainMenuVisible(false); - setSpawnMenuVisible(false); - setUnitControlMenuVisible(false); - setMeasureMenuVisible(false); - setDrawingMenuVisible(false); - } - - return ( -
- - {hideAllMenus(); setMainMenuVisible(!mainMenuVisible)}, - toggleSpawnMenuVisible: () => {hideAllMenus(); setSpawnMenuVisible(!spawnMenuVisible)}, - toggleUnitControlMenuVisible: () => {hideAllMenus(); setUnitControlMenuVisible(!unitControlMenuVisible)}, - toggleMeasureMenuVisible: () => {hideAllMenus(); setMeasureMenuVisible(!measureMenuVisible)}, - toggleDrawingMenuVisible: () => {hideAllMenus(); setDrawingMenuVisible(!drawingMenuVisible)}, - } - }> -
-
-
- setMainMenuVisible(false)}/> - setSpawnMenuVisible(false)}/> - setUnitControlMenuVisible(false)}/> -
- - -
- ) -} diff --git a/frontend/react/src/ui/components/olaccordion.tsx b/frontend/react/src/ui/components/olaccordion.tsx index 0c23e185..55326490 100644 --- a/frontend/react/src/ui/components/olaccordion.tsx +++ b/frontend/react/src/ui/components/olaccordion.tsx @@ -1,18 +1,12 @@ 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"; +import { faArrowCircleDown } from "@fortawesome/free-solid-svg-icons"; export function OlAccordion(props) { + var [open, setOpen] = useState(false); var [scrolledUp, setScrolledUp] = useState(true); var [scrolledDown, setScrolledDown] = useState(false); - const bodyId = useId(); - const accordionId = useId(); - const headingId = useId(); - var contentRef = useRef(null); useEffect(() => { @@ -22,20 +16,18 @@ export function OlAccordion(props) { setScrolledUp(e.target.scrollTop === 0); } }) - - initFlowbite(); }) - return
-

-

-
+
{props.showArrows &&
{!scrolledUp && }
}
{props.children} diff --git a/frontend/react/src/ui/components/olcheckbox.tsx b/frontend/react/src/ui/components/olcheckbox.tsx new file mode 100644 index 00000000..2542aaf2 --- /dev/null +++ b/frontend/react/src/ui/components/olcheckbox.tsx @@ -0,0 +1,7 @@ +import React, { useState, useId } from "react"; + +export function OlCheckbox(props) { + const id = useId(); + + return +} \ No newline at end of file diff --git a/frontend/react/src/ui/components/olcoalitiontoggle.tsx b/frontend/react/src/ui/components/olcoalitiontoggle.tsx new file mode 100644 index 00000000..fd051770 --- /dev/null +++ b/frontend/react/src/ui/components/olcoalitiontoggle.tsx @@ -0,0 +1,9 @@ +import React from "react"; + +export function OlCoalitionToggle() { + return
+ +
+ Large toggle +
+} \ 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 710a1f9c..9b2810a2 100644 --- a/frontend/react/src/ui/components/oldropdown.tsx +++ b/frontend/react/src/ui/components/oldropdown.tsx @@ -1,30 +1,89 @@ -import React, { useId, useState } from "react"; +import React, { useId, useState, useEffect, useRef } from "react"; import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'; import { library } from '@fortawesome/fontawesome-svg-core' import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; library.add(faChevronDown, faChevronUp); -export function OlDropdown(props) { - var [value, setValue] = useState(props.items[0] ?? "N/A" ) +export function OlTextDropdown(props) { + var [value, setValue] = useState(props.items[0] ?? "N/A") const buttonId = useId(); const dropdownId = useId() - + return
-
- +
+} + +export function OlElementDropdown(props) { + var [open, setOpen] = useState(false); + var contentRef = useRef(null); + var buttonRef = useRef(null); + + function setPosition(content: HTMLDivElement, button: HTMLButtonElement) { + content.style.left = `0px`; + content.style.top = `0px`; + + var [cxl, cyt, cxr, cyb, cw, ch] = [content.getBoundingClientRect().x, content.getBoundingClientRect().y, content.getBoundingClientRect().x + content.clientWidth, content.getBoundingClientRect().y + content.clientHeight, content.clientWidth, content.clientHeight]; + var [bxl, byt, bxr, byb, bbw, bh] = [button.getBoundingClientRect().x, button.getBoundingClientRect().y, button.getBoundingClientRect().x + button.clientWidth, button.getBoundingClientRect().y + button.clientHeight, button.clientWidth, button.clientHeight]; + + var cxc = (cxl + cxr) / 2; + var bxc = (bxl + bxr) / 2; + + var offsetX = bxc - cxc; + var offsetY = byb - cyt; + + cxl += offsetX; + cxr += offsetX; + + if (cxl < 0) + offsetX -= cxl; + if (cxr > window.innerWidth) + offsetX -= (cxr - window.innerWidth) + + content.style.left = `${offsetX}px` + content.style.top = `${offsetY + 5}px` + } + + useEffect(() => { + if (contentRef.current && buttonRef.current) { + const content = contentRef.current as HTMLDivElement; + const button = buttonRef.current as HTMLButtonElement; + + setPosition(content, button); + } + }) + + return
+ + +
+
+ {props.children} +
+
+
+} + +export function OlDropdownItem(props) { + return
{ })} className="px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white flex flex-row content-center gap-2"> + {props.children} +
} \ No newline at end of file diff --git a/frontend/react/src/ui/components/ollabeltoggle.tsx b/frontend/react/src/ui/components/ollabeltoggle.tsx new file mode 100644 index 00000000..22a6a09c --- /dev/null +++ b/frontend/react/src/ui/components/ollabeltoggle.tsx @@ -0,0 +1,11 @@ +import React, { useState } from "react"; + +export function OlLabelToggle(props) { + var [toggled, setToggled] = useState(false); + + return
{setToggled(!toggled)}} className="relative flex flex-row contents-center justify-between w-32 h-10 dark:bg-gray-700 rounded-md py-1 px-1 select-none cursor-pointer"> + + MSL + AGL +
+} \ No newline at end of file diff --git a/frontend/react/src/ui/components/olnumberinput.tsx b/frontend/react/src/ui/components/olnumberinput.tsx new file mode 100644 index 00000000..4fd8fdfd --- /dev/null +++ b/frontend/react/src/ui/components/olnumberinput.tsx @@ -0,0 +1,19 @@ +import React, {useEffect, useId} from "react"; + +export function OlNumberInput(props) { + return
+
+ + + +
+
+} \ No newline at end of file diff --git a/frontend/react/src/ui/components/olrangeslider.tsx b/frontend/react/src/ui/components/olrangeslider.tsx new file mode 100644 index 00000000..88c22542 --- /dev/null +++ b/frontend/react/src/ui/components/olrangeslider.tsx @@ -0,0 +1,11 @@ +import React, { useState } from "react"; + +export function OlRangeSlider(props) { + return { props.onChange(Number(ev.target?.value ?? props.value)) }} + value={props.value} + min={props.minValue ?? 0} + max={props.maxValue ?? 100} + step={props.step ?? 1} + className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700" /> +} \ No newline at end of file diff --git a/frontend/react/src/ui/components/olstatebutton.tsx b/frontend/react/src/ui/components/olstatebutton.tsx index e7980a58..8847dc02 100644 --- a/frontend/react/src/ui/components/olstatebutton.tsx +++ b/frontend/react/src/ui/components/olstatebutton.tsx @@ -2,10 +2,19 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" import React from "react" export function OlStateButton(props) { - return +} + +export function OlRoundStateButton(props) { + const className = (props.className ?? '') + ` h-[40px] w-[40px] m-auto border border-gray-900 font-medium rounded-full text-sm ` + + `dark:bg-transparent dark:data-[checked='true']:bg-white dark:text-white dark:data-[checked='true']:text-gray-900 dark:border-gray-600 `; + + return } \ No newline at end of file diff --git a/frontend/react/src/ui/components/oltoggle.tsx b/frontend/react/src/ui/components/oltoggle.tsx deleted file mode 100644 index 751b83e1..00000000 --- a/frontend/react/src/ui/components/oltoggle.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { ChangeEvent } from "react"; - -type OlToggleState = { - checked: boolean -} - -type OlToggleProps = { - checkedLabel: string, - uncheckedLabel: string -} - -export class OlToggle extends React.Component { - constructor(props) { - super(props); - - this.state = { - checked: false - } - - this.onToggle = this.onToggle.bind(this); - } - - onToggle(e: ChangeEvent) { - this.setState({ - checked: e.target.checked - }) - } - - render() { - return - } -} \ No newline at end of file diff --git a/frontend/react/src/ui/components/olunitlistentry.tsx b/frontend/react/src/ui/components/olunitlistentry.tsx index e6e3b81c..b9c19571 100644 --- a/frontend/react/src/ui/components/olunitlistentry.tsx +++ b/frontend/react/src/ui/components/olunitlistentry.tsx @@ -1,8 +1,5 @@ -import React, { useId, useState, useRef } from "react"; +import React from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; - -import 'flowbite'; export function OlUnitEntryList(props) { return
diff --git a/frontend/react/src/ui/components/olunitsummary.tsx b/frontend/react/src/ui/components/olunitsummary.tsx index 2bababe6..552383e9 100644 --- a/frontend/react/src/ui/components/olunitsummary.tsx +++ b/frontend/react/src/ui/components/olunitsummary.tsx @@ -2,8 +2,7 @@ import React from "react"; import { UnitBlueprint } from "../../interfaces"; export function OlUnitSummary(props: {blueprint: UnitBlueprint}) { - console.log(props.blueprint) - return
+ return
{props.blueprint.label}
@@ -13,7 +12,7 @@ export function OlUnitSummary(props: {blueprint: UnitBlueprint}) {
{props.blueprint.abilities?.split(" ").map((tag) => { - return
+ return
{tag}
})} diff --git a/frontend/react/src/ui/panels/components/blueprintsaccordion.tsx b/frontend/react/src/ui/panels/components/blueprintsaccordion.tsx deleted file mode 100644 index 39e93946..00000000 --- a/frontend/react/src/ui/panels/components/blueprintsaccordion.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React from "react"; -import { UnitBlueprint } from "../../../interfaces"; -import { IconProp, library } from '@fortawesome/fontawesome-svg-core' -import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - -library.add(faChevronLeft); - -export type BlueprintsAccordionProps = { - title: string, - icon: string, - blueprints: { [key: string]: UnitBlueprint }, - searchString: string, - callback: CallableFunction -} - -export type BlueprintsAccordionState = { - open: boolean -} - -export class BlueprintsAccordion extends React.Component { - constructor(props) { - super(props); - this.state = { - open: this.props.searchString !== '' - } - - this.toggleOpen = this.toggleOpen.bind(this); - } - - toggleOpen() { - this.setState({ open: !this.state.open }); - } - - checkSearch(key) { - const blueprint = this.props.blueprints[key]; - if (blueprint.label.includes(this.props.searchString)) - return true; - else - return false; - } - - render() { - if (this.props.searchString !== '' && !this.state.open) - this.setState({ open: true }); - - return
-
-
{this.props.title}
- -
-
- { - this.state.open && - Object.keys(this.props.blueprints).filter((key) => { - return this.checkSearch(key); - }).map((key) => { - return
this.props.callback(this.props.blueprints[key])}> - -
{this.props.blueprints[key].label}
-
{this.props.blueprints[key].era === "WW2" ? "WW2" : this.props.blueprints[key].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 983b1d20..cbd075c7 100644 --- a/frontend/react/src/ui/panels/components/menu.tsx +++ b/frontend/react/src/ui/panels/components/menu.tsx @@ -1,20 +1,11 @@ -import { Drawer, DrawerInterface } from "flowbite"; -import React, { useEffect, useId, useRef } from "react"; +import React from "react"; export function Menu(props) { - const ref = useRef(null); - const labelId = useId(); - - useEffect(() => { - const drawer: DrawerInterface = new Drawer(ref.current, { backdrop: false }); - props.open ? drawer.show() : drawer.hide(); - }) - - return
-
+ return
+
{props.title}
-