From a2664dc676b1386b03cf064e4226ef04268ce39c Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 22 May 2023 17:39:33 +0200 Subject: [PATCH] Refactoring of files and more svg tidy up --- client/package.json | 2 +- client/public/javascripts/svg-inject.js | 697 ++++++++++++++++++ client/public/stylesheets/{ => aic}/aic.css | 0 client/public/stylesheets/{ => atc}/atc.css | 0 .../stylesheets/{ => atc}/unitdatatable.css | 0 .../stylesheets/{ => layout}/layout.css | 0 .../stylesheets/{ => leaflet}/leaflet.css | 0 .../stylesheets/{ => markers}/airbase.css | 0 .../stylesheets/{ => markers}/units.css | 16 +- client/public/stylesheets/olympus.css | 45 +- .../stylesheets/{ => other}/contextmenus.css | 0 .../public/stylesheets/{ => other}/popup.css | 0 .../connectionstatus.css} | 0 .../mouseinfo.css} | 0 .../unitcontrol.css} | 0 .../unitinfo.css} | 0 .../public/stylesheets/{ => uikit}/uikit.css | 0 .../olympus/images/buttons/roe/designated.svg | 2 +- .../olympus/images/buttons/roe/free.svg | 2 +- .../olympus/images/buttons/roe/hold.svg | 2 +- .../olympus/images/buttons/roe/return.svg | 2 +- .../olympus/images/buttons/threat/evade.svg | 10 +- .../images/buttons/threat/manoeuvre.svg | 2 +- .../olympus/images/buttons/threat/none.svg | 2 +- .../olympus/images/buttons/threat/passive.svg | 12 +- .../images/buttons/visibility/airbase.svg | 76 ++ .../images/buttons/visibility/aircraft.svg | 44 +- .../olympus/images/buttons/visibility/dcs.svg | 32 + .../buttons/visibility/groundunit-other.svg | 75 +- .../buttons/visibility/groundunit-sam.svg | 103 ++- .../images/buttons/visibility/human.svg | 67 ++ .../images/buttons/visibility/navyunit.svg | 44 +- .../images/buttons/visibility/threatring.svg | 5 - .../themes/olympus/images/units/aircraft.svg | 2 +- .../themes/olympus/images/units/bomb.svg | 2 +- .../themes/olympus/images/units/death.svg | 2 +- .../olympus/images/units/groundunit-other.svg | 2 +- .../olympus/images/units/groundunit-sam.svg | 2 +- .../themes/olympus/images/units/missile.svg | 2 +- .../themes/olympus/images/units/navyunit.svg | 2 +- .../themes/olympus/images/units/static.svg | 2 +- client/public/themes/olympus/theme.css | 3 + client/src/aic/aic.ts | 2 +- client/src/{units => atc}/unitdatatable.ts | 2 +- client/src/{ => features}/featureswitches.ts | 0 .../src/{ => features}/toggleablefeature.ts | 0 client/src/index.ts | 6 +- client/src/map/map.ts | 21 + client/src/units/unit.ts | 16 +- client/src/units/unitsmanager.ts | 18 + client/views/{ => aic}/aic.ejs | 0 client/views/{ => atc}/atc.ejs | 4 +- client/views/{ => atc}/unitdatatable.ejs | 0 client/views/index.ejs | 50 +- client/views/{ => log}/log.ejs | 0 client/views/{ => other}/contextmenus.ejs | 0 client/views/{ => other}/dialogs.ejs | 0 client/views/{ => other}/popups.ejs | 0 .../connectionstatus.ejs} | 0 .../hotgroup.ejs} | 0 .../mouseinfo.ejs} | 0 client/views/{ => panels}/navbar.ejs | 21 +- .../unitcontrol.ejs} | 0 .../unitinfo.ejs} | 0 client/views/{ => uikit}/uikit.ejs | 0 65 files changed, 1241 insertions(+), 158 deletions(-) create mode 100644 client/public/javascripts/svg-inject.js rename client/public/stylesheets/{ => aic}/aic.css (100%) rename client/public/stylesheets/{ => atc}/atc.css (100%) rename client/public/stylesheets/{ => atc}/unitdatatable.css (100%) rename client/public/stylesheets/{ => layout}/layout.css (100%) rename client/public/stylesheets/{ => leaflet}/leaflet.css (100%) rename client/public/stylesheets/{ => markers}/airbase.css (100%) rename client/public/stylesheets/{ => markers}/units.css (95%) rename client/public/stylesheets/{ => other}/contextmenus.css (100%) rename client/public/stylesheets/{ => other}/popup.css (100%) rename client/public/stylesheets/{connectionstatuspanel.css => panels/connectionstatus.css} (100%) rename client/public/stylesheets/{mouseinfopanel.css => panels/mouseinfo.css} (100%) rename client/public/stylesheets/{unitcontrolpanel.css => panels/unitcontrol.css} (100%) rename client/public/stylesheets/{unitinfopanel.css => panels/unitinfo.css} (100%) rename client/public/stylesheets/{ => uikit}/uikit.css (100%) create mode 100644 client/public/themes/olympus/images/buttons/visibility/airbase.svg create mode 100644 client/public/themes/olympus/images/buttons/visibility/dcs.svg create mode 100644 client/public/themes/olympus/images/buttons/visibility/human.svg delete mode 100644 client/public/themes/olympus/images/buttons/visibility/threatring.svg rename client/src/{units => atc}/unitdatatable.ts (97%) rename client/src/{ => features}/featureswitches.ts (100%) rename client/src/{ => features}/toggleablefeature.ts (100%) rename client/views/{ => aic}/aic.ejs (100%) rename client/views/{ => atc}/atc.ejs (80%) rename client/views/{ => atc}/unitdatatable.ejs (100%) rename client/views/{ => log}/log.ejs (100%) rename client/views/{ => other}/contextmenus.ejs (100%) rename client/views/{ => other}/dialogs.ejs (100%) rename client/views/{ => other}/popups.ejs (100%) rename client/views/{connectionstatuspanel.ejs => panels/connectionstatus.ejs} (100%) rename client/views/{hotgrouppanel.ejs => panels/hotgroup.ejs} (100%) rename client/views/{mouseinfopanel.ejs => panels/mouseinfo.ejs} (100%) rename client/views/{ => panels}/navbar.ejs (65%) rename client/views/{unitcontrolpanel.ejs => panels/unitcontrol.ejs} (100%) rename client/views/{unitinfopanel.ejs => panels/unitinfo.ejs} (100%) rename client/views/{ => uikit}/uikit.ejs (100%) diff --git a/client/package.json b/client/package.json index f9cb7d2a..678d17e5 100644 --- a/client/package.json +++ b/client/package.json @@ -5,7 +5,7 @@ "version": "v0.2.1-alpha", "private": true, "scripts": { - "copy": "copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet.css", + "copy": "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", "start": "npm run copy & concurrently --kill-others \"npm run watch\" \"nodemon ./bin/www\"", "watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]" }, diff --git a/client/public/javascripts/svg-inject.js b/client/public/javascripts/svg-inject.js new file mode 100644 index 00000000..4e74af07 --- /dev/null +++ b/client/public/javascripts/svg-inject.js @@ -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 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 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 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 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 `` 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 `` 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); \ No newline at end of file diff --git a/client/public/stylesheets/aic.css b/client/public/stylesheets/aic/aic.css similarity index 100% rename from client/public/stylesheets/aic.css rename to client/public/stylesheets/aic/aic.css diff --git a/client/public/stylesheets/atc.css b/client/public/stylesheets/atc/atc.css similarity index 100% rename from client/public/stylesheets/atc.css rename to client/public/stylesheets/atc/atc.css diff --git a/client/public/stylesheets/unitdatatable.css b/client/public/stylesheets/atc/unitdatatable.css similarity index 100% rename from client/public/stylesheets/unitdatatable.css rename to client/public/stylesheets/atc/unitdatatable.css diff --git a/client/public/stylesheets/layout.css b/client/public/stylesheets/layout/layout.css similarity index 100% rename from client/public/stylesheets/layout.css rename to client/public/stylesheets/layout/layout.css diff --git a/client/public/stylesheets/leaflet.css b/client/public/stylesheets/leaflet/leaflet.css similarity index 100% rename from client/public/stylesheets/leaflet.css rename to client/public/stylesheets/leaflet/leaflet.css diff --git a/client/public/stylesheets/airbase.css b/client/public/stylesheets/markers/airbase.css similarity index 100% rename from client/public/stylesheets/airbase.css rename to client/public/stylesheets/markers/airbase.css diff --git a/client/public/stylesheets/units.css b/client/public/stylesheets/markers/units.css similarity index 95% rename from client/public/stylesheets/units.css rename to client/public/stylesheets/markers/units.css index a523f47c..121a1059 100644 --- a/client/public/stylesheets/units.css +++ b/client/public/stylesheets/markers/units.css @@ -67,23 +67,23 @@ } /*** Basic colours ***/ -[data-coalition="blue"] .unit-marker>svg>.background { - fill: var(--primary-blue); +[data-coalition="blue"] .unit-marker svg>*:first-child { + fill: var(--unit-background-blue); } -[data-coalition="red"] .unit-marker>svg>.background { - fill: var(--primary-red); +[data-coalition="red"] .unit-marker svg>*:first-child { + fill: var(--unit-background-red); } -[data-coalition="neutral"] .unit-marker>svg>.background { - fill: var(--primary-neutral); +[data-coalition="neutral"] .unit-marker svg>*:first-child { + fill: var(--unit-background-neutral); } -[data-is-selected] .unit-marker>svg>.background { +[data-is-selected] .unit-marker svg>*:first-child { fill: white; } -[data-is-highlighted] .unit-marker>svg>.background { +[data-is-highlighted] .unit-marker svg>*:first-child { stroke: white; } diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 3a0d7326..413afbc1 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -1,14 +1,15 @@ -@import url("layout.css"); -@import url("airbase.css"); -@import url("atc.css"); -@import url("connectionstatuspanel.css"); -@import url("contextmenus.css"); -@import url("mouseinfopanel.css"); -@import url("/stylesheets/units.css"); -@import url("unitdatatable.css"); -@import url("unitcontrolpanel.css"); -@import url("unitinfopanel.css"); -@import url("popup.css"); +@import url("layout/layout.css"); +@import url("atc/atc.css"); +@import url("atc/unitdatatable.css"); +@import url("aic/aic.css"); +@import url("panels/connectionstatus.css"); +@import url("panels/mouseinfo.css"); +@import url("panels/unitcontrol.css"); +@import url("panels/unitinfo.css"); +@import url("other/contextmenus.css"); +@import url("other/popup.css"); +@import url("markers/airbase.css"); +@import url("markers/units.css"); * { -moz-box-sizing: border-box; @@ -593,22 +594,28 @@ nav.ol-panel> :last-child { #unit-visibility-control button svg { pointer-events: none; + height: 16px; + width: 16px; } -#unit-visibility-control button svg .background { - fill: white; +#unit-visibility-control button { + background-color: white; + border: 1px solid transparent; } -#unit-visibility-control button.off svg .foreground { - fill: var(--background-steel); +#unit-visibility-control button.off { + background-color: transparent; + border: 1px solid white; } -#unit-visibility-control button.off svg .background { - fill: none; +#unit-visibility-control button.off svg * { + fill: white !important; + stroke: white !important; } -#unit-visibility-control button.off svg .foreground { - fill: white; +#unit-visibility-control button svg * { + fill: var(--background-steel) !important; + stroke: var(--background-steel) !important; } #atc-navbar-control { diff --git a/client/public/stylesheets/contextmenus.css b/client/public/stylesheets/other/contextmenus.css similarity index 100% rename from client/public/stylesheets/contextmenus.css rename to client/public/stylesheets/other/contextmenus.css diff --git a/client/public/stylesheets/popup.css b/client/public/stylesheets/other/popup.css similarity index 100% rename from client/public/stylesheets/popup.css rename to client/public/stylesheets/other/popup.css diff --git a/client/public/stylesheets/connectionstatuspanel.css b/client/public/stylesheets/panels/connectionstatus.css similarity index 100% rename from client/public/stylesheets/connectionstatuspanel.css rename to client/public/stylesheets/panels/connectionstatus.css diff --git a/client/public/stylesheets/mouseinfopanel.css b/client/public/stylesheets/panels/mouseinfo.css similarity index 100% rename from client/public/stylesheets/mouseinfopanel.css rename to client/public/stylesheets/panels/mouseinfo.css diff --git a/client/public/stylesheets/unitcontrolpanel.css b/client/public/stylesheets/panels/unitcontrol.css similarity index 100% rename from client/public/stylesheets/unitcontrolpanel.css rename to client/public/stylesheets/panels/unitcontrol.css diff --git a/client/public/stylesheets/unitinfopanel.css b/client/public/stylesheets/panels/unitinfo.css similarity index 100% rename from client/public/stylesheets/unitinfopanel.css rename to client/public/stylesheets/panels/unitinfo.css diff --git a/client/public/stylesheets/uikit.css b/client/public/stylesheets/uikit/uikit.css similarity index 100% rename from client/public/stylesheets/uikit.css rename to client/public/stylesheets/uikit/uikit.css diff --git a/client/public/themes/olympus/images/buttons/roe/designated.svg b/client/public/themes/olympus/images/buttons/roe/designated.svg index 8815698c..0846e4e6 100644 --- a/client/public/themes/olympus/images/buttons/roe/designated.svg +++ b/client/public/themes/olympus/images/buttons/roe/designated.svg @@ -37,7 +37,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4" /> - diff --git a/client/public/themes/olympus/images/buttons/roe/free.svg b/client/public/themes/olympus/images/buttons/roe/free.svg index ea72b826..f07b2db6 100644 --- a/client/public/themes/olympus/images/buttons/roe/free.svg +++ b/client/public/themes/olympus/images/buttons/roe/free.svg @@ -36,7 +36,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4" /> - diff --git a/client/public/themes/olympus/images/buttons/roe/hold.svg b/client/public/themes/olympus/images/buttons/roe/hold.svg index 3e2cbd79..f735bc21 100644 --- a/client/public/themes/olympus/images/buttons/roe/hold.svg +++ b/client/public/themes/olympus/images/buttons/roe/hold.svg @@ -36,7 +36,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4" /> - diff --git a/client/public/themes/olympus/images/buttons/roe/return.svg b/client/public/themes/olympus/images/buttons/roe/return.svg index 4d64a998..5c884188 100644 --- a/client/public/themes/olympus/images/buttons/roe/return.svg +++ b/client/public/themes/olympus/images/buttons/roe/return.svg @@ -36,7 +36,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4" /> - diff --git a/client/public/themes/olympus/images/buttons/threat/evade.svg b/client/public/themes/olympus/images/buttons/threat/evade.svg index c5691783..687f852e 100644 --- a/client/public/themes/olympus/images/buttons/threat/evade.svg +++ b/client/public/themes/olympus/images/buttons/threat/evade.svg @@ -36,25 +36,25 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg12" /> - - - - - diff --git a/client/public/themes/olympus/images/buttons/threat/manoeuvre.svg b/client/public/themes/olympus/images/buttons/threat/manoeuvre.svg index de72b10b..fcc9376b 100644 --- a/client/public/themes/olympus/images/buttons/threat/manoeuvre.svg +++ b/client/public/themes/olympus/images/buttons/threat/manoeuvre.svg @@ -36,7 +36,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg12" /> - diff --git a/client/public/themes/olympus/images/buttons/threat/none.svg b/client/public/themes/olympus/images/buttons/threat/none.svg index 6663b0e5..53414370 100644 --- a/client/public/themes/olympus/images/buttons/threat/none.svg +++ b/client/public/themes/olympus/images/buttons/threat/none.svg @@ -36,7 +36,7 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg4" /> - diff --git a/client/public/themes/olympus/images/buttons/threat/passive.svg b/client/public/themes/olympus/images/buttons/threat/passive.svg index f0d3b893..ac558fef 100644 --- a/client/public/themes/olympus/images/buttons/threat/passive.svg +++ b/client/public/themes/olympus/images/buttons/threat/passive.svg @@ -36,28 +36,28 @@ inkscape:window-y="-8" inkscape:window-maximized="1" inkscape:current-layer="svg14" /> - - - - - - diff --git a/client/public/themes/olympus/images/buttons/visibility/airbase.svg b/client/public/themes/olympus/images/buttons/visibility/airbase.svg new file mode 100644 index 00000000..28ad42a4 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/airbase.svg @@ -0,0 +1,76 @@ + + + + + + + + image/svg+xml + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/aircraft.svg b/client/public/themes/olympus/images/buttons/visibility/aircraft.svg index 6b19238d..09a9e322 100644 --- a/client/public/themes/olympus/images/buttons/visibility/aircraft.svg +++ b/client/public/themes/olympus/images/buttons/visibility/aircraft.svg @@ -1,5 +1,41 @@ - - - - + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/dcs.svg b/client/public/themes/olympus/images/buttons/visibility/dcs.svg new file mode 100644 index 00000000..bb7bd479 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/dcs.svg @@ -0,0 +1,32 @@ + + + + + + image/svg+xml + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg b/client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg index ef41b8b1..0a448b6f 100644 --- a/client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg +++ b/client/public/themes/olympus/images/buttons/visibility/groundunit-other.svg @@ -1,13 +1,64 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg b/client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg index d75e0075..2a568fbb 100644 --- a/client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg +++ b/client/public/themes/olympus/images/buttons/visibility/groundunit-sam.svg @@ -1,19 +1,86 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/human.svg b/client/public/themes/olympus/images/buttons/visibility/human.svg new file mode 100644 index 00000000..51278e9e --- /dev/null +++ b/client/public/themes/olympus/images/buttons/visibility/human.svg @@ -0,0 +1,67 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/navyunit.svg b/client/public/themes/olympus/images/buttons/visibility/navyunit.svg index 3810ab30..d4ff6fec 100644 --- a/client/public/themes/olympus/images/buttons/visibility/navyunit.svg +++ b/client/public/themes/olympus/images/buttons/visibility/navyunit.svg @@ -1,5 +1,41 @@ - - - - + + + + + diff --git a/client/public/themes/olympus/images/buttons/visibility/threatring.svg b/client/public/themes/olympus/images/buttons/visibility/threatring.svg deleted file mode 100644 index 6fff5bfe..00000000 --- a/client/public/themes/olympus/images/buttons/visibility/threatring.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/client/public/themes/olympus/images/units/aircraft.svg b/client/public/themes/olympus/images/units/aircraft.svg index c932ee11..104f9bcf 100644 --- a/client/public/themes/olympus/images/units/aircraft.svg +++ b/client/public/themes/olympus/images/units/aircraft.svg @@ -1,4 +1,4 @@ - + diff --git a/client/public/themes/olympus/images/units/bomb.svg b/client/public/themes/olympus/images/units/bomb.svg index b4447e5c..a38a168e 100644 --- a/client/public/themes/olympus/images/units/bomb.svg +++ b/client/public/themes/olympus/images/units/bomb.svg @@ -1,3 +1,3 @@ - + diff --git a/client/public/themes/olympus/images/units/death.svg b/client/public/themes/olympus/images/units/death.svg index acb3d38a..c68ab1d1 100644 --- a/client/public/themes/olympus/images/units/death.svg +++ b/client/public/themes/olympus/images/units/death.svg @@ -1,3 +1,3 @@ - + diff --git a/client/public/themes/olympus/images/units/groundunit-other.svg b/client/public/themes/olympus/images/units/groundunit-other.svg index a60bf5ea..6b6dcc7c 100644 --- a/client/public/themes/olympus/images/units/groundunit-other.svg +++ b/client/public/themes/olympus/images/units/groundunit-other.svg @@ -1,5 +1,5 @@ - + diff --git a/client/public/themes/olympus/images/units/groundunit-sam.svg b/client/public/themes/olympus/images/units/groundunit-sam.svg index 6d5b0390..996af35d 100644 --- a/client/public/themes/olympus/images/units/groundunit-sam.svg +++ b/client/public/themes/olympus/images/units/groundunit-sam.svg @@ -1,4 +1,4 @@ - + diff --git a/client/public/themes/olympus/images/units/missile.svg b/client/public/themes/olympus/images/units/missile.svg index ecce0114..d7711d7c 100644 --- a/client/public/themes/olympus/images/units/missile.svg +++ b/client/public/themes/olympus/images/units/missile.svg @@ -1,5 +1,5 @@ - + diff --git a/client/public/themes/olympus/images/units/navyunit.svg b/client/public/themes/olympus/images/units/navyunit.svg index d6c82025..b3b2ed1b 100644 --- a/client/public/themes/olympus/images/units/navyunit.svg +++ b/client/public/themes/olympus/images/units/navyunit.svg @@ -1,5 +1,5 @@ - + diff --git a/client/public/themes/olympus/images/units/static.svg b/client/public/themes/olympus/images/units/static.svg index b986a43f..ead61396 100644 --- a/client/public/themes/olympus/images/units/static.svg +++ b/client/public/themes/olympus/images/units/static.svg @@ -1,5 +1,5 @@ - + diff --git a/client/public/themes/olympus/theme.css b/client/public/themes/olympus/theme.css index 01af652e..a7b402af 100644 --- a/client/public/themes/olympus/theme.css +++ b/client/public/themes/olympus/theme.css @@ -5,16 +5,19 @@ --primary-neutral: #949ba7; --secondary-neutral-outline: #111111; --secondary-neutral-text: #111111; + --unit-background-neutral: #CFD9E8; /*** Coalition: blue ***/ --primary-blue: #247be2; --secondary-blue-outline: #082e44; --secondary-blue-text: #017DC1; + --unit-background-blue: #3BB9FF; /*** Coalition: red ***/ --primary-red: #ff5858; --secondary-red-outline: #262222; --secondary-red-text: #D42121; + --unit-background-red: #FF5858; /*** UI Colours **/ --accent-green: #8bff63; diff --git a/client/src/aic/aic.ts b/client/src/aic/aic.ts index 770fd695..2aaf8a1c 100644 --- a/client/src/aic/aic.ts +++ b/client/src/aic/aic.ts @@ -1,4 +1,4 @@ -import { ToggleableFeature } from "../toggleablefeature"; +import { ToggleableFeature } from "../features/toggleablefeature"; import { AICFormation_Azimuth } from "./aicformation/azimuth"; import { AICFormation_Range } from "./aicformation/range"; import { AICFormation_Single } from "./aicformation/single"; diff --git a/client/src/units/unitdatatable.ts b/client/src/atc/unitdatatable.ts similarity index 97% rename from client/src/units/unitdatatable.ts rename to client/src/atc/unitdatatable.ts index e54b541c..3b0b97b8 100644 --- a/client/src/units/unitdatatable.ts +++ b/client/src/atc/unitdatatable.ts @@ -1,6 +1,6 @@ import { getUnitsManager } from ".."; import { Panel } from "../panels/panel"; -import { Unit } from "./unit"; +import { Unit } from "../units/unit"; export class UnitDataTable extends Panel { constructor(id: string) { diff --git a/client/src/featureswitches.ts b/client/src/features/featureswitches.ts similarity index 100% rename from client/src/featureswitches.ts rename to client/src/features/featureswitches.ts diff --git a/client/src/toggleablefeature.ts b/client/src/features/toggleablefeature.ts similarity index 100% rename from client/src/toggleablefeature.ts rename to client/src/features/toggleablefeature.ts diff --git a/client/src/index.ts b/client/src/index.ts index 2a2dc3ae..16d9a8b2 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -7,17 +7,15 @@ import { UnitControlPanel } from "./panels/unitcontrolpanel"; import { MouseInfoPanel } from "./panels/mouseinfopanel"; import { AIC } from "./aic/aic"; import { ATC } from "./atc/atc"; -import { FeatureSwitches } from "./featureswitches"; +import { FeatureSwitches } from "./features/featureswitches"; import { LogPanel } from "./panels/logpanel"; import { getConfig, getPaused, setAddress, setCredentials, setPaused, startUpdate, toggleDemoEnabled } from "./server/server"; -import { UnitDataTable } from "./units/unitdatatable"; +import { UnitDataTable } from "./atc/unitdatatable"; import { keyEventWasInInput } from "./other/utils"; import { Popup } from "./popups/popup"; import { Dropdown } from "./controls/dropdown"; import { HotgroupPanel } from "./panels/hotgrouppanel"; -import "@iconfu/svg-inject"; - var map: Map; var unitsManager: UnitsManager; diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 682c6cc9..f98a58c9 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -32,6 +32,8 @@ var destinationPreviewIcon = new L.DivIcon({ className: "ol-destination-preview" }) +const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; + export class ClickableMiniMap extends MiniMap { constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) { super(layer, options); @@ -69,6 +71,7 @@ export class Map extends L.Map { #airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu"); #mapSourceDropdown: Dropdown; + #optionButtons: { [key: string]: HTMLButtonElement[] } = {} constructor(ID: string) { /* Init the leaflet map */ @@ -130,6 +133,15 @@ export class Map extends L.Map { this.panBy(new L.Point( ((this.#panLeft? -1: 0) + (this.#panRight? 1: 0)) * this.#deafultPanDelta, ((this.#panUp? -1: 0) + (this.#panDown? 1: 0)) * this.#deafultPanDelta)); }, 20); + + /* Option buttons */ + this.#optionButtons["visibility"] = visibilityControls.map((option: string, index: number) => { + return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, "", (e: any) => { + getUnitsManager().setHiddenType(option, (e?.currentTarget as HTMLElement)?.classList.contains("off")); + (e?.currentTarget as HTMLElement)?.classList.toggle("off"); + }); + }); + document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]); } setLayer(layerName: string) { @@ -535,4 +547,13 @@ export class Map extends L.Map { this.#destinationPreviewMarkers[idx].setLatLng(!e.originalEvent.shiftKey? latlng: this.getMouseCoordinates()); }) } + + #createOptionButton(value: string, url: string, title: string, callback: EventListenerOrEventListenerObject) { + var button = document.createElement("button"); + button.title = title; + button.value = value; + button.innerHTML = `` + button.addEventListener("click", callback); + return button; + } } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index f6636c44..a7a7ec09 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -287,9 +287,15 @@ export class Unit extends Marker { /********************** Visibility *************************/ updateVisibility() { - this.setHidden(document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null || - document.body.getAttribute(`data-hide-${this.getMarkerCategory()}`) != null || - !this.getBaseData().alive) + var hidden = false; + const hiddenUnits = getUnitsManager().getHiddenTypes(); + if (this.getMissionData().flags.Human && hiddenUnits.includes("human")) + hidden = true; + else if (this.getBaseData().AI == false && hiddenUnits.includes("dcs")) + hidden = true; + else if (hiddenUnits.includes(this.getMarkerCategory())) + hidden = true; + this.setHidden(document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null || hidden || !this.getBaseData().alive); } setHidden(hidden: boolean) { @@ -739,7 +745,7 @@ export class GroundUnit extends Unit { getMarkerHTML() { var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; return `
-
+
${role?.substring(0, 1)?.toUpperCase() || ""}
@@ -750,7 +756,7 @@ export class GroundUnit extends Unit { getMarkerCategory() { // TODO this is very messy var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; - var markerCategory = (role === "SAM") ? "sam" : "other"; + var markerCategory = (role === "SAM") ? "groundunit-sam" : "groundunit-other"; return markerCategory; } } diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index cba8fb29..07964d7a 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -10,6 +10,7 @@ export class UnitsManager { #copiedUnits: Unit[]; #selectionEventDisabled: boolean = false; #pasteDisabled: boolean = false; + #hiddenTypes: string[] = []; constructor() { this.#units = {}; @@ -87,6 +88,23 @@ export class UnitsManager { }); } + setHiddenType(key: string, value: boolean) + { + if (value) + { + if (this.#hiddenTypes.includes(key)) + delete this.#hiddenTypes[this.#hiddenTypes.indexOf(key)]; + } + else + this.#hiddenTypes.push(key); + Object.values(this.getUnits()).forEach((unit: Unit) => unit.updateVisibility()); + } + + getHiddenTypes() + { + return this.#hiddenTypes; + } + selectUnit(ID: number, deselectAllUnits: boolean = true) { if (deselectAllUnits) this.getSelectedUnits().filter((unit: Unit) => unit.ID !== ID).forEach((unit: Unit) => unit.setSelected(false)); diff --git a/client/views/aic.ejs b/client/views/aic/aic.ejs similarity index 100% rename from client/views/aic.ejs rename to client/views/aic/aic.ejs diff --git a/client/views/atc.ejs b/client/views/atc/atc.ejs similarity index 80% rename from client/views/atc.ejs rename to client/views/atc/atc.ejs index 19046c70..65984a3a 100644 --- a/client/views/atc.ejs +++ b/client/views/atc/atc.ejs @@ -1,10 +1,10 @@ -<%- include('atc/board.ejs', { +<%- include('board.ejs', { "boardId": "strip-board-tower", "boardType": "tower", "headers": [ "Flight", "a. Alt", "alt", "a. Speed", "Speed" ] }) %> -<%- include('atc/board.ejs', { +<%- include('board.ejs', { "boardId": "strip-board-ground", "boardType": "ground", "headers": [ "Flight", "Status", "T/O Time", "TTG" ] diff --git a/client/views/unitdatatable.ejs b/client/views/atc/unitdatatable.ejs similarity index 100% rename from client/views/unitdatatable.ejs rename to client/views/atc/unitdatatable.ejs diff --git a/client/views/index.ejs b/client/views/index.ejs index fe565ac0..863572aa 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -2,47 +2,39 @@ Olympus client - - - + + + + - - - - +
- <%- include('aic.ejs') %> - <%- include('atc.ejs') %> - <%- include('contextmenus.ejs') %> - <%- include('unitcontrolpanel.ejs') %> - <%- include('unitinfopanel.ejs') %> - <%- include('mouseinfopanel.ejs') %> - <%- include('navbar.ejs') %> - <%- include('connectionstatuspanel.ejs') %> - <%- include('dialogs.ejs') %> - <%- include('unitdatatable.ejs') %> - <%- include('popups.ejs') %> - <%- include('hotgrouppanel.ejs') %> + <%- include('aic/aic.ejs') %> -
+ <%- include('atc/atc.ejs') %> + <%- include('atc/unitdatatable.ejs') %> + + <%- include('panels/unitcontrol.ejs') %> + <%- include('panels/unitinfo.ejs') %> + <%- include('panels/mouseinfo.ejs') %> + <%- include('panels/connectionstatus.ejs') %> + <%- include('panels/hotgroup.ejs') %> + <%- include('panels/navbar.ejs') %> + + <%- include('other/dialogs.ejs') %> + <%- include('other/popups.ejs') %> + <%- include('other/contextmenus.ejs') %> + +
- <% /* %> - <%- include('log.ejs') %> - <% */ %> - diff --git a/client/views/log.ejs b/client/views/log/log.ejs similarity index 100% rename from client/views/log.ejs rename to client/views/log/log.ejs diff --git a/client/views/contextmenus.ejs b/client/views/other/contextmenus.ejs similarity index 100% rename from client/views/contextmenus.ejs rename to client/views/other/contextmenus.ejs diff --git a/client/views/dialogs.ejs b/client/views/other/dialogs.ejs similarity index 100% rename from client/views/dialogs.ejs rename to client/views/other/dialogs.ejs diff --git a/client/views/popups.ejs b/client/views/other/popups.ejs similarity index 100% rename from client/views/popups.ejs rename to client/views/other/popups.ejs diff --git a/client/views/connectionstatuspanel.ejs b/client/views/panels/connectionstatus.ejs similarity index 100% rename from client/views/connectionstatuspanel.ejs rename to client/views/panels/connectionstatus.ejs diff --git a/client/views/hotgrouppanel.ejs b/client/views/panels/hotgroup.ejs similarity index 100% rename from client/views/hotgrouppanel.ejs rename to client/views/panels/hotgroup.ejs diff --git a/client/views/mouseinfopanel.ejs b/client/views/panels/mouseinfo.ejs similarity index 100% rename from client/views/mouseinfopanel.ejs rename to client/views/panels/mouseinfo.ejs diff --git a/client/views/navbar.ejs b/client/views/panels/navbar.ejs similarity index 65% rename from client/views/navbar.ejs rename to client/views/panels/navbar.ejs index 4c46723c..06aeeeb9 100644 --- a/client/views/navbar.ejs +++ b/client/views/panels/navbar.ejs @@ -30,26 +30,7 @@
- - - - - +
diff --git a/client/views/unitcontrolpanel.ejs b/client/views/panels/unitcontrol.ejs similarity index 100% rename from client/views/unitcontrolpanel.ejs rename to client/views/panels/unitcontrol.ejs diff --git a/client/views/unitinfopanel.ejs b/client/views/panels/unitinfo.ejs similarity index 100% rename from client/views/unitinfopanel.ejs rename to client/views/panels/unitinfo.ejs diff --git a/client/views/uikit.ejs b/client/views/uikit/uikit.ejs similarity index 100% rename from client/views/uikit.ejs rename to client/views/uikit/uikit.ejs