mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
More tests on react components
This commit is contained in:
parent
f056cc62a8
commit
8e9e6749db
16
frontend/react/.vscode/launch.json
vendored
Normal file
16
frontend/react/.vscode/launch.json
vendored
Normal file
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
frontend/react/.vscode/tasks.json
vendored
Normal file
13
frontend/react/.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "dev",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: dev",
|
||||
"detail": "vite --port=8080",
|
||||
"isBackground": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -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";
|
||||
|
||||
@ -473,4 +473,19 @@ export function getGroundElevation(latlng: LatLng, callback: CallableFunction) {
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
@ -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 */
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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 <div id={accordionId} data-accordion="collapse" data-active-classes="bg-white dark:bg-transparent text-gray-900 dark:text-white" data-inactive-classes="text-gray-500 dark:text-gray-400">
|
||||
return <div id={accordionId} data-accordion="collapse" data-active-classes="bg-white dark:bg-transparent text-gray-900 dark:text-white" data-inactive-classes="text-gray-500 dark:text-gray-300">
|
||||
<h3 id={headingId}>
|
||||
<button type="button" className="flex items-center justify-between w-full py-2 font-medium rtl:text-right text-gray-500 border-gray-200 dark:border-gray-700 dark:text-gray-300 gap-3" data-accordion-target={"#" + CSS.escape(bodyId)} aria-expanded="false" aria-controls={bodyId}>
|
||||
<span>{props.title}</span>
|
||||
<svg data-accordion-icon className="w-3 h-3 rotate-180 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" strokeWidth="2" d="M9 5 5 1 1 5" />
|
||||
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5 5 1 1 5" />
|
||||
</svg>
|
||||
</button>
|
||||
</h3>
|
||||
<div id={bodyId} className="hidden relative" aria-labelledby={headingId}>
|
||||
<div className="rotate-180"> {!scrolledUp && <FontAwesomeIcon icon={faArrowCircleDown} className="text-white animate-bounce opacity-20 absolute w-full"/>}</div>
|
||||
<div id={bodyId} className="hidden" aria-labelledby={headingId}>
|
||||
{props.showArrows && <div className="rotate-180"> {!scrolledUp && <FontAwesomeIcon icon={faArrowCircleDown} className="text-white animate-bounce opacity-20 absolute w-full"/>}</div>}
|
||||
<div ref={contentRef} className="py-2 border-gray-200 dark:border-gray-700">
|
||||
{props.children}
|
||||
</div>
|
||||
<div>{!scrolledDown && <FontAwesomeIcon icon={faArrowCircleDown} className="text-white animate-bounce opacity-20 absolute w-full"/>}</div>
|
||||
{props.showArrows && <div>{!scrolledDown && <FontAwesomeIcon icon={faArrowCircleDown} className="text-white animate-bounce opacity-20 absolute w-full"/>}</div>}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -10,14 +10,14 @@ export function OlDropdown(props) {
|
||||
var [value, setValue] = useState(props.items[0] ?? "N/A" )
|
||||
const buttonId = useId();
|
||||
const dropdownId = useId()
|
||||
|
||||
|
||||
return <div>
|
||||
<button id={buttonId} data-dropdown-toggle={dropdownId} className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center border-[1px] dark:border-gray-600 dark:text-gray-400 dark:bg-gray-700 dark:hover:bg-gray-800 dark:focus:ring-blue-800" type="button"><FontAwesomeIcon icon={props.leftIcon} className="mr-3" />{value}<svg className="w-2.5 h-2.5 ms-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4" />
|
||||
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="m1 1 4 4 4-4" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div id={dropdownId} className="z-10 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700">
|
||||
<div id={dropdownId} className="z-ui hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700">
|
||||
<ul className="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby={buttonId}>
|
||||
{props.items.map((item) => {
|
||||
return <li>
|
||||
|
||||
15
frontend/react/src/ui/components/olunitlistentry.tsx
Normal file
15
frontend/react/src/ui/components/olunitlistentry.tsx
Normal file
@ -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 <div {...props} className="relative text-sm cursor-pointer select-none flex justify-between items-center dark:text-gray-300 dark:hover:bg-white dark:hover:bg-opacity-10 px-2 py-1 rounded-sm mr-2">
|
||||
<FontAwesomeIcon icon={props.icon} className="text-sm"></FontAwesomeIcon>
|
||||
<div className="font-normal text-left flex-1 px-2">{props.blueprint.label}</div>
|
||||
<div className="bg-black bg-opacity-20 dark:text-gray-400 rounded-full px-2 py-0.5 text-xs">{props.blueprint.era === "WW2" ? "WW2" : props.blueprint.era.split(" ").map((word) => {
|
||||
return word.charAt(0).toLocaleUpperCase();
|
||||
})}</div>
|
||||
</div>
|
||||
}
|
||||
27
frontend/react/src/ui/components/olunitsummary.tsx
Normal file
27
frontend/react/src/ui/components/olunitsummary.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import { UnitBlueprint } from "../../interfaces";
|
||||
|
||||
export function OlUnitSummary(props: {blueprint: UnitBlueprint}) {
|
||||
console.log(props.blueprint)
|
||||
return <div {...props} className="relative border-l-4 border-blue-600 flex flex-col gap-2 p-2 items-start shadow-lg bg-white rounded-md hover:bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-700">
|
||||
<div className="flex flex-row gap-2 content-center">
|
||||
<img className="object-cover h-8 ml-2 rounded-tl-md rotate-180 invert" src={"images/units/"+props.blueprint.filename} alt="" />
|
||||
<div className="my-auto w-full font-bold tracking-tight text-gray-900 dark:text-white">{props.blueprint.label}</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-between px-2 leading-normal h-fit">
|
||||
<p className="font-normal text-gray-700 dark:text-gray-400">{props.blueprint.description}</p>
|
||||
</div>
|
||||
<div className="flex flex-row gap-2 p-2">
|
||||
{props.blueprint.abilities?.split(" ").map((tag) => {
|
||||
return <div className="bg-black bg-opacity-20 dark:text-gray-400 rounded-full px-2 py-0.5 text-xs">
|
||||
{tag}
|
||||
</div>
|
||||
})}
|
||||
|
||||
<div className="bg-black bg-opacity-20 dark:text-gray-400 rounded-full px-2 py-0.5 text-xs">{props.blueprint.era === "WW2" ? "WW2" : props.blueprint.era.split(" ").map((word) => {
|
||||
return word.charAt(0).toLocaleUpperCase();
|
||||
})}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
}
|
||||
@ -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 <div ref={ref} className="no-scrollbar w-[430px] absolute top-[80px] left-0 z-ui h-screen p-4 overflow-y-auto transition-transform -translate-x-full bg-white dark:bg-gray-800" tabIndex={-1} aria-labelledby={labelId}>
|
||||
return <div ref={ref} className="w-[430px] absolute top-[80px] left-0 z-ui h-screen p-4 overflow-y-auto transition-transform -translate-x-full bg-white dark:bg-gray-800" tabIndex={-1} aria-labelledby={labelId}>
|
||||
<h5 id={labelId} className="w-full inline-flex items-center pb-3 mb-4 border-b-2 dark:border-gray-700 text-base font-semibold text-gray-500 dark:text-gray-400">
|
||||
{props.title}
|
||||
</h5>
|
||||
<button type="button" onClick={props.closeCallback} className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 absolute top-2.5 end-2.5 flex items-center justify-center dark:hover:bg-gray-600 dark:hover:text-white" >
|
||||
<svg className="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" strokeWidth={2} d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
||||
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
|
||||
</svg>
|
||||
<span className="sr-only">Close menu</span>
|
||||
</button>
|
||||
|
||||
@ -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 <Menu {...props} title="Spawn menu" titleIcon="fa-solid fa-plus">
|
||||
<OlSearchBar className="mb-4" />
|
||||
<OlAccordion title="Aircraft">
|
||||
<div className="flex flex-col gap-2 no-scrollbar max-h-80 overflow-scroll">
|
||||
{getApp() && Object.keys(getApp().getAircraftDatabase().blueprints).map((key) => {
|
||||
return <div className="text-sm text-gray-300 font-thin"><FontAwesomeIcon icon={faJetFighter} className="text-sm mr-2" /> {getApp().getAircraftDatabase().blueprints[key].label}</div>;
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title="Helicopter"></OlAccordion>
|
||||
<OlAccordion title="Air Defense"></OlAccordion>
|
||||
</Menu>
|
||||
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 <Menu {...props} title="Spawn menu" titleIcon="fa-solid fa-plus">
|
||||
{!blueprint && <div>
|
||||
<OlSearchBar className="mb-4" />
|
||||
<OlAccordion title="Aircrafts">
|
||||
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
|
||||
{Object.keys(filteredAircraft).map((key) => {
|
||||
const blueprint = getApp().getAircraftDatabase().blueprints[key];
|
||||
return <OlUnitEntryList key={key} icon={faJetFighter} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title="Helicopters">
|
||||
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
|
||||
{Object.keys(filteredHelicopters).map((key) => {
|
||||
return <OlUnitEntryList key={key} icon={faHelicopter} blueprint={getApp().getHelicopterDatabase().blueprints[key]} />
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title="Air defence (SAM & AAA)">
|
||||
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
|
||||
{Object.keys(filteredAirDefense).map((key) => {
|
||||
return <OlUnitEntryList key={key} icon={faShieldAlt} blueprint={getApp().getGroundUnitDatabase().blueprints[key]} />
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title="Ground units">
|
||||
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
|
||||
{Object.keys(filteredGroundUnits).map((key) => {
|
||||
const blueprint = getApp().getGroundUnitDatabase().blueprints[key];
|
||||
return <OlUnitEntryList key={key} icon={faTruck} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title="Ships and submarines">
|
||||
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
|
||||
{Object.keys(filteredNavyUnits).map((key) => {
|
||||
return <OlUnitEntryList key={key} icon={faShip} blueprint={getApp().getNavyUnitDatabase().blueprints[key]} />
|
||||
})}
|
||||
</div>
|
||||
</OlAccordion>
|
||||
<OlAccordion title="Effects (smokes, explosions etc)">
|
||||
|
||||
</OlAccordion>
|
||||
</div>
|
||||
}
|
||||
|
||||
{blueprint && <UnitSpawnMenu blueprint={blueprint} />}
|
||||
</Menu>
|
||||
}
|
||||
8
frontend/react/src/ui/panels/unitspawnmenu.tsx
Normal file
8
frontend/react/src/ui/panels/unitspawnmenu.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import React from "react";
|
||||
import { OlUnitSummary } from "../components/olunitsummary";
|
||||
|
||||
export function UnitSpawnMenu(props) {
|
||||
return <div>
|
||||
<OlUnitSummary blueprint={props.blueprint}/>
|
||||
</div>
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user