fix: Toolbar button color different when hovering and selected

This commit is contained in:
Davide Passoni 2025-01-23 10:58:11 +01:00
parent d31aa30da8
commit 20db9647bd
17 changed files with 304 additions and 406 deletions

View File

@ -515,7 +515,159 @@ export enum ContextActionType {
DELETE,
}
export const CONTEXT_ACTION_COLORS = [null, "white", "green", "purple", "blue", "red"];
export enum colors {
ALICE_BLUE = "#F0F8FF",
ANTIQUE_WHITE = "#FAEBD7",
AQUA = "#00FFFF",
AQUAMARINE = "#7FFFD4",
AZURE = "#F0FFFF",
BEIGE = "#F5F5DC",
BISQUE = "#FFE4C4",
BLACK = "#000000",
BLANCHED_ALMOND = "#FFEBCD",
BLUE = "#0000FF",
BLUE_VIOLET = "#8A2BE2",
BROWN = "#A52A2A",
BURLY_WOOD = "#DEB887",
CADET_BLUE = "#5F9EA0",
CHARTREUSE = "#7FFF00",
CHOCOLATE = "#D2691E",
CORAL = "#FF7F50",
CORNFLOWER_BLUE = "#6495ED",
CORNSILK = "#FFF8DC",
CRIMSON = "#DC143C",
CYAN = "#00FFFF",
DARK_BLUE = "#00008B",
DARK_CYAN = "#008B8B",
DARK_GOLDEN_ROD = "#B8860B",
DARK_GRAY = "#A9A9A9",
DARK_GREEN = "#006400",
DARK_KHAKI = "#BDB76B",
DARK_MAGENTA = "#8B008B",
DARK_OLIVE_GREEN = "#556B2F",
DARKORANGE = "#FF8C00",
DARK_ORCHID = "#9932CC",
DARK_RED = "#8B0000",
DARK_SALMON = "#E9967A",
DARK_SEA_GREEN = "#8FBC8F",
DARK_SLATE_BLUE = "#483D8B",
DARK_SLATE_GRAY = "#2F4F4F",
DARK_TURQUOISE = "#00CED1",
DARK_VIOLET = "#9400D3",
DEEP_PINK = "#FF1493",
DEEP_SKY_BLUE = "#00BFFF",
DIM_GRAY = "#696969",
DODGER_BLUE = "#1E90FF",
FIRE_BRICK = "#B22222",
FLORAL_WHITE = "#FFFAF0",
FOREST_GREEN = "#228B22",
FUCHSIA = "#FF00FF",
GAINSBORO = "#DCDCDC",
GHOST_WHITE = "#F8F8FF",
GOLD = "#FFD700",
GOLDEN_ROD = "#DAA520",
GRAY = "#808080",
GREEN = "#008000",
GREEN_YELLOW = "#ADFF2F",
HONEY_DEW = "#F0FFF0",
HOT_PINK = "#FF69B4",
INDIAN_RED = "#CD5C5C",
INDIGO = "#4B0082",
IVORY = "#FFFFF0",
KHAKI = "#F0E68C",
LAVENDER = "#E6E6FA",
LAVENDER_BLUSH = "#FFF0F5",
LAWN_GREEN = "#7CFC00",
LEMON_CHIFFON = "#FFFACD",
LIGHT_BLUE = "#ADD8E6",
LIGHT_CORAL = "#F08080",
LIGHT_CYAN = "#E0FFFF",
LIGHT_GOLDEN_ROD_YELLOW = "#FAFAD2",
LIGHT_GREY = "#D3D3D3",
LIGHT_GREEN = "#90EE90",
LIGHT_PINK = "#FFB6C1",
LIGHT_SALMON = "#FFA07A",
LIGHT_SEA_GREEN = "#20B2AA",
LIGHT_SKY_BLUE = "#87CEFA",
LIGHT_SLATE_GRAY = "#778899",
LIGHT_STEEL_BLUE = "#B0C4DE",
LIGHT_YELLOW = "#FFFFE0",
LIME = "#00FF00",
LIME_GREEN = "#32CD32",
LINEN = "#FAF0E6",
MAGENTA = "#FF00FF",
MAROON = "#800000",
MEDIUM_AQUA_MARINE = "#66CDAA",
MEDIUM_BLUE = "#0000CD",
MEDIUM_ORCHID = "#BA55D3",
MEDIUM_PURPLE = "#9370D8",
MEDIUM_SEA_GREEN = "#3CB371",
MEDIUM_SLATE_BLUE = "#7B68EE",
MEDIUM_SPRING_GREEN = "#00FA9A",
MEDIUM_TURQUOISE = "#48D1CC",
MEDIUM_VIOLET_RED = "#C71585",
MIDNIGHT_BLUE = "#191970",
MINT_CREAM = "#F5FFFA",
MISTY_ROSE = "#FFE4E1",
MOCCASIN = "#FFE4B5",
NAVAJO_WHITE = "#FFDEAD",
NAVY = "#000080",
OLD_LACE = "#FDF5E6",
OLIVE = "#808000",
OLIVE_DRAB = "#6B8E23",
ORANGE = "#FFA500",
ORANGE_RED = "#FF4500",
ORCHID = "#DA70D6",
PALE_GOLDEN_ROD = "#EEE8AA",
PALE_GREEN = "#98FB98",
PALE_TURQUOISE = "#AFEEEE",
PALE_VIOLET_RED = "#D87093",
PAPAYA_WHIP = "#FFEFD5",
PEACH_PUFF = "#FFDAB9",
PERU = "#CD853F",
PINK = "#FFC0CB",
PLUM = "#DDA0DD",
POWDER_BLUE = "#B0E0E6",
PURPLE = "#800080",
RED = "#FF0000",
ROSY_BROWN = "#BC8F8F",
ROYAL_BLUE = "#4169E1",
SADDLE_BROWN = "#8B4513",
SALMON = "#FA8072",
SANDY_BROWN = "#F4A460",
SEA_GREEN = "#2E8B57",
SEA_SHELL = "#FFF5EE",
SIENNA = "#A0522D",
SILVER = "#C0C0C0",
SKY_BLUE = "#87CEEB",
SLATE_BLUE = "#6A5ACD",
SLATE_GRAY = "#708090",
SNOW = "#FFFAFA",
SPRING_GREEN = "#00FF7F",
STEEL_BLUE = "#4682B4",
TAN = "#D2B48C",
TEAL = "#008080",
THISTLE = "#D8BFD8",
TOMATO = "#FF6347",
TURQUOISE = "#40E0D0",
VIOLET = "#EE82EE",
WHEAT = "#F5DEB3",
WHITE = "#FFFFFF",
WHITE_SMOKE = "#F5F5F5",
YELLOW = "#FFFF00",
YELLOW_GREEN = "#9ACD32",
BLUE_COALITION = "#2563EB",
NEUTRAL_COALITION = "#9CA3AF",
RED_COALITION = "#EF4444",
OLYMPUS_LIGHT_BLUE = "#3B82F6",
OLYMPUS_BLUE = "#243141",
OLYMPUS_RED = "#F05252",
OLYMPUS_ORANGE = "#FF9900",
OLYMPUS_GREEN = "#8BFF63"
}
export const CONTEXT_ACTION_COLORS = [undefined, colors.WHITE, colors.GREEN, colors.PURPLE, colors.BLUE, colors.RED];
export namespace ContextActions {
export const STOP = new ContextAction(

View File

@ -1,7 +1,7 @@
import { LatLngExpression, Map, Circle, DivIcon, Marker, CircleOptions, LatLng } from "leaflet";
import { getApp } from "../../olympusapp";
import { CoalitionAreaHandle } from "./coalitionareahandle";
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
import { BLUE_COMMANDER, colors, RED_COMMANDER } from "../../constants/constants";
import { Coalition } from "../../types/types";
import * as turf from "@turf/turf";
import { CoalitionAreaChangedEvent, CoalitionAreaSelectedEvent } from "../../events";
@ -125,12 +125,12 @@ export class CoalitionCircle extends Circle {
}
#setColors() {
let coalitionColor = "#FFFFFF";
if (this.getCoalition() === "blue") coalitionColor = "#247be2";
else if (this.getCoalition() === "red") coalitionColor = "#ff5858";
let coalitionColor = colors.NEUTRAL_COALITION;
if (this.getCoalition() === "blue") coalitionColor = colors.BLUE_COALITION;
else if (this.getCoalition() === "red") coalitionColor = colors.RED_COALITION;
this.setStyle({
color: this.getSelected() ? "white" : coalitionColor,
color: this.getSelected() ? colors.WHITE : coalitionColor,
fillColor: coalitionColor,
});
}

View File

@ -2,7 +2,7 @@ import { LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions, DivIcon
import { getApp } from "../../olympusapp";
import { CoalitionAreaHandle } from "./coalitionareahandle";
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
import { BLUE_COMMANDER, colors, RED_COMMANDER } from "../../constants/constants";
import { Coalition } from "../../types/types";
import { polyCenter } from "../../other/utils";
import { CoalitionAreaChangedEvent, CoalitionAreaSelectedEvent } from "../../events";
@ -150,9 +150,9 @@ export class CoalitionPolygon extends Polygon {
}
#setColors() {
let coalitionColor = "#FFFFFF";
if (this.getCoalition() === "blue") coalitionColor = "#247be2";
else if (this.getCoalition() === "red") coalitionColor = "#ff5858";
let coalitionColor = colors.NEUTRAL_COALITION;
if (this.getCoalition() === "blue") coalitionColor = colors.BLUE_COALITION;
else if (this.getCoalition() === "red") coalitionColor = colors.RED_COALITION;
this.setStyle({
color: this.getSelected() ? "white" : coalitionColor,

View File

@ -25,6 +25,7 @@ import {
SHORT_PRESS_MILLISECONDS,
DEBOUNCE_MILLISECONDS,
DrawSubState,
colors,
} from "../constants/constants";
import { MapHiddenTypes, MapOptions } from "../types/types";
import { EffectRequestTable, OlympusConfig, SpawnRequestTable } from "../interfaces";
@ -177,7 +178,7 @@ export class Map extends L.Map {
maxZoom: 13,
});
this.#miniMapLayerGroup = new L.LayerGroup([minimapLayer]);
this.#miniMapPolyline = new L.Polyline([], { color: "#202831" });
this.#miniMapPolyline = new L.Polyline([], { color: colors.DARK_GRAY });
this.#miniMapPolyline.addTo(this.#miniMapLayerGroup);
/* Register event handles */

View File

@ -525,4 +525,79 @@ export function toDCSFormationOffset(offset: {x: number, y: number, z: number})
export function fromDCSFormationOffset(offset: {x: number, y: number, z: number}) {
return { x: offset.z, y: -offset.x, z: offset.y };
}
/**
* Adjusts the brightness of a color.
* @param {string} color - The color in hashtag format (e.g., "#RRGGBB").
* @param {number} percent - The percentage to adjust the brightness (positive to lighten, negative to darken).
* @returns {string} - The adjusted color in hashtag format.
*/
export function adjustBrightness(color, percent) {
// Ensure the color is in the correct format
if (!/^#[0-9A-F]{6}$/i.test(color)) {
throw new Error('Invalid color format. Use #RRGGBB.');
}
// Parse the color components
let r = parseInt(color.slice(1, 3), 16);
let g = parseInt(color.slice(3, 5), 16);
let b = parseInt(color.slice(5, 7), 16);
// Adjust the brightness
r = Math.min(255, Math.max(0, r + Math.round((percent / 100) * 255)));
g = Math.min(255, Math.max(0, g + Math.round((percent / 100) * 255)));
b = Math.min(255, Math.max(0, b + Math.round((percent / 100) * 255)));
// Convert back to hexadecimal and return the adjusted color
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase()}`;
}
/**
* Sets the opacity of a color.
* @param {string} color - The color in hashtag format (e.g., "#RRGGBB").
* @param {number} opacity - The opacity value (between 0 and 1).
* @returns {string} - The color in rgba format.
*/
export function setOpacity(color, opacity) {
// Ensure the color is in the correct format
if (!/^#[0-9A-F]{6}$/i.test(color)) {
throw new Error('Invalid color format. Use #RRGGBB.');
}
// Ensure the opacity is within the valid range
if (opacity < 0 || opacity > 1) {
throw new Error('Opacity must be between 0 and 1.');
}
// Parse the color components
let r = parseInt(color.slice(1, 3), 16);
let g = parseInt(color.slice(3, 5), 16);
let b = parseInt(color.slice(5, 7), 16);
// Return the color in rgba format
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
}
/**
* Computes the brightness of a color.
* @param {string} color - The color in hashtag format (e.g., "#RRGGBB").
* @returns {number} - The brightness value (0 to 255).
*/
export function computeBrightness(color) {
// Ensure the color is in the correct format
if (!/^#[0-9A-F]{6}$/i.test(color)) {
throw new Error('Invalid color format. Use #RRGGBB.');
}
// Parse the color components
let r = parseInt(color.slice(1, 3), 16);
let g = parseInt(color.slice(3, 5), 16);
let b = parseInt(color.slice(5, 7), 16);
// Compute the brightness using the luminance formula
// The formula is: 0.299*R + 0.587*G + 0.114*B
let brightness = 0.299 * r + 0.587 * g + 0.114 * b;
return brightness;
}

View File

@ -104,9 +104,12 @@ export function OlDropdown(props: {
`}
type="button"
>
{props.leftIcon && <FontAwesomeIcon icon={props.leftIcon} className={`
mr-3
`} />}
{props.leftIcon && (
<FontAwesomeIcon
icon={props.leftIcon}
className={`mr-3`}
/>
)}
<span className="overflow-hidden text-ellipsis text-nowrap">{props.label ?? ""}</span>
<svg
className={`
@ -150,7 +153,7 @@ export function OlDropdown(props: {
}
/* Conveniency Component for dropdown elements */
export function OlDropdownItem(props: { onClick?: () => void; className?: string; children?: string | JSX.Element | JSX.Element[] }) {
export function OlDropdownItem(props: { onClick?: () => void; className?: string; borderColor?: string; children?: string | JSX.Element | JSX.Element[] }) {
return (
<button
onClick={props.onClick ?? (() => {})}
@ -161,6 +164,9 @@ export function OlDropdownItem(props: { onClick?: () => void; className?: string
dark:hover:bg-gray-600 dark:hover:text-white
hover:bg-gray-100
`}
style={{
border: props.borderColor ? `2px solid ${props.borderColor}` : "2px solid transparent",
}}
>
{props.children}
</button>

View File

@ -1,12 +1,14 @@
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { faExternalLink, faLock, faLockOpen, faUnlock, faUnlockAlt } from "@fortawesome/free-solid-svg-icons";
import { faLock, faUnlockAlt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useRef, useState } from "react";
import { OlTooltip } from "./oltooltip";
import { computeBrightness, setOpacity } from "../../other/utils";
import { colors } from "../../constants/constants";
export function OlStateButton(props: {
className?: string;
buttonColor?: string | null;
buttonColor?: string;
checked: boolean;
icon?: IconProp;
tooltip?: string | (() => JSX.Element | JSX.Element[]);
@ -27,13 +29,6 @@ export function OlStateButton(props: {
dark:bg-olympus-600 dark:text-gray-300
`;
let textColor = "white";
if ((props.checked || hover) && props.buttonColor == "white") {
textColor = "#243141";
}
const opacity = hover && !props.checked ? "AA" : "FF";
return (
<>
<button
@ -50,7 +45,7 @@ export function OlStateButton(props: {
className={className}
style={{
border: props.buttonColor ? "2px solid " + props.buttonColor : "0px solid transparent",
background: props.checked || hover ? (props.buttonColor ? props.buttonColor : "#3b82f6" + opacity) : "#243141" + opacity,
background: setOpacity(props.checked || hover ? (props.buttonColor ? props.buttonColor : colors.OLYMPUS_LIGHT_BLUE) : colors.OLYMPUS_BLUE, (!props.checked && hover)? 0.3: 1),
}}
onMouseEnter={() => {
setHover(true);
@ -60,7 +55,10 @@ export function OlStateButton(props: {
}}
>
<div className={`m-auto flex w-fit content-center justify-center gap-2`}>
{props.icon && <FontAwesomeIcon icon={props.icon} className="m-auto" style={{ color: textColor }} />}
{props.icon && <FontAwesomeIcon icon={props.icon} data-bright={props.buttonColor && props.checked && computeBrightness(props.buttonColor) > 200} className={`
m-auto text-gray-200
data-[bright='true']:text-gray-800
`} />}
{props.children}
</div>
</button>

View File

@ -110,17 +110,13 @@ export function MapContextMenu(props: {}) {
>
{contextActionSet &&
reorderedActions.map((contextActionIt) => {
const colorString = `
border-2
border-${CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type]}-500
`;
return (
<OlDropdownItem
className={`
flex w-full content-center gap-2 text-white
${colorString}
flex w-full content-center gap-2 border-2 text-white
`}
key={contextActionIt.getLabel()}
borderColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type] as string}
onClick={() => {
if (contextActionIt.getTarget() === ContextActionTarget.NONE) {
contextActionIt.executeCallback(null, null);

View File

@ -1,5 +1,5 @@
import React, { useEffect, useRef, useState } from "react";
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, GAME_MASTER, NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
import { BLUE_COMMANDER, colors, COMMAND_MODE_OPTIONS_DEFAULTS, GAME_MASTER, NO_SUBSTATE, OlympusState, OlympusSubState } from "../../constants/constants";
import { LatLng } from "leaflet";
import {
AppStateChangedEvent,
@ -187,35 +187,35 @@ export function SpawnContextMenu(props: {}) {
onClick={() => (openAccordion !== CategoryGroup.AIRCRAFT ? setOpenAccordion(CategoryGroup.AIRCRAFT) : setOpenAccordion(CategoryGroup.NONE))}
icon={olButtonsVisibilityAircraft}
tooltip="Show aircraft units"
buttonColor={spawnCoalition === "blue" ? "#2563eb" : spawnCoalition === "neutral" ? "#9ca3af" : "#ef4444"}
buttonColor={spawnCoalition === "blue" ? colors.BLUE_COALITION : spawnCoalition === "neutral" ? colors.NEUTRAL_COALITION : colors.RED_COALITION}
/>
<OlStateButton
checked={openAccordion === CategoryGroup.HELICOPTER}
onClick={() => (openAccordion !== CategoryGroup.HELICOPTER ? setOpenAccordion(CategoryGroup.HELICOPTER) : setOpenAccordion(CategoryGroup.NONE))}
icon={olButtonsVisibilityHelicopter}
tooltip="Show helicopter units"
buttonColor={spawnCoalition === "blue" ? "#2563eb" : spawnCoalition === "neutral" ? "#9ca3af" : "#ef4444"}
buttonColor={spawnCoalition === "blue" ? colors.BLUE_COALITION : spawnCoalition === "neutral" ? colors.NEUTRAL_COALITION : colors.RED_COALITION}
/>
<OlStateButton
checked={openAccordion === CategoryGroup.AIR_DEFENCE}
onClick={() => (openAccordion !== CategoryGroup.AIR_DEFENCE ? setOpenAccordion(CategoryGroup.AIR_DEFENCE) : setOpenAccordion(CategoryGroup.NONE))}
icon={olButtonsVisibilityGroundunitSam}
tooltip="Show air defence units"
buttonColor={spawnCoalition === "blue" ? "#2563eb" : spawnCoalition === "neutral" ? "#9ca3af" : "#ef4444"}
buttonColor={spawnCoalition === "blue" ? colors.BLUE_COALITION : spawnCoalition === "neutral" ? colors.NEUTRAL_COALITION : colors.RED_COALITION}
/>
<OlStateButton
checked={openAccordion === CategoryGroup.GROUND_UNIT}
onClick={() => (openAccordion !== CategoryGroup.GROUND_UNIT ? setOpenAccordion(CategoryGroup.GROUND_UNIT) : setOpenAccordion(CategoryGroup.NONE))}
icon={olButtonsVisibilityGroundunit}
tooltip="Show ground units"
buttonColor={spawnCoalition === "blue" ? "#2563eb" : spawnCoalition === "neutral" ? "#9ca3af" : "#ef4444"}
buttonColor={spawnCoalition === "blue" ? colors.BLUE_COALITION : spawnCoalition === "neutral" ? colors.NEUTRAL_COALITION : colors.RED_COALITION}
/>
<OlStateButton
checked={openAccordion === CategoryGroup.NAVY_UNIT}
onClick={() => (openAccordion !== CategoryGroup.NAVY_UNIT ? setOpenAccordion(CategoryGroup.NAVY_UNIT) : setOpenAccordion(CategoryGroup.NONE))}
icon={olButtonsVisibilityNavyunit}
tooltip="Show navy units"
buttonColor={spawnCoalition === "blue" ? "#2563eb" : spawnCoalition === "neutral" ? "#9ca3af" : "#ef4444"}
buttonColor={spawnCoalition === "blue" ? colors.BLUE_COALITION : spawnCoalition === "neutral" ? colors.NEUTRAL_COALITION : colors.RED_COALITION}
/>
<OlStateButton checked={showMore} onClick={() => setShowMore(!showMore)} icon={faEllipsisVertical} tooltip="Show more options" />
{showMore && (

View File

@ -244,11 +244,8 @@ export function MapToolBar(props: {}) {
tooltipPosition="side"
buttonColor={CONTEXT_ACTION_COLORS[contextActionIt.getOptions().type ?? 0]}
onClick={() => {
if (contextActionIt.getTarget() === ContextActionTarget.NONE) {
contextActionIt.executeCallback(null, null);
} else {
contextActionIt !== contextAction ? getApp().getMap().setContextAction(contextActionIt) : getApp().getMap().setContextAction(null);
}
if (contextActionIt.getTarget() === ContextActionTarget.NONE) contextActionIt.executeCallback(null, null);
else contextActionIt !== contextAction ? getApp().getMap().setContextAction(contextActionIt) : getApp().getMap().setContextAction(null);
}}
/>
</div>

View File

@ -4,7 +4,7 @@ import { DateAndTime, ServerStatus } from "../../interfaces";
import { getApp } from "../../olympusapp";
import { FaChevronDown, FaChevronUp } from "react-icons/fa6";
import { MapOptionsChangedEvent, ServerStatusUpdatedEvent } from "../../events";
import { MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
import { colors, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
export function MiniMapPanel(props: {}) {
const [serverStatus, setServerStatus] = useState({} as ServerStatus);
@ -41,15 +41,13 @@ export function MiniMapPanel(props: {}) {
let timeString = `${zeroAppend(hours, 2)}:${zeroAppend(minutes, 2)}:${zeroAppend(seconds, 2)}`;
// Choose frame rate string color
let frameRateColor = "#8BFF63";
if (serverStatus.frameRate < 30) frameRateColor = "#F05252";
else if (serverStatus.frameRate >= 30 && serverStatus.frameRate < 60) frameRateColor = "#FF9900";
let loadColor = colors.OLYMPUS_GREEN;
if (serverStatus.load > 1000) loadColor = colors.OLYMPUS_RED;
else if (serverStatus.load >= 100 && serverStatus.load < 1000) loadColor = colors.OLYMPUS_ORANGE;
// Choose load string color
let loadColor = "#8BFF63";
if (serverStatus.load > 1000) loadColor = "#F05252";
else if (serverStatus.load >= 100 && serverStatus.load < 1000) loadColor = "#FF9900";
let frameRateColor = colors.OLYMPUS_GREEN;
if (serverStatus.frameRate < 30) frameRateColor = colors.OLYMPUS_RED;
else if (serverStatus.frameRate >= 30 && serverStatus.frameRate < 60) frameRateColor = colors.OLYMPUS_ORANGE;
return (
<div

View File

@ -3,6 +3,7 @@ import { ServerStatusUpdatedEvent } from "../events";
import { ServerStatus } from "../interfaces";
import { FaCheck, FaXmark } from "react-icons/fa6";
import { zeroAppend } from "../other/utils";
import { colors } from "../constants/constants";
export function ServerOverlay() {
const [serverStatus, setServerStatus] = useState({} as ServerStatus);
@ -11,14 +12,13 @@ export function ServerOverlay() {
ServerStatusUpdatedEvent.on((status) => setServerStatus(status));
}, []);
let loadColor = "#8BFF63";
if (serverStatus.load > 1000) loadColor = "#F05252";
else if (serverStatus.load >= 100 && serverStatus.load < 1000) loadColor = "#FF9900";
let frameRateColor = "#8BFF63";
if (serverStatus.frameRate < 30) frameRateColor = "#F05252";
else if (serverStatus.frameRate >= 30 && serverStatus.frameRate < 60) frameRateColor = "#FF9900";
let loadColor = colors.OLYMPUS_GREEN;
if (serverStatus.load > 1000) loadColor = colors.OLYMPUS_RED;
else if (serverStatus.load >= 100 && serverStatus.load < 1000) loadColor = colors.OLYMPUS_ORANGE;
let frameRateColor = colors.OLYMPUS_GREEN;
if (serverStatus.frameRate < 30) frameRateColor = colors.OLYMPUS_RED;
else if (serverStatus.frameRate >= 30 && serverStatus.frameRate < 60) frameRateColor = colors.OLYMPUS_ORANGE;
const MThours = serverStatus.missionTime? serverStatus.missionTime.h: 0;
const MTminutes = serverStatus.missionTime? serverStatus.missionTime.m: 0;

View File

@ -1,63 +0,0 @@
//import { Dialog } from "../../dialog/dialog";
//import { createCheckboxOption } from "../../other/utils";
var categoryMap = {
Aircraft: "Aircraft",
Helicopter: "Helicopter",
GroundUnit: "Ground units",
NavyUnit: "Naval units",
};
export abstract class UnitDataFile {
protected data: any;
//protected dialog!: Dialog;
constructor() {}
buildCategoryCoalitionTable() {
const categories = this.#getCategoriesFromData();
const coalitions = ["blue", "neutral", "red"];
let headersHTML: string = ``;
let matrixHTML: string = ``;
//categories.forEach((category: string, index) => {
// matrixHTML += `<tr><td>${categoryMap[category as keyof typeof categoryMap]}</td>`;
//
// coalitions.forEach((coalition: string) => {
// if (index === 0)
// headersHTML += `<th data-coalition="${coalition}">${coalition[0].toUpperCase() + coalition.substring(1)}</th>`;
//
// const optionIsValid = this.data[category].hasOwnProperty(coalition);
// let checkboxHTML = createCheckboxOption(``, category, optionIsValid, () => { }, {
// "disabled": !optionIsValid,
// "name": "category-coalition-selection",
// "readOnly": !optionIsValid,
// "value" : `${category}:${coalition}`
// }).outerHTML;
//
// if (optionIsValid)
// checkboxHTML = checkboxHTML.replace(`"checkbox"`, `"checkbox" checked`); // inner and outerHTML screw default checked up
//
// matrixHTML += `<td data-coalition="${coalition}">${checkboxHTML}</td>`;
//
// });
// matrixHTML += "</tr>";
//});
//
//const table = <HTMLTableElement>this.dialog.getElement().querySelector("table.categories-coalitions");
//(<HTMLElement>table.tHead).innerHTML = `<tr><td>&nbsp;</td>${headersHTML}</tr>`;
//(<HTMLElement>table.querySelector(`tbody`)).innerHTML = matrixHTML;
}
#getCategoriesFromData() {
const categories = Object.keys(this.data);
categories.sort();
return categories;
}
getData() {
return this.data;
}
}

View File

@ -1,98 +0,0 @@
import { getApp } from "../../olympusapp";
//import { Dialog } from "../../dialog/dialog";
import { zeroAppend } from "../../other/utils";
import { Unit } from "../unit";
import { UnitDataFile } from "./unitdatafile";
export class UnitDataFileExport extends UnitDataFile {
protected data!: any;
//protected dialog: Dialog;
#element!: HTMLElement;
#filename: string = "export.json";
constructor(elementId: string) {
super();
//this.dialog = new Dialog(elementId);
//this.#element = this.dialog.getElement();
this.#element.querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => {
this.#doExport();
});
}
/**
* Show the form to start the export journey
*/
showForm(units: Unit[]) {
//this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
// el.classList.toggle("hide", el.getAttribute("data-on-error") === "show");
//});
//
//const data: any = {};
//const unitCanBeExported = (unit: Unit) => !["Aircraft", "Helicopter"].includes(unit.getCategory());
//
//units.filter((unit: Unit) => unit.getAlive() && unitCanBeExported(unit)).forEach((unit: Unit) => {
// const category = unit.getCategory();
// const coalition = unit.getCoalition();
//
// if (!data.hasOwnProperty(category)) {
// data[category] = {};
// }
//
// if (!data[category].hasOwnProperty(coalition))
// data[category][coalition] = [];
//
// data[category][coalition].push(unit);
//});
//
//this.data = data;
//this.buildCategoryCoalitionTable();
//this.dialog.show();
//
//const date = new Date();
//this.#filename = `olympus_${getApp().getMissionManager().getTheatre().replace(/[^\w]/gi, "").toLowerCase()}_${date.getFullYear()}${zeroAppend(date.getMonth() + 1, 2)}${zeroAppend(date.getDate(), 2)}_${zeroAppend(date.getHours(), 2)}${zeroAppend(date.getMinutes(), 2)}${zeroAppend(date.getSeconds(), 2)}.json`;
//var input = this.#element.querySelector("#export-filename") as HTMLInputElement;
//input.onchange = (ev: Event) => {
// this.#filename = (ev.currentTarget as HTMLInputElement).value;
//}
//if (input)
// input.value = this.#filename;
}
#doExport() {
let selectedUnits: Unit[] = [];
this.#element
.querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`)
.forEach(<HTMLInputElement>(checkbox: HTMLInputElement) => {
if (checkbox instanceof HTMLInputElement) {
const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition"
selectedUnits = selectedUnits.concat(this.data[category][coalition]);
}
});
if (selectedUnits.length === 0) {
alert("Please select at least one option for export.");
return;
}
var unitsToExport: { [key: string]: any } = {};
selectedUnits.forEach((unit: Unit) => {
var data: any = unit.getData();
if (unit.getGroupName() in unitsToExport) unitsToExport[unit.getGroupName()].push(data);
else unitsToExport[unit.getGroupName()] = [data];
});
const a = document.createElement("a");
const file = new Blob([JSON.stringify(unitsToExport)], {
type: "text/plain",
});
a.href = URL.createObjectURL(file);
var filename = this.#filename;
if (!this.#filename.toLowerCase().endsWith(".json")) filename += ".json";
a.download = filename;
a.click();
//this.dialog.hide();
}
}

View File

@ -1,147 +0,0 @@
import { getApp } from "../../olympusapp";
//import { Dialog } from "../../dialog/dialog";
import { UnitData } from "../../interfaces";
//import { ImportFileJSONSchemaValidator } from "../../schemas/schema";
import { UnitDataFile } from "./unitdatafile";
export class UnitDataFileImport extends UnitDataFile {
protected data!: any;
//protected dialog: Dialog;
#fileData!: { [key: string]: UnitData[] };
constructor(elementId: string) {
super();
//this.dialog = new Dialog(elementId);
//this.dialog.getElement().querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => {
// this.#doImport();
// this.dialog.hide();
//});
}
#doImport() {
//let selectedCategories: any = {};
//const unitsManager = getApp().getUnitsManager();
//
//this.dialog.getElement().querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach(<HTMLInputElement>(checkbox: HTMLInputElement) => {
// if (checkbox instanceof HTMLInputElement) {
// const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition"
// selectedCategories[category] = selectedCategories[category] || {};
// selectedCategories[category][coalition] = true;
// }
//});
//
//for (const [groupName, groupData] of Object.entries(this.#fileData)) {
// if (groupName === "" || groupData.length === 0 || !this.#unitGroupDataCanBeImported(groupData))
// continue;
//
// let { category, coalition } = groupData[0];
//
// if (!selectedCategories.hasOwnProperty(category)
// || !selectedCategories[category].hasOwnProperty(coalition)
// || selectedCategories[category][coalition] !== true)
// continue;
//
// let unitsToSpawn = groupData.filter((unitData: UnitData) => this.#unitDataCanBeImported(unitData)).map((unitData: UnitData) => {
// return { unitType: unitData.name, location: unitData.position, liveryID: "", skill: "High" }
// });
//
// unitsManager.spawnUnits(category, unitsToSpawn, coalition, false);
//}
}
selectFile() {
var input = document.createElement("input");
input.type = "file";
input.addEventListener("change", (e: any) => {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = (e: any) => {
try {
this.#fileData = JSON.parse(e.target.result);
//const validator = new ImportFileJSONSchemaValidator();
//if (!validator.validate(this.#fileData)) {
// const errors = validator.getErrors().reduce((acc:any, error:any) => {
// let errorString = error.instancePath.substring(1) + ": " + error.message;
// if (error.params) {
// const {allowedValues} = error.params;
// if (allowedValues)
// errorString += ": " + allowedValues.join(', ');
// }
// acc.push(errorString);
// return acc;
// }, [] as string[]);
// this.#showFileDataErrors(errors);
//} else {
// this.#showForm();
//}
} catch (e: any) {
this.#showFileDataErrors([e]);
}
};
reader.readAsText(file);
});
input.click();
}
#showFileDataErrors(reasons: string[]) {
//this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
// el.classList.toggle("hide", el.getAttribute("data-on-error") === "hide");
//});
//
//const reasonsList = this.dialog.getElement().querySelector(".import-error-reasons");
//if (reasonsList instanceof HTMLElement)
// reasonsList.innerHTML = `<li>${reasons.join("</li><li>")}</li>`;
//
//this.dialog.show();
}
#showForm() {
//this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
// el.classList.toggle("hide", el.getAttribute("data-on-error") === "show");
//});
//
//const data: any = {};
//
//for (const [group, units] of Object.entries(this.#fileData)) {
// if (group === "" || units.length === 0)
// continue;
//
// if (units.some((unit: UnitData) => !this.#unitDataCanBeImported(unit)))
// continue;
//
// const category = units[0].category;
//
// if (!data.hasOwnProperty(category)) {
// data[category] = {};
// }
//
// units.forEach((unit: UnitData) => {
// if (!data[category].hasOwnProperty(unit.coalition))
// data[category][unit.coalition] = [];
//
// data[category][unit.coalition].push(unit);
// });
//
//}
//
//this.data = data;
//this.buildCategoryCoalitionTable();
//this.dialog.show();
}
#unitDataCanBeImported(unitData: UnitData) {
return unitData.alive && this.#unitGroupDataCanBeImported([unitData]);
}
#unitGroupDataCanBeImported(unitGroupData: UnitData[]) {
return (
unitGroupData.every((unitData: UnitData) => {
return !["Aircraft", "Helicopter"].includes(unitData.category);
}) && unitGroupData.some((unitData: UnitData) => unitData.alive)
);
}
}

View File

@ -16,6 +16,7 @@ import {
nmToM,
zeroAppend,
computeBearingRangeString,
adjustBrightness,
} from "../other/utils";
import { CustomMarker } from "../map/markers/custommarker";
import { SVGInjector } from "@tanem/svg-injector";
@ -43,6 +44,7 @@ import {
ContextActionTarget,
SHORT_PRESS_MILLISECONDS,
TRAIL_LENGTH,
colors,
} from "../constants/constants";
import { DataExtractor } from "../server/dataextractor";
import { Weapon } from "../weapon/weapon";
@ -319,7 +321,7 @@ export abstract class Unit extends CustomMarker {
this.ID = ID;
this.#pathPolyline = new Polyline([], {
color: "#2d3e50",
color: colors.GRAY,
weight: 3,
opacity: 0.5,
smoothFactor: 1,
@ -327,7 +329,7 @@ export abstract class Unit extends CustomMarker {
this.#pathPolyline.addTo(getApp().getMap());
this.#targetPositionMarker = new TargetMarker(new LatLng(0, 0));
this.#targetPositionPolyline = new Polyline([], {
color: "#FF0000",
color: colors.WHITE,
weight: 3,
opacity: 0.5,
smoothFactor: 1,
@ -601,8 +603,9 @@ export abstract class Unit extends CustomMarker {
if (!this.#blueprint && this.#name !== "") {
const blueprint = getApp().getUnitsManager().getDatabase().getByName(this.#name);
this.#blueprint = blueprint ?? null;
/* Refresh the marker */
this.#redrawMarker();
if (this.#blueprint)
/* Refresh the marker */
this.#redrawMarker();
}
/* Update the blueprint to use when the unit is grouped */
@ -1438,9 +1441,9 @@ export abstract class Unit extends CustomMarker {
if (this.#alive && drawMiniMapMarker) {
if (this.#miniMapMarker == null) {
this.#miniMapMarker = new CircleMarker(new LatLng(this.#position.lat, this.#position.lng), { radius: 0.5 });
if (this.#coalition == "neutral") this.#miniMapMarker.setStyle({ color: "#CFD9E8", radius: 2 });
else if (this.#coalition == "red") this.#miniMapMarker.setStyle({ color: "#ff5858", radius: 2 });
else this.#miniMapMarker.setStyle({ color: "#247be2", radius: 2 });
if (this.#coalition == "neutral") this.#miniMapMarker.setStyle({ color: colors.NEUTRAL_COALITION, radius: 2 });
else if (this.#coalition == "red") this.#miniMapMarker.setStyle({ color: colors.RED_COALITION, radius: 2 });
else this.#miniMapMarker.setStyle({ color: colors.BLUE_COALITION, radius: 2 });
this.#miniMapMarker.addTo(getApp().getMap().getMiniMapLayerGroup());
this.#miniMapMarker.bringToBack();
} else {
@ -1585,7 +1588,7 @@ export abstract class Unit extends CustomMarker {
/* Draw the contact trail */
if (/*TODO getApp().getMap().getOptions().AWACSMode*/ false) {
this.#trailPolylines = this.#trailPositions.map(
(latlng, idx) => new Polyline([latlng, latlng], { color: "#FFFFFF", opacity: 1 - (idx + 1) / TRAIL_LENGTH })
(latlng, idx) => new Polyline([latlng, latlng], { color: colors.WHITE, opacity: 1 - (idx + 1) / TRAIL_LENGTH })
);
this.#trailPolylines.forEach((polyline) => polyline.addTo(getApp().getMap()));
}
@ -1678,10 +1681,10 @@ export abstract class Unit extends CustomMarker {
} else endLatLng = new LatLng(contact.getPosition().lat, contact.getPosition().lng);
var color;
if (contactData.detectionMethod === VISUAL || contactData.detectionMethod === OPTIC) color = "#FF00FF";
else if (contactData.detectionMethod === RADAR || contactData.detectionMethod === IRST) color = "#FFFF00";
else if (contactData.detectionMethod === RWR) color = "#00FF00";
else color = "#FFFFFF";
if (contactData.detectionMethod === VISUAL || contactData.detectionMethod === OPTIC) color = colors.MAGENTA;
else if (contactData.detectionMethod === RADAR || contactData.detectionMethod === IRST) color = colors.YELLOW;
else if (contactData.detectionMethod === RWR) color = colors.GREEN;
else color = colors.WHITE;
var contactPolyline = new Polyline([startLatLng, endLatLng], {
color: color,
weight: 3,
@ -1742,13 +1745,13 @@ export abstract class Unit extends CustomMarker {
this.#acquisitionCircle.addTo(getApp().getMap());
switch (this.getCoalition()) {
case "red":
this.#acquisitionCircle.options.color = "#D42121";
this.#acquisitionCircle.options.color = adjustBrightness(colors.RED_COALITION, -20);
break;
case "blue":
this.#acquisitionCircle.options.color = "#017DC1";
this.#acquisitionCircle.options.color = adjustBrightness(colors.BLUE_COALITION, -20);
break;
default:
this.#acquisitionCircle.options.color = "#111111";
this.#acquisitionCircle.options.color = adjustBrightness(colors.NEUTRAL_COALITION, -20);
break;
}
}
@ -1768,13 +1771,13 @@ export abstract class Unit extends CustomMarker {
this.#engagementCircle.addTo(getApp().getMap());
switch (this.getCoalition()) {
case "red":
this.#engagementCircle.options.color = "#FF5858";
this.#engagementCircle.options.color = colors.RED_COALITION;
break;
case "blue":
this.#engagementCircle.options.color = "#3BB9FF";
this.#engagementCircle.options.color = colors.BLUE_COALITION;
break;
default:
this.#engagementCircle.options.color = "#CFD9E8";
this.#engagementCircle.options.color = colors.NEUTRAL_COALITION;
break;
}
}

View File

@ -19,8 +19,6 @@ import { citiesDatabase } from "./databases/citiesdatabase";
import { TemporaryUnitMarker } from "../map/markers/temporaryunitmarker";
import { Contact, UnitBlueprint, UnitData, UnitSpawnTable } from "../interfaces";
import { Group } from "./group";
import { UnitDataFileExport } from "./importexport/unitdatafileexport";
import { UnitDataFileImport } from "./importexport/unitdatafileimport";
import { CoalitionCircle } from "../map/coalitionarea/coalitioncircle";
import { ContextActionSet } from "./contextactionset";
import {
@ -52,8 +50,6 @@ export class UnitsManager {
#selectionEventDisabled: boolean = false;
#units: { [ID: number]: Unit } = {};
#groups: { [groupName: string]: Group } = {};
#unitDataExport!: UnitDataFileExport;
#unitDataImport!: UnitDataFileImport;
#unitDatabase: UnitDatabase;
#protectionCallback: (units: Unit[]) => void = (units) => {};
#AWACSReference: Unit | null = null;
@ -1421,22 +1417,6 @@ export class UnitsManager {
});
}
/** Export all the ground and navy units to file. Does not work on Aircraft and Helicopter units.
* TODO: Extend to aircraft and helicopters
*/
exportToFile() {
if (!this.#unitDataExport) this.#unitDataExport = new UnitDataFileExport("unit-export-dialog");
this.#unitDataExport.showForm(Object.values(this.#units));
}
/** Import ground and navy units from file
* TODO: extend to support aircraft and helicopters
*/
importFromFile() {
if (!this.#unitDataImport) this.#unitDataImport = new UnitDataFileImport("unit-import-dialog");
this.#unitDataImport.selectFile();
}
/** Spawn a new group of units
*
* @param category Category of the new units