More work on React components

This commit is contained in:
Davide Passoni
2024-04-09 18:12:05 +02:00
parent 8e9e6749db
commit 45e290d656
33 changed files with 528 additions and 500 deletions

View File

@@ -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 <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}>
return <div className="bg-white dark:bg-transparent text-gray-900 dark:text-white">
<h3>
<button type="button" onClick={() => setOpen(!open)} className="flex items-center justify-between w-full py-2 font-medium rtl:text-right text-gray-700 border-gray-200 dark:border-gray-700 dark:text-gray-300 gap-3">
<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">
<svg data-open={open} className="w-3 h-3 data-[open='false']:-scale-y-100 shrink-0 transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5 5 1 1 5" />
</svg>
</button>
</h3>
<div id={bodyId} className="hidden" aria-labelledby={headingId}>
<div className={open? "": "hidden"}>
{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}

View File

@@ -0,0 +1,7 @@
import React, { useState, useId } from "react";
export function OlCheckbox(props) {
const id = useId();
return <input id={id} onChange={props.onChange} type="checkbox" checked={props.checked} value="" className="w-4 h-4 my-auto text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
}

View File

@@ -0,0 +1,9 @@
import React from "react";
export function OlCoalitionToggle() {
return <div className="inline-flex items-center cursor-pointer">
<input type="checkbox" value="" className="sr-only peer" />
<div className="relative w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:start-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 after:w-6 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600"></div>
<span className="ms-3 text-sm font-medium text-gray-900 dark:text-gray-300">Large toggle</span>
</div>
}

View File

@@ -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 <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" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="m1 1 4 4 4-4" />
</svg>
</button>
<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}>
<div id={dropdownId} className="z-ui-2 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700">
<div className="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby={buttonId}>
{props.items.map((item) => {
return <li>
<a href="#" onClick={() => setValue(item)} className="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">{item}</a>
</li>
return <OlDropdownItem onClick={() => setValue(item)}>
{item}
</OlDropdownItem>
})}
</ul>
</div>
</div>
</div>
}
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 <div className="relative">
<button ref={buttonRef} onClick={() => {setOpen(!open)}} 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" />{props.label}<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" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="m1 1 4 4 4-4" />
</svg>
</button>
<div ref={contentRef} data-open={open} className="absolute z-ui-2 w-fit data-[open='false']:hidden bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700">
<div className="py-2 text-sm text-gray-700 dark:text-gray-200 w-fit">
{props.children}
</div>
</div>
</div>
}
export function OlDropdownItem(props) {
return <div onClick={props.onClick ?? (() => { })} 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}
</div>
}

View File

@@ -0,0 +1,11 @@
import React, { useState } from "react";
export function OlLabelToggle(props) {
var [toggled, setToggled] = useState(false);
return <div onClick={() => {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">
<span data-toggled={toggled} className="absolute my-auto h-8 w-14 bg-blue-500 rounded-md data-[toggled='true']:translate-x-16 transition-transform"></span>
<span data-active={!toggled} className="my-auto dark:data-[active='true']:text-white dark:data-[active='false']:text-gray-400 text-thin pl-3 z-ui-2 transition-colors">MSL</span>
<span data-active={toggled} className="my-auto dark:data-[active='true']:text-white dark:data-[active='false']:text-gray-400 text-thin pr-3 z-ui-2 transition-colors">AGL</span>
</div>
}

View File

@@ -0,0 +1,19 @@
import React, {useEffect, useId} from "react";
export function OlNumberInput(props) {
return <div className="w-fit">
<div className="relative flex items-center max-w-[8rem]">
<button type="button" className="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:border-gray-600 hover:bg-gray-200 border border-gray-300 rounded-s-lg p-3 h-9 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none">
<svg className="w-3 h-3 text-gray-900 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 2">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M1 1h16"/>
</svg>
</button>
<input type="text" className="bg-gray-50 border-x-0 border-gray-300 h-9 text-center text-gray-900 text-sm focus:ring-blue-500 focus:border-blue-500 block w-full py-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={props.placeHolder} />
<button type="button" className="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:border-gray-600 hover:bg-gray-200 border border-gray-300 rounded-e-lg p-3 h-9 focus:ring-gray-100 dark:focus:ring-gray-700 focus:ring-2 focus:outline-none">
<svg className="w-3 h-3 text-gray-900 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 1v16M1 9h16"/>
</svg>
</button>
</div>
</div>
}

View File

@@ -0,0 +1,11 @@
import React, { useState } from "react";
export function OlRangeSlider(props) {
return <input type="range"
onChange={(ev) => { 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" />
}

View File

@@ -2,10 +2,19 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import React from "react"
export function OlStateButton(props) {
return <button {...props} type="button" className={`h-[40px] w-[40px] m-auto text-gray-900 bg-white border border-gray-300 focus:outline-none
hover:bg-gray-100 focus:ring-4 focus:ring-gray-100 font-medium rounded-lg text-sm
dark:bg-${props.checked? 'white': 'transparent'} dark:text-${props.checked? 'gray-900': 'white'} dark:border-gray-600
dark:hover:bg-${props.checked? 'white': 'gray-700'} dark:hover:border-gray-600 dark:focus:ring-gray-700`}>
const className = (props.className ?? '') + ` h-[40px] w-[40px] m-auto border border-gray-900 font-medium rounded-md 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 <button onClick={props.onClick} data-checked={props.checked} type="button" className={className}>
<FontAwesomeIcon icon={props.icon} />
</button>
}
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 <button onClick={props.onClick} data-checked={props.checked} type="button" className={className}>
<FontAwesomeIcon icon={props.icon} />
</button>
}

View File

@@ -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<OlToggleProps, OlToggleState> {
constructor(props) {
super(props);
this.state = {
checked: false
}
this.onToggle = this.onToggle.bind(this);
}
onToggle(e: ChangeEvent<HTMLInputElement>) {
this.setState({
checked: e.target.checked
})
}
render() {
return <label className="flex items-center cursor-pointer -mr-8">
<input type="checkbox" value="" className="sr-only peer" onChange={this.onToggle}/>
<div className="relative w-16 h-6 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-500 peer-checked:after:translate-x-[200%] rtl:peer-checked:after:-translate-x-[200%] peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-interaction-blue"></div>
<span className={(this.state.checked? "-translate-x-[250%]": "-translate-x-[180%]") + " ms-3 text-sm font-medium text-gray-900 dark:text-white transition-all select-none relative w-7"}>
{this.state.checked? this.props.checkedLabel: this.props.uncheckedLabel}
</span>
</label>
}
}

View File

@@ -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 <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">

View File

@@ -2,8 +2,7 @@ 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">
return <div {...props} className="relative border-l-4 border-blue-600 flex flex-col gap-2 p-2 pt-4 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>
@@ -13,7 +12,7 @@ export function OlUnitSummary(props: {blueprint: UnitBlueprint}) {
</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">
return <div key={tag} className="bg-black bg-opacity-20 dark:text-gray-400 rounded-full px-2 py-0.5 text-xs">
{tag}
</div>
})}

View File

@@ -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<BlueprintsAccordionProps, BlueprintsAccordionState> {
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 <div>
<div className="cursor-pointer select-none flex justify-between p-2 items-center" onClick={this.toggleOpen}>
<div className="">{this.props.title}</div>
<FontAwesomeIcon icon="chevron-left" className={"transition-transform " + (this.state.open ? "-rotate-90" : "")}></FontAwesomeIcon>
</div>
<div className="flex flex-col px-3">
{
this.state.open &&
Object.keys(this.props.blueprints).filter((key) => {
return this.checkSearch(key);
}).map((key) => {
return <div key={key} className="cursor-pointer select-none flex justify-between items-center hover:bg-white hover:bg-opacity-10 px-2 py-1 rounded-sm" onClick={() => this.props.callback(this.props.blueprints[key])}>
<FontAwesomeIcon icon={this.props.icon as IconProp} className="text-sm"></FontAwesomeIcon>
<div className="font-normal text-left flex-1 px-2">{this.props.blueprints[key].label}</div>
<div className="bg-black bg-opacity-20 text-gray-400 rounded-full px-2 py-0.5 text-xs">{this.props.blueprints[key].era === "WW2" ? "WW2" : this.props.blueprints[key].era.split(" ").map((word) => {
return word.charAt(0).toLocaleUpperCase();
})}</div>
</div>
})
}
</div>
</div>
}
}

View File

@@ -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 <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">
return <div data-open={props.open} className="w-[430px] absolute top-[80px] left-20 z-ui-0 h-screen p-4 overflow-y-auto transition-transform data-[open='false']:-translate-x-full bg-gray-200 dark:bg-gray-800" tabIndex={-1}>
<h5 className="w-full inline-flex items-center pb-3 mb-4 border-b-2 dark:border-gray-700 text-base font-semibold text-gray-900 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" >
<button type="button" onClick={props.closeCallback} className="text-gray-900 dark: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" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>

View File

@@ -1,10 +1,13 @@
import React from 'react'
import { OlStateButton } from '../components/olstatebutton';
import { faPlus, faGamepad, faRuler, faPencil, faMap } from '@fortawesome/free-solid-svg-icons';
import React, { useEffect } from 'react'
import { OlRoundStateButton } from '../components/olstatebutton';
import { faPlus, faGamepad, faRuler, faPencil, faMap, faLock, faPerson, faBrain, faRobot, faJetFighter, faHelicopter, faShield, faTruck, faShip, faPlaneDeparture, faSkull, faShieldAlt, faGears } from '@fortawesome/free-solid-svg-icons';
import { library } from '@fortawesome/fontawesome-svg-core'
import { EventsConsumer } from '../../eventscontext';
import { StateConsumer } from '../../statecontext';
import { OlDropdown } from '../components/oldropdown';
import { OlDropdownItem, OlElementDropdown, OlTextDropdown } from '../components/oldropdown';
import { OlCheckbox } from '../components/olcheckbox';
import { MAP_OPTIONS_DEFAULTS, MAP_OPTIONS_TOOLTIPS } from '../../constants/constants';
import { getApp } from '../../olympusapp';
library.add(faPlus, faGamepad, faRuler, faPencil, faMap);
@@ -13,16 +16,72 @@ export function Header(props) {
{(appState) =>
<EventsConsumer>
{(events) =>
<nav className="bg-white border-gray-200 dark:bg-[#171C26] dark:border-gray-700">
<div className="max-w-screen flex flex-wrap items-center justify-between p-4">
<nav className="bg-gray-300 border-gray-200 dark:bg-[#171C26] dark:border-gray-700">
<div className="max-w-screen flex flex-wrap items-center justify-between p-4 gap-3">
<div className="flex flex-row items-center justify-center gap-1">
<img src="images/icon.png" className='h-12 border-2 p-0 bg-white rounded-md mr-2 cursor-pointer' onClick={events.toggleMainMenuVisible}></img>
<OlStateButton onClick={events.toggleSpawnMenuVisible} checked={appState.spawnMenuVisible} icon="fa-solid fa-plus"></OlStateButton>
<OlStateButton onClick={events.toggleUnitControlMenuVisible} checked={appState.unitControlMenuVisible} icon="fa-solid fa-gamepad"></OlStateButton>
<OlStateButton onClick={events.toggleMeasureMenuVisible} checked={appState.measureMenuVisible} icon="fa-solid fa-ruler"></OlStateButton>
<OlStateButton onClick={events.toggleDrawingMenuVisible} checked={appState.drawingMenuVisible} icon="fa-solid fa-pencil"></OlStateButton>
<img src="images/icon.png" className='h-12 p-0 rounded-md mr-2 cursor-pointer' onClick={events.toggleMainMenuVisible}></img>
</div>
<OlDropdown items={["DCS Sat", "DCS Alt"]} leftIcon='fa-solid fa-map' />
<div className="ml-auto">
<OlRoundStateButton icon={faLock} />
</div>
<div className="flex flex-row h-fit items-center justify-start gap-1">
{
Object.entries({
'human': faPerson,'olympus': faBrain, 'dcs': faRobot
}).map((entry) => {
return <OlRoundStateButton
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]} />
})
}
</div>
<div className='h-10 w-0 border-l-2 border-gray-500'></div>
<div className="flex flex-row h-fit items-center justify-start gap-1">
{
Object.entries({
'aircraft': faJetFighter,'helicopter': faHelicopter, 'groundunit-sam': faShieldAlt,
'groundunit': faTruck, 'navyunit': faShip, 'airbase': faPlaneDeparture, 'dead': faSkull
}).map((entry) => {
return <OlRoundStateButton
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]} />
})
}
</div>
<div className='h-10 w-0 border-l-2 border-gray-500'></div>
<div className="flex flex-row h-fit items-center justify-start gap-1">
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType( 'blue', !appState.mapHiddenTypes['blue'] )}
checked={!appState.mapHiddenTypes['blue']}
icon={faShield} className={"!text-blue-500"} />
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType('red', !appState.mapHiddenTypes['red'] )}
checked={!appState.mapHiddenTypes['red']}
icon={faShield} className={"!text-red-500"} />
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType('neutral', !appState.mapHiddenTypes['neutral'] )}
checked={!appState.mapHiddenTypes['neutral']}
icon={faShield} className={"!text-gray-500"} />
</div>
<OlTextDropdown items={["DCS Sat", "DCS Alt"]} leftIcon='fa-solid fa-map' />
<OlElementDropdown leftIcon={faGears} label="Options" className="w-80">
{Object.keys(MAP_OPTIONS_TOOLTIPS).map((key) => {
return <OlDropdownItem>
<OlCheckbox
checked = {appState.mapOptions[key]}
onChange = {(ev) => {
getApp().getMap()?.setOption(key, ev.target.checked);
}}/>
<span className="text-nowrap">{ MAP_OPTIONS_TOOLTIPS[key] }</span>
</OlDropdownItem>
})}
</OlElementDropdown>
</div>
</nav>
}

View File

@@ -7,13 +7,13 @@ import { faGithub } from "@fortawesome/free-brands-svg-icons";
export function MainMenu(props) {
return <Menu {...props} title="DCS Olympus">
<div className="flex flex-col gap-2 text-md font-normal font text-white">
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center text-[#8BFF63]"><FontAwesomeIcon icon={faCheckCircle} className="my-auto" />Version {VERSION}</div>
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faGithub} className="my-auto text-gray-400" />Overview</div>
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faFileAlt} className="my-auto text-gray-400" />User guide</div>
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faDatabase} className="my-auto text-gray-400" />Database Manager</div>
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faFileExport} className="my-auto text-gray-400" />Export to file</div>
<div className="flex gap-3 p-1 cursor-pointer select-none dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faFileImport} className="my-auto text-gray-400" />Import from file</div>
<div className="flex flex-col gap-2 text-md font-normal font text-gray-900 dark:text-white">
<div className="flex gap-3 p-1 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-white/10 rounded-sm content-center text-green-700 dark:text-[#8BFF63]"><FontAwesomeIcon icon={faCheckCircle} className="my-auto" />Version {VERSION}</div>
<div className="flex gap-3 p-1 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faGithub} className="my-auto text-gray-800 dark:text-gray-400" />Overview</div>
<div className="flex gap-3 p-1 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faFileAlt} className="my-auto text-gray-800 dark:text-gray-400" />User guide</div>
<div className="flex gap-3 p-1 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faDatabase} className="my-auto text-gray-800 dark:text-gray-400" />Database Manager</div>
<div className="flex gap-3 p-1 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faFileExport} className="my-auto text-gray-800 dark:text-gray-400" />Export to file</div>
<div className="flex gap-3 p-1 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-white/10 rounded-sm content-center"><FontAwesomeIcon icon={faFileImport} className="my-auto text-gray-800 dark:text-gray-400" />Import from file</div>
</div>
</Menu>
}

View File

@@ -0,0 +1,29 @@
import React from 'react'
import { OlStateButton } from '../components/olstatebutton';
import { faPlus, faGamepad, faRuler, faPencil, faMap } from '@fortawesome/free-solid-svg-icons';
import { library } from '@fortawesome/fontawesome-svg-core'
import { EventsConsumer } from '../../eventscontext';
import { StateConsumer } from '../../statecontext';
library.add(faPlus, faGamepad, faRuler, faPencil, faMap);
export function SideBar(props) {
return <StateConsumer>
{(appState) =>
<EventsConsumer>
{(events) =>
<nav className="z-ui-1 h-full bg-gray-300 border-gray-200 dark:bg-[#171C26] dark:border-gray-700">
<div className="flex flex-wrap items-center justify-center p-4 w-20">
<div className="flex flex-col items-center justify-center gap-1">
<OlStateButton onClick={events.toggleSpawnMenuVisible} checked={appState.spawnMenuVisible} icon="fa-solid fa-plus"></OlStateButton>
<OlStateButton onClick={events.toggleUnitControlMenuVisible} checked={appState.unitControlMenuVisible} icon="fa-solid fa-gamepad"></OlStateButton>
<OlStateButton onClick={events.toggleMeasureMenuVisible} checked={appState.measureMenuVisible} icon="fa-solid fa-ruler"></OlStateButton>
<OlStateButton onClick={events.toggleDrawingMenuVisible} checked={appState.drawingMenuVisible} icon="fa-solid fa-pencil"></OlStateButton>
</div>
</div>
</nav>
}
</EventsConsumer>
}
</StateConsumer>
}

View File

@@ -6,7 +6,6 @@ import { OlSearchBar } from "../components/olsearchbar";
import { OlAccordion } from "../components/olaccordion";
import { getApp } from "../../olympusapp";
import { OlUnitEntryList } from "../components/olunitlistentry";
import { UnitBlueprint } from "../../interfaces";
import { UnitSpawnMenu } from "./unitspawnmenu";
library.add(faPlus);

View File

@@ -1,8 +1,28 @@
import React from "react";
import React, {useState} from "react";
import { OlUnitSummary } from "../components/olunitsummary";
import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
import { OlNumberInput } from "../components/olnumberinput";
import { OlLabelToggle } from "../components/ollabeltoggle";
import { OlRangeSlider } from "../components/olrangeslider";
export function UnitSpawnMenu(props) {
return <div>
var [spawnAltitude, setSpawnAltitude] = useState(1000);
return <div className="flex flex-col gap-3">
<OlUnitSummary blueprint={props.blueprint}/>
<div className="flex flex-row content-center justify-between w-full">
<OlCoalitionToggle />
<OlNumberInput placeHolder={1} minValue={1} maxValue={4}/>
</div>
<div>
<div className="flex flex-row content-center justify-between">
<div className="flex flex-col">
<span className="dark: text-white">Altitude</span>
<span className="dark:text-blue-500">{`${spawnAltitude} FT`}</span>
</div>
<OlLabelToggle />
</div>
<OlRangeSlider onChange={setSpawnAltitude} minValue={0} maxValue={30000} step={500}/>
</div>
</div>
}

View File

@@ -0,0 +1,29 @@
/* 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 {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}

View File

@@ -0,0 +1,91 @@
import React, { useState, useEffect } from 'react'
import './ui.css'
import { EventsProvider } from '../eventscontext'
import { StateProvider } from '../statecontext'
import { Header } from './panels/header'
import { SpawnMenu } from './panels/spawnmenu'
import { UnitControlMenu } from './panels/unitcontrolmenu'
import { MainMenu } from './panels/mainmenu'
import { SideBar } from './panels/sidebar';
import { MapHiddenTypes, MapOptions } from '../types/types'
import { MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from '../constants/constants'
import { getApp } from '../olympusapp'
import { Dropdown } from 'flowbite'
export type OlympusState = {
mainMenuVisible: boolean,
spawnMenuVisible: boolean,
unitControlMenuVisible: boolean,
measureMenuVisible: boolean,
drawingMenuVisible: boolean,
mapHiddenTypes: MapHiddenTypes;
mapOptions: MapOptions;
}
export function UI(props) {
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);
var [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
var [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
document.addEventListener("hiddenTypesChanged", (ev) => {
setMapHiddenTypes({...getApp().getMap().getHiddenTypes()});
})
document.addEventListener("mapOptionsChanged", (ev) => {
setMapOptions({...getApp().getMap().getOptions()});
})
function hideAllMenus() {
setMainMenuVisible(false);
setSpawnMenuVisible(false);
setUnitControlMenuVisible(false);
setMeasureMenuVisible(false);
setDrawingMenuVisible(false);
}
return (
<div className="absolute top-0 left-0 h-screen w-screen font-sans overflow-hidden">
<StateProvider value={{
mainMenuVisible: mainMenuVisible,
spawnMenuVisible: spawnMenuVisible,
unitControlMenuVisible: unitControlMenuVisible,
measureMenuVisible: measureMenuVisible,
drawingMenuVisible: drawingMenuVisible,
mapOptions: mapOptions,
mapHiddenTypes: mapHiddenTypes
}}>
<EventsProvider value={
{
setMainMenuVisible: setMainMenuVisible,
setSpawnMenuVisible: setSpawnMenuVisible,
setUnitControlMenuVisible: setUnitControlMenuVisible,
setDrawingMenuVisible: setDrawingMenuVisible,
setMeasureMenuVisible: setMeasureMenuVisible,
toggleMainMenuVisible: () => { hideAllMenus(); setMainMenuVisible(!mainMenuVisible) },
toggleSpawnMenuVisible: () => { hideAllMenus(); setSpawnMenuVisible(!spawnMenuVisible) },
toggleUnitControlMenuVisible: () => { hideAllMenus(); setUnitControlMenuVisible(!unitControlMenuVisible) },
toggleMeasureMenuVisible: () => { hideAllMenus(); setMeasureMenuVisible(!measureMenuVisible) },
toggleDrawingMenuVisible: () => { hideAllMenus(); setDrawingMenuVisible(!drawingMenuVisible) },
}
}>
<div className='absolute top-0 left-0 h-full w-full flex flex-col'>
<Header />
<div className='flex h-full'>
<SideBar />
<div id='map-container' className='relative h-full w-screen top-0 left-0' />
<MainMenu open={mainMenuVisible} closeCallback={() => setMainMenuVisible(false)} />
<SpawnMenu open={spawnMenuVisible} closeCallback={() => setSpawnMenuVisible(false)} />
<UnitControlMenu open={unitControlMenuVisible} closeCallback={() => setUnitControlMenuVisible(false)} />
</div>
</div>
</EventsProvider>
</StateProvider>
</div>
)
}