mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Completed frontend controls
This commit is contained in:
parent
4087dbde21
commit
1dd4014e61
@ -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
|
||||
|
||||
@ -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",
|
||||
},
|
||||
|
||||
697
client/public/javascripts/svg-inject.js
Normal file
697
client/public/javascripts/svg-inject.js
Normal 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);
|
||||
@ -62,6 +62,7 @@
|
||||
position: absolute;
|
||||
width: fit-content;
|
||||
z-index: 1000;
|
||||
padding: 24px 30px;
|
||||
}
|
||||
|
||||
#info-popup {
|
||||
|
||||
@ -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));
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
22
client/src/@types/unit.d.ts
vendored
22
client/src/@types/unit.d.ts
vendored
@ -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;
|
||||
}
|
||||
102
client/src/constants/constants.ts
Normal file
102
client/src/constants/constants.ts
Normal 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 © Esri — 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: '© <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 © <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 © Esri — Copyright: ©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: © <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
@ -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 *********/
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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 © Esri — 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: '© <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 © <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 © Esri — Copyright: ©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: © <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());
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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`);
|
||||
}
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"server": {
|
||||
"address": "localhost",
|
||||
"address": "136.243.170.132",
|
||||
"port": 30000
|
||||
},
|
||||
"authentication": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user