Completed frontend controls

This commit is contained in:
Pax1601 2023-06-01 17:18:14 +02:00
parent 4087dbde21
commit 1dd4014e61
29 changed files with 1297 additions and 541 deletions

View File

@ -1,3 +1,2 @@
copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css
copy .\\node_modules\\@iconfu\\svg-inject\\dist\\svg-inject.js .\\public\\javascripts\\svg-inject.js
copy .\\node_modules\\leaflet.nauticscale\\dist\\leaflet.nauticscale.js .\\public\\javascripts\\leaflet.nauticscale.js

View File

@ -50,16 +50,11 @@ const DEMO_UNIT_DATA = {
currentState: "Idle",
activePath: undefined,
targetSpeed: 400,
targetSpeedType: "CAS",
targetAltitude: 3000,
targetAltitudeType: "ASL",
isTanker: false,
TACANOn: false,
TACANChannel: 32,
TACANXY: "Y",
TACANCallsign: "ASD",
radioFrequency: 123.750,
radioCallsign: 2,
radioCallsignNumber: 3,
radioAMFM: "FM"
},
optionsData: {
ROE: "Designated",
@ -145,7 +140,8 @@ const DEMO_UNIT_DATA = {
currentTask: "Example task",
activePath: undefined,
targetSpeed: 400,
targetAltitude: 3000
targetAltitude: 3000,
onOff: false
},
optionsData: {
ROE: "None",
@ -157,7 +153,6 @@ const DEMO_UNIT_DATA = {
AI: true,
name: "2S6 Tunguska",
unitName: "Olympus 1-4",
groupName: "Group 1",
alive: true,
category: "GroundUnit",
},

View File

@ -0,0 +1,697 @@
/**
* SVGInject - Version 1.2.3
* A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM.
*
* https://github.com/iconfu/svg-inject
*
* Copyright (c) 2018 INCORS, the creators of iconfu.com
* @license MIT License - https://github.com/iconfu/svg-inject/blob/master/LICENSE
*/
(function(window, document) {
// constants for better minification
var _CREATE_ELEMENT_ = 'createElement';
var _GET_ELEMENTS_BY_TAG_NAME_ = 'getElementsByTagName';
var _LENGTH_ = 'length';
var _STYLE_ = 'style';
var _TITLE_ = 'title';
var _UNDEFINED_ = 'undefined';
var _SET_ATTRIBUTE_ = 'setAttribute';
var _GET_ATTRIBUTE_ = 'getAttribute';
var NULL = null;
// constants
var __SVGINJECT = '__svgInject';
var ID_SUFFIX = '--inject-';
var ID_SUFFIX_REGEX = new RegExp(ID_SUFFIX + '\\d+', "g");
var LOAD_FAIL = 'LOAD_FAIL';
var SVG_NOT_SUPPORTED = 'SVG_NOT_SUPPORTED';
var SVG_INVALID = 'SVG_INVALID';
var ATTRIBUTE_EXCLUSION_NAMES = ['src', 'alt', 'onload', 'onerror'];
var A_ELEMENT = document[_CREATE_ELEMENT_]('a');
var IS_SVG_SUPPORTED = typeof SVGRect != _UNDEFINED_;
var DEFAULT_OPTIONS = {
useCache: true,
copyAttributes: true,
makeIdsUnique: true
};
// Map of IRI referenceable tag names to properties that can reference them. This is defined in
// https://www.w3.org/TR/SVG11/linking.html#processingIRI
var IRI_TAG_PROPERTIES_MAP = {
clipPath: ['clip-path'],
'color-profile': NULL,
cursor: NULL,
filter: NULL,
linearGradient: ['fill', 'stroke'],
marker: ['marker', 'marker-end', 'marker-mid', 'marker-start'],
mask: NULL,
pattern: ['fill', 'stroke'],
radialGradient: ['fill', 'stroke']
};
var INJECTED = 1;
var FAIL = 2;
var uniqueIdCounter = 1;
var xmlSerializer;
var domParser;
// creates an SVG document from an SVG string
function svgStringToSvgDoc(svgStr) {
domParser = domParser || new DOMParser();
return domParser.parseFromString(svgStr, 'text/xml');
}
// searializes an SVG element to an SVG string
function svgElemToSvgString(svgElement) {
xmlSerializer = xmlSerializer || new XMLSerializer();
return xmlSerializer.serializeToString(svgElement);
}
// Returns the absolute url for the specified url
function getAbsoluteUrl(url) {
A_ELEMENT.href = url;
return A_ELEMENT.href;
}
// Load svg with an XHR request
function loadSvg(url, callback, errorCallback) {
if (url) {
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
if (req.readyState == 4) {
// readyState is DONE
var status = req.status;
if (status == 200) {
// request status is OK
callback(req.responseXML, req.responseText.trim());
} else if (status >= 400) {
// request status is error (4xx or 5xx)
errorCallback();
} else if (status == 0) {
// request status 0 can indicate a failed cross-domain call
errorCallback();
}
}
};
req.open('GET', url, true);
req.send();
}
}
// Copy attributes from img element to svg element
function copyAttributes(imgElem, svgElem) {
var attribute;
var attributeName;
var attributeValue;
var attributes = imgElem.attributes;
for (var i = 0; i < attributes[_LENGTH_]; i++) {
attribute = attributes[i];
attributeName = attribute.name;
// Only copy attributes not explicitly excluded from copying
if (ATTRIBUTE_EXCLUSION_NAMES.indexOf(attributeName) == -1) {
attributeValue = attribute.value;
// If img attribute is "title", insert a title element into SVG element
if (attributeName == _TITLE_) {
var titleElem;
var firstElementChild = svgElem.firstElementChild;
if (firstElementChild && firstElementChild.localName.toLowerCase() == _TITLE_) {
// If the SVG element's first child is a title element, keep it as the title element
titleElem = firstElementChild;
} else {
// If the SVG element's first child element is not a title element, create a new title
// ele,emt and set it as the first child
titleElem = document[_CREATE_ELEMENT_ + 'NS']('http://www.w3.org/2000/svg', _TITLE_);
svgElem.insertBefore(titleElem, firstElementChild);
}
// Set new title content
titleElem.textContent = attributeValue;
} else {
// Set img attribute to svg element
svgElem[_SET_ATTRIBUTE_](attributeName, attributeValue);
}
}
}
}
// This function appends a suffix to IDs of referenced elements in the <defs> in order to to avoid ID collision
// between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is
// incremented with each injection. References to the IDs are adjusted accordingly.
// We assume tha all IDs within the injected SVG are unique, therefore the same suffix can be used for all IDs of one
// injected SVG.
// If the onlyReferenced argument is set to true, only those IDs will be made unique that are referenced from within the SVG
function makeIdsUnique(svgElem, onlyReferenced) {
var idSuffix = ID_SUFFIX + uniqueIdCounter++;
// Regular expression for functional notations of an IRI references. This will find occurences in the form
// url(#anyId) or url("#anyId") (for Internet Explorer) and capture the referenced ID
var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g;
// Get all elements with an ID. The SVG spec recommends to put referenced elements inside <defs> elements, but
// this is not a requirement, therefore we have to search for IDs in the whole SVG.
var idElements = svgElem.querySelectorAll('[id]');
var idElem;
// An object containing referenced IDs as keys is used if only referenced IDs should be uniquified.
// If this object does not exist, all IDs will be uniquified.
var referencedIds = onlyReferenced ? [] : NULL;
var tagName;
var iriTagNames = {};
var iriProperties = [];
var changed = false;
var i, j;
if (idElements[_LENGTH_]) {
// Make all IDs unique by adding the ID suffix and collect all encountered tag names
// that are IRI referenceable from properities.
for (i = 0; i < idElements[_LENGTH_]; i++) {
tagName = idElements[i].localName; // Use non-namespaced tag name
// Make ID unique if tag name is IRI referenceable
if (tagName in IRI_TAG_PROPERTIES_MAP) {
iriTagNames[tagName] = 1;
}
}
// Get all properties that are mapped to the found IRI referenceable tags
for (tagName in iriTagNames) {
(IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) {
// Add mapped properties to array of iri referencing properties.
// Use linear search here because the number of possible entries is very small (maximum 11)
if (iriProperties.indexOf(mappedProperty) < 0) {
iriProperties.push(mappedProperty);
}
});
}
if (iriProperties[_LENGTH_]) {
// Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"'
iriProperties.push(_STYLE_);
}
// Run through all elements of the SVG and replace IDs in references.
// To get all descending elements, getElementsByTagName('*') seems to perform faster than querySelectorAll('*').
// Since svgElem.getElementsByTagName('*') does not return the svg element itself, we have to handle it separately.
var descElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*');
var element = svgElem;
var propertyName;
var value;
var newValue;
for (i = -1; element != NULL;) {
if (element.localName == _STYLE_) {
// If element is a style element, replace IDs in all occurences of "url(#anyId)" in text content
value = element.textContent;
newValue = value && value.replace(funcIriRegex, function(match, id) {
if (referencedIds) {
referencedIds[id] = 1;
}
return 'url(#' + id + idSuffix + ')';
});
if (newValue !== value) {
element.textContent = newValue;
}
} else if (element.hasAttributes()) {
// Run through all property names for which IDs were found
for (j = 0; j < iriProperties[_LENGTH_]; j++) {
propertyName = iriProperties[j];
value = element[_GET_ATTRIBUTE_](propertyName);
newValue = value && value.replace(funcIriRegex, function(match, id) {
if (referencedIds) {
referencedIds[id] = 1;
}
return 'url(#' + id + idSuffix + ')';
});
if (newValue !== value) {
element[_SET_ATTRIBUTE_](propertyName, newValue);
}
}
// Replace IDs in xlink:ref and href attributes
['xlink:href', 'href'].forEach(function(refAttrName) {
var iri = element[_GET_ATTRIBUTE_](refAttrName);
if (/^\s*#/.test(iri)) { // Check if iri is non-null and internal reference
iri = iri.trim();
element[_SET_ATTRIBUTE_](refAttrName, iri + idSuffix);
if (referencedIds) {
// Add ID to referenced IDs
referencedIds[iri.substring(1)] = 1;
}
}
});
}
element = descElements[++i];
}
for (i = 0; i < idElements[_LENGTH_]; i++) {
idElem = idElements[i];
// If set of referenced IDs exists, make only referenced IDs unique,
// otherwise make all IDs unique.
if (!referencedIds || referencedIds[idElem.id]) {
// Add suffix to element's ID
idElem.id += idSuffix;
changed = true;
}
}
}
// return true if SVG element has changed
return changed;
}
// For cached SVGs the IDs are made unique by simply replacing the already inserted unique IDs with a
// higher ID counter. This is much more performant than a call to makeIdsUnique().
function makeIdsUniqueCached(svgString) {
return svgString.replace(ID_SUFFIX_REGEX, ID_SUFFIX + uniqueIdCounter++);
}
// Inject SVG by replacing the img element with the SVG element in the DOM
function inject(imgElem, svgElem, absUrl, options) {
if (svgElem) {
svgElem[_SET_ATTRIBUTE_]('data-inject-url', absUrl);
var parentNode = imgElem.parentNode;
if (parentNode) {
if (options.copyAttributes) {
copyAttributes(imgElem, svgElem);
}
// Invoke beforeInject hook if set
var beforeInject = options.beforeInject;
var injectElem = (beforeInject && beforeInject(imgElem, svgElem)) || svgElem;
// Replace img element with new element. This is the actual injection.
parentNode.replaceChild(injectElem, imgElem);
// Mark img element as injected
imgElem[__SVGINJECT] = INJECTED;
removeOnLoadAttribute(imgElem);
// Invoke afterInject hook if set
var afterInject = options.afterInject;
if (afterInject) {
afterInject(imgElem, injectElem);
}
}
} else {
svgInvalid(imgElem, options);
}
}
// Merges any number of options objects into a new object
function mergeOptions() {
var mergedOptions = {};
var args = arguments;
// Iterate over all specified options objects and add all properties to the new options object
for (var i = 0; i < args[_LENGTH_]; i++) {
var argument = args[i];
for (var key in argument) {
if (argument.hasOwnProperty(key)) {
mergedOptions[key] = argument[key];
}
}
}
return mergedOptions;
}
// Adds the specified CSS to the document's <head> element
function addStyleToHead(css) {
var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0];
if (head) {
var style = document[_CREATE_ELEMENT_](_STYLE_);
style.type = 'text/css';
style.appendChild(document.createTextNode(css));
head.appendChild(style);
}
}
// Builds an SVG element from the specified SVG string
function buildSvgElement(svgStr, verify) {
if (verify) {
var svgDoc;
try {
// Parse the SVG string with DOMParser
svgDoc = svgStringToSvgDoc(svgStr);
} catch(e) {
return NULL;
}
if (svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) {
// DOMParser does not throw an exception, but instead puts parsererror tags in the document
return NULL;
}
return svgDoc.documentElement;
} else {
var div = document.createElement('div');
div.innerHTML = svgStr;
return div.firstElementChild;
}
}
function removeOnLoadAttribute(imgElem) {
// Remove the onload attribute. Should only be used to remove the unstyled image flash protection and
// make the element visible, not for removing the event listener.
imgElem.removeAttribute('onload');
}
function errorMessage(msg) {
console.error('SVGInject: ' + msg);
}
function fail(imgElem, status, options) {
imgElem[__SVGINJECT] = FAIL;
if (options.onFail) {
options.onFail(imgElem, status);
} else {
errorMessage(status);
}
}
function svgInvalid(imgElem, options) {
removeOnLoadAttribute(imgElem);
fail(imgElem, SVG_INVALID, options);
}
function svgNotSupported(imgElem, options) {
removeOnLoadAttribute(imgElem);
fail(imgElem, SVG_NOT_SUPPORTED, options);
}
function loadFail(imgElem, options) {
fail(imgElem, LOAD_FAIL, options);
}
function removeEventListeners(imgElem) {
imgElem.onload = NULL;
imgElem.onerror = NULL;
}
function imgNotSet(msg) {
errorMessage('no img element');
}
function createSVGInject(globalName, options) {
var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options);
var svgLoadCache = {};
if (IS_SVG_SUPPORTED) {
// If the browser supports SVG, add a small stylesheet that hides the <img> elements until
// injection is finished. This avoids showing the unstyled SVGs before style is applied.
addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}');
}
/**
* SVGInject
*
* Injects the SVG specified in the `src` attribute of the specified `img` element or array of `img`
* elements. Returns a Promise object which resolves if all passed in `img` elements have either been
* injected or failed to inject (Only if a global Promise object is available like in all modern browsers
* or through a polyfill).
*
* Options:
* useCache: If set to `true` the SVG will be cached using the absolute URL. Default value is `true`.
* copyAttributes: If set to `true` the attributes will be copied from `img` to `svg`. Dfault value
* is `true`.
* makeIdsUnique: If set to `true` the ID of elements in the `<defs>` element that can be references by
* property values (for example 'clipPath') are made unique by appending "--inject-X", where X is a
* running number which increases with each injection. This is done to avoid duplicate IDs in the DOM.
* beforeLoad: Hook before SVG is loaded. The `img` element is passed as a parameter. If the hook returns
* a string it is used as the URL instead of the `img` element's `src` attribute.
* afterLoad: Hook after SVG is loaded. The loaded `svg` element and `svg` string are passed as a
* parameters. If caching is active this hook will only get called once for injected SVGs with the
* same absolute path. Changes to the `svg` element in this hook will be applied to all injected SVGs
* with the same absolute path. It's also possible to return an `svg` string or `svg` element which
* will then be used for the injection.
* beforeInject: Hook before SVG is injected. The `img` and `svg` elements are passed as parameters. If
* any html element is returned it gets injected instead of applying the default SVG injection.
* afterInject: Hook after SVG is injected. The `img` and `svg` elements are passed as parameters.
* onAllFinish: Hook after all `img` elements passed to an SVGInject() call have either been injected or
* failed to inject.
* onFail: Hook after injection fails. The `img` element and a `status` string are passed as an parameter.
* The `status` can be either `'SVG_NOT_SUPPORTED'` (the browser does not support SVG),
* `'SVG_INVALID'` (the SVG is not in a valid format) or `'LOAD_FAILED'` (loading of the SVG failed).
*
* @param {HTMLImageElement} img - an img element or an array of img elements
* @param {Object} [options] - optional parameter with [options](#options) for this injection.
*/
function SVGInject(img, options) {
options = mergeOptions(defaultOptions, options);
var run = function(resolve) {
var allFinish = function() {
var onAllFinish = options.onAllFinish;
if (onAllFinish) {
onAllFinish();
}
resolve && resolve();
};
if (img && typeof img[_LENGTH_] != _UNDEFINED_) {
// an array like structure of img elements
var injectIndex = 0;
var injectCount = img[_LENGTH_];
if (injectCount == 0) {
allFinish();
} else {
var finish = function() {
if (++injectIndex == injectCount) {
allFinish();
}
};
for (var i = 0; i < injectCount; i++) {
SVGInjectElement(img[i], options, finish);
}
}
} else {
// only one img element
SVGInjectElement(img, options, allFinish);
}
};
// return a Promise object if globally available
return typeof Promise == _UNDEFINED_ ? run() : new Promise(run);
}
// Injects a single svg element. Options must be already merged with the default options.
function SVGInjectElement(imgElem, options, callback) {
if (imgElem) {
var svgInjectAttributeValue = imgElem[__SVGINJECT];
if (!svgInjectAttributeValue) {
removeEventListeners(imgElem);
if (!IS_SVG_SUPPORTED) {
svgNotSupported(imgElem, options);
callback();
return;
}
// Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load
// URL path. Else use the imgElem's src attribute value.
var beforeLoad = options.beforeLoad;
var src = (beforeLoad && beforeLoad(imgElem)) || imgElem[_GET_ATTRIBUTE_]('src');
if (!src) {
// If no image src attribute is set do no injection. This can only be reached by using javascript
// because if no src attribute is set the onload and onerror events do not get called
if (src === '') {
loadFail(imgElem, options);
}
callback();
return;
}
// set array so later calls can register callbacks
var onFinishCallbacks = [];
imgElem[__SVGINJECT] = onFinishCallbacks;
var onFinish = function() {
callback();
onFinishCallbacks.forEach(function(onFinishCallback) {
onFinishCallback();
});
};
var absUrl = getAbsoluteUrl(src);
var useCacheOption = options.useCache;
var makeIdsUniqueOption = options.makeIdsUnique;
var setSvgLoadCacheValue = function(val) {
if (useCacheOption) {
svgLoadCache[absUrl].forEach(function(svgLoad) {
svgLoad(val);
});
svgLoadCache[absUrl] = val;
}
};
if (useCacheOption) {
var svgLoad = svgLoadCache[absUrl];
var handleLoadValue = function(loadValue) {
if (loadValue === LOAD_FAIL) {
loadFail(imgElem, options);
} else if (loadValue === SVG_INVALID) {
svgInvalid(imgElem, options);
} else {
var hasUniqueIds = loadValue[0];
var svgString = loadValue[1];
var uniqueIdsSvgString = loadValue[2];
var svgElem;
if (makeIdsUniqueOption) {
if (hasUniqueIds === NULL) {
// IDs for the SVG string have not been made unique before. This may happen if previous
// injection of a cached SVG have been run with the option makedIdsUnique set to false
svgElem = buildSvgElement(svgString, false);
hasUniqueIds = makeIdsUnique(svgElem, false);
loadValue[0] = hasUniqueIds;
loadValue[2] = hasUniqueIds && svgElemToSvgString(svgElem);
} else if (hasUniqueIds) {
// Make IDs unique for already cached SVGs with better performance
svgString = makeIdsUniqueCached(uniqueIdsSvgString);
}
}
svgElem = svgElem || buildSvgElement(svgString, false);
inject(imgElem, svgElem, absUrl, options);
}
onFinish();
};
if (typeof svgLoad != _UNDEFINED_) {
// Value for url exists in cache
if (svgLoad.isCallbackQueue) {
// Same url has been cached, but value has not been loaded yet, so add to callbacks
svgLoad.push(handleLoadValue);
} else {
handleLoadValue(svgLoad);
}
return;
} else {
var svgLoad = [];
// set property isCallbackQueue to Array to differentiate from array with cached loaded values
svgLoad.isCallbackQueue = true;
svgLoadCache[absUrl] = svgLoad;
}
}
// Load the SVG because it is not cached or caching is disabled
loadSvg(absUrl, function(svgXml, svgString) {
// Use the XML from the XHR request if it is an instance of Document. Otherwise
// (for example of IE9), create the svg document from the svg string.
var svgElem = svgXml instanceof Document ? svgXml.documentElement : buildSvgElement(svgString, true);
var afterLoad = options.afterLoad;
if (afterLoad) {
// Invoke afterLoad hook which may modify the SVG element. After load may also return a new
// svg element or svg string
var svgElemOrSvgString = afterLoad(svgElem, svgString) || svgElem;
if (svgElemOrSvgString) {
// Update svgElem and svgString because of modifications to the SVG element or SVG string in
// the afterLoad hook, so the modified SVG is also used for all later cached injections
var isString = typeof svgElemOrSvgString == 'string';
svgString = isString ? svgElemOrSvgString : svgElemToSvgString(svgElem);
svgElem = isString ? buildSvgElement(svgElemOrSvgString, true) : svgElemOrSvgString;
}
}
if (svgElem instanceof SVGElement) {
var hasUniqueIds = NULL;
if (makeIdsUniqueOption) {
hasUniqueIds = makeIdsUnique(svgElem, false);
}
if (useCacheOption) {
var uniqueIdsSvgString = hasUniqueIds && svgElemToSvgString(svgElem);
// set an array with three entries to the load cache
setSvgLoadCacheValue([hasUniqueIds, svgString, uniqueIdsSvgString]);
}
inject(imgElem, svgElem, absUrl, options);
} else {
svgInvalid(imgElem, options);
setSvgLoadCacheValue(SVG_INVALID);
}
onFinish();
}, function() {
loadFail(imgElem, options);
setSvgLoadCacheValue(LOAD_FAIL);
onFinish();
});
} else {
if (Array.isArray(svgInjectAttributeValue)) {
// svgInjectAttributeValue is an array. Injection is not complete so register callback
svgInjectAttributeValue.push(callback);
} else {
callback();
}
}
} else {
imgNotSet();
}
}
/**
* Sets the default [options](#options) for SVGInject.
*
* @param {Object} [options] - default [options](#options) for an injection.
*/
SVGInject.setOptions = function(options) {
defaultOptions = mergeOptions(defaultOptions, options);
};
// Create a new instance of SVGInject
SVGInject.create = createSVGInject;
/**
* Used in onerror Event of an `<img>` element to handle cases when the loading the original src fails
* (for example if file is not found or if the browser does not support SVG). This triggers a call to the
* options onFail hook if available. The optional second parameter will be set as the new src attribute
* for the img element.
*
* @param {HTMLImageElement} img - an img element
* @param {String} [fallbackSrc] - optional parameter fallback src
*/
SVGInject.err = function(img, fallbackSrc) {
if (img) {
if (img[__SVGINJECT] != FAIL) {
removeEventListeners(img);
if (!IS_SVG_SUPPORTED) {
svgNotSupported(img, defaultOptions);
} else {
removeOnLoadAttribute(img);
loadFail(img, defaultOptions);
}
if (fallbackSrc) {
removeOnLoadAttribute(img);
img.src = fallbackSrc;
}
}
} else {
imgNotSet();
}
};
window[globalName] = SVGInject;
return SVGInject;
}
var SVGInjectInstance = createSVGInject('SVGInject');
if (typeof module == 'object' && typeof module.exports == 'object') {
module.exports = SVGInjectInstance;
}
})(window, document);

View File

@ -62,6 +62,7 @@
position: absolute;
width: fit-content;
z-index: 1000;
padding: 24px 30px;
}
#info-popup {

View File

@ -67,6 +67,7 @@ button>img:first-child {
position: relative;
aspect-ratio: initial;
height: 100%;
pointer-events: none;
}
form {
@ -316,16 +317,15 @@ form>div {
.ol-panel-board>.panel-section {
border-right: 1px solid #555;
margin: 10px 0;
padding: 0 30px;
}
.ol-panel-board>.panel-section:first-child {
padding-left: 20px;
padding-left: 0px;
}
.ol-panel-board>.panel-section:last-child {
padding-right: 20px;
padding-right: 0px;
}
.ol-panel-board>.panel-section:last-of-type {
@ -861,6 +861,7 @@ nav.ol-panel> :last-child {
.ol-destination-preview-icon {
background-color: var(--secondary-yellow);
border-radius: 999px;
cursor: grab;
height: 52px;
pointer-events: none;
width: 52px;
@ -1130,9 +1131,12 @@ input[type=number]::-webkit-outer-spin-button {
.ol-switch[data-value="false"]>.ol-switch-fill::before {
transform: translateX(calc(var(--width) - 100%));
}
.ol-switch[data-value="true"]>.ol-switch-fill::after {
transform: translateX(calc(var(--width) - var(--height)));
}
.ol-switch[data-value="undefined"]>.ol-switch-fill::after {
transform: translateX(calc((var(--width) - var(--height)) * 0.5));
}

View File

@ -29,8 +29,22 @@
width: fit-content;
}
#context-menu-switch {
#coalition-switch {
margin-right: 10px;
height: 25px;
width: 50px;
}
#coalition-switch[data-value="false"]>.ol-switch-fill {
background-color: var(--primary-blue);
}
#coalition-switch[data-value="true"]>.ol-switch-fill {
background-color: var(--primary-red);
}
#coalition-switch[data-value="undefined"]>.ol-switch-fill {
background-color: var(--primary-neutral);
}
#map-contextmenu>div:nth-child(2) {
@ -109,11 +123,6 @@
border-top-right-radius: var(--border-radius-sm);
}
#context-menu-switch .ol-switch-fill {
width: 40;
}
[data-active-coalition="blue"].ol-switch-fill,
[data-active-coalition="blue"].unit-spawn-button:hover,
[data-active-coalition="blue"].unit-spawn-button.is-open,
[data-active-coalition="blue"]#active-coalition-label,
@ -122,7 +131,6 @@
background-color: var(--primary-blue)
}
[data-active-coalition="red"].ol-switch-fill,
[data-active-coalition="red"].unit-spawn-button:hover,
[data-active-coalition="red"].unit-spawn-button.is-open,
[data-active-coalition="red"]#active-coalition-label,
@ -131,7 +139,6 @@
background-color: var(--primary-red)
}
[data-active-coalition="neutral"].ol-switch-fill,
[data-active-coalition="neutral"].unit-spawn-button:hover,
[data-active-coalition="neutral"].unit-spawn-button.is-open,
[data-active-coalition="neutral"]#active-coalition-label,
@ -158,18 +165,6 @@
cursor: default;
}
[data-active-coalition="blue"].ol-switch-fill::after {
transform: translateX(0%);
}
[data-active-coalition="red"].ol-switch-fill::after {
transform: translateX(100%);
}
[data-active-coalition="neutral"].ol-switch-fill::after {
transform: translateX(50%);
}
[data-active-coalition="blue"]#active-coalition-label::after {
content: "Create blue unit";
}
@ -250,6 +245,17 @@
background-color: orange;
}
#aircraft-spawn-menu .ol-slider-value {
color: var(--accent-light-blue);
cursor: pointer;
font-size: 14px;
font-weight: bold;
}
#aircraft-spawn-altitude-slider {
padding: 0px 10px;
}
/* Unit context menu */
#unit-contextmenu {
display: flex;

View File

@ -29,10 +29,10 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
display: flex;
font-size: 11px;
height: 32px;
justify-content: space-between;
margin-right: 5px;
position: relative;
width: calc(100% - 5px);
justify-content: space-between;
}
#unit-control-panel #selected-units-container button::after {
@ -41,8 +41,8 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
content: attr(data-label);
font-size: 10px;
padding: 4px 6px;
width: fit-content;
white-space: nowrap;
width: fit-content;
}
#unit-control-panel #selected-units-container button:hover::after {
@ -139,11 +139,11 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
content: "ASL";
}
#airspeed-type-switch[data-value="true"]>.ol-switch-fill::before {
#speed-type-switch[data-value="true"]>.ol-switch-fill::before {
content: "GS";
}
#airspeed-type-switch[data-value="false"]>.ol-switch-fill::before {
#speed-type-switch[data-value="false"]>.ol-switch-fill::before {
content: "CAS";
}
@ -154,48 +154,48 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
font-weight: bold;
}
#ai-on-off {
#unit-control-panel .switch-control {
align-items: center;
display: grid;
grid-template-columns: 1.35fr 0.65fr ;
grid-template-columns: 1.35fr 0.65fr;
}
#ai-on-off>*:nth-child(2) {
#unit-control-panel .switch-control>*:nth-child(2) {
justify-self: end;
}
#ai-on-off>*:nth-child(3) {
#unit-control-panel .switch-control>*:nth-child(3) {
color: var(--secondary-semitransparent-white);
}
#ai-on-off h4 {
#unit-control-panel .switch-control h4 {
margin: 0px;
}
#on-off-switch {
width: 60px;
#unit-control-panel .switch-control .ol-switch {
height: 25px;
width: 60px;
}
#on-off-switch>.ol-switch-fill {
#unit-control-panel .switch-control .ol-switch-fill {
background-color: var(--accent-light-blue);
}
#on-off-switch>.ol-switch-fill::after {
#unit-control-panel .switch-control .ol-switch-fill::after {
background-color: white;
}
#on-off-switch[data-value="true"]>.ol-switch-fill::before {
content: "ON";
#unit-control-panel .switch-control .ol-switch[data-value="true"]>.ol-switch-fill::before {
content: "YES";
}
#on-off-switch[data-value="false"]>.ol-switch-fill::before {
content: "OFF";
#unit-control-panel .switch-control .ol-switch[data-value="false"]>.ol-switch-fill::before {
content: "NO";
}
#advanced-settings-div {
display: flex;
column-gap: 5px;
display: flex;
}
#advanced-settings-div>*:nth-child(2) {
@ -207,58 +207,20 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
}
/* Element visibility control */
#unit-control-panel:not([data-show-categories-tooltip]) #categories-tooltip {
display: none;
}
#unit-control-panel:not([data-show-airspeed-slider]) #airspeed-slider {
display: none;
}
#unit-control-panel:not([data-show-altitude-slider]) #altitude-slider {
display: none;
}
#unit-control-panel:not([data-show-roe]) #roe {
display: none;
}
#unit-control-panel:not([data-show-threat]) #threat {
display: none;
}
#unit-control-panel:not([data-show-emissions-countermeasures]) #emissions-countermeasures {
display: none;
}
#unit-control-panel:not([data-show-on-off]) #ai-on-off {
display: none;
}
#unit-control-panel:not([data-show-advanced-settings-button]) #advanced-settings-button {
display: none;
}
#advanced-settings-dialog:not([data-show-settings]) #general-settings {
display: none;
}
#advanced-settings-dialog:not([data-show-tasking]) #tasking {
display: none;
}
#advanced-settings-dialog:not([data-show-tanker]) #tanker-checkbox {
display: none;
}
#advanced-settings-dialog:not([data-show-AWACS]) #AWACS-checkbox {
display: none;
}
#advanced-settings-dialog:not([data-show-TACAN]) #TACAN-options {
display: none;
}
#unit-control-panel:not([data-show-categories-tooltip]) #categories-tooltip,
#unit-control-panel:not([data-show-speed-slider]) #speed-slider,
#unit-control-panel:not([data-show-altitude-slider]) #altitude-slider,
#unit-control-panel:not([data-show-roe]) #roe,
#unit-control-panel:not([data-show-threat]) #threat,
#unit-control-panel:not([data-show-emissions-countermeasures]) #emissions-countermeasures,
#unit-control-panel:not([data-show-on-off]) #ai-on-off,
#unit-control-panel:not([data-show-follow-roads]) #follow-roads,
#unit-control-panel:not([data-show-advanced-settings-button]) #advanced-settings-button,
#advanced-settings-dialog:not([data-show-settings]) #general-settings,
#advanced-settings-dialog:not([data-show-tasking]) #tasking,
#advanced-settings-dialog:not([data-show-tanker]) #tanker-checkbox,
#advanced-settings-dialog:not([data-show-AWACS]) #AWACS-checkbox,
#advanced-settings-dialog:not([data-show-TACAN]) #TACAN-options,
#advanced-settings-dialog:not([data-show-radio]) #radio-options {
display: none;
}
}

View File

@ -1,15 +1,38 @@
#unit-info-panel #unit-name {
padding: 0px 0;
margin-bottom: 4px;
#unit-info-panel>* {
position: relative;
min-height: 100px;
bottom: 0px;
}
#unit-info-panel #current-task {
#general {
display: flex;
flex-direction: column;
justify-content: space-between;
row-gap: 4px;
position: relative;
}
#unit-label {
font-weight: bold;
}
#unit-control {
color: var(--secondary-lighter-grey);
font-weight: bold;
}
#unit-name {
margin-bottom: 4px;
padding: 0px 0;
}
#current-task {
border-radius: var(--border-radius-lg);
margin-top: 8px;
margin-top: auto;
padding: 6px 16px;
}
#unit-info-panel #current-task::after {
#current-task::after {
content: attr(data-current-task);
display: block;
}
@ -17,25 +40,21 @@
#loadout {
display: flex;
overflow: visible;
width: 100%;
min-width: 125px;
}
#loadout-container {
display: flex;
flex-direction: column;
justify-content: space-between;
}
#loadout-silhouette {
align-items: center;
display: flex;
justify-content: center;
width: 100px;
}
#loadout-silhouette::before {
background-image: var(--loadout-background-image);
background-repeat: no-repeat;
background-size: 75px 75px;
content: "";
display: block;
filter: invert(100%);
height: 75px;
translate: -10px 0;
width: 75px;
height: 100px;
margin-right: 25px;
width: 100px;
}
#loadout-items {
@ -45,37 +64,37 @@
row-gap: 8px;
}
#loadout-items>* {
align-items: center;
column-gap: 8px;
display: flex;
justify-content: flex-end;
white-space: nowrap;
}
#loadout-items>*::before {
align-items: center;
background-color: var(--secondary-light-grey);
border-radius: var(--border-radius-sm);
border-radius: 999px;
content: attr(data-qty);
display: flex;
font-weight: var(--font-weight-bolder);
padding: 1px 4px;
font-size: 11px;
font-weight: bold;
padding: 4px 6px;
}
#loadout-items>*::after {
content: attr(data-item);
max-width: 125px;
overflow: hidden;
position: relative;
text-overflow: ellipsis;
width: 80px;
width: 100%;
}
#fuel-percentage {
align-items: center;
display: flex;
margin-top: auto;
}
#fuel-percentage::before {
@ -91,7 +110,6 @@
content: attr(data-percentage) "%";
}
#fuel-display {
background-color: var(--background-grey);
border-radius: var(--border-radius-md);

View File

@ -31,6 +31,7 @@
--secondary-dark-steel: #181e25;
--secondary-gunmetal-grey: #2f2f2f;
--secondary-lighter-grey: #949ba7;
--secondary-light-grey: #797e83;
--secondary-semitransparent-white: #FFFFFFAA;
--secondary-transparent-white: #FFFFFF30;

View File

@ -37,9 +37,13 @@ interface TaskData {
currentTask: string;
activePath: any;
targetSpeed: number;
targetSpeedType: string;
targetAltitude: number;
targetAltitudeType: string;
isTanker: boolean;
isAWACS: boolean;
onOff: boolean;
followRoads: boolean;
}
interface OptionsData {
@ -51,15 +55,6 @@ interface OptionsData {
generalSettings: GeneralSettings;
}
interface UnitData {
baseData: BaseData;
flightData: FlightData;
missionData: MissionData;
formationData: FormationData;
taskData: TaskData;
optionsData: OptionsData;
}
interface TACAN {
isOn: boolean;
channel: number;
@ -91,4 +86,13 @@ interface UnitIconOptions {
showAmmo: boolean,
showSummary: boolean,
rotateToHeading: boolean
}
interface UnitData {
baseData: BaseData;
flightData: FlightData;
missionData: MissionData;
formationData: FormationData;
taskData: TaskData;
optionsData: OptionsData;
}

View File

@ -0,0 +1,102 @@
import { LatLng, LatLngBounds, TileLayer, tileLayer } from "leaflet";
export const ROEs: string[] = ["Hold", "Return", "Designated", "Free"];
export const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"];
export const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"];
export const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"];
export const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"];
export const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar OFF, no ECM)", "Attack (Radar only for targeting, ECM only if locked)", "Defend (Radar for searching, ECM if locked)", "Always on (Radar and ECM always on)"];
export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
export const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 };
export const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 };
export const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 };
export const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 };
export const minimapBoundaries = [
[ // NTTR
new LatLng(39.7982463, -119.985425),
new LatLng(34.4037128, -119.7806729),
new LatLng(34.3483316, -112.4529351),
new LatLng(39.7372411, -112.1130805),
new LatLng(39.7982463, -119.985425)
],
[ // Syria
new LatLng(37.3630556, 29.2686111),
new LatLng(31.8472222, 29.8975),
new LatLng(32.1358333, 42.1502778),
new LatLng(37.7177778, 42.3716667),
new LatLng(37.3630556, 29.2686111)
],
[ // Caucasus
new LatLng(39.6170191, 27.634935),
new LatLng(38.8735863, 47.1423108),
new LatLng(47.3907982, 49.3101946),
new LatLng(48.3955879, 26.7753625),
new LatLng(39.6170191, 27.634935)
],
[ // Persian Gulf
new LatLng(32.9355285, 46.5623682),
new LatLng(21.729393, 47.572675),
new LatLng(21.8501348, 63.9734737),
new LatLng(33.131584, 64.7313594),
new LatLng(32.9355285, 46.5623682)
],
[ // Marianas
new LatLng(22.09, 135.0572222),
new LatLng(10.5777778, 135.7477778),
new LatLng(10.7725, 149.3918333),
new LatLng(22.5127778, 149.5427778),
new LatLng(22.09, 135.0572222)
]
];
export const mapBounds = {
"Syria": { bounds: new LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]), zoom: 5 },
"MarianaIslands": { bounds: new LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]), zoom: 5 },
"Nevada": { bounds: new LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]), zoom: 5 },
"PersianGulf": { bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]), zoom: 5 },
"Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 },
// TODO "Falklands"
}
export const layers = {
"ArcGIS Satellite": {
urlTemplate: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
maxZoom: 20,
minZoom: 1,
attribution: "Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
},
"USGS Topo": {
urlTemplate: 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}',
minZoom: 1,
maxZoom: 20,
attribution: 'Tiles courtesy of the <a href="https://usgs.gov/">U.S. Geological Survey</a>'
},
"OpenStreetMap Mapnik": {
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
minZoom: 1,
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
},
"OPENVKarte": {
urlTemplate: 'https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png',
minZoom: 1,
maxZoom: 18,
attribution: 'Map <a href="https://memomaps.de/">memomaps.de</a> <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
},
"Esri.DeLorme": {
urlTemplate: 'https://server.arcgisonline.com/ArcGIS/rest/services/Specialty/DeLorme_World_Base_Map/MapServer/tile/{z}/{y}/{x}',
minZoom: 1,
maxZoom: 11,
attribution: 'Tiles &copy; Esri &mdash; Copyright: &copy;2012 DeLorme',
},
"CyclOSM": {
urlTemplate: 'https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png',
minZoom: 1,
maxZoom: 20,
attribution: '<a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> | Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}
}

View File

@ -1,5 +1,6 @@
export class Control {
#container: HTMLElement | null;
expectedValue: any = null;
constructor(ID: string) {
this.#container = document.getElementById(ID);
@ -18,4 +19,16 @@ export class Control {
getContainer() {
return this.#container;
}
setExpectedValue(expectedValue: any) {
this.expectedValue = expectedValue;
}
resetExpectedValue() {
this.expectedValue = null;
}
checkExpectedValue(value: any) {
return this.expectedValue === null || value === this.expectedValue;
}
}

View File

@ -19,14 +19,12 @@ export class Dropdown {
}
this.#value.addEventListener("click", (ev) => {
this.#element.classList.toggle("is-open");
this.#options.classList.toggle("scrollbar-visible", this.#options.scrollHeight > this.#options.clientHeight);
this.#clip();
this.#toggle();
});
document.addEventListener("click", (ev) => {
if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#element.contains(ev.target as Node))) {
this.#element.classList.remove("is-open");
this.#close();
}
});
@ -46,12 +44,7 @@ export class Dropdown {
button.addEventListener("click", (e: MouseEvent) => {
e.stopPropagation();
this.#value = document.createElement("div");
this.#value.classList.add("ol-ellipsed");
this.#value.innerText = option;
this.#close();
this.#callback(option, e);
this.#index = idx;
this.selectValue(idx);
});
return div;
}));
@ -113,6 +106,8 @@ export class Dropdown {
#open() {
this.#element.classList.add("is-open");
this.#options.classList.toggle("scrollbar-visible", this.#options.scrollHeight > this.#options.clientHeight);
this.#clip();
}
#toggle() {

View File

@ -5,6 +5,8 @@ import { aircraftDatabase } from "../units/aircraftdatabase";
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
import { ContextMenu } from "./contextmenu";
import { Dropdown } from "./dropdown";
import { Switch } from "./switch";
import { Slider } from "./slider";
export interface SpawnOptions {
role: string;
@ -13,24 +15,29 @@ export interface SpawnOptions {
coalition: string;
loadout: string | null;
airbaseName: string | null;
altitude: number | null;
}
export class MapContextMenu extends ContextMenu {
#coalitionSwitch: Switch;
#aircraftRoleDropdown: Dropdown;
#aircraftTypeDropdown: Dropdown;
#aircraftLoadoutDropdown: Dropdown;
#aircrafSpawnAltitudeSlider: Slider;
#groundUnitRoleDropdown: Dropdown;
#groundUnitTypeDropdown: Dropdown;
#spawnOptions: SpawnOptions = { role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null };
#spawnOptions: SpawnOptions = { role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: 20000 };
constructor(id: string) {
super(id);
this.getContainer()?.querySelector("#context-menu-switch")?.addEventListener('click', (e) => this.#onToggleLeftClick(e));
this.getContainer()?.querySelector("#context-menu-switch")?.addEventListener('contextmenu', (e) => this.#onToggleRightClick(e));
this.#coalitionSwitch = new Switch("coalition-switch", this.#onSwitchClick);
this.#coalitionSwitch.setValue(false);
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick(e));
this.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role));
this.#aircraftTypeDropdown = new Dropdown("aircraft-type-options", (type: string) => this.#setAircraftType(type));
this.#aircraftLoadoutDropdown = new Dropdown("loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout));
this.#aircrafSpawnAltitudeSlider = new Slider("aircraft-spawn-altitude-slider", 0, 50000, "ft", (value: number) => {this.#spawnOptions.altitude = value;});
this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role));
this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type));
@ -61,6 +68,10 @@ export class MapContextMenu extends ContextMenu {
spawnSmoke(e.detail.color, this.getLatLng());
});
this.#aircrafSpawnAltitudeSlider.setIncrement(500);
this.#aircrafSpawnAltitudeSlider.setValue(20000);
this.#aircrafSpawnAltitudeSlider.setActive(true);
this.hide();
}
@ -102,26 +113,13 @@ export class MapContextMenu extends ContextMenu {
this.#spawnOptions.latlng = latlng;
}
#onToggleLeftClick(e: any) {
if (this.getContainer() != null) {
if (e.srcElement.dataset.activeCoalition == "blue")
setActiveCoalition("neutral");
else if (e.srcElement.dataset.activeCoalition == "neutral")
setActiveCoalition("red");
else
setActiveCoalition("blue");
}
#onSwitchClick(value: boolean) {
value? setActiveCoalition("red"): setActiveCoalition("blue");
}
#onToggleRightClick(e: any) {
if (this.getContainer() != null) {
if (e.srcElement.dataset.activeCoalition == "red")
setActiveCoalition("neutral");
else if (e.srcElement.dataset.activeCoalition == "neutral")
setActiveCoalition("blue");
else
setActiveCoalition("red");
}
#onSwitchRightClick(e: any) {
this.#coalitionSwitch.setValue(undefined);
setActiveCoalition("neutral");
}
/********* Aircraft spawn menu *********/

View File

@ -2,38 +2,38 @@ import { zeroPad } from "../other/utils";
import { Control } from "./control";
export class Slider extends Control {
#callback: CallableFunction;
#callback: CallableFunction | null = null;
#slider: HTMLInputElement | null = null;
#valueText: HTMLElement | null = null;
#minValue: number;
#maxValue: number;
#increment: number;
#minValue: number = 0;
#maxValue: number = 0;
#increment: number = 0;
#minMaxValueDiv: HTMLElement | null = null;
#unit: string;
#unitOfMeasure: string;
#dragged: boolean = false;
#value: number = 0;
constructor(ID: string, minValue: number, maxValue: number, unit: string, callback: CallableFunction) {
constructor(ID: string, minValue: number, maxValue: number, unitOfMeasure: string, callback: CallableFunction) {
super(ID);
this.#callback = callback;
this.#minValue = minValue;
this.#maxValue = maxValue;
this.#increment = 1;
this.#unit = unit;
this.#callback = callback;
this.#unitOfMeasure = unitOfMeasure;
this.#slider = this.getContainer()?.querySelector("input") as HTMLInputElement;
if (this.#slider != null) {
this.#slider.addEventListener("input", (e: any) => this.#onInput());
this.#slider.addEventListener("input", (e: any) => this.#update());
this.#slider.addEventListener("mousedown", (e: any) => this.#onStart());
this.#slider.addEventListener("mouseup", (e: any) => this.#onFinalize());
}
this.#valueText = this.getContainer()?.querySelector(".ol-slider-value") as HTMLElement;
this.#minMaxValueDiv = this.getContainer()?.querySelector(".ol-slider-min-max") as HTMLElement;
this.setIncrement(1);
this.setMinMax(minValue, maxValue);
}
setActive(newActive: boolean) {
if (!this.#dragged) {
if (!this.getDragged()) {
this.getContainer()?.classList.toggle("active", newActive);
if (!newActive && this.#valueText != null)
this.#valueText.innerText = "Mixed values";
@ -41,27 +41,31 @@ export class Slider extends Control {
}
setMinMax(newMinValue: number, newMaxValue: number) {
this.#minValue = newMinValue;
this.#maxValue = newMaxValue;
this.#updateMax();
if (this.#minMaxValueDiv != null) {
this.#minMaxValueDiv.setAttribute('data-min-value', `${this.#minValue}${this.#unit}`);
this.#minMaxValueDiv.setAttribute('data-max-value', `${this.#maxValue}${this.#unit}`);
if (this.#minValue != newMinValue || this.#maxValue != newMaxValue) {
this.#minValue = newMinValue;
this.#maxValue = newMaxValue;
this.#updateMaxValue();
if (this.#minMaxValueDiv != null) {
this.#minMaxValueDiv.setAttribute('data-min-value', `${this.#minValue}${this.#unitOfMeasure}`);
this.#minMaxValueDiv.setAttribute('data-max-value', `${this.#maxValue}${this.#unitOfMeasure}`);
}
}
}
setIncrement(newIncrement: number) {
this.#increment = newIncrement;
this.#updateMax();
if (this.#increment != newIncrement) {
this.#increment = newIncrement;
this.#updateMaxValue();
}
}
setValue(newValue: number) {
// Disable value setting if the user is dragging the element
if (!this.#dragged) {
setValue(newValue: number, ignoreExpectedValue: boolean = true) {
if (!this.getDragged() && (ignoreExpectedValue || this.checkExpectedValue(newValue))) {
this.#value = newValue;
if (this.#slider != null)
this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max));
this.#onValue()
this.#update();
}
}
@ -69,45 +73,51 @@ export class Slider extends Control {
return this.#value;
}
setDragged(newDragged: boolean) {
this.#dragged = newDragged;
}
getDragged() {
return this.#dragged;
}
#updateMax() {
#updateMaxValue() {
var oldValue = this.getValue();
if (this.#slider != null)
this.#slider.max = String((this.#maxValue - this.#minValue) / this.#increment);
this.setValue(oldValue);
}
#onValue() {
#update() {
if (this.#valueText != null && this.#slider != null)
{
/* Update the text value */
var value = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue));
var strValue = String(value);
if (value > 1000)
strValue = String(Math.floor(value / 1000)) + "," + zeroPad(value - Math.floor(value / 1000) * 1000, 3);
this.#valueText.innerText = strValue + " " + this.#unit.toUpperCase();
this.#valueText.innerText = `${strValue} ${this.#unitOfMeasure.toUpperCase()}`;
/* Update the position of the slider */
var percentValue = parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * 90 + 5;
this.#slider.style.background = 'linear-gradient(to right, var(--accent-light-blue) 5%, var(--accent-light-blue) ' + percentValue + '%, var(--background-grey) ' + percentValue + '%, var(--background-grey) 100%)'
this.#slider.style.background = `linear-gradient(to right, var(--accent-light-blue) 5%, var(--accent-light-blue) ${percentValue}%, var(--background-grey) ${percentValue}%, var(--background-grey) 100%)`
}
this.setActive(true);
}
#onInput() {
this.#onValue();
}
#onStart() {
this.#dragged = true;
this.setDragged(true);
}
#onFinalize() {
this.#dragged = false;
this.setDragged(false);
if (this.#slider != null) {
this.#value = this.#minValue + parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue);
this.#callback(this.getValue());
this.resetExpectedValue();
this.setValue(this.#minValue + parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue));
if (this.#callback) {
this.#callback(this.getValue());
this.setExpectedValue(this.getValue());
}
}
}
}

View File

@ -1,12 +1,16 @@
import { Control } from "./control";
export class Switch extends Control {
#value: boolean = false;
constructor(ID: string, initialValue?: boolean) {
#value: boolean | undefined = false;
#callback: CallableFunction | null = null;
constructor(ID: string, callback: CallableFunction, initialValue?: boolean) {
super(ID);
this.getContainer()?.addEventListener('click', (e) => this.#onToggle());
this.setValue(initialValue !== undefined? initialValue: true);
this.#callback = callback;
/* Add the toggle itself to the document */
const container = this.getContainer();
if (container != undefined){
@ -14,14 +18,17 @@ export class Switch extends Control {
const height = getComputedStyle(container).height;
var el = document.createElement("div");
el.classList.add("ol-switch-fill");
el.style.setProperty("--width", width? width: "0px");
el.style.setProperty("--height", height? height: "0px");
el.style.setProperty("--width", width? width: "0");
el.style.setProperty("--height", height? height: "0");
this.getContainer()?.appendChild(el);
}
}
setValue(value: boolean) {
this.#value = value;
this.getContainer()?.setAttribute("data-value", String(value));
setValue(newValue: boolean | undefined, ignoreExpectedValue: boolean = true) {
if (ignoreExpectedValue || this.checkExpectedValue(newValue)) {
this.#value = newValue;
this.getContainer()?.setAttribute("data-value", String(newValue));
}
}
getValue() {
@ -29,6 +36,11 @@ export class Switch extends Control {
}
#onToggle() {
this.resetExpectedValue();
this.setValue(!this.getValue());
if (this.#callback) {
this.#callback(this.getValue());
this.setExpectedValue(this.getValue());
}
}
}

View File

@ -98,8 +98,6 @@ function readConfig(config: any) {
}
function setupEvents() {
window.onanimationiteration = console.log;
/* Generic clicks */
document.addEventListener("click", (ev) => {
if (ev instanceof MouseEvent && ev.target instanceof HTMLElement) {

View File

@ -6,7 +6,7 @@ export class DestinationPreviewMarker extends CustomMarker {
this.setIcon(new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 26],
className: "leaflet-destination-preview"
className: "leaflet-destination-preview",
}));
var el = document.createElement("div");
el.classList.add("ol-destination-preview-icon");

View File

@ -12,6 +12,7 @@ import { DestinationPreviewMarker } from "./destinationpreviewmarker";
import { TemporaryUnitMarker } from "./temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector'
import { layers as mapLayers, mapBounds, minimapBoundaries } from "../constants/constants";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
@ -58,10 +59,10 @@ export class Map extends L.Map {
super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
this.setView([37.23, -115.8], 10);
this.setLayer("ArcGIS Satellite");
this.setLayer(Object.keys(mapLayers)[0]);
/* Minimap */
var minimapLayer = new L.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { minZoom: 0, maxZoom: 13 });
var minimapLayer = new L.TileLayer(mapLayers[Object.keys(mapLayers)[0] as keyof typeof mapLayers].urlTemplate, { minZoom: 0, maxZoom: 13 });
this.#miniMapLayerGroup = new L.LayerGroup([minimapLayer]);
var miniMapPolyline = new L.Polyline(this.#getMinimapBoundaries(), { color: '#202831' });
miniMapPolyline.addTo(this.#miniMapLayerGroup);
@ -124,59 +125,30 @@ export class Map extends L.Map {
}
setLayer(layerName: string) {
if (this.#layer != null) {
if (this.#layer != null)
this.removeLayer(this.#layer)
if (layerName in mapLayers){
const layerData = mapLayers[layerName as keyof typeof mapLayers];
var options: L.TileLayerOptions = {
attribution: layerData.attribution,
minZoom: layerData.minZoom,
maxZoom: layerData.maxZoom
};
this.#layer = new L.TileLayer(layerData.urlTemplate, options);
}
if (layerName == "ArcGIS Satellite") {
this.#layer = L.tileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", {
attribution: "Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community"
});
}
else if (layerName == "USGS Topo") {
this.#layer = L.tileLayer('https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}', {
maxZoom: 20,
attribution: 'Tiles courtesy of the <a href="https://usgs.gov/">U.S. Geological Survey</a>'
});
}
else if (layerName == "OpenStreetMap Mapnik") {
this.#layer = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
}
else if (layerName == "OPENVKarte") {
this.#layer = L.tileLayer('https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map <a href="https://memomaps.de/">memomaps.de</a> <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
}
else if (layerName == "Esri.DeLorme") {
this.#layer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Specialty/DeLorme_World_Base_Map/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Copyright: &copy;2012 DeLorme',
minZoom: 1,
maxZoom: 11
});
}
else if (layerName == "CyclOSM") {
this.#layer = L.tileLayer('https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', {
maxZoom: 20,
attribution: '<a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> | Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
}
this.#layer?.addTo(this);
}
getLayers() {
return ["ArcGIS Satellite", "USGS Topo", "OpenStreetMap Mapnik", "OPENVKarte", "Esri.DeLorme", "CyclOSM"]
return Object.keys(mapLayers);
}
/* State machine */
setState(state: string) {
this.#state = state;
if (this.#state === IDLE) {
L.DomUtil.removeClass(this.getContainer(), 'crosshair-cursor-enabled');
/* Remove all the destination preview markers */
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
this.removeLayer(marker);
@ -188,8 +160,6 @@ export class Map extends L.Map {
this.#destinationRotationCenter = null;
}
else if (this.#state === MOVE_UNIT) {
L.DomUtil.addClass(this.getContainer(), 'crosshair-cursor-enabled');
/* Remove all the exising destination preview markers */
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
this.removeLayer(marker);
@ -199,7 +169,7 @@ export class Map extends L.Map {
if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 1 && getUnitsManager().getSelectedUnits({ excludeHumans: true }).length < 20) {
/* Create the unit destination preview markers */
this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true }).map((unit: Unit) => {
var marker = new DestinationPreviewMarker(this.getMouseCoordinates());
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false});
marker.addTo(this);
return marker;
})
@ -295,20 +265,9 @@ export class Map extends L.Map {
setTheatre(theatre: string) {
var bounds = new L.LatLngBounds([-90, -180], [90, 180]);
var miniMapZoom = 5;
if (theatre == "Syria")
bounds = new L.LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]);
else if (theatre == "MarianaIslands")
bounds = new L.LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]);
else if (theatre == "Nevada")
bounds = new L.LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805])
else if (theatre == "PersianGulf")
bounds = new L.LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594])
else if (theatre == "Falklands") {
// TODO
}
else if (theatre == "Caucasus") {
bounds = new L.LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946])
miniMapZoom = 4;
if (theatre in mapBounds) {
bounds = mapBounds[theatre as keyof typeof mapBounds].bounds;
miniMapZoom = mapBounds[theatre as keyof typeof mapBounds].zoom;
}
this.setView(bounds.getCenter(), 8);
@ -426,7 +385,7 @@ export class Map extends L.Map {
if (!e.originalEvent.ctrlKey) {
getUnitsManager().selectedUnitsClearDestinations();
}
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, !e.originalEvent.shiftKey, this.#destinationGroupRotation)
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation)
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
@ -481,48 +440,13 @@ export class Map extends L.Map {
#getMinimapBoundaries() {
/* Draw the limits of the maps in the minimap*/
return [[ // NTTR
new L.LatLng(39.7982463, -119.985425),
new L.LatLng(34.4037128, -119.7806729),
new L.LatLng(34.3483316, -112.4529351),
new L.LatLng(39.7372411, -112.1130805),
new L.LatLng(39.7982463, -119.985425)
],
[ // Syria
new L.LatLng(37.3630556, 29.2686111),
new L.LatLng(31.8472222, 29.8975),
new L.LatLng(32.1358333, 42.1502778),
new L.LatLng(37.7177778, 42.3716667),
new L.LatLng(37.3630556, 29.2686111)
],
[ // Caucasus
new L.LatLng(39.6170191, 27.634935),
new L.LatLng(38.8735863, 47.1423108),
new L.LatLng(47.3907982, 49.3101946),
new L.LatLng(48.3955879, 26.7753625),
new L.LatLng(39.6170191, 27.634935)
],
[ // Persian Gulf
new L.LatLng(32.9355285, 46.5623682),
new L.LatLng(21.729393, 47.572675),
new L.LatLng(21.8501348, 63.9734737),
new L.LatLng(33.131584, 64.7313594),
new L.LatLng(32.9355285, 46.5623682)
],
[ // Marianas
new L.LatLng(22.09, 135.0572222),
new L.LatLng(10.5777778, 135.7477778),
new L.LatLng(10.7725, 149.3918333),
new L.LatLng(22.5127778, 149.5427778),
new L.LatLng(22.09, 135.0572222)
]
];
return minimapBoundaries;
}
#updateDestinationPreview(e: any) {
Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(), this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
if (idx < this.#destinationPreviewMarkers.length)
this.#destinationPreviewMarkers[idx].setLatLng(!e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates());
this.#destinationPreviewMarkers[idx].setLatLng(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates());
})
}

View File

@ -37,7 +37,7 @@ export class MissionHandler
for (let idx in data.airbases)
{
var airbase = data.airbases[idx]
if (this.#airbases[idx] === undefined)
if (this.#airbases[idx] === undefined && airbase.callsign != '')
{
this.#airbases[idx] = new Airbase({
position: new LatLng(airbase.latitude, airbase.longitude),
@ -45,7 +45,8 @@ export class MissionHandler
}).addTo(getMap());
this.#airbases[idx].on('contextmenu', (e) => this.#onAirbaseClick(e));
}
if (airbase.latitude && airbase.longitude && airbase.coalition)
if (this.#airbases[idx] != undefined && airbase.latitude && airbase.longitude && airbase.coalition)
{
this.#airbases[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude));
this.#airbases[idx].setCoalition(airbase.coalition);

View File

@ -3,38 +3,21 @@ import { getUnitsManager } from "..";
import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
import { Aircraft, GroundUnit, Unit } from "../units/unit";
import { UnitDatabase } from "../units/unitdatabase";
import { Unit } from "../units/unit";
import { Panel } from "./panel";
import { Switch } from "../controls/switch";
const ROEs: string[] = ["Hold", "Return", "Designated", "Free"];
const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"];
const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"];
const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"];
const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"];
const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar OFF, no ECM)", "Attack (Radar only for targeting, ECM only if locked)", "Defend (Radar for searching, ECM if locked)", "Always on (Radar and ECM always on)"];
const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 };
const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 };
const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 };
const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 };
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants";
export class UnitControlPanel extends Panel {
#altitudeSlider: Slider;
#altitudeTypeSwitch: Switch;
#airspeedSlider: Slider;
#airspeedTypeSwitch: Switch;
#speedSlider: Slider;
#speedTypeSwitch: Switch;
#onOffSwitch: Switch;
#followRoadsSwitch: Switch;
#TACANXYDropdown: Dropdown;
#radioDecimalsDropdown: Dropdown;
#radioCallsignDropdown: Dropdown;
#expectedAltitude: number = -1;
#expectedSpeed: number = -1;
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
#advancedSettingsDialog: HTMLElement;
@ -42,17 +25,11 @@ export class UnitControlPanel extends Panel {
super(ID);
/* Unit control sliders */
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => {
this.#expectedAltitude = value;
getUnitsManager().selectedUnitsSetAltitude(value * 0.3048);
});
this.#altitudeTypeSwitch = new Switch("altitude-type-switch");
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getUnitsManager().selectedUnitsSetAltitude(value * 0.3048); });
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetAltitudeType(value? "AGL": "ASL"); });
this.#airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => {
this.#expectedSpeed = value;
getUnitsManager().selectedUnitsSetSpeed(value / 1.94384);
});
this.#airspeedTypeSwitch = new Switch("airspeed-type-switch");
this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getUnitsManager().selectedUnitsSetSpeed(value / 1.94384); });
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "GS": "CAS"); });
/* Option buttons */
this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => {
@ -72,7 +49,14 @@ export class UnitControlPanel extends Panel {
this.getElement().querySelector("#emissions-countermeasures-buttons-container")?.append(...this.#optionButtons["emissionsCountermeasures"]);
/* On off switch */
this.#onOffSwitch = new Switch("on-off-switch");
this.#onOffSwitch = new Switch("on-off-switch", (value: boolean) => {
getUnitsManager().selectedUnitsSetOnOff(value);
});
/* Follow roads switch */
this.#followRoadsSwitch = new Switch("follow-roads-switch", (value: boolean) => {
getUnitsManager().selectedUnitsSetFollowRoads(value);
});
/* Advanced settings dialog */
this.#advancedSettingsDialog = <HTMLElement> document.querySelector("#advanced-settings-dialog");
@ -98,26 +82,18 @@ export class UnitControlPanel extends Panel {
this.hide();
}
// Do this after panel is hidden (make sure there's a reset)
hide() {
super.hide();
this.#expectedAltitude = -1;
this.#expectedSpeed = -1;
show() {
super.show();
this.#speedTypeSwitch.resetExpectedValue();
this.#altitudeTypeSwitch.resetExpectedValue();
this.#onOffSwitch.resetExpectedValue();
this.#followRoadsSwitch.resetExpectedValue();
}
addButtons() {
var units = getUnitsManager().getSelectedUnits();
if (units.length < 20) {
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => {
let database: UnitDatabase | null;
if (unit instanceof Aircraft)
database = aircraftDatabase;
else if (unit instanceof GroundUnit)
database = groundUnitsDatabase;
else
database = null; // TODO add databases for other unit types
var button = document.createElement("button");
var callsign = unit.getBaseData().unitName || "";
var label = unit.getDatabase()?.getByName(unit.getBaseData().name)?.label || unit.getBaseData().name;
@ -150,42 +126,42 @@ export class UnitControlPanel extends Panel {
if (element != null && units.length > 0) {
/* Toggle visibility of control elements */
element.toggleAttribute("data-show-categories-tooltip", selectedUnitsTypes.length > 1);
element.toggleAttribute("data-show-airspeed-slider", selectedUnitsTypes.length == 1);
element.toggleAttribute("data-show-speed-slider", selectedUnitsTypes.length == 1);
element.toggleAttribute("data-show-altitude-slider", selectedUnitsTypes.length == 1 && (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-roe", true);
element.toggleAttribute("data-show-threat", (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")) && !(selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-emissions-countermeasures", (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")) && !(selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-on-off", (selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit")) && !(selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-follow-roads", (selectedUnitsTypes.length == 1 && selectedUnitsTypes.includes("GroundUnit")));
element.toggleAttribute("data-show-advanced-settings-button", units.length == 1);
/* Flight controls */
var targetAltitude = getUnitsManager().getSelectedUnitsTargetAltitude();
var targetSpeed = getUnitsManager().getSelectedUnitsTargetSpeed();
var targetAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().targetAltitude});
var targetAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().targetAltitudeType});
var targetSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().targetSpeed});
var targetSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().targetSpeedType});
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().onOff});
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().followRoads});
if (selectedUnitsTypes.length == 1) {
this.#airspeedSlider.setMinMax(minSpeedValues[selectedUnitsTypes[0]], maxSpeedValues[selectedUnitsTypes[0]]);
this.#altitudeTypeSwitch.setValue(targetAltitudeType != undefined? targetAltitudeType == "AGL": undefined, false);
this.#speedTypeSwitch.setValue(targetSpeedType != undefined? targetSpeedType == "GS": undefined, false);
this.#speedSlider.setMinMax(minSpeedValues[selectedUnitsTypes[0]], maxSpeedValues[selectedUnitsTypes[0]]);
this.#altitudeSlider.setMinMax(minAltitudeValues[selectedUnitsTypes[0]], maxAltitudeValues[selectedUnitsTypes[0]]);
this.#airspeedSlider.setIncrement(speedIncrements[selectedUnitsTypes[0]]);
this.#speedSlider.setIncrement(speedIncrements[selectedUnitsTypes[0]]);
this.#altitudeSlider.setIncrement(altitudeIncrements[selectedUnitsTypes[0]]);
this.#airspeedSlider.setActive(targetSpeed != undefined);
if (targetSpeed != undefined) {
targetSpeed *= 1.94384;
if (this.#updateCanSetSpeedSlider(targetSpeed)) {
this.#airspeedSlider.setValue(targetSpeed);
}
}
this.#speedSlider.setActive(targetSpeed != undefined);
if (targetSpeed != undefined)
this.#speedSlider.setValue(targetSpeed * 1.94384, false);
this.#altitudeSlider.setActive(targetAltitude != undefined);
if (targetAltitude != undefined) {
targetAltitude /= 0.3048;
if (this.#updateCanSetAltitudeSlider(targetAltitude)) {
this.#altitudeSlider.setValue(targetAltitude);
}
}
if (targetAltitude != undefined)
this.#altitudeSlider.setValue(targetAltitude / 0.3048, false);
}
else {
this.#airspeedSlider.setActive(false);
this.#speedSlider.setActive(false);
this.#altitudeSlider.setActive(false);
}
@ -201,27 +177,13 @@ export class UnitControlPanel extends Panel {
this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().emissionsCountermeasures === button.value))
});
this.#onOffSwitch.setValue(onOff, false);
this.#followRoadsSwitch.setValue(followRoads, false);
}
}
}
/* Update function will only be allowed to update the sliders once it's matched the expected value for the first time (due to lag of Ajax request) */
#updateCanSetAltitudeSlider(altitude: number) {
if (this.#expectedAltitude < 0 || altitude === this.#expectedAltitude) {
this.#expectedAltitude = -1;
return true;
}
return false;
}
#updateCanSetSpeedSlider(altitude: number) {
if (this.#expectedSpeed < 0 || altitude === this.#expectedSpeed) {
this.#expectedSpeed = -1;
return true;
}
return false;
}
#updateAdvancedSettingsDialog(units: Unit[])
{
if (units.length == 1)

View File

@ -16,7 +16,7 @@ export class UnitInfoPanel extends Panel {
#latitude: HTMLElement;
#longitude: HTMLElement;
#loadoutContainer: HTMLElement;
#silhouette: HTMLElement;
#silhouette: HTMLImageElement;
#unitControl: HTMLElement;
#unitLabel: HTMLElement;
#unitName: HTMLElement;
@ -24,21 +24,21 @@ export class UnitInfoPanel extends Panel {
constructor(ID: string) {
super(ID);
this.#altitude = <HTMLElement>(this.getElement().querySelector("#altitude"));
this.#currentTask = <HTMLElement>(this.getElement().querySelector("#current-task"));
this.#groundSpeed = <HTMLElement>(this.getElement().querySelector("#ground-speed"));
this.#fuelBar = <HTMLElement>(this.getElement().querySelector("#fuel-bar"));
this.#fuelPercentage = <HTMLElement>(this.getElement().querySelector("#fuel-percentage"));
this.#groupName = <HTMLElement>(this.getElement().querySelector("#group-name"));
this.#heading = <HTMLElement>(this.getElement().querySelector("#heading"));
this.#name = <HTMLElement>(this.getElement().querySelector("#name"));
this.#latitude = <HTMLElement>(this.getElement().querySelector("#latitude"));
this.#loadoutContainer = <HTMLElement>(this.getElement().querySelector("#loadout-container"));
this.#longitude = <HTMLElement>(this.getElement().querySelector("#longitude"));
this.#silhouette = <HTMLElement>(this.getElement().querySelector("#loadout-silhouette"));
this.#unitControl = <HTMLElement>(this.getElement().querySelector("#unit-control"));
this.#unitLabel = <HTMLElement>(this.getElement().querySelector("#unit-label"));
this.#unitName = <HTMLElement>(this.getElement().querySelector("#unit-name"));
this.#altitude = (this.getElement().querySelector("#altitude")) as HTMLElement;
this.#currentTask = (this.getElement().querySelector("#current-task")) as HTMLElement;
this.#groundSpeed = (this.getElement().querySelector("#ground-speed")) as HTMLElement;
this.#fuelBar = (this.getElement().querySelector("#fuel-bar")) as HTMLElement;
this.#fuelPercentage = (this.getElement().querySelector("#fuel-percentage")) as HTMLElement;
this.#groupName = (this.getElement().querySelector("#group-name")) as HTMLElement;
this.#heading = (this.getElement().querySelector("#heading")) as HTMLElement;
this.#name = (this.getElement().querySelector("#name")) as HTMLElement;
this.#latitude = (this.getElement().querySelector("#latitude")) as HTMLElement;
this.#loadoutContainer = (this.getElement().querySelector("#loadout-container")) as HTMLElement;
this.#longitude = (this.getElement().querySelector("#longitude")) as HTMLElement;
this.#silhouette = (this.getElement().querySelector("#loadout-silhouette")) as HTMLImageElement;
this.#unitControl = (this.getElement().querySelector("#unit-control")) as HTMLElement;
this.#unitLabel = (this.getElement().querySelector("#unit-label")) as HTMLElement;
this.#unitName = (this.getElement().querySelector("#unit-name")) as HTMLElement;
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => this.#onUnitsSelection(e.detail));
document.addEventListener("unitsDeselection", (e: CustomEvent<Unit[]>) => this.#onUnitsDeselection(e.detail));
@ -69,18 +69,15 @@ export class UnitInfoPanel extends Panel {
this.#currentTask.dataset.currentTask = unit.getTaskData().currentTask !== ""? unit.getTaskData().currentTask: "No task";
this.#currentTask.dataset.coalition = unit.getMissionData().coalition;
this.#silhouette.setAttribute( "style", `--loadout-background-image:url('/images/units/${aircraftDatabase.getByName( baseData.name )?.filename}');` );;
this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(baseData.name)?.filename}`;
this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(baseData.name)?.filename == undefined || unit.getDatabase()?.getByName(baseData.name)?.filename == '');
/* Add the loadout elements */
const items = <HTMLElement>this.#loadoutContainer.querySelector( "#loadout-items" );
if ( items ) {
const ammo = Object.values( unit.getMissionData().ammo );
if ( ammo.length > 0 ) {
items.replaceChildren(...Object.values(unit.getMissionData().ammo).map(
(ammo: any) => {
var el = document.createElement("div");
@ -91,25 +88,28 @@ export class UnitInfoPanel extends Panel {
));
} else {
items.innerText = "No loadout";
items.innerText = "No loadout";
}
}
}
}
#onUnitsSelection(units: Unit[]){
if (units.length == 1)
{
this.show();
this.#onUnitUpdate(units[0]);
}
else
this.hide();
}
#onUnitsDeselection(units: Unit[]){
if (units.length == 1)
{
this.show();
this.#onUnitUpdate(units[0]);
}
else
this.hide();
}

View File

@ -29,7 +29,7 @@ export function setCredentials(newUsername: string, newCredentials: string) {
credentials = newCredentials;
}
export function GET(callback: CallableFunction, uri: string, options?: {time?: number}) {
export function GET(callback: CallableFunction, uri: string, options?: { time?: number }) {
var xmlHttp = new XMLHttpRequest();
/* Assemble the request options string */
@ -37,15 +37,14 @@ export function GET(callback: CallableFunction, uri: string, options?: {time?: n
if (options?.time != undefined)
optionsString = `time=${options.time}`;
xmlHttp.open("GET", `${demoEnabled? DEMO_ADDRESS: REST_ADDRESS}/${uri}${optionsString? `?${optionsString}`: ''}`, true);
xmlHttp.open("GET", `${demoEnabled ? DEMO_ADDRESS : REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
if (credentials)
xmlHttp.setRequestHeader("Authorization", "Basic " + credentials);
xmlHttp.onload = function (e) {
if (xmlHttp.status == 200) {
var data = JSON.parse(xmlHttp.responseText);
if (uri !== UNITS_URI || (options?.time == 0) || parseInt(data.time) > lastUpdateTime)
{
if (uri !== UNITS_URI || (options?.time == 0) || parseInt(data.time) > lastUpdateTime) {
callback(data);
lastUpdateTime = parseInt(data.time);
if (isNaN(lastUpdateTime))
@ -66,14 +65,14 @@ export function GET(callback: CallableFunction, uri: string, options?: {time?: n
xmlHttp.send(null);
}
export function POST(request: object, callback: CallableFunction){
export function POST(request: object, callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("PUT", demoEnabled? DEMO_ADDRESS: REST_ADDRESS);
xmlHttp.open("PUT", demoEnabled ? DEMO_ADDRESS : REST_ADDRESS);
xmlHttp.setRequestHeader("Content-Type", "application/json");
if (credentials)
xmlHttp.setRequestHeader("Authorization", "Basic " + credentials);
xmlHttp.onreadystatechange = () => {
callback();
xmlHttp.onreadystatechange = () => {
callback();
};
xmlHttp.send(JSON.stringify(request));
}
@ -113,7 +112,7 @@ export function getMission(callback: CallableFunction) {
}
export function getUnits(callback: CallableFunction, refresh: boolean = false) {
GET(callback, `${UNITS_URI}`, {time: refresh? 0: lastUpdateTime});
GET(callback, `${UNITS_URI}`, { time: refresh ? 0 : lastUpdateTime });
}
export function addDestination(ID: number, path: any) {
@ -135,7 +134,7 @@ export function spawnGroundUnit(spawnOptions: SpawnOptions) {
}
export function spawnAircraft(spawnOptions: SpawnOptions) {
var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "payloadName": spawnOptions.loadout != null? spawnOptions.loadout: "", "airbaseName": spawnOptions.airbaseName != null? spawnOptions.airbaseName: ""};
var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "" };
var data = { "spawnAir": command }
POST(data, () => { });
}
@ -146,12 +145,12 @@ export function attackUnit(ID: number, targetID: number) {
POST(data, () => { });
}
export function followUnit(ID: number, targetID: number, offset: {"x": number, "y": number, "z": number}) {
export function followUnit(ID: number, targetID: number, offset: { "x": number, "y": number, "z": number }) {
// X: front-rear, positive front
// Y: top-bottom, positive bottom
// Z: left-right, positive right
var command = { "ID": ID, "targetID": targetID, "offsetX": offset["x"], "offsetY": offset["y"], "offsetZ": offset["z"]};
var command = { "ID": ID, "targetID": targetID, "offsetX": offset["x"], "offsetY": offset["y"], "offsetZ": offset["z"] };
var data = { "followUnit": command }
POST(data, () => { });
}
@ -162,8 +161,8 @@ export function cloneUnit(ID: number, latlng: L.LatLng) {
POST(data, () => { });
}
export function deleteUnit(ID: number) {
var command = { "ID": ID};
export function deleteUnit(ID: number, explosion: boolean) {
var command = { "ID": ID, "explosion": explosion };
var data = { "deleteUnit": command }
POST(data, () => { });
}
@ -175,50 +174,74 @@ export function landAt(ID: number, latlng: L.LatLng) {
}
export function changeSpeed(ID: number, speedChange: string) {
var command = {"ID": ID, "change": speedChange}
var data = {"changeSpeed": command}
var command = { "ID": ID, "change": speedChange }
var data = { "changeSpeed": command }
POST(data, () => { });
}
export function setSpeed(ID: number, speed: number) {
var command = {"ID": ID, "speed": speed}
var data = {"setSpeed": command}
var command = { "ID": ID, "speed": speed }
var data = { "setSpeed": command }
POST(data, () => { });
}
export function setSpeedType(ID: number, speedType: string) {
var command = { "ID": ID, "speedType": speedType }
var data = { "setSpeedType": command }
POST(data, () => { });
}
export function changeAltitude(ID: number, altitudeChange: string) {
var command = {"ID": ID, "change": altitudeChange}
var data = {"changeAltitude": command}
var command = { "ID": ID, "change": altitudeChange }
var data = { "changeAltitude": command }
POST(data, () => { });
}
export function setAltitudeType(ID: number, altitudeType: string) {
var command = { "ID": ID, "altitudeType": altitudeType }
var data = { "setAltitudeType": command }
POST(data, () => { });
}
export function setAltitude(ID: number, altitude: number) {
var command = {"ID": ID, "altitude": altitude}
var data = {"setAltitude": command}
var command = { "ID": ID, "altitude": altitude }
var data = { "setAltitude": command }
POST(data, () => { });
}
export function createFormation(ID: number, isLeader: boolean, wingmenIDs: number[]) {
var command = {"ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader}
var data = {"setLeader": command}
var command = { "ID": ID, "wingmenIDs": wingmenIDs, "isLeader": isLeader }
var data = { "setLeader": command }
POST(data, () => { });
}
export function setROE(ID: number, ROE: string) {
var command = {"ID": ID, "ROE": ROE}
var data = {"setROE": command}
var command = { "ID": ID, "ROE": ROE }
var data = { "setROE": command }
POST(data, () => { });
}
export function setReactionToThreat(ID: number, reactionToThreat: string) {
var command = {"ID": ID, "reactionToThreat": reactionToThreat}
var data = {"setReactionToThreat": command}
var command = { "ID": ID, "reactionToThreat": reactionToThreat }
var data = { "setReactionToThreat": command }
POST(data, () => { });
}
export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) {
var command = {"ID": ID, "emissionsCountermeasures": emissionCountermeasure}
var data = {"setEmissionsCountermeasures": command}
var command = { "ID": ID, "emissionsCountermeasures": emissionCountermeasure }
var data = { "setEmissionsCountermeasures": command }
POST(data, () => { });
}
export function setOnOff(ID: number, onOff: boolean) {
var command = { "ID": ID, "onOff": onOff }
var data = { "setOnOff": command }
POST(data, () => { });
}
export function setFollowRoads(ID: number, followRoads: boolean) {
var command = { "ID": ID, "followRoads": followRoads }
var data = { "setFollowRoads": command }
POST(data, () => { });
}
@ -228,14 +251,14 @@ export function refuel(ID: number) {
POST(data, () => { });
}
export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings)
{
var command = { "ID": ID,
"isTanker": isTanker,
"isAWACS": isAWACS,
"TACAN": TACAN,
"radio": radio,
"generalSettings": generalSettings
export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) {
var command = {
"ID": ID,
"isTanker": isTanker,
"isAWACS": isAWACS,
"TACAN": TACAN,
"radio": radio,
"generalSettings": generalSettings
};
var data = { "setAdvancedOptions": command };

View File

@ -1,7 +1,7 @@
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } from 'leaflet';
import { getMap, getUnitsManager } from '..';
import { rad2deg } from '../other/utils';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures } from '../server/server';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads } from '../server/server';
import { aircraftDatabase } from './aircraftdatabase';
import { groundUnitsDatabase } from './groundunitsdatabase';
import { CustomMarker } from '../map/custommarker';
@ -49,9 +49,13 @@ export class Unit extends CustomMarker {
currentTask: "",
activePath: {},
targetSpeed: 0,
targetSpeedType: "GS",
targetAltitude: 0,
targetAltitudeType: "AGL",
isTanker: false,
isAWACS: false,
onOff: true,
followRoads: false
},
optionsData: {
ROE: "",
@ -116,8 +120,6 @@ export class Unit extends CustomMarker {
/* Set the unit data */
this.setData(data);
}
getMarkerCategory() {
@ -203,48 +205,19 @@ export class Unit extends CustomMarker {
const aliveChanged = (data.baseData != undefined && data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive);
var updateMarker = (positionChanged || headingChanged || aliveChanged || !getMap().hasLayer(this));
if (data.baseData != undefined) {
for (let key in this.#data.baseData)
if (key in data.baseData)
//@ts-ignore
this.#data.baseData[key] = data.baseData[key];
}
if (data.flightData != undefined) {
for (let key in this.#data.flightData)
if (key in data.flightData)
//@ts-ignore
this.#data.flightData[key] = data.flightData[key];
}
if (data.missionData != undefined) {
for (let key in this.#data.missionData)
if (key in data.missionData)
//@ts-ignore
this.#data.missionData[key] = data.missionData[key];
}
if (data.formationData != undefined) {
for (let key in this.#data.formationData)
if (key in data.formationData)
//@ts-ignore
this.#data.formationData[key] = data.formationData[key];
}
if (data.taskData != undefined) {
for (let key in this.#data.taskData)
if (key in data.taskData)
//@ts-ignore
this.#data.taskData[key] = data.taskData[key];
}
if (data.optionsData != undefined) {
for (let key in this.#data.optionsData)
if (key in data.optionsData)
//@ts-ignore
this.#data.optionsData[key] = data.optionsData[key];
}
/* Load the data from the received json */
Object.keys(this.#data).forEach((key1: string) => {
Object.keys(this.#data[key1 as keyof(UnitData)]).forEach((key2: string) => {
if (key1 in data && key2 in data[key1]) {
var value1 = this.#data[key1 as keyof(UnitData)];
var value2 = value1[key2 as keyof typeof value1];
if (typeof data[key1][key2] === typeof value2)
//@ts-ignore
this.#data[key1 as keyof(UnitData)][key2 as keyof typeof struct] = data[key1][key2];
}
});
});
/* Fire an event when a unit dies */
if (aliveChanged && this.getBaseData().alive == false)
document.dispatchEvent(new CustomEvent("unitDeath", { detail: this }));
@ -485,11 +458,21 @@ export class Unit extends CustomMarker {
setSpeed(this.ID, speed);
}
setSpeedType(speedType: string) {
if (!this.getMissionData().flags.Human)
setSpeedType(this.ID, speedType);
}
setAltitude(altitude: number) {
if (!this.getMissionData().flags.Human)
setAltitude(this.ID, altitude);
}
setAltitudeType(altitudeType: string) {
if (!this.getMissionData().flags.Human)
setAltitudeType(this.ID, altitudeType);
}
setROE(ROE: string) {
if (!this.getMissionData().flags.Human)
setROE(this.ID, ROE);
@ -510,9 +493,19 @@ export class Unit extends CustomMarker {
setLeader(this.ID, isLeader, wingmenIDs);
}
delete() {
setOnOff(onOff: boolean) {
if (!this.getMissionData().flags.Human)
setOnOff(this.ID, onOff);
}
setFollowRoads(followRoads: boolean) {
if (!this.getMissionData().flags.Human)
setFollowRoads(this.ID, followRoads);
}
delete(explosion: boolean) {
// TODO: add confirmation popup
deleteUnit(this.ID);
deleteUnit(this.ID, explosion);
}
refuel() {

View File

@ -21,7 +21,8 @@ export class UnitsManager {
document.addEventListener('unitSelection', (e: CustomEvent) => this.#onUnitSelection(e.detail));
document.addEventListener('unitDeselection', (e: CustomEvent) => this.#onUnitDeselection(e.detail));
document.addEventListener('keydown', (event) => this.#onKeyDown(event));
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete())
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete());
document.addEventListener('explodeSelectedUnits', () => this.selectedUnitsDelete(true));
}
getSelectableAircraft() {
@ -155,25 +156,16 @@ export class UnitsManager {
});
};
getSelectedUnitsTargetSpeed() {
getSelectedUnitsVariable(variableGetter: CallableFunction) {
if (this.getSelectedUnits().length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return unit.getTaskData().targetSpeed
return variableGetter(unit);
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
};
getSelectedUnitsTargetAltitude() {
if (this.getSelectedUnits().length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return unit.getTaskData().targetAltitude
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
};
getSelectedUnitsCoalition() {
if (this.getSelectedUnits().length == 0)
@ -261,6 +253,14 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `setting speed to ${speed * 1.94384} kts`);
}
selectedUnitsSetSpeedType(speedType: string) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
selectedUnits[idx].setSpeedType(speedType);
}
this.#showActionMessage(selectedUnits, `setting speed type to ${speedType}`);
}
selectedUnitsSetAltitude(altitude: number) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
@ -269,6 +269,14 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `setting altitude to ${altitude / 0.3048} ft`);
}
selectedUnitsSetAltitudeType(altitudeType: string) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
selectedUnits[idx].setAltitudeType(altitudeType);
}
this.#showActionMessage(selectedUnits, `setting altitude type to ${altitudeType}`);
}
selectedUnitsSetROE(ROE: string) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
@ -290,7 +298,23 @@ export class UnitsManager {
for (let idx in selectedUnits) {
selectedUnits[idx].setEmissionsCountermeasures(emissionCountermeasure);
}
this.#showActionMessage(selectedUnits, `reaction to threat set to ${emissionCountermeasure}`);
this.#showActionMessage(selectedUnits, `emissions & countermeasures set to ${emissionCountermeasure}`);
}
selectedUnitsSetOnOff(onOff: boolean) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
selectedUnits[idx].setOnOff(onOff);
}
this.#showActionMessage(selectedUnits, `unit acitve set to ${onOff}`);
}
selectedUnitsSetFollowRoads(followRoads: boolean) {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
for (let idx in selectedUnits) {
selectedUnits[idx].setFollowRoads(followRoads);
}
this.#showActionMessage(selectedUnits, `follow roads set to ${followRoads}`);
}
@ -302,10 +326,10 @@ export class UnitsManager {
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
}
selectedUnitsDelete() {
selectedUnitsDelete(explosion: boolean = false) {
var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
for (let idx in selectedUnits) {
selectedUnits[idx].delete();
selectedUnits[idx].delete(explosion);
}
this.#showActionMessage(selectedUnits, `deleted`);
}

View File

@ -1,9 +1,7 @@
<div id="map-contextmenu" oncontextmenu="return false;">
<div id="active-coalition-label" data-active-coalition="blue"></div>
<div id="upper-bar" class="ol-panel">
<label id="context-menu-switch" class="ol-switch" for="context-menu-ol-switch">
<div data-active-coalition="blue" class="ol-switch-fill"></div>
</label>
<div id="coalition-switch" class="ol-switch"></div>
<button data-active-coalition="blue" id="aircraft-spawn-button" title="Spawn aircraft" data-on-click="contextMenuShow"
data-on-click-params='{ "type": "aircraft" }' class="unit-spawn-button"></button>
<button data-active-coalition="blue" id="ground-unit-spawn-button" title="Spawn ground unit" data-on-click="contextMenuShow"
@ -38,6 +36,17 @@
</div>
</div>
</div>
<div id="aircraft-spawn-altitude-slider" class="ol-slider-container flight-control-ol-slider">
<dl class="ol-data-grid">
<dt> Spawn altitude
</dt>
<dd>
<div class="ol-slider-value"></div>
</dd>
</dl>
<input type="range" min="0" max="100" value="0" class="ol-slider">
</div>
<div id="loadout-preview">
<img id="unit-image" class="hide">
<div id="loadout-list">

View File

@ -14,12 +14,12 @@
<div id="flight-data">
<h4>Controls</h4>
<div id="airspeed-slider" class="ol-slider-container flight-control-ol-slider">
<div id="speed-slider" class="ol-slider-container flight-control-ol-slider">
<dl class="ol-data-grid">
<dt>Speed</dt>
<dd>
<div class="ol-slider-value"></div>
<div id="airspeed-type-switch" class="ol-switch"></div>
<div id="speed-type-switch" class="ol-switch"></div>
</dd>
</dl>
<input type="range" min="0" max="100" value="0" class="ol-slider">
@ -61,12 +61,17 @@
</div>
</div>
<div id="ai-on-off">
<div id="ai-on-off" class="switch-control">
<h4>Unit active</h4>
<div id="on-off-switch" class="ol-switch"></div>
<div>Toggling this disables unit AI completely. It will no longer move, react or emit radio waves.</div>
</div>
<div id="follow-roads" class="switch-control">
<h4>Follow roads</h4>
<div id="follow-roads-switch" class="ol-switch"></div>
</div>
<hr />
<div id="advanced-settings-div">

View File

@ -14,7 +14,7 @@
<div id="loadout-container" class="panel-section">
<div id="loadout">
<div id="loadout-silhouette"></div>
<img id="loadout-silhouette"/>
<div id="loadout-items">
</div>
</div>

View File

@ -1,6 +1,6 @@
{
"server": {
"address": "localhost",
"address": "136.243.170.132",
"port": 30000
},
"authentication": {