Testing work with flowbite

This commit is contained in:
Davide Passoni
2024-04-05 17:50:59 +02:00
parent d72a00a005
commit 90ccf57f01
577 changed files with 18740 additions and 345 deletions

View File

@@ -1,14 +1,14 @@
import { createContext } from "react";
export const EventsContext = createContext({
showSpawnMenu: (e: boolean) => {},
toggleSpawnMenu: () => {},
showUnitControlMenu: (e: boolean) => {},
toggleUnitControlMenu: () => {},
showMeasureMenu: (e: boolean) => {},
toggleMeasureMenu: () => {},
showDrawingMenu: (e: boolean) => {},
toggleDrawingMenu: () => {}
setSpawnMenuVisible: (e: boolean) => {},
setUnitControlMenuVisible: (e: boolean) => {},
setMeasureMenuVisible: (e: boolean) => {},
setDrawingMenuVisible: (e: boolean) => {},
toggleSpawnMenuVisible: () => {},
toggleUnitControlMenuVisible: () => {},
toggleMeasureMenuVisible: () => {},
toggleDrawingMenuVisible: () => {},
})
export const EventsProvider = EventsContext.Provider;

View File

@@ -1,73 +1,5 @@
@import "../node_modules/leaflet/dist/leaflet.css";
:root {
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,9 +1,10 @@
/***************** UI *******************/
import React from 'react'
import ReactDOM from 'react-dom/client'
import UI from './ui.js'
import './index.css'
import { setupApp } from './olympusapp.js'
import 'flowbite';
import { UI } from './ui.js';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>

View File

@@ -23,6 +23,14 @@ import { DestinationPreviewHandle } from "./markers/destinationpreviewHandle";
import { ContextActionSet } from "../unit/contextactionset";
import { DCSLayer } from "./dcslayer";
import './markers/stylesheets/airbase.css'
import './markers/stylesheets/bullseye.css'
import './markers/stylesheets/units.css'
// Temporary
import './theme.css'
var hasTouchScreen = false;
//if ("maxTouchPoints" in navigator)
// hasTouchScreen = navigator.maxTouchPoints > 0;

View File

@@ -0,0 +1,27 @@
.airbase-icon {
align-items: center;
cursor: pointer;
display: flex;
justify-content: center;
position: relative;
width: 40px;
height: 40px;
}
.airbase-icon svg {
width: 40px;
height: 40px;
}
.airbase-icon[data-coalition="red"] svg * {
stroke: var(--unit-background-red);
}
.airbase-icon[data-coalition="blue"] svg * {
stroke: var(--unit-background-blue);
}
.airbase-icon[data-coalition="neutral"] svg * {
stroke: var(--unit-background-neutral);
}

View File

@@ -0,0 +1,24 @@
.bullseye-icon {
align-items: center;
cursor: pointer;
display: flex;
justify-content: center;
position: relative;
width: 100%;
height: 100%;
}
.bullseye-icon[data-coalition="red"] svg * {
stroke: var(--unit-background-red);
fill: var(--unit-background-red);
}
.bullseye-icon[data-coalition="blue"] svg * {
stroke: var(--unit-background-blue);
fill: var(--unit-background-blue);
}
.bullseye-icon[data-coalition="neutral"] svg * {
stroke: var(--unit-background-neutral);
fill: var(--unit-background-neutral);
}

View File

@@ -0,0 +1,383 @@
/*** Unit marker elements ***/
[data-object|="unit"] {
align-items: center;
cursor: pointer;
display: flex;
height: 100%;
justify-content: center;
position: relative;
width: 100%;
}
.unit-vvi {
align-self: center;
background: var(--secondary-gunmetal-grey);
display: flex;
justify-self: center;
padding-bottom: calc((var(--unit-width) / 2) + var(--unit-stroke-width));
position: absolute;
transform-origin: bottom;
translate: 0 -50%;
width: var(--unit-vvi-width);
}
.unit-hotgroup {
align-content: center;
background-color: var(--background-steel);
border-radius: var(--border-radius-xs);
display: none;
height: 15px;
justify-content: center;
position: absolute;
transform: rotate(-45deg);
translate: 0 -200%;
width: 15px;
}
.unit-hotgroup-id {
background-color: transparent;
color: white;
font-size: 9px;
font-weight: bolder;
transform: rotate(45deg);
translate: -1px 1px;
}
.unit-icon {
height: var(--unit-height);
position: absolute;
transform-origin: center;
width: var(--unit-width);
}
.unit-icon svg {
height: 100%;
width: 100%;
}
[data-is-selected] .unit-icon::before {
background-color: var(--unit-spotlight-fill);
border-radius: 50%;
content: "";
height: 100%;
position: absolute;
width: 100%;
z-index: -1;
}
/*** Basic colours ***/
[data-coalition="blue"] .unit-icon svg>*:first-child {
fill: var(--unit-background-blue);
}
[data-coalition="red"] .unit-icon svg>*:first-child {
fill: var(--unit-background-red);
}
[data-coalition="neutral"] .unit-icon svg>*:first-child {
fill: var(--unit-background-neutral);
}
[data-is-selected] .unit-icon svg>*:first-child {
fill: white;
}
[data-is-highlighted] .unit-icon svg>*:first-child {
stroke: white;
}
/*** Cursors ***/
[data-is-dead],
[data-object|="unit-missile"],
[data-object|="unit-bomb"] {
cursor: default;
}
/*** Labels ***/
[data-object|="unit"] .unit-short-label {
color: var(--secondary-gunmetal-grey);
font-size: var(--unit-font-size);
font-weight: var(--unit-font-weight);
line-height: normal;
position: absolute;
}
[data-object|="unit-groundunit"] .unit-short-label {
transform: translateY(7px);
}
/*** Health indicator ***/
[data-object|="unit"] .unit-health {
background: white;
border: var(--unit-health-border-width) solid var(--secondary-dark-steel);
border-radius: var(--border-radius-sm);
display: none;
height: var(--unit-health-height);
position: absolute;
translate: var(--unit-health-x) var(--unit-health-y);
width: var(--unit-health-width);
}
/*** Fuel indicator ***/
[data-object|="unit"] .unit-fuel {
background: white;
border: var(--unit-fuel-border-width) solid var(--secondary-dark-steel);
border-radius: var(--border-radius-sm);
display: none;
height: var(--unit-fuel-height);
position: absolute;
translate: var(--unit-fuel-x) var(--unit-fuel-y);
width: var(--unit-fuel-width);
}
[data-object|="unit"] .unit-fuel-level,
[data-object|="unit"] .unit-health-level {
background-color: var(--secondary-light-grey);
height: 100%;
width: 100%;
}
/*** Ammo indicator ***/
[data-object|="unit"] .unit-ammo {
column-gap: var(--unit-ammo-spacing);
display: none;
height: fit-content;
position: absolute;
translate: var(--unit-ammo-x) var(--unit-ammo-y);
width: fit-content;
}
[data-object|="unit"] .unit-ammo>* {
background-color: white;
border: var(--unit-ammo-border-width) solid var(--secondary-dark-steel);
border-radius: 50%;
padding: var(--unit-ammo-radius);
}
/*** Unit summary ***/
[data-object|="unit"] .unit-summary {
color: white;
column-gap: 6px;
display: flex;
flex-wrap: wrap;
font-size: 11px;
font-weight: bold;
justify-content: right;
line-height: 12px;
pointer-events: none;
position: absolute;
row-gap: 1px;
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
right: 100%;
width: fit-content;
}
[data-hide-labels] [data-object|="unit"] .unit-summary {
display: none;
}
[data-object|="unit"] .unit-summary>* {
padding: 1px;
}
[data-object|="unit"] .unit-summary .unit-callsign {
color: white;
overflow: hidden;
text-align: right;
transform-origin: right;
white-space: nowrap;
width: 80px;
}
[data-object|="unit"]:hover .unit-summary .unit-callsign{
direction: rtl;
overflow: visible;
}
/*** Common ***/
[data-object|="unit"]:hover .unit-ammo,
[data-object|="unit"]:hover .unit-health ,
[data-object|="unit"]:hover .unit-fuel {
display: flex;
}
[data-object|="unit"][data-has-low-fuel] .unit-fuel, [data-object|="unit"][data-has-low-health] .unit-health {
animation: pulse 1.5s linear infinite;
}
[data-object|="unit"][data-is-in-hotgroup] .unit-hotgroup,
[data-object|="unit"][data-is-selected] .unit-ammo,
[data-object|="unit"][data-is-selected] .unit-fuel,
[data-object|="unit"][data-is-selected] .unit-health,
[data-object|="unit"][data-is-selected] .unit-selected-spotlight {
display: flex;
}
[data-object|="unit"][data-has-fox-1] .unit-ammo>div:nth-child(1),
[data-object|="unit"][data-has-fox-2] .unit-ammo>div:nth-child(2),
[data-object|="unit"][data-has-fox-3] .unit-ammo>div:nth-child(3),
[data-object|="unit"][data-has-other-ammo] .unit-ammo>div:nth-child(4) {
background-color: var(--secondary-gunmetal-grey);
}
[data-object|="unit"][data-coalition="blue"][data-is-selected] .unit-short-label {
color: var(--secondary-blue-text);
}
[data-object|="unit"][data-coalition="blue"] .unit-fuel-level,
[data-object|="unit"][data-coalition="blue"] .unit-health-level,
[data-object|="unit"][data-coalition="blue"][data-has-fox-1] .unit-ammo>div:nth-child(1),
[data-object|="unit"][data-coalition="blue"][data-has-fox-2] .unit-ammo>div:nth-child(2),
[data-object|="unit"][data-coalition="blue"][data-has-fox-3] .unit-ammo>div:nth-child(3),
[data-object|="unit"][data-coalition="blue"][data-has-other-ammo] .unit-ammo>div:nth-child(4) {
background-color: var(--primary-blue);
}
[data-object|="unit"][data-coalition="blue"] .unit-vvi {
background-color: var(--secondary-blue-outline);
}
[data-object|="unit"][data-coalition="red"][data-is-selected] .unit-short-label {
color: var(--secondary-red-text);
}
[data-object|="unit"][data-coalition="red"] .unit-fuel-level,
[data-object|="unit"][data-coalition="red"] .unit-health-level,
[data-object|="unit"][data-coalition="red"][data-has-fox-1] .unit-ammo>div:nth-child(1),
[data-object|="unit"][data-coalition="red"][data-has-fox-2] .unit-ammo>div:nth-child(2),
[data-object|="unit"][data-coalition="red"][data-has-fox-3] .unit-ammo>div:nth-child(3),
[data-object|="unit"][data-coalition="red"][data-has-other-ammo] .unit-ammo>div:nth-child(4) {
background-color: var(--primary-red);
}
[data-object|="unit"][data-coalition="blue"] .unit-vvi {
background-color: var(--secondary-red-outline);
}
/*** Unit state ***/
[data-object|="unit"] .unit-state {
background-repeat: no-repeat;
height: 20px;
position: absolute;
width: 20px;
left: 0px;
top: 0px;
}
[data-object|="unit"][data-state="rtb"] .unit-state {
background-image: url("/resources/theme/images/states/rtb.svg");
}
[data-object|="unit"][data-state="land"] .unit-state {
background-image: url("/resources/theme/images/states/rtb.svg");
}
[data-object|="unit"][data-state="idle"] .unit-state {
background-image: url("/resources/theme/images/states/idle.svg");
}
[data-object*="groundunit"][data-state="idle"] .unit-state,
[data-object*="navyunit"][data-state="idle"] .unit-state {
background-image: url(""); /* To avoid clutter, dont show the idle state for non flying units */
}
[data-object|="unit"][data-state="attack"] .unit-state,
[data-object|="unit"][data-state="bomb-point"] .unit-state,
[data-object|="unit"][data-state="carpet-bombing"] .unit-state,
[data-object|="unit"][data-state="fire-at-area"] .unit-state {
background-image: url("/resources/theme/images/states/attack.svg");
}
[data-object|="unit"][data-state="follow"] .unit-state {
background-image: url("/resources/theme/images/states/follow.svg");
}
[data-object|="unit"][data-state="refuel"] .unit-state {
background-image: url("/resources/theme/images/states/refuel.svg");
}
[data-object|="unit"][data-state="human"] .unit-state {
background-image: url("/resources/theme/images/states/human.svg");
}
[data-object|="unit"][data-state="dcs"] .unit-state {
background-image: url("/resources/theme/images/states/dcs.svg");
}
[data-object|="unit"][data-state="land-at-point"] .unit-state {
background-image: url("/resources/theme/images/states/land-at-point.svg");
}
[data-object|="unit"][data-state="no-task"] .unit-state {
background-image: url("/resources/theme/images/states/no-task.svg");
}
[data-object|="unit"][data-state="off"] .unit-state {
background-image: url("/resources/theme/images/states/off.svg");
}
[data-object|="unit"][data-state="tanker"] .unit-state {
background-image: url("/resources/theme/images/states/tanker.svg");
}
[data-object|="unit"][data-state="AWACS"] .unit-state {
background-image: url("/resources/theme/images/states/awacs.svg");
}
[data-object|="unit"][data-state="miss-on-purpose"] .unit-state {
background-image: url("/resources/theme/images/states/miss-on-purpose.svg");
}
[data-object|="unit"][data-state="scenic-aaa"] .unit-state {
background-image: url("/resources/theme/images/states/scenic-aaa.svg");
}
[data-object|="unit"][data-state="simulate-fire-fight"] .unit-state {
background-image: url("/resources/theme/images/states/simulate-fire-fight.svg");
}
[data-object|="unit"] .unit-health::before {
background-image: url("/resources/theme/images/icons/health.svg");
background-repeat: no-repeat;
background-size: contain;
content: " ";
height: 6px;
left: 0;
position: absolute;
top: 0;
translate: -10px -2px;
width: 6px;
}
/*** Dead unit ***/
[data-object|="unit"][data-is-dead] .unit-selected-spotlight,
[data-object|="unit"][data-is-dead] .unit-short-label,
[data-object|="unit"][data-is-dead] .unit-vvi,
[data-object|="unit"][data-is-dead] .unit-hotgroup,
[data-object|="unit"][data-is-dead] .unit-hotgroup-id,
[data-object|="unit"][data-is-dead] .unit-state,
[data-object|="unit"][data-is-dead] .unit-fuel,
[data-object|="unit"][data-is-dead] .unit-health,
[data-object|="unit"][data-is-dead] .unit-ammo,
[data-object|="unit"][data-is-dead]:hover .unit-fuel,
[data-object|="unit"][data-is-dead]:hover .unit-ammo {
display: none;
}
[data-object|="unit"][data-is-dead] .unit-summary>* {
display: none;
}
[data-object|="unit"][data-is-dead] .unit-summary .unit-callsign {
display: block;
}
.ol-temporary-marker {
opacity: 0.5;
}

View File

@@ -0,0 +1,99 @@
:root {
/** Colours **/
/*** Coalition: neutral ***/
--primary-neutral: #949ba7;
--secondary-neutral-outline: #111111;
--secondary-neutral-text: #111111;
--unit-background-neutral: #CFD9E8;
/*** Coalition: blue ***/
--primary-blue: #247be2;
--secondary-blue-outline: #082e44;
--secondary-blue-text: #017DC1;
--unit-background-blue: #3BB9FF;
/*** Coalition: red ***/
--primary-red: #ff5858;
--secondary-red-outline: #262222;
--secondary-red-text: #D42121;
--unit-background-red: #FF5858;
/*** UI Colours **/
--accent-amber: #ffd828;
--accent-green: #8bff63;
--accent-light-blue: #5ca7ff;
--accent-dark-blue: #017DC1;
--transparent-accent-light-blue: rgba(92, 167, 255, .33);
--accent-light-red: #F5B6B6;
--background-grey: #3d4651;
--background-dark-grey: #35393d;
--background-slate-blue: #363c43;
--background-offwhite: #f2f2f3;
--background-steel: #202831;
--secondary-dark-steel: #181e25;
--secondary-gunmetal-grey: #2f2f2f;
--secondary-lighter-grey: #949ba7;
--secondary-light-grey: #797e83;
--secondary-semitransparent-white: #FFFFFFAA;
--secondary-transparent-white: #FFFFFF30;
--secondary-yellow: #ffd46893;
--background-hover: #f2f2f333;
--nav-text: #ECECEC;
--ol-select-secondary: #545F6C;
--ol-switch-off:#686868;
--ol-switch-undefined:#383838;
--ol-dialog-disabled-text-color: #ffffff20;
/*** General border radii **/
--border-radius-xs: 2px;
--border-radius-sm: 5px;
--border-radius-md: 10px;
--border-radius-lg: 15px;
/*** Fonts **/
--font-weight-bolder: 600;
/*** Unit marker settings ***/
/*** All markers **/
--unit-border-radius: var(--border-radius-xs);
--unit-font-size: 14px;
--unit-font-weight: bolder;
--unit-label-border-width: 2px;
--unit-spotlight-fill: var(--secondary-yellow);
--unit-spotlight-radius: 26px;
--unit-stroke-width: 3px;
--unit-height: 50px;
--unit-width: 50px;
--unit-health-border-width: 2px;
--unit-health-height: 6px;
--unit-health-width: 36px;
--unit-health-x: 0px;
--unit-health-y: 26px;
/*** Air units ***/
--unit-ammo-gap: calc(2px + var(--unit-stroke-width));
--unit-ammo-border-radius: 50%;
--unit-ammo-border-width: 2px;
--unit-ammo-radius: 2px;
--unit-ammo-spacing: 2px;
--unit-ammo-x: 0px;
--unit-ammo-y: 30px;
--unit-fuel-border-width: 2px;
--unit-fuel-height: 6px;
--unit-fuel-width: 36px;
--unit-fuel-x: 0px;
--unit-fuel-y: 22px;
--unit-vvi-width: 4px;
}
* {
font-weight:600;
}

View File

@@ -1,10 +1,10 @@
import { LatLng } from 'leaflet';
import { getApp } from '../olympusapp';
import { AIRBASES_URI, BULLSEYE_URI, COMMANDS_URI, LOGS_URI, MISSION_URI, NONE, ROEs, UNITS_URI, WEAPONS_URI, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
import { ServerStatusPanel } from '../panels/serverstatuspanel';
import { LogPanel } from '../panels/logpanel';
import { Popup } from '../popups/popup';
import { ConnectionStatusPanel } from '../panels/connectionstatuspanel';
//import { ServerStatusPanel } from '../panels/serverstatuspanel';
//import { LogPanel } from '../panels/logpanel';
//import { Popup } from '../popups/popup';
//import { ConnectionStatusPanel } from '../panels/connectionstatuspanel';
import { AirbasesData, BullseyesData, GeneralSettings, MissionData, Radio, ServerRequestOptions, TACAN } from '../interfaces';
import { zeroAppend } from '../other/utils';
@@ -453,7 +453,7 @@ export class ServerManager {
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
this.getLogs((data: any) => {
this.checkSessionHash(data.sessionHash);
(getApp().getPanelsManager().get("log") as LogPanel).appendLogs(data.logs)
//(getApp().getPanelsManager().get("log") as LogPanel).appendLogs(data.logs)
return data.time;
});
}
@@ -488,17 +488,17 @@ export class ServerManager {
this.#serverIsPaused = (elapsedMissionTime === this.#previousMissionElapsedTime);
this.#previousMissionElapsedTime = elapsedMissionTime;
const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel);
//const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel);
if (this.getConnected()) {
if (this.getServerIsPaused()) {
csp.showServerPaused();
} else {
csp.showConnected();
}
} else {
csp.showDisconnected();
}
//if (this.getConnected()) {
// if (this.getServerIsPaused()) {
// csp.showServerPaused();
// } else {
// csp.showConnected();
// }
//} else {
// csp.showDisconnected();
//}
}
}, (this.getServerIsPaused() ? 500 : 5000)));
@@ -512,11 +512,11 @@ export class ServerManager {
const elapsedMissionTime = getApp().getMissionManager().getDateAndTime().elapsedTime;
const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel);
const mt = getApp().getMissionManager().getDateAndTime().time;
//const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel);
//const mt = getApp().getMissionManager().getDateAndTime().time;
csp.setMissionTime([mt.h, mt.m, mt.s].map(n => zeroAppend(n, 2)).join(":"));
csp.setElapsedTime(new Date(elapsedMissionTime * 1000).toISOString().substring(11, 19));
//csp.setMissionTime([mt.h, mt.m, mt.s].map(n => zeroAppend(n, 2)).join(":"));
//csp.setElapsedTime(new Date(elapsedMissionTime * 1000).toISOString().substring(11, 19));
}, 1000));
@@ -545,7 +545,7 @@ export class ServerManager {
this.getLogs((data: any) => {
this.checkSessionHash(data.sessionHash);
(getApp().getPanelsManager().get("log") as LogPanel).appendLogs(data.logs)
//(getApp().getPanelsManager().get("log") as LogPanel).appendLogs(data.logs)
return data.time;
});
@@ -587,7 +587,7 @@ export class ServerManager {
setPaused(newPaused: boolean) {
this.#paused = newPaused;
this.#paused ? (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View paused") : (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View unpaused");
//this.#paused ? (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View paused") : (getApp().getPopupsManager().get("infoPopup") as Popup).setText("View unpaused");
}
getPaused() {

View File

@@ -1,40 +1 @@
#root {
padding: 0px;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@@ -1,15 +1,11 @@
import React from 'react'
import React, { useState } from 'react'
import './ui.css'
import { MapContainer, TileLayer } from 'react-leaflet'
import { Map } from './map/map'
import { Header } from './ui/panels/header'
import { EventsProvider } from './eventscontext'
import { StateProvider } from './statecontext'
import { SpawnMenu } from './ui/panels/spawnmenu'
const position = [51.505, -0.09]
import { UnitControlMenu } from './ui/panels/unitcontrolmenu'
export type OlympusState = {
spawnMenuVisible: boolean,
@@ -17,129 +13,48 @@ export type OlympusState = {
measureMenuVisible: boolean,
drawingMenuVisible: boolean
}
1
export function UI(props) {
var [spawnMenuVisible, setSpawnMenuVisible] = useState(false);
var [unitControlMenuVisible, setUnitControlMenuVisible] = useState(false);
var [measureMenuVisible, setMeasureMenuVisible] = useState(false);
var [drawingMenuVisible, setDrawingMenuVisible] = useState(false);
export default class UI extends React.Component<{}, OlympusState> {
constructor(props) {
super(props);
/* State initialization */
this.state = {
spawnMenuVisible: false,
unitControlMenuVisible: false,
measureMenuVisible: false,
drawingMenuVisible: false
}
/* Methods bindings */
this.showSpawnMenu = this.showSpawnMenu.bind(this);
this.toggleSpawnMenu = this.toggleSpawnMenu.bind(this);
this.showUnitControlMenu = this.showUnitControlMenu.bind(this);
this.toggleUnitControlMenu = this.toggleUnitControlMenu.bind(this);
this.showMeasureMenu = this.showMeasureMenu.bind(this);
this.toggleMeasureMenu = this.toggleMeasureMenu.bind(this);
this.showDrawingMenu = this.showDrawingMenu.bind(this);
this.toggleDrawingMenu = this.toggleDrawingMenu.bind(this);
function hideAllMenus() {
setSpawnMenuVisible(false);
setUnitControlMenuVisible(false);
setMeasureMenuVisible(false);
setDrawingMenuVisible(false);
}
showSpawnMenu(show: boolean) {
this.setState({
spawnMenuVisible: show,
unitControlMenuVisible: false,
measureMenuVisible: false,
drawingMenuVisible: false
});
}
toggleSpawnMenu() {
this.setState({
spawnMenuVisible: !this.state.spawnMenuVisible,
unitControlMenuVisible: false,
measureMenuVisible: false,
drawingMenuVisible: false
});
}
showUnitControlMenu(show: boolean) {
this.setState({
spawnMenuVisible: false,
unitControlMenuVisible: show,
measureMenuVisible: false,
drawingMenuVisible: false
});
}
toggleUnitControlMenu() {
this.setState({
spawnMenuVisible: false,
unitControlMenuVisible: !this.state.unitControlMenuVisible,
measureMenuVisible: false,
drawingMenuVisible: false
});
}
showMeasureMenu(show: boolean) {
this.setState({
spawnMenuVisible: false,
unitControlMenuVisible: false,
measureMenuVisible: show,
drawingMenuVisible: false
});
}
toggleMeasureMenu() {
this.setState({
spawnMenuVisible: false,
unitControlMenuVisible: false,
measureMenuVisible: !this.state.measureMenuVisible,
drawingMenuVisible: false
});
}
showDrawingMenu(show: boolean) {
this.setState({
spawnMenuVisible: false,
unitControlMenuVisible: false,
measureMenuVisible: false,
drawingMenuVisible: show
});
}
toggleDrawingMenu() {
this.setState({
spawnMenuVisible: false,
unitControlMenuVisible: false,
measureMenuVisible: false,
drawingMenuVisible: !this.state.drawingMenuVisible
});
}
render() {
return (
<div className="h-full w-full font-sans">
<StateProvider value={this.state}>
<EventsProvider value={
{
showSpawnMenu: this.showSpawnMenu,
toggleSpawnMenu: this.toggleSpawnMenu,
showUnitControlMenu: this.showUnitControlMenu,
toggleUnitControlMenu: this.toggleUnitControlMenu,
showMeasureMenu: this.showMeasureMenu,
toggleMeasureMenu: this.toggleMeasureMenu,
showDrawingMenu: this.showMeasureMenu,
toggleDrawingMenu: this.toggleDrawingMenu
}
}>
<div id='map-container' className='absolute top-0 left-0 w-full h-full'>
</div>
<div className='absolute top-0 left-0 z-ui w-full h-full flex flex-col'>
<Header ></Header>
<SpawnMenu ></SpawnMenu>
</div>
</EventsProvider>
</StateProvider>
</div>
)
}
return (
<div className="absolute top-0 left-0 h-screen w-screen font-sans overflow-hidden">
<StateProvider value={{
spawnMenuVisible: spawnMenuVisible,
unitControlMenuVisible: unitControlMenuVisible,
measureMenuVisible: measureMenuVisible,
drawingMenuVisible: drawingMenuVisible
}}>
<EventsProvider value={
{
setSpawnMenuVisible: setSpawnMenuVisible,
setUnitControlMenuVisible: setUnitControlMenuVisible,
setDrawingMenuVisible: setDrawingMenuVisible,
setMeasureMenuVisible: setMeasureMenuVisible,
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 id='map-container' className='relative h-screen w-screen top-0 left-0' />
<SpawnMenu open={spawnMenuVisible} closeCallback={() => setSpawnMenuVisible(false)}/>
<UnitControlMenu open={unitControlMenuVisible} closeCallback={() => setUnitControlMenuVisible(false)}/>
</div>
</EventsProvider>
</StateProvider>
</div>
)
}

View File

@@ -1,28 +0,0 @@
import React, { MouseEventHandler } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { IconProp } from '@fortawesome/fontawesome-svg-core'
type ButtonProps = {
icon: string,
onClick: CallableFunction,
active: boolean
}
export class StateButton extends React.Component<ButtonProps, {}> {
constructor(props) {
super(props);
this.state = {
active: true
}
}
render() {
var computedClassName = "";
computedClassName += this.props.active? 'bg-white text-background-darker': 'bg-transparent text-white border-white';
return (
<FontAwesomeIcon icon={this.props.icon as IconProp} className={computedClassName + " rounded w-5 h-5 p-2 border-2"} onClick={this.props.onClick as MouseEventHandler}>
</FontAwesomeIcon>
);
}
}

View File

@@ -0,0 +1,30 @@
import React, { useId, useState } 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" )
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" />
</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">
<ul 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>
})}
</ul>
</div>
</div>
}

View File

@@ -0,0 +1,8 @@
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-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700">
<FontAwesomeIcon icon={props.icon} />
</button>
}

View File

@@ -0,0 +1,38 @@
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

@@ -0,0 +1,70 @@
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

@@ -0,0 +1,26 @@
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Drawer, DrawerInterface } from "flowbite";
import React, { useEffect, useId, useRef } 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-16 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="inline-flex items-center mb-4 text-base font-semibold text-gray-500 dark:text-gray-400">
<FontAwesomeIcon icon={props.titleIcon} className="mr-2"/>
{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" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
<span className="sr-only">Close menu</span>
</button>
</div>
}

View File

@@ -1,9 +0,0 @@
import React from "react";
export class MenuTitle extends React.Component<{title: string}, {}> {
render() {
return <div className="h-12 w-full bg-background-dark flex items-center px-4 font-semibold">
{this.props.title}
</div>
}
}

View File

@@ -1,35 +1,31 @@
import React from 'react'
import { StateButton } from '../buttons/statebutton';
import { faPlus, faGamepad, faRuler, faPencil } from '@fortawesome/free-solid-svg-icons';
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, EventsContext } from '../../eventscontext';
import { EventsConsumer } from '../../eventscontext';
import { StateConsumer } from '../../statecontext';
import { OlDropdown } from '../components/oldropdown';
library.add(faPlus, faGamepad, faRuler, faPencil)
library.add(faPlus, faGamepad, faRuler, faPencil, faMap);
export class Header extends React.Component<{}, {}> {
constructor(props) {
super(props);
}
render() {
return (
<StateConsumer>
{(appState) =>
<EventsConsumer>
{(events) =>
<div className='h-16 w-full bg-background-darker flex flex-row items-center px-5'>
<div className="flex flex-row items-center gap-1">
<StateButton onClick={events.toggleSpawnMenu} active={appState.spawnMenuVisible} icon="fa-solid fa-plus"></StateButton>
<StateButton onClick={events.toggleUnitControlMenu} active={appState.unitControlMenuVisible} icon="fa-solid fa-gamepad"></StateButton>
<StateButton onClick={events.toggleMeasureMenu} active={appState.measureMenuVisible} icon="fa-solid fa-ruler"></StateButton>
<StateButton onClick={events.toggleDrawingMenu} active={appState.drawingMenuVisible} icon="fa-solid fa-pencil"></StateButton>
</div>
export function Header(props) {
return <StateConsumer>
{(appState) =>
<EventsConsumer>
{(events) =>
<nav className="bg-white border-gray-200 dark:bg-gray-800 dark:border-gray-700">
<div className="max-w-screen flex flex-wrap items-center justify-between p-4">
<div className="flex flex-row 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>
}
</EventsConsumer>
<OlDropdown items={["DCS Sat", "DCS Alt"]} leftIcon='fa-solid fa-map' />
</div>
</nav>
}
</StateConsumer>
);
}
</EventsConsumer>
}
</StateConsumer>
}

View File

@@ -1,14 +1,13 @@
import React from "react";
import { MenuTitle } from "./components/menutitle";
import { Menu } from "./components/menu";
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { library } from '@fortawesome/fontawesome-svg-core'
export class SpawnMenu extends React.Component<{}, {}> {
constructor(props) {
super(props);
}
library.add(faPlus);
render() {
return <div className="h-full w-96 bg-background-neutral z-ui">
<MenuTitle title="Spawn menu"></MenuTitle>
</div>
}
export function SpawnMenu(props) {
return <Menu {...props} title="Spawn menu" titleIcon="fa-solid fa-plus">
</Menu>
}

View File

@@ -0,0 +1,13 @@
import React from "react";
import { Menu } from "./components/menu";
import { faGamepad } from '@fortawesome/free-solid-svg-icons';
import { library } from '@fortawesome/fontawesome-svg-core'
library.add(faGamepad);
export function UnitControlMenu(props) {
return <Menu {...props} title="Unit control menu" titleIcon="fa-solid fa-gamepad">
</Menu>
}

View File

@@ -4,7 +4,8 @@ import { UnitDatabase } from "./unitdatabase"
export class AircraftDatabase extends UnitDatabase {
constructor() {
super('api/databases/units/aircraftdatabase');
//Temporary
super('http://localhost:3000/api/databases/units/aircraftdatabase');
}
getCategory() {

View File

@@ -4,7 +4,8 @@ import { UnitDatabase } from "./unitdatabase"
export class GroundUnitDatabase extends UnitDatabase {
constructor() {
super('api/databases/units/groundunitdatabase');
// Temporary
super('http://localhost:3000/api/databases/units/groundunitdatabase');
}
getSpawnPointsByName(name: string) {

View File

@@ -4,7 +4,8 @@ import { UnitDatabase } from "./unitdatabase"
export class HelicopterDatabase extends UnitDatabase {
constructor() {
super('api/databases/units/helicopterdatabase');
// Temporary
super('http://localhost:3000/api/databases/units/helicopterdatabase');
}
getSpawnPointsByName(name: string) {

View File

@@ -4,7 +4,8 @@ import { UnitDatabase } from "./unitdatabase"
export class NavyUnitDatabase extends UnitDatabase {
constructor() {
super('api/databases/units/navyunitdatabase');
// Temporary
super('http://localhost:3000/api/databases/units/navyunitdatabase');
}
getSpawnPointsByName(name: string) {