mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
More work on unit context actions
This commit is contained in:
parent
b089a8cfd8
commit
79016d72e3
@ -178,6 +178,7 @@ export const defaultMapLayers = {
|
||||
export const IDLE = "Idle";
|
||||
export const MOVE_UNIT = "Move unit";
|
||||
export const SPAWN_UNIT = "Spawn unit";
|
||||
export const CONTEXT_ACTION = "Context action";
|
||||
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
|
||||
|
||||
export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"];
|
||||
|
||||
@ -11,7 +11,7 @@ import { bearing, /*createCheckboxOption, createSliderInputOption, createTextInp
|
||||
import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
|
||||
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
|
||||
import { ClickableMiniMap } from "./clickableminimap";
|
||||
import { mapMirrors, defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, /*MAP_MARKER_CONTROLS,*/ DCS_LINK_PORT, DCS_LINK_RATIO, MAP_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, SPAWN_UNIT } from "../constants/constants";
|
||||
import { mapMirrors, defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, /*MAP_MARKER_CONTROLS,*/ DCS_LINK_PORT, DCS_LINK_RATIO, MAP_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, SPAWN_UNIT, CONTEXT_ACTION } from "../constants/constants";
|
||||
import { CoalitionArea } from "./coalitionarea/coalitionarea";
|
||||
//import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
|
||||
import { DrawingCursor } from "./coalitionarea/drawingcursor";
|
||||
@ -30,6 +30,7 @@ import './markers/stylesheets/units.css'
|
||||
import './theme.css'
|
||||
import { Coalition, MapHiddenTypes, MapOptions } from "../types/types";
|
||||
import { SpawnRequestTable, UnitBlueprint, UnitSpawnTable } from "../interfaces";
|
||||
import { ContextAction } from "../unit/contextaction";
|
||||
|
||||
var hasTouchScreen = false;
|
||||
//if ("maxTouchPoints" in navigator)
|
||||
@ -109,6 +110,8 @@ export class Map extends L.Map {
|
||||
#bradcastPositionXmlHttp: XMLHttpRequest | null = null;
|
||||
#cameraZoomRatio: number = 1.0;
|
||||
|
||||
#contextAction: null | ContextAction = null;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param ID - the ID of the HTML element which will contain the context menu
|
||||
@ -305,7 +308,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
/* State machine */
|
||||
setState(state: string, options?: { spawnRequestTable: SpawnRequestTable }) {
|
||||
setState(state: string, options?: { spawnRequestTable?: SpawnRequestTable, contextAction?: ContextAction }) {
|
||||
this.#state = state;
|
||||
|
||||
/* Operations to perform if you are NOT in a state */
|
||||
@ -319,6 +322,9 @@ export class Map extends L.Map {
|
||||
this.#spawnCursor?.removeFrom(this);
|
||||
this.#spawnCursor = new TemporaryUnitMarker(new L.LatLng(0, 0), this.#spawnRequestTable?.unit.unitType ?? "unknown", this.#spawnRequestTable?.coalition ?? 'blue');
|
||||
}
|
||||
else if (this.#state === CONTEXT_ACTION ) {
|
||||
this.#contextAction = options?.contextAction ?? null;
|
||||
}
|
||||
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
|
||||
this.#coalitionAreas.push(new CoalitionArea([]));
|
||||
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
|
||||
@ -440,10 +446,10 @@ export class Map extends L.Map {
|
||||
return this.#lastMouseCoordinates;
|
||||
}
|
||||
|
||||
centerOnUnit(ID: number | null) {
|
||||
if (ID != null) {
|
||||
centerOnUnit(unit: Unit | null) {
|
||||
if (unit !== null) {
|
||||
this.options.scrollWheelZoom = 'center';
|
||||
this.#centerUnit = getApp().getUnitsManager().getUnitByID(ID);
|
||||
this.#centerUnit = unit;
|
||||
}
|
||||
else {
|
||||
this.options.scrollWheelZoom = undefined;
|
||||
@ -707,6 +713,10 @@ export class Map extends L.Map {
|
||||
this.#computeDestinationRotation = false;
|
||||
}
|
||||
}
|
||||
else if (this.#state === CONTEXT_ACTION) {
|
||||
if (this.#contextAction)
|
||||
this.#contextAction.executeCallback(null, e.latlng);
|
||||
}
|
||||
else {
|
||||
this.setState(IDLE);
|
||||
}
|
||||
|
||||
@ -49,7 +49,7 @@ export function OlDropdown(props: {
|
||||
if (cxr > window.innerWidth)
|
||||
offsetX -= (cxr - window.innerWidth)
|
||||
if (cyb > window.innerHeight)
|
||||
offsetY = -ch - 8;
|
||||
offsetY -= bh + ch + 16;
|
||||
|
||||
/* Apply the offset */
|
||||
content.style.left = `${offsetX}px`
|
||||
|
||||
@ -1,42 +1,61 @@
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { faExternalLink, faLock, faLockOpen, faUnlock, faUnlockAlt } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import React from "react"
|
||||
import React, { useRef, useState } from "react"
|
||||
import { OlTooltip } from "./oltooltip";
|
||||
|
||||
export function OlStateButton(props: {
|
||||
className?: string,
|
||||
checked: boolean,
|
||||
icon: IconProp,
|
||||
tooltip: string,
|
||||
onClick: () => void
|
||||
}) {
|
||||
var [hover, setHover] = useState(false);
|
||||
var buttonRef = useRef(null);
|
||||
|
||||
const className = (props.className ?? '') + ` h-[40px] w-[40px] flex-none font-medium rounded-md text-lg dark:bg-olympus-600 dark:hover:bg-olympus-300 dark:data-[checked='true']:bg-blue-500 dark:data-[checked='true']:text-white dark:text-gray-300 dark:border-gray-600 `;
|
||||
|
||||
return <button onClick={props.onClick} data-checked={props.checked} type="button" className={className}>
|
||||
return <><button ref={buttonRef} onClick={props.onClick} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
|
||||
<FontAwesomeIcon icon={props.icon} />
|
||||
</button>
|
||||
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
|
||||
</>
|
||||
}
|
||||
|
||||
export function OlRoundStateButton(props: {
|
||||
className?: string,
|
||||
checked: boolean,
|
||||
icon: IconProp,
|
||||
tooltip: string,
|
||||
onClick: () => void
|
||||
}) {
|
||||
var [hover, setHover] = useState(false);
|
||||
var buttonRef = useRef(null);
|
||||
|
||||
const className = (props.className ?? '') + ` h-8 w-8 flex-none m-auto border-2 border-gray-900 font-medium rounded-full text-sm dark:bg-[transparent] dark:data-[checked='true']:bg-white dark:text-gray-400 dark:data-[checked='true']:text-gray-900 dark:data-[checked='true']:border-white dark:border-gray-400 dark:data-[checked='true']:hover:bg-gray-200 dark:data-[checked='true']:hover:border-gray-200 dark:hover:bg-gray-800`;
|
||||
|
||||
return <button onClick={props.onClick} data-checked={props.checked} type="button" className={className}>
|
||||
return <><button ref={buttonRef} onClick={props.onClick} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
|
||||
<FontAwesomeIcon className="pt-[3px]" icon={props.icon} />
|
||||
</button>
|
||||
{ hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} /> }
|
||||
</>
|
||||
}
|
||||
|
||||
export function OlLockStateButton(props: {
|
||||
className?: string,
|
||||
checked: boolean,
|
||||
tooltip: string,
|
||||
onClick: () => void
|
||||
}) {
|
||||
var [hover, setHover] = useState(false);
|
||||
var buttonRef = useRef(null);
|
||||
|
||||
const className = (props.className ?? '') + ` h-8 w-8 flex-none m-auto border-gray-900 font-medium rounded-full text-sm dark:bg-red-500 dark:data-[checked='true']:bg-green-500 dark:text-olympus-900 dark:data-[checked='true']:text-green-900 dark:data-[checked='true']:hover:bg-green-400 dark:hover:bg-red-400`;
|
||||
|
||||
return <button onClick={props.onClick} data-checked={props.checked} type="button" className={className}>
|
||||
<FontAwesomeIcon className="pt-[3px]" icon={props.checked==true ? faUnlockAlt:faLock} />
|
||||
return <><button ref={buttonRef} onClick={props.onClick} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
|
||||
<FontAwesomeIcon className="pt-[3px]" icon={props.checked == true ? faUnlockAlt : faLock} />
|
||||
</button>
|
||||
{ hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} /> }
|
||||
</>
|
||||
}
|
||||
63
frontend/react/src/ui/components/oltooltip.tsx
Normal file
63
frontend/react/src/ui/components/oltooltip.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
|
||||
export function OlTooltip(props: {
|
||||
content: string,
|
||||
buttonRef: React.MutableRefObject<null>
|
||||
}) {
|
||||
var contentRef = useRef(null);
|
||||
|
||||
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
|
||||
/* Reset the position of the content */
|
||||
content.style.left = "0px";
|
||||
content.style.top = "0px";
|
||||
content.style.height = "";
|
||||
|
||||
/* Get the position and size of the button and the content elements */
|
||||
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];
|
||||
|
||||
/* Limit the maximum height */
|
||||
if (ch > 400) {
|
||||
ch = 400;
|
||||
content.style.height = `${ch}px`;
|
||||
}
|
||||
|
||||
/* Compute the horizontal position of the center of the button and the content */
|
||||
var cxc = (cxl + cxr) / 2;
|
||||
var bxc = (bxl + bxr) / 2;
|
||||
|
||||
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
|
||||
var offsetX = bxc - cxc;
|
||||
var offsetY = byb - cyt + 8;
|
||||
|
||||
/* Compute the new position of the left and right margins of the content */
|
||||
cxl += offsetX;
|
||||
cxr += offsetX;
|
||||
cyb += offsetY;
|
||||
|
||||
/* Try and move the content so it is inside the screen */
|
||||
if (cxl < 0)
|
||||
offsetX -= cxl;
|
||||
if (cxr > window.innerWidth)
|
||||
offsetX -= (cxr - window.innerWidth)
|
||||
if (cyb > window.innerHeight)
|
||||
offsetY -= bh + ch + 16;
|
||||
|
||||
/* Apply the offset */
|
||||
content.style.left = `${offsetX}px`
|
||||
content.style.top = `${offsetY}px`
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (contentRef.current && props.buttonRef.current) {
|
||||
const content = contentRef.current as HTMLDivElement;
|
||||
const button = props.buttonRef.current as HTMLButtonElement;
|
||||
|
||||
setPosition(content, button);
|
||||
}
|
||||
})
|
||||
|
||||
return props.content !== "" && <div ref={contentRef} className={`absolute whitespace-nowrap z-ui-4 px-3 py-2 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm dark:bg-gray-700`}>
|
||||
{ props.content }
|
||||
</div>
|
||||
}
|
||||
@ -24,7 +24,7 @@ export function Header() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="ml-auto">
|
||||
<OlLockStateButton checked={false} onClick={() => {}}/>
|
||||
<OlLockStateButton checked={false} onClick={() => {}} tooltip="Lock/unlock protected units (from scripted mission)"/>
|
||||
</div>
|
||||
<div className="flex flex-row h-fit items-center justify-start gap-1">
|
||||
{
|
||||
@ -36,7 +36,8 @@ export function Header() {
|
||||
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
|
||||
}}
|
||||
checked={!appState.mapHiddenTypes[entry[0]]}
|
||||
icon={entry[1]} />
|
||||
icon={entry[1]}
|
||||
tooltip={"Hide/show " + entry[0] + " units" } />
|
||||
})
|
||||
}
|
||||
</div>
|
||||
@ -52,7 +53,8 @@ export function Header() {
|
||||
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
|
||||
}}
|
||||
checked={!appState.mapHiddenTypes[entry[0]]}
|
||||
icon={entry[1]} />
|
||||
icon={entry[1]}
|
||||
tooltip={"Hide/show " + entry[0] + " units" } />
|
||||
})
|
||||
}
|
||||
</div>
|
||||
@ -61,18 +63,21 @@ export function Header() {
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType( 'blue', !appState.mapHiddenTypes['blue'] )}
|
||||
checked={!appState.mapHiddenTypes['blue']}
|
||||
icon={faFlag} className={"!text-blue-500"} />
|
||||
icon={faFlag} className={"!text-blue-500"}
|
||||
tooltip={"Hide/show blue units" } />
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType('red', !appState.mapHiddenTypes['red'] )}
|
||||
checked={!appState.mapHiddenTypes['red']}
|
||||
icon={faFlag} className={"!text-red-500"} />
|
||||
icon={faFlag} className={"!text-red-500"}
|
||||
tooltip={"Hide/show red units" } />
|
||||
<OlRoundStateButton
|
||||
onClick={() => getApp().getMap().setHiddenType('neutral', !appState.mapHiddenTypes['neutral'] )}
|
||||
checked={!appState.mapHiddenTypes['neutral']}
|
||||
icon={faFlag} className={"!text-gray-500"} />
|
||||
icon={faFlag} className={"!text-gray-500"}
|
||||
tooltip={"Hide/show neutral units" } />
|
||||
</div>
|
||||
<OlLabelToggle toggled={false} leftLabel={"Live"} rightLabel={"Map"} onClick={() => {}}></OlLabelToggle>
|
||||
<OlStateButton checked={false} icon={faCamera} onClick={() => {}} />
|
||||
<OlStateButton checked={false} icon={faCamera} onClick={() => {}} tooltip="Activate/deactivate camera plugin" />
|
||||
<OlDropdown label="DCS Sat" className="w-40">
|
||||
<OlDropdownItem className="w-full">DCS Sat</OlDropdownItem>
|
||||
<OlDropdownItem className="w-full">DCS Alt</OlDropdownItem>
|
||||
|
||||
@ -12,17 +12,17 @@ export function SideBar() {
|
||||
<nav className="flex flex-col z-ui-1 h-full bg-gray-300 dark:bg-olympus-900">
|
||||
<div className="flex-1 flex-wrap items-center justify-center p-4 w-16">
|
||||
<div className="flex flex-col items-center justify-center gap-2.5">
|
||||
<OlStateButton onClick={events.toggleMainMenuVisible} checked={appState.mainMenuVisible} icon={faEllipsisV}></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleSpawnMenuVisible} checked={appState.spawnMenuVisible} icon={faPlusSquare}></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleUnitControlMenuVisible} checked={appState.unitControlMenuVisible} icon={faGamepad}></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleMeasureMenuVisible} checked={appState.measureMenuVisible} icon={faRuler}></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleDrawingMenuVisible} checked={appState.drawingMenuVisible} icon={faPencil}></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleMainMenuVisible} checked={appState.mainMenuVisible} icon={faEllipsisV} tooltip="Hide/show main menu"></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleSpawnMenuVisible} checked={appState.spawnMenuVisible} icon={faPlusSquare} tooltip="Hide/show unit spawn menu"></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleUnitControlMenuVisible} checked={appState.unitControlMenuVisible} icon={faGamepad} tooltip=""></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleMeasureMenuVisible} checked={appState.measureMenuVisible} icon={faRuler} tooltip=""></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleDrawingMenuVisible} checked={appState.drawingMenuVisible} icon={faPencil} tooltip="Hide/show drawing menu"></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap content-end justify-center p-4 w-16">
|
||||
<div className="flex flex-col items-center justify-center gap-2.5">
|
||||
<OlStateButton onClick={() => window.open("https://github.com/Pax1601/DCSOlympus/wiki")} checked={false} icon={faQuestionCircle}></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleOptionsMenuVisible} checked={appState.optionsMenuVisible} icon={faCog}></OlStateButton>
|
||||
<OlStateButton onClick={() => window.open("https://github.com/Pax1601/DCSOlympus/wiki")} checked={false} icon={faQuestionCircle} tooltip="Open user guide on separate window"></OlStateButton>
|
||||
<OlStateButton onClick={events.toggleOptionsMenuVisible} checked={appState.optionsMenuVisible} icon={faCog} tooltip="Hide/show settings menu"></OlStateButton>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@ -5,15 +5,19 @@ import { OlStateButton } from '../components/olstatebutton';
|
||||
import { faAccessibleIcon } from '@fortawesome/free-brands-svg-icons';
|
||||
import { faCamera } from '@fortawesome/free-solid-svg-icons';
|
||||
import { getApp } from '../../olympusapp';
|
||||
import { ContextAction } from '../../unit/contextaction';
|
||||
import { CONTEXT_ACTION } from '../../constants/constants';
|
||||
import { FaInfoCircle, FaQuestionCircle } from 'react-icons/fa';
|
||||
|
||||
export function UnitMouseControlBar(props: {
|
||||
|
||||
}) {
|
||||
var [open, setOpen] = useState(false);
|
||||
var [open, setOpen] = useState(false);
|
||||
var [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||
var [contextActionsSet, setContextActionsSet] = useState(new ContextActionSet());
|
||||
var [activeContextAction, setActiveContextAction] = useState(null as null | ContextAction);
|
||||
|
||||
/* When a unit is selected, open the menu */
|
||||
/* When a unit is selected, open the menu */
|
||||
document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
|
||||
setOpen(true);
|
||||
setSelectedUnits(ev.detail as Unit[]);
|
||||
@ -34,22 +38,43 @@ export function UnitMouseControlBar(props: {
|
||||
updateData();
|
||||
})
|
||||
|
||||
/* Update the current values of the shown data */
|
||||
/* Update the current values of the shown data */
|
||||
function updateData() {
|
||||
var newContextActionSet = new ContextActionSet();
|
||||
|
||||
getApp().getUnitsManager().getSelectedUnits().forEach((unit: Unit) => {
|
||||
unit.appendContextActions(newContextActionSet);
|
||||
})
|
||||
getApp().getUnitsManager().getSelectedUnits().forEach((unit: Unit) => {
|
||||
unit.appendContextActions(newContextActionSet);
|
||||
})
|
||||
|
||||
setContextActionsSet(newContextActionSet);
|
||||
}
|
||||
setActiveContextAction(null);
|
||||
}
|
||||
|
||||
return <div className='flex gap-2 rounded-md absolute top-20 left-[50%] translate-x-[-50%] bg-gray-200 dark:bg-olympus-900 z-ui-1 p-2'>
|
||||
{
|
||||
Object.values(contextActionsSet.getContextActions()).map((contextAction) => {
|
||||
return <OlStateButton checked={false} icon={contextAction.getIcon()} onClick={() => {}} />
|
||||
})
|
||||
return <> {
|
||||
open && <>
|
||||
<div className='flex gap-2 rounded-md absolute top-20 left-[50%] translate-x-[-50%] bg-gray-200 dark:bg-olympus-900 z-ui-1 p-2'>
|
||||
{
|
||||
Object.values(contextActionsSet.getContextActions()).map((contextAction) => {
|
||||
return <OlStateButton checked={contextAction === activeContextAction} icon={contextAction.getIcon()} tooltip={contextAction.getLabel()} onClick={() => {
|
||||
if (contextAction.getOptions().executeImmediately) {
|
||||
setActiveContextAction(null);
|
||||
contextAction.executeCallback(null, null);
|
||||
} else {
|
||||
setActiveContextAction(contextAction);
|
||||
getApp().getMap().setState(CONTEXT_ACTION, { contextAction: contextAction });
|
||||
}
|
||||
}} />
|
||||
})
|
||||
}
|
||||
</div>
|
||||
{ activeContextAction && <div className='flex gap-2 p-4 items-center rounded-md absolute top-36 left-[50%] translate-x-[-50%] bg-gray-200 dark:bg-olympus-800 z-ui-1'>
|
||||
<FaInfoCircle className="text-blue-500 mr-2 text-sm"/>
|
||||
<div className='border-l-[1px] px-5 dark:text-gray-400'>
|
||||
{activeContextAction.getDescription()}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
@ -1,29 +1,30 @@
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { Unit } from "./unit";
|
||||
import { LatLng } from "leaflet";
|
||||
|
||||
export interface ContextActionOptions {
|
||||
isScenic?: boolean
|
||||
executeImmediately?: boolean
|
||||
}
|
||||
|
||||
export type ContextActionCallback = (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => void;
|
||||
|
||||
export class ContextAction {
|
||||
#id: string = "";
|
||||
#label: string = "";
|
||||
#description: string = "";
|
||||
#callback: CallableFunction | null = null;
|
||||
#callback: ContextActionCallback | null = null;
|
||||
#units: Unit[] = [];
|
||||
#hideContextAfterExecution: boolean = true
|
||||
#icon: IconDefinition;
|
||||
#options: ContextActionOptions;
|
||||
|
||||
constructor(id: string, label: string, description: string, icon: IconDefinition, callback: CallableFunction, hideContextAfterExecution: boolean = true, options: ContextActionOptions) {
|
||||
constructor(id: string, label: string, description: string, icon: IconDefinition, callback: ContextActionCallback, options: ContextActionOptions) {
|
||||
this.#id = id;
|
||||
this.#label = label;
|
||||
this.#description = description;
|
||||
this.#callback = callback;
|
||||
this.#icon = icon;
|
||||
this.#hideContextAfterExecution = hideContextAfterExecution;
|
||||
this.#options = {
|
||||
"isScenic": false,
|
||||
executeImmediately: false,
|
||||
...options
|
||||
}
|
||||
}
|
||||
@ -56,12 +57,8 @@ export class ContextAction {
|
||||
return this.#icon;
|
||||
}
|
||||
|
||||
executeCallback() {
|
||||
executeCallback(targetUnit: Unit | null, targetPosition: LatLng | null) {
|
||||
if (this.#callback)
|
||||
this.#callback(this.#units);
|
||||
}
|
||||
|
||||
getHideContextAfterExecution() {
|
||||
return this.#hideContextAfterExecution;
|
||||
this.#callback(this.#units, targetUnit, targetPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,15 @@
|
||||
import { LatLng } from "leaflet";
|
||||
import { ContextAction, ContextActionOptions } from "./contextaction";
|
||||
import { ContextAction, ContextActionCallback, ContextActionOptions } from "./contextaction";
|
||||
import { Unit } from "./unit";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
export class ContextActionSet {
|
||||
#contextActions: {[key: string]: ContextAction} = {};
|
||||
#contextActions: { [key: string]: ContextAction } = {};
|
||||
|
||||
addContextAction(unit: Unit, id: string, label: string, description: string, icon: IconDefinition, callback: (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => void, hideContextAfterExecution: boolean = true, options?:ContextActionOptions) {
|
||||
addContextAction(unit: Unit, id: string, label: string, description: string, icon: IconDefinition, callback: ContextActionCallback, options?: ContextActionOptions) {
|
||||
options = options || {};
|
||||
|
||||
if (!(id in this.#contextActions)) {
|
||||
this.#contextActions[id] = new ContextAction(id, label, description, icon, callback, hideContextAfterExecution, options);
|
||||
this.#contextActions[id] = new ContextAction(id, label, description, icon, callback, options);
|
||||
}
|
||||
this.#contextActions[id].addUnit(unit);
|
||||
}
|
||||
|
||||
@ -1000,6 +1000,7 @@ export abstract class Unit extends CustomMarker {
|
||||
showFollowOptions(units: Unit[]) {
|
||||
var contextActionSet = new ContextActionSet();
|
||||
|
||||
// TODO FIX
|
||||
contextActionSet.addContextAction(this, 'trail', "Trail", "Follow unit in trail formation", olIconsTrail, () => this.applyFollowOptions('trail', units));
|
||||
contextActionSet.addContextAction(this, 'echelon-lh', "Echelon (LH)", "Follow unit in echelon left formation", olIconsEchelonLh, () => this.applyFollowOptions('echelon-lh', units));
|
||||
contextActionSet.addContextAction(this, 'echelon-rh', "Echelon (RH)", "Follow unit in echelon right formation", olIconsEchelonRh, () => this.applyFollowOptions('echelon-rh', units));
|
||||
@ -1111,11 +1112,11 @@ export abstract class Unit extends CustomMarker {
|
||||
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" });
|
||||
this.#miniMapMarker.setStyle({ color: "#CFD9E8", radius: 2 });
|
||||
else if (this.#coalition == "red")
|
||||
this.#miniMapMarker.setStyle({ color: "#ff5858" });
|
||||
this.#miniMapMarker.setStyle({ color: "#ff5858", radius: 2 });
|
||||
else
|
||||
this.#miniMapMarker.setStyle({ color: "#247be2" });
|
||||
this.#miniMapMarker.setStyle({ color: "#247be2", radius: 2 });
|
||||
this.#miniMapMarker.addTo(getApp().getMap().getMiniMapLayerGroup());
|
||||
this.#miniMapMarker.bringToBack();
|
||||
}
|
||||
@ -1498,12 +1499,33 @@ export abstract class AirUnit extends Unit {
|
||||
}
|
||||
|
||||
appendContextActions(contextActionSet: ContextActionSet) {
|
||||
contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", olStatesAttack, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
|
||||
contextActionSet.addContextAction(this, "follow", "Follow unit", "Follow this unit in formation", olIconsFollow, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { targetUnit.showFollowOptions(units); }, false); // Don't hide the context menu after the execution (to show the follow options)
|
||||
contextActionSet.addContextAction(this, "refuel", "Refuel", "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB", olStatesRefuel, (units: Unit[]) => { getApp().getUnitsManager().refuel(units) });
|
||||
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, () => { getApp().getMap().centerOnUnit(this.ID); });
|
||||
contextActionSet.addContextAction(this, "bomb", "Precision bombing", "Precision bombing of a specific point", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().bombPoint(targetPosition, units) });
|
||||
contextActionSet.addContextAction(this, "carpet-bomb", "Carpet bombing", "Carpet bombing close to a point", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().carpetBomb(targetPosition, units) });
|
||||
/* Context actions to be executed immediately */
|
||||
contextActionSet.addContextAction(this, "refuel", "Refuel", "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB", olStatesRefuel, (units: Unit[]) => {
|
||||
getApp().getUnitsManager().refuel(units)
|
||||
}, { executeImmediately: true });
|
||||
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, (units: Unit[]) => {
|
||||
getApp().getMap().centerOnUnit(units[0]);
|
||||
}, { executeImmediately: true });
|
||||
|
||||
/* Context actions with a target unit */
|
||||
contextActionSet.addContextAction(this, "attack", "Attack unit", "Click on a unit to attack it using A/A or A/G weapons", olStatesAttack, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
if (targetUnit)
|
||||
getApp().getUnitsManager().attackUnit(targetUnit.ID, units)
|
||||
});
|
||||
contextActionSet.addContextAction(this, "follow", "Follow unit", "Click on a unit to follow it in formation", olIconsFollow, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
if (targetUnit)
|
||||
targetUnit.showFollowOptions(units);
|
||||
});
|
||||
|
||||
/* Context actions with a target position */
|
||||
contextActionSet.addContextAction(this, "bomb", "Precision bombing", "Click on a point to execute a precision bombing attack", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
if (targetPosition)
|
||||
getApp().getUnitsManager().bombPoint(targetPosition , units)
|
||||
});
|
||||
contextActionSet.addContextAction(this, "carpet-bomb", "Carpet bombing", "Click on a point to execute a carpet bombing attack", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
if (targetPosition)
|
||||
getApp().getUnitsManager().carpetBomb(targetPosition , units)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1540,7 +1562,10 @@ export class Helicopter extends AirUnit {
|
||||
|
||||
appendContextActions(contextActionSet: ContextActionSet) {
|
||||
super.appendContextActions(contextActionSet);
|
||||
contextActionSet.addContextAction(this, "land-at-point", "Land here", "Land at this precise location", olIconsLandAtPoint, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().landAtPoint(targetPosition, units) });
|
||||
contextActionSet.addContextAction(this, "land-at-point", "Land here", "Click on a point to land there", olIconsLandAtPoint, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
if (targetPosition)
|
||||
getApp().getUnitsManager().landAtPoint(targetPosition , units)
|
||||
});
|
||||
}
|
||||
|
||||
getMarkerCategory() {
|
||||
@ -1575,21 +1600,38 @@ export class GroundUnit extends Unit {
|
||||
}
|
||||
|
||||
appendContextActions(contextActionSet: ContextActionSet) {
|
||||
contextActionSet.addContextAction(this, "group-ground", "Group ground units", "Create a group of ground units", olIconsGroupGround, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().createGroup(units) });
|
||||
contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
|
||||
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, () => { getApp().getMap().centerOnUnit(this.ID); });
|
||||
|
||||
if (this.canTargetPoint()) {
|
||||
contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) });
|
||||
contextActionSet.addContextAction(this, "simulate-fire-fight", "Simulate fire fight", "Simulate a fire fight by shooting randomly in a certain large area.\nWARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().simulateFireFight(targetPosition, units) });
|
||||
}
|
||||
/* Context actions to be executed immediately */
|
||||
contextActionSet.addContextAction(this, "group-ground", "Group ground units", "Create a group of ground units", olIconsGroupGround, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
getApp().getUnitsManager().createGroup(units)
|
||||
}, { executeImmediately: true });
|
||||
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, (units: Unit[]) => {
|
||||
getApp().getMap().centerOnUnit(units[0]);
|
||||
}, { executeImmediately: true });
|
||||
|
||||
if (this.canAAA()) {
|
||||
contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when an enemy unit gets close enough.\nWARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[]) => { getApp().getUnitsManager().scenicAAA(units) }, undefined, {
|
||||
"isScenic": true
|
||||
contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when an enemy unit gets close enough. WARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[]) => {
|
||||
getApp().getUnitsManager().scenicAAA(units)
|
||||
}, { executeImmediately: true });
|
||||
contextActionSet.addContextAction(this, "miss-aaa", "Dynamic accuracy AAA", "Shoot AAA towards the closest enemy unit, but don't aim precisely. WARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[]) => {
|
||||
getApp().getUnitsManager().missOnPurpose(units)
|
||||
}, { executeImmediately: true });
|
||||
}
|
||||
|
||||
/* Context actions that require a target unit */
|
||||
contextActionSet.addContextAction(this, "attack", "Attack unit", "Click on a unit to attack it", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
if (targetUnit)
|
||||
getApp().getUnitsManager().attackUnit(targetUnit.ID, units)
|
||||
});
|
||||
|
||||
/* Context actions that require a target position */
|
||||
if (this.canTargetPoint()) {
|
||||
contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Click on a point to precisely fire at it (if possible)", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
if (targetPosition)
|
||||
getApp().getUnitsManager().fireAtArea(targetPosition , units)
|
||||
});
|
||||
contextActionSet.addContextAction(this, "miss-aaa", "Dynamic accuracy AAA", "Shoot AAA towards the closest enemy unit, but don't aim precisely.\nWARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[]) => { getApp().getUnitsManager().missOnPurpose(units) }, undefined, {
|
||||
"isScenic": true
|
||||
contextActionSet.addContextAction(this, "simulate-fire-fight", "Simulate fire fight", "Simulate a fire fight by shooting randomly in a certain large area. WARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
if (targetPosition)
|
||||
getApp().getUnitsManager().simulateFireFight(targetPosition , units)
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1663,10 +1705,25 @@ export class NavyUnit extends Unit {
|
||||
}
|
||||
|
||||
appendContextActions(contextActionSet: ContextActionSet) {
|
||||
contextActionSet.addContextAction(this, "group-navy", "Group navy units", "Create a group of navy units", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().createGroup(units) });
|
||||
contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
|
||||
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, () => { getApp().getMap().centerOnUnit(this.ID); });
|
||||
contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) });
|
||||
/* Context actions to be executed immediately */
|
||||
contextActionSet.addContextAction(this, "group-navy", "Group navy units", "Create a group of navy units", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
getApp().getUnitsManager().createGroup(units)
|
||||
}, { executeImmediately: true });
|
||||
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, (units: Unit[]) => {
|
||||
getApp().getMap().centerOnUnit(units[0]);
|
||||
}, { executeImmediately: true });
|
||||
|
||||
/* Context actions that require a target unit */
|
||||
contextActionSet.addContextAction(this, "attack", "Attack unit", "Click on a unit to attack it", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
if (targetUnit)
|
||||
getApp().getUnitsManager().attackUnit(targetUnit.ID, units)
|
||||
});
|
||||
|
||||
/* Context actions that require a target position */
|
||||
contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Click on a point to precisely fire at it (if possible)", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
|
||||
if (targetPosition)
|
||||
getApp().getUnitsManager().fireAtArea(targetPosition , units)
|
||||
});
|
||||
}
|
||||
|
||||
getCategory() {
|
||||
|
||||
@ -462,7 +462,7 @@ module.exports = function (configLocation) {
|
||||
};
|
||||
|
||||
mission(req, res){
|
||||
var ret = {mission: {theatre: "PersianGulf"}};
|
||||
var ret = {mission: {theatre: "Nevada"}};
|
||||
ret.time = Date.now();
|
||||
|
||||
ret.mission.dateAndTime = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user