diff --git a/client/copy.bat b/client/copy.bat new file mode 100644 index 00000000..1acc220e --- /dev/null +++ b/client/copy.bat @@ -0,0 +1,3 @@ +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 diff --git a/client/demo.js b/client/demo.js index ac750b83..292e2f52 100644 --- a/client/demo.js +++ b/client/demo.js @@ -677,15 +677,18 @@ class DemoDataGenerator { var ret = {bullseyes: { "0": { latitude: 37.25, - longitude: -115.8 + longitude: -115.8, + coalition: "neutral" }, "1": { latitude: 37.25, - longitude: -115.75 + longitude: -115.75, + coalition: "red" }, "2": { latitude: 37.25, - longitude: -115.7 + longitude: -115.7, + coalition: "blue" } }}; ret.time = Date.now(); diff --git a/client/package-lock.json b/client/package-lock.json index b4457d1c..44990dba 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -25,6 +25,7 @@ "devDependencies": { "@babel/preset-env": "^7.21.4", "@iconfu/svg-inject": "^1.2.3", + "@tanem/svg-injector": "^10.1.55", "@types/gtag.js": "^0.0.12", "@types/node": "^18.16.1", "@types/sortablejs": "^1.15.0", @@ -1688,9 +1689,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "dev": true, "dependencies": { "regenerator-runtime": "^0.13.11" @@ -1831,6 +1832,17 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "node_modules/@tanem/svg-injector": { + "version": "10.1.55", + "resolved": "https://registry.npmjs.org/@tanem/svg-injector/-/svg-injector-10.1.55.tgz", + "integrity": "sha512-xh8ejdvjDaH1eddZC0CdI45eeid4BIU2ppjNEhiTiWMYcLGT19KWjbES/ttDS4mq9gIAQfXx57g5zimEVohqYA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.5", + "content-type": "^1.0.5", + "tslib": "^2.5.0" + } + }, "node_modules/@types/geojson": { "version": "7946.0.10", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", @@ -2824,9 +2836,9 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } @@ -5344,9 +5356,9 @@ } }, "node_modules/tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==", "dev": true }, "node_modules/tty-browserify": { @@ -6842,9 +6854,9 @@ "dev": true }, "@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "dev": true, "requires": { "regenerator-runtime": "^0.13.11" @@ -6960,6 +6972,17 @@ } } }, + "@tanem/svg-injector": { + "version": "10.1.55", + "resolved": "https://registry.npmjs.org/@tanem/svg-injector/-/svg-injector-10.1.55.tgz", + "integrity": "sha512-xh8ejdvjDaH1eddZC0CdI45eeid4BIU2ppjNEhiTiWMYcLGT19KWjbES/ttDS4mq9gIAQfXx57g5zimEVohqYA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.21.5", + "content-type": "^1.0.5", + "tslib": "^2.5.0" + } + }, "@types/geojson": { "version": "7946.0.10", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz", @@ -7768,9 +7791,9 @@ "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==" }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "convert-source-map": { "version": "1.1.3", @@ -9806,9 +9829,9 @@ } }, "tslib": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==", "dev": true }, "tty-browserify": { diff --git a/client/package.json b/client/package.json index 678d17e5..8fa54c31 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\\leaflet.css & copy .\\node_modules\\@iconfu\\svg-inject\\dist\\svg-inject.js .\\public\\javascripts\\svg-inject.js", + "copy": "copy.bat", "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 ]" }, @@ -16,17 +16,15 @@ "debug": "~2.6.9", "ejs": "^3.1.8", "express": "~4.16.1", - "geomag": "^1.0.0", "leaflet": "^1.9.3", "leaflet-control-mini-map": "^0.4.0", "leaflet.nauticscale": "^1.1.0", - "milsymbol": "^2.0.0", "morgan": "~1.9.1", "save": "^2.9.0" }, "devDependencies": { "@babel/preset-env": "^7.21.4", - "@iconfu/svg-inject": "^1.2.3", + "@tanem/svg-injector": "^10.1.55", "@types/gtag.js": "^0.0.12", "@types/node": "^18.16.1", "@types/sortablejs": "^1.15.0", diff --git a/client/prepare.bat b/client/prepare.bat new file mode 100644 index 00000000..f163f70e --- /dev/null +++ b/client/prepare.bat @@ -0,0 +1,2 @@ +copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css +copy .\\node_modules\\leaflet.nauticscale\\dist\\leaflet.nauticscale.js .\\public\\javascripts\\leaflet.nauticscale.js diff --git a/client/public/images/bullseye.png b/client/public/images/bullseye.png deleted file mode 100644 index f7024954..00000000 Binary files a/client/public/images/bullseye.png and /dev/null differ diff --git a/client/public/images/bullseye.xcf b/client/public/images/bullseye.xcf deleted file mode 100644 index 097722f6..00000000 Binary files a/client/public/images/bullseye.xcf and /dev/null differ diff --git a/client/public/images/bullseye0.png b/client/public/images/bullseye0.png deleted file mode 100644 index a51fe39f..00000000 Binary files a/client/public/images/bullseye0.png and /dev/null differ diff --git a/client/public/images/bullseye1.png b/client/public/images/bullseye1.png deleted file mode 100644 index 6d574bbd..00000000 Binary files a/client/public/images/bullseye1.png and /dev/null differ diff --git a/client/public/images/bullseye2.png b/client/public/images/bullseye2.png deleted file mode 100644 index 4e99f79e..00000000 Binary files a/client/public/images/bullseye2.png and /dev/null differ diff --git a/client/public/images/buttons/edit.svg b/client/public/images/buttons/edit.svg deleted file mode 100644 index 5b51c67c..00000000 --- a/client/public/images/buttons/edit.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/images/buttons/reorder.svg b/client/public/images/buttons/reorder.svg deleted file mode 100644 index 4ae9b04e..00000000 --- a/client/public/images/buttons/reorder.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - diff --git a/client/public/images/formations/azimuth.png b/client/public/images/formations/azimuth.png deleted file mode 100644 index 1264e20a..00000000 Binary files a/client/public/images/formations/azimuth.png and /dev/null differ diff --git a/client/public/images/formations/range.png b/client/public/images/formations/range.png deleted file mode 100644 index 9df137cb..00000000 Binary files a/client/public/images/formations/range.png and /dev/null differ diff --git a/client/public/images/formations/single.png b/client/public/images/formations/single.png deleted file mode 100644 index 2a92faee..00000000 Binary files a/client/public/images/formations/single.png and /dev/null differ diff --git a/client/public/images/icons/bullseye-solid.svg b/client/public/images/icons/bullseye-solid.svg deleted file mode 100644 index c6400970..00000000 --- a/client/public/images/icons/bullseye-solid.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/public/images/icons/formation.png b/client/public/images/icons/formation.png deleted file mode 100644 index b140e9f6..00000000 Binary files a/client/public/images/icons/formation.png and /dev/null differ diff --git a/client/public/images/icons/leader.png b/client/public/images/icons/leader.png deleted file mode 100644 index 8744386f..00000000 Binary files a/client/public/images/icons/leader.png and /dev/null differ diff --git a/client/public/images/icons/singleton.png b/client/public/images/icons/singleton.png deleted file mode 100644 index 06644361..00000000 Binary files a/client/public/images/icons/singleton.png and /dev/null differ diff --git a/client/public/images/icons/square-check-regular.svg b/client/public/images/icons/square-check-regular.svg deleted file mode 100644 index 4ce0ff82..00000000 --- a/client/public/images/icons/square-check-regular.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/public/images/icons/trash-can-regular.svg b/client/public/images/icons/trash-can-regular.svg deleted file mode 100644 index 011e1a5e..00000000 --- a/client/public/images/icons/trash-can-regular.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/public/javascripts/leaflet.nauticscale.js b/client/public/javascripts/leaflet.nauticscale.js new file mode 100644 index 00000000..ccf19ac3 --- /dev/null +++ b/client/public/javascripts/leaflet.nauticscale.js @@ -0,0 +1,38 @@ +L.Control.ScaleNautic = L.Control.Scale.extend({ + options: { + nautic: false + }, + + _addScales: function(options, className, container) { + L.Control.Scale.prototype._addScales.call(this, options, className, container); + L.setOptions(options); + if (this.options.nautic) { + this._nScale = L.DomUtil.create('div', className, container); + } + }, + + _updateScales: function (maxMeters) { + L.Control.Scale.prototype._updateScales.call(this, maxMeters); + if (this.options.nautic && maxMeters) { + this._updateNautic(maxMeters); + } + }, + + _updateNautic: function (maxMeters) { + var scale = this._nScale, + maxNauticalMiles = maxMeters / 1852, nauticalMiles; + + if(maxMeters >= 1852) { + nauticalMiles = L.Control.Scale.prototype._getRoundNum.call(this, maxNauticalMiles); + } else { + nauticalMiles = maxNauticalMiles > 0.1 ? Math.round(maxNauticalMiles * 10) / 10 : Math.round(maxNauticalMiles * 100) / 100; + } + + scale.style.width = Math.round(this.options.maxWidth * (nauticalMiles / maxNauticalMiles)) - 10 + 'px'; + scale.innerHTML = nauticalMiles + ' nm'; + } +}); + +L.control.scalenautic = function (options) { + return new L.Control.ScaleNautic(options); +}; diff --git a/client/public/javascripts/svg-inject.js b/client/public/javascripts/svg-inject.js deleted file mode 100644 index 4e74af07..00000000 --- a/client/public/javascripts/svg-inject.js +++ /dev/null @@ -1,697 +0,0 @@ -/** - * 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/layout/layout.css b/client/public/stylesheets/layout/layout.css index dfa23432..91926b4b 100644 --- a/client/public/stylesheets/layout/layout.css +++ b/client/public/stylesheets/layout/layout.css @@ -7,240 +7,69 @@ #primary-toolbar { align-items: center; - display:flex; - position: absolute; + display: flex; left: 10px; + position: absolute; top: 10px; z-index: 1000; } -#olympus-toolbar-summary { +#toolbar-summary { background-image: url("/images/icon-round.png"); background-position: 20px 22px; background-repeat: no-repeat; background-size: 45px 45px; display: flex; flex-direction: column; - text-indent: 60px; padding: 20px; + text-indent: 60px; } -dl.ol-data-grid { - align-items: center; - display: flex; - flex-direction: row; - flex-wrap: wrap; - margin: 0; - row-gap: 4px; -} - -dl.ol-data-grid dt { - width: 60%; -} - -dl.ol-data-grid dd { - width: 40%; -} - - -dl.ol-data-grid dt.icon { - text-indent: 10px; -} - -dl.ol-data-grid dt.icon::before { - content: url(/images/icons/speed.svg ); - display: inline-block; - filter: invert(100%); - width: 20px; - translate: -20px 2px; -} - - -dl.ol-data-grid dt.icon-speed::before { - content: url(/images/icons/speed.svg ); -} - - -dl.ol-data-grid dt.icon-altitude::before { - content: url(/images/icons/altitude.svg ); -} - -dl.ol-data-grid dd { - display: flex; - justify-content: flex-end; - margin-left: auto; -} - -.br-info::after { - content: attr(data-bearing) '\00B0 / ' attr(data-distance) attr(data-distance-units); -} - -.br-info[data-message]::after { - content: attr(data-message); -} - -.coordinates::after { - content: attr(data-dd) "\00b0 " attr(data-mm) "'" attr(data-ss) "." attr(data-sss) '"' attr(data-label); -} - -.ol-button-box { - column-gap: 6px; - display: flex; - flex-direction: row; - flex-wrap: wrap; - margin: 5px 0; - row-gap: 5px; -} - -.ol-button-box button { - background-repeat: no-repeat; - ; - border: 1px solid var(--accent-light-blue); - color: var(--accent-light-blue); -} - -.ol-dialog { - align-self: center; - background-color: var(--background-slate-blue); - color: white; - justify-self: center; +#connection-status-panel { + bottom: 20px; + font-size: 12px; position: absolute; + right: 10px; + width: 160px; z-index: 1000; } -.ol-panel.ol-dialog { - padding: 24px 30px; -} - -.ol-dialog-close { - cursor: pointer; - font-size: 16px; - font-weight: var(--font-weight-bolder); +#mouse-info-panel { + bottom: 60px; + display: flex; + flex-direction: column; + height: fit-content; position: absolute; - right: 20px; - top: 10px; -} - -.ol-dialog-close::before { - content: "\d7"; -} - -.ol-dialog-header { - border-bottom: 1px solid var(--background-grey); - padding-bottom: 10px; -} - -.ol-dialog-content { - margin:4px 0; -} - -.ol-dialog-footer { - border-top: 1px solid var(--background-grey); - padding-top: 15px; - display: flex; + right: 10px; row-gap: 10px; + width: 160px; + z-index: 1000; } -.ol-dialog.scrollable .ol-dialog-content { - overflow-y: auto; +#unit-control-panel { + height: fit-content; + left: 10px; + position: absolute; + top: 80px; + width: 240px; + z-index: 1000; } -.ol-checkbox label { - align-items: center; - cursor: pointer; - display: flex; - flex-wrap: nowrap; - white-space: nowrap; +#unit-info-panel { + bottom: 20px; + font-size: 12px; + left: 10px; + position: absolute; + width: fit-content; + z-index: 1000; } -.ol-checkbox input[type="checkbox"] { - appearance: none; - background-color: transparent; - border: none; - margin: 0; -} - -.ol-checkbox input[type="checkbox"]::before { - align-self: center; - background-image: url("/images/icons/square-regular.svg"); - background-repeat: no-repeat; - content: ""; - filter: invert(100%); - display: flex; - height: 16px; - margin-right: 10px; - width: 16px; -} - -.ol-checkbox input[type="checkbox"]:checked::before { - background-image: url("/images/icons/square-check-solid.svg"); -} - -.ol-text-input input { - height: 32px; - border-radius: 5px; - color: var(--background-offwhite); - background-color: var(--background-grey); - border: 1px solid var(--background-grey); - border-radius: var(--border-radius-sm); - text-align: center; - box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); -} - -input[type=number] { - -moz-appearance: textfield; - appearance: textfield; - margin: 0; -} - -input[type=number]::-webkit-inner-spin-button, -input[type=number]::-webkit-outer-spin-button { - -webkit-appearance: none; - margin: 0; -} - -[class|="ol-button"] { - align-items: center; - background-repeat: no-repeat; - display: flex; - font-weight: normal; - padding: 8px 10px; - white-space: nowrap; -} - -[class|="ol-button"]::before { - margin-right: 8px; -} - -.ol-button-close { - background: transparent; - border: 1px solid white; -} - -.ol-button-close::before { - content: "\d7"; -} - -.ol-button-apply { - background: transparent; - border: 1px solid white; -} - -.ol-button-apply::before { - content: "\2713"; -} - -.ol-button-settings { - background-color: var(--background-slate-blue); -} - -.ol-button-settings::before { - background-image: url("/images/icons/gears-solid.svg"); - background-position: 0 50%; - background-size: 24px 24px; - content: ""; - display: flex; - filter: invert(100%); - height: 24px; - width: 24px; +#info-popup { + position: absolute; + width: fit-content; + height: fit-content; + top: 100px; + left: 50%; + translate: -50% 0%; + z-index: 9999; } \ No newline at end of file diff --git a/client/public/stylesheets/markers/airbase.css b/client/public/stylesheets/markers/airbase.css index fac5497b..a9640cf3 100644 --- a/client/public/stylesheets/markers/airbase.css +++ b/client/public/stylesheets/markers/airbase.css @@ -1,9 +1,4 @@ -:root { - --airbase-marker-height: 40px; - --airbase-marker-width: 40px; -} - -[data-object|="airbase"] { +.airbase-icon { align-items: center; cursor: pointer; display: flex; @@ -11,35 +6,15 @@ position: relative; } -[data-hide-airbase] #map-container [data-object|="airbase"] { - display: none; +.airbase-icon[data-coalition="red"] svg * { + stroke: var(--unit-background-red); } -/****************************** -Marker -******************************/ - -[data-object|="airbase"] .airbase { - background-color: transparent; - background-repeat: no-repeat; - background-size: cover; - position: absolute; - transform-origin: center; - z-index: 3; +.airbase-icon[data-coalition="blue"] svg * { + stroke: var(--unit-background-blue); } -/* Airbase */ -[data-object|="airbase"] .airbase-marker { - background-image: var(--airbase-marker-neutral-url); - background-size: contain; - height: var(--airbase-marker-height); - width: var(--airbase-marker-width); +.airbase-icon[data-coalition="neutral"] svg * { + stroke: var(--unit-background-neutral); } -[data-object|="airbase"][data-coalition="red"] .airbase-marker { - background-image: var(--airbase-marker-red-url); -} - -[data-object|="airbase"][data-coalition="blue"] .airbase-marker { - background-image: var(--airbase-marker-blue-url); -} \ No newline at end of file diff --git a/client/public/stylesheets/markers/bullseye.css b/client/public/stylesheets/markers/bullseye.css new file mode 100644 index 00000000..81663b09 --- /dev/null +++ b/client/public/stylesheets/markers/bullseye.css @@ -0,0 +1,22 @@ +.bullseye-icon { + align-items: center; + cursor: pointer; + display: flex; + justify-content: center; + position: relative; +} + +.bullseye-icon[data-coalition="red"] svg * { + stroke: var(--unit-background-red); + fill: var(--unit-background-red); +} + +.bullseye-icon[data-coalition="blue"] svg * { + stroke: var(--unit-background-blue); + fill: var(--unit-background-blue); +} + +.bullseye-icon[data-coalition="neutral"] svg * { + stroke: var(--unit-background-neutral); + fill: var(--unit-background-neutral); +} diff --git a/client/public/stylesheets/markers/units.css b/client/public/stylesheets/markers/units.css index 121a1059..056a92d3 100644 --- a/client/public/stylesheets/markers/units.css +++ b/client/public/stylesheets/markers/units.css @@ -9,9 +9,9 @@ align-items: center; cursor: pointer; display: flex; + height: 100%; justify-content: center; position: relative; - height: 100%; width: 100%; } @@ -20,10 +20,10 @@ background: var(--secondary-gunmetal-grey); display: flex; justify-self: center; - transform-origin: bottom; - translate: 0 -50%; padding-bottom: calc((var(--unit-aircraft-width) / 2) + var(--unit-stroke-width)); position: absolute; + transform-origin: bottom; + translate: 0 -50%; width: var(--unit-aircraft-vvi-width); } @@ -49,41 +49,41 @@ translate: -1px 1px; } -.unit-marker { +.unit-icon { + height: var(--unit-height); position: absolute; transform-origin: center; - height: var(--unit-height); width: var(--unit-width); } -[data-is-selected] .unit-marker::before { - content: ""; - height: 100%; - width: 100%; +[data-is-selected] .unit-icon::before { background-color: var(--unit-spotlight-fill); border-radius: 50%; + content: ""; + height: 100%; position: absolute; + width: 100%; z-index: -1; } /*** Basic colours ***/ -[data-coalition="blue"] .unit-marker svg>*:first-child { +[data-coalition="blue"] .unit-icon svg>*:first-child { fill: var(--unit-background-blue); } -[data-coalition="red"] .unit-marker svg>*:first-child { +[data-coalition="red"] .unit-icon svg>*:first-child { fill: var(--unit-background-red); } -[data-coalition="neutral"] .unit-marker svg>*:first-child { +[data-coalition="neutral"] .unit-icon svg>*:first-child { fill: var(--unit-background-neutral); } -[data-is-selected] .unit-marker svg>*:first-child { +[data-is-selected] .unit-icon svg>*:first-child { fill: white; } -[data-is-highlighted] .unit-marker svg>*:first-child { +[data-is-highlighted] .unit-icon svg>*:first-child { stroke: white; } @@ -140,15 +140,15 @@ /*** Unit summary ***/ [data-object|="unit"] .unit-summary { - pointer-events: none; - column-gap: 6px; color: white; + column-gap: 6px; display: flex; flex-wrap: wrap; font-size: 11px; font-weight: bold; justify-content: right; line-height: 12px; + pointer-events: none; position: absolute; row-gap: 1px; text-shadow: @@ -247,41 +247,41 @@ /*** Unit state ***/ [data-object|="unit"] .unit-state { background-repeat: no-repeat; - position: absolute; height: 20px; + position: absolute; width: 20px; } [data-object|="unit"][data-state="rtb"] .unit-state { - background-image: url("/theme/images/states/rtb.svg"); + background-image: url("/resources/theme/images/states/rtb.svg"); } [data-object|="unit"][data-state="land"] .unit-state { - background-image: url("/theme/images/states/rtb.svg"); + background-image: url("/resources/theme/images/states/rtb.svg"); } [data-object|="unit"][data-state="idle"] .unit-state { - background-image: url("/theme/images/states/idle.svg"); + background-image: url("/resources/theme/images/states/idle.svg"); } [data-object|="unit"][data-state="attack"] .unit-state { - background-image: url("/theme/images/states/attack.svg"); + background-image: url("/resources/theme/images/states/attack.svg"); } [data-object|="unit"][data-state="follow"] .unit-state { - background-image: url("/theme/images/states/follow.svg"); + background-image: url("/resources/theme/images/states/follow.svg"); } [data-object|="unit"][data-state="refuel"] .unit-state { - background-image: url("/theme/images/states/refuel.svg"); + background-image: url("/resources/theme/images/states/refuel.svg"); } [data-object|="unit"][data-state="human"] .unit-state { - background-image: url("/theme/images/states/human.svg"); + background-image: url("/resources/theme/images/states/human.svg"); } [data-object|="unit"][data-state="dcs"] .unit-state { - background-image: url("/theme/images/states/dcs.svg"); + background-image: url("/resources/theme/images/states/dcs.svg"); } /*** Dead unit ***/ diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 413afbc1..d32ccc42 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -9,6 +9,7 @@ @import url("other/contextmenus.css"); @import url("other/popup.css"); @import url("markers/airbase.css"); +@import url("markers/bullseye.css"); @import url("markers/units.css"); * { @@ -88,8 +89,8 @@ form>div { .ol-scrollable::-webkit-scrollbar-track { background-color: transparent; - border-top-right-radius: 10px; border-bottom-right-radius: 10px; + border-top-right-radius: 10px; margin-top: 0px; } @@ -104,8 +105,8 @@ form>div { .ol-scrollable::-webkit-scrollbar-thumb { background-color: white; border-radius: 100px; - opacity: 0.8; margin-top: 10px; + opacity: 0.8; } .ol-panel { @@ -137,15 +138,15 @@ form>div { .ol-ellipsed { display: inline-block; - width: calc(100%); overflow: hidden; - text-overflow: ellipsis; text-align: left; + text-overflow: ellipsis; + width: calc(100%); } .ol-select { - position: relative; color: var(--nav-text); + position: relative; } .ol-select>.ol-select-value { @@ -154,10 +155,10 @@ form>div { cursor: pointer; display: flex; justify-content: left; + min-width: 0; text-align: center; white-space: nowrap; width: 100%; - min-width: 0; } .ol-select:not(.ol-select-image)>.ol-select-value { @@ -165,12 +166,12 @@ form>div { background-color: var(--background-grey); border-radius: var(--border-radius-sm); height: 32px; - padding-right: 30px; - padding-left: 20px; - - width: calc(100%); overflow: hidden; + padding-left: 20px; + padding-right: 30px; text-overflow: ellipsis; + + width: calc(100%); } .ol-select.narrow:not(.ol-select-image)>.ol-select-value { @@ -183,22 +184,22 @@ form>div { } .ol-select:not(.ol-select-image)>.ol-select-value:after { + content: url("/resources/theme/images/icons/chevron-down.svg"); position: absolute; - content: url("/themes/olympus/images/chevron-down.svg"); right: 10px; } .ol-select>.ol-select-options { border-radius: var(--border-radius-md); + max-height: 0; overflow: hidden; position: absolute; - max-height: 0; z-index: 1000; } .ol-select-options.scrollbar-visible { - border-top-right-radius: 0px !important; border-bottom-right-radius: 0px !important; + border-top-right-radius: 0px !important; } .ol-select.ol-select-image>.ol-select-options { @@ -207,11 +208,11 @@ form>div { .ol-select.is-open>.ol-select-options { max-height: 382px; + min-width: 100%; overflow: visible; overflow-y: auto; - min-width: 100%; - z-index: 9999; translate: 0px 5px; + z-index: 9999; } .ol-select.is-open[data-position="top"]>.ol-select-options { @@ -247,16 +248,16 @@ form>div { background-color: transparent; border: none; border-radius: 0; + border-radius: var(--border-radius-sm); color: white; display: block; font-size: 12px; font-weight: normal; padding: 6px 2px; + padding: 5px; text-align: left; white-space: nowrap; width: 100%; - padding: 5px; - border-radius: var(--border-radius-sm); } .ol-select>.ol-select-options>div a:hover, @@ -368,13 +369,13 @@ nav.ol-panel> :last-child { } .ol-panel .ol-group { + align-items: center; border-radius: var(--border-radius-sm); column-gap: 10px; display: flex; flex-direction: row; flex-wrap: nowrap; row-gap: 4px; - align-items: center; } .ol-group-header { @@ -405,7 +406,7 @@ nav.ol-panel> :last-child { } .ol-panel .ol-group-button-toggle button::before { - background-image: url("/images/icons/square-check-solid.svg"); + background-image: url("/resources/theme/images/icons/square-check-solid.svg"); background-repeat: no-repeat; content: ""; filter: invert(100%); @@ -415,7 +416,7 @@ nav.ol-panel> :last-child { } .ol-panel .ol-group-button-toggle button.off::before { - background-image: url("/images/icons/square-regular.svg"); + background-image: url("/resources/theme/images/icons/square-regular.svg"); } .highlight-primary { @@ -470,9 +471,9 @@ nav.ol-panel> :last-child { } .icon-small { - width: 20px; - padding: 2px; filter: invert(100%); + padding: 2px; + width: 20px; } .ol-data-grid { @@ -485,17 +486,17 @@ nav.ol-panel> :last-child { } .slider { - width: 100%; -webkit-appearance: none; appearance: none; - height: 2px; background: #d3d3d3; - outline: none; + height: 2px; + margin-bottom: 10px; + margin-top: 10px; opacity: 0.7; + outline: none; -webkit-transition: .2s; transition: opacity .2s; - margin-top: 10px; - margin-bottom: 10px; + width: 100%; } .slider:hover { @@ -505,11 +506,11 @@ nav.ol-panel> :last-child { .slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; - width: 20px; - height: 20px; background: gray; - cursor: pointer; border-radius: 999px; + cursor: pointer; + height: 20px; + width: 20px; } .active .slider::-webkit-slider-thumb { @@ -517,11 +518,11 @@ nav.ol-panel> :last-child { } .slider::-moz-range-thumb { - width: 20px; - height: 20px; background: gray; - cursor: pointer; border-radius: 999px; + cursor: pointer; + height: 20px; + width: 20px; } .active .slider::-moz-range-thumb { @@ -529,25 +530,25 @@ nav.ol-panel> :last-child { } .main-logo { - width: 40px; height: 40px; + width: 40px; } .ol-measure-box { - position: absolute; + background-color: var(--background-steel); + border-radius: 999px; + color: var(--primary-neutral); + font-size: 12px; + font-weight: var(--font-weight-bolder); + height: fit-content; + padding-bottom: 0.2em; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.2em; - padding-bottom: 0.2em; - background-color: var(--background-steel); - border-radius: 999px; - width: fit-content; - height: fit-content; + position: absolute; text-align: center; - color: var(--primary-neutral); - font-size: 12px; + width: fit-content; z-index: 2000; - font-weight: var(--font-weight-bolder); } .ol-sortable .handle { @@ -575,7 +576,7 @@ nav.ol-panel> :last-child { width: 28px; } -#unit-selection #unit-identification [data-object|="unit"] .unit-marker { +#unit-selection #unit-identification [data-object|="unit"] .unit-icon { background-size: 28px 28px; height: 28px; width: 28px; @@ -588,13 +589,13 @@ nav.ol-panel> :last-child { #unit-visibility-control button { border: none; height: 32px; - width: 32px; padding: 0px; + width: 32px; } #unit-visibility-control button svg { - pointer-events: none; height: 16px; + pointer-events: none; width: 16px; } @@ -633,31 +634,27 @@ nav.ol-panel> :last-child { #roe-buttons-container button, #reaction-to-threat-buttons-container button, #emissions-countermeasures-buttons-container button { - display: flex; + align-items: center; background-color: transparent; border: 1px solid var(--accent-light-blue); + display: flex; height: 30px; - width: 30px; - align-items: center; justify-content: center; + width: 30px; } -#roe-buttons-container button.selected, -#reaction-to-threat-buttons-container button.selected, -#emissions-countermeasures-buttons-container button.selected { +#unit-control-panel .ol-option-button button.selected { background-color: white; border-color: white; } -#roe-buttons-container button.selected svg .foreground, -#reaction-to-threat-buttons-container button.selected svg .foreground, -#emissions-countermeasures-buttons-container button.selected svg .foreground { +#unit-control-panel .ol-option-button button.selected svg * { fill: var(--background-steel); } /****************************************************************************************/ #splash-screen { - background-image: url("/images/splash/splash_pic_ship.png"); + background-image: url("/resources/theme/images/splash/1.png"); background-position: 100% 50%; background-size: 60%; border-radius: var(--border-radius-lg); @@ -707,9 +704,9 @@ nav.ol-panel> :last-child { #splash-content #app-summary>* { height: fit-content; line-height: 25px; + padding: 2px; white-space: nowrap; width: fit-content; - padding: 2px; } #splash-content .app-version { @@ -721,8 +718,8 @@ nav.ol-panel> :last-child { } #splash-content #legal-stuff p { + color: #FFF7; font-size: 10px; - color:#FFF7; width: 120%; } @@ -735,34 +732,34 @@ nav.ol-panel> :last-child { } #gray-out { - position: fixed; - height: 100%; - width: 100%; - left: 0px; - top: 0px; - z-index: 9999; background-color: #000A; + height: 100%; + left: 0px; + position: fixed; + top: 0px; + width: 100%; + z-index: 9999; } #authentication-form { - display: flex; align-items: end; column-gap: 10px; - margin: 10px 0px; + display: flex; flex-direction: row; + margin: 10px 0px; } #authentication-form>div { - display: flex; align-items: start; - row-gap: 4px; + display: flex; flex-direction: column; + row-gap: 4px; } #authentication-form>div>input { - height: 35px; - border-radius: var(--border-radius-sm); border: 0px solid transparent; + border-radius: var(--border-radius-sm); + height: 35px; width: 200px; } @@ -776,13 +773,13 @@ nav.ol-panel> :last-child { } #connection-status[data-status="connecting"]::before { - content: "Connecting..."; animation: blinker 1s linear infinite; + content: "Connecting..."; } #connection-status[data-status="failed"]::before { - content: "Incorrect username/password!"; color: var(--primary-red); + content: "Incorrect username/password!"; } @keyframes blinker { @@ -792,31 +789,31 @@ nav.ol-panel> :last-child { } #hotgroup-panel { - position: absolute; bottom: 40px; + column-gap: 10px; + display: flex; left: 50%; + position: absolute; translate: -50%; z-index: 9999; - display: flex; - column-gap: 10px; } #hotgroup-panel>div { - width: 50px; - height: 50px; + align-items: center; background-color: var(--background-steel); + border: 0px solid transparent; border-radius: var(--border-radius-sm); color: white; display: flex; - align-items: center; - justify-content: center; font-weight: bold; - border: 0px solid transparent; + height: 50px; + justify-content: center; + width: 50px; } #hotgroup-panel>div:hover { - cursor: pointer; border: 2px solid white; + cursor: pointer; } .hotgroup-selector>.unit-hotgroup { @@ -825,13 +822,230 @@ nav.ol-panel> :last-child { } .ol-destination-preview-icon { - width: 52px; - height: 52px; background-color: var(--secondary-yellow); - pointer-events: none; border-radius: 999px; + height: 52px; + pointer-events: none; + width: 52px; } .ol-destination-preview { pointer-events: none; +} + +dl.ol-data-grid { + align-items: center; + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: 0; + row-gap: 4px; +} + +dl.ol-data-grid dt { + width: 60%; +} + +dl.ol-data-grid dd { + width: 40%; +} + +dl.ol-data-grid dt.icon { + text-indent: 10px; +} + +dl.ol-data-grid dt.icon::before { + content: url("/resources/theme/images/icons/speed.svg"); + display: inline-block; + filter: invert(100%); + translate: -20px 2px; + width: 20px; +} + +dl.ol-data-grid dt.icon-speed::before { + content: url("/images/icons/speed.svg"); +} + +dl.ol-data-grid dt.icon-altitude::before { + content: url("/images/icons/altitude.svg"); +} + +dl.ol-data-grid dd { + display: flex; + justify-content: flex-end; + margin-left: auto; +} + +.br-info::after { + content: attr(data-bearing) '\00B0 / ' attr(data-distance) attr(data-distance-units); +} + +.br-info[data-message]::after { + content: attr(data-message); +} + +.coordinates::after { + content: attr(data-dd) "\00b0 " attr(data-mm) "'" attr(data-ss) "." attr(data-sss) '"' attr(data-label); +} + +.ol-button-box { + column-gap: 6px; + display: flex; + flex-direction: row; + flex-wrap: wrap; + margin: 5px 0; + row-gap: 5px; +} + +.ol-button-box button { + background-repeat: no-repeat; + ; + border: 1px solid var(--accent-light-blue); + color: var(--accent-light-blue); +} + +.ol-dialog { + align-self: center; + background-color: var(--background-slate-blue); + color: white; + justify-self: center; + position: absolute; + z-index: 1000; +} + +.ol-panel.ol-dialog { + padding: 24px 30px; +} + +.ol-dialog-close { + cursor: pointer; + font-size: 16px; + font-weight: var(--font-weight-bolder); + position: absolute; + right: 20px; + top: 10px; +} + +.ol-dialog-close::before { + content: "\d7"; +} + +.ol-dialog-header { + border-bottom: 1px solid var(--background-grey); + padding-bottom: 10px; +} + +.ol-dialog-content { + margin: 4px 0; +} + +.ol-dialog-footer { + border-top: 1px solid var(--background-grey); + display: flex; + padding-top: 15px; + row-gap: 10px; +} + +.ol-dialog.scrollable .ol-dialog-content { + overflow-y: auto; +} + +.ol-checkbox label { + align-items: center; + cursor: pointer; + display: flex; + flex-wrap: nowrap; + white-space: nowrap; +} + +.ol-checkbox input[type="checkbox"] { + appearance: none; + background-color: transparent; + border: none; + margin: 0; +} + +.ol-checkbox input[type="checkbox"]::before { + align-self: center; + background-image: url("/resources/theme/images/icons/square-regular.svg"); + background-repeat: no-repeat; + content: ""; + display: flex; + filter: invert(100%); + height: 16px; + margin-right: 10px; + width: 16px; +} + +.ol-checkbox input[type="checkbox"]:checked::before { + background-image: url("/resources/theme/images/icons/square-check-solid.svg"); +} + +.ol-text-input input { + background-color: var(--background-grey); + border: 1px solid var(--background-grey); + border-radius: 5px; + border-radius: var(--border-radius-sm); + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + color: var(--background-offwhite); + height: 32px; + text-align: center; +} + +input[type=number] { + -moz-appearance: textfield; + appearance: textfield; + margin: 0; +} + +input[type=number]::-webkit-inner-spin-button, +input[type=number]::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +[class|="ol-button"] { + align-items: center; + background-repeat: no-repeat; + display: flex; + font-weight: normal; + padding: 8px 10px; + white-space: nowrap; +} + +[class|="ol-button"]::before { + margin-right: 8px; +} + +.ol-button-close { + background: transparent; + border: 1px solid white; +} + +.ol-button-close::before { + content: "\d7"; +} + +.ol-button-apply { + background: transparent; + border: 1px solid white; +} + +.ol-button-apply::before { + content: "\2713"; +} + +.ol-button-settings { + background-color: var(--background-slate-blue); +} + +.ol-button-settings::before { + background-image: url("/resources/theme/images/icons/gears-solid.svg"); + background-position: 0 50%; + background-size: 24px 24px; + content: ""; + display: flex; + filter: invert(100%); + height: 24px; + width: 24px; } \ No newline at end of file diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css index 573bba4b..361a6a0b 100644 --- a/client/public/stylesheets/other/contextmenus.css +++ b/client/public/stylesheets/other/contextmenus.css @@ -17,23 +17,23 @@ } #active-coalition-label { - position: absolute; - top: -28px; border-radius: 999px; - width: fit-content; + color: var(--nav-text); + font-size: 12px; + font-weight: 600; height: fit-content; padding: 3px 10px; padding-bottom: 3px; - font-weight: 600; - color: var(--nav-text); - font-size: 12px; + position: absolute; + top: -28px; + width: fit-content; } #context-menu-switch { margin-right: 10px; } -#map-contextmenu>div:nth-child(2){ +#map-contextmenu>div:nth-child(2) { align-items: center; display: flex; flex-direction: row; @@ -41,22 +41,22 @@ padding-right: 0px; } -#map-contextmenu>ul{ +#map-contextmenu>ul { max-height: 200px; overflow-x: hidden; overflow-y: auto; } #map-contextmenu .ol-panel { - width: 100%; border-radius: var(--border-radius-sm); + width: 100%; } #map-contextmenu ul { margin: 0px; } -#map-contextmenu>div:nth-child(n+3){ +#map-contextmenu>div:nth-child(n+3) { align-items: center; display: flex; flex-direction: column; @@ -64,10 +64,10 @@ row-gap: 5px; } -#map-contextmenu .ol-select-container{ - width: 100%; - flex:0 0 auto; +#map-contextmenu .ol-select-container { align-self: stretch; + flex: 0 0 auto; + width: 100%; } #aircraft-spawn-menu .ol-select.is-open .ol-select-options { @@ -76,28 +76,28 @@ #aircraft-spawn-menu>button, #ground-unit-spawn-menu>button { - width: 100%; text-align: center; + width: 100%; } #aircraft-spawn-button { - background-image: var( --spawn-aircraft-url ); + background-image: url("/resources/theme/images/buttons/spawn/aircraft.svg"); background-size: 48px; } #ground-unit-spawn-button { - background-image: var( --spawn-groundunit-url ); + background-image: url("/resources/theme/images/buttons/spawn/ground.svg"); background-size: 48px; } #smoke-spawn-button { - background-image: var( --spawn-smoke-url ); + background-image: url("/resources/theme/images/buttons/spawn/smoke.svg"); background-size: 48px; } .unit-spawn-button { - border-radius: 0px; border: none; + border-radius: 0px; height: 48px; margin-bottom: -10px; margin-top: -10px; @@ -105,37 +105,34 @@ } .unit-spawn-button:last-of-type { - border-top-right-radius: var(--border-radius-sm); border-bottom-right-radius: var(--border-radius-sm); + border-top-right-radius: var(--border-radius-sm); } [data-active-coalition="blue"].toggle-fill, -[data-active-coalition="blue"].unit-spawn-button:hover, -[data-active-coalition="blue"].unit-spawn-button.is-open, +[data-active-coalition="blue"].unit-spawn-button:hover, +[data-active-coalition="blue"].unit-spawn-button.is-open, [data-active-coalition="blue"]#active-coalition-label, [data-active-coalition="blue"].deploy-unit-button, -[data-active-coalition="blue"]#spawn-airbase-aircraft-button -{ +[data-active-coalition="blue"]#spawn-airbase-aircraft-button { background-color: var(--primary-blue) } [data-active-coalition="red"].toggle-fill, -[data-active-coalition="red"].unit-spawn-button:hover, -[data-active-coalition="red"].unit-spawn-button.is-open, +[data-active-coalition="red"].unit-spawn-button:hover, +[data-active-coalition="red"].unit-spawn-button.is-open, [data-active-coalition="red"]#active-coalition-label, [data-active-coalition="red"].deploy-unit-button, -[data-active-coalition="red"]#spawn-airbase-aircraft-button -{ +[data-active-coalition="red"]#spawn-airbase-aircraft-button { background-color: var(--primary-red) } [data-active-coalition="neutral"].toggle-fill, -[data-active-coalition="neutral"].unit-spawn-button:hover, -[data-active-coalition="neutral"].unit-spawn-button.is-open, +[data-active-coalition="neutral"].unit-spawn-button:hover, +[data-active-coalition="neutral"].unit-spawn-button.is-open, [data-active-coalition="neutral"]#active-coalition-label, [data-active-coalition="neutral"].deploy-unit-button, -[data-active-coalition="neutral"]#spawn-airbase-aircraft-button -{ +[data-active-coalition="neutral"]#spawn-airbase-aircraft-button { background-color: var(--primary-neutral) } @@ -144,11 +141,13 @@ border: 1px solid var(--primary-blue); cursor: default; } + [data-active-coalition="red"].deploy-unit-button:disabled { background-color: transparent; border: 1px solid var(--primary-red); cursor: default; } + [data-active-coalition="neutral"].deploy-unit-button:disabled { background-color: transparent; border: 1px solid var(--primary-neutral); @@ -158,9 +157,11 @@ [data-active-coalition="blue"].toggle-fill::after { transform: translateX(0); } + [data-active-coalition="red"].toggle-fill::after { transform: translateX(var(--height)); } + [data-active-coalition="neutral"].toggle-fill::after { transform: translateX(calc(var(--height) / 2)); } @@ -168,182 +169,198 @@ [data-active-coalition="blue"]#active-coalition-label::after { content: "Create blue unit"; } + [data-active-coalition="red"]#active-coalition-label::after { content: "Create red unit"; } + [data-active-coalition="neutral"]#active-coalition-label::after { content: "Create neutral unit"; } #loadout-preview { - display: flex; - flex-direction: row; align-content: space-between; align-items: center; - width: 100%; column-gap: 20px; + display: flex; + flex-direction: row; + width: 100%; } #loadout-list { + align-content: center; display: flex; flex-direction: column; - align-content: center; height: 100%; } #unit-image { - width: 100px; - height: 100px; filter: invert(100%); - margin-top: 10px; + height: 100px; margin-bottom: 10px; + margin-top: 10px; + width: 100px; } #smoke-spawn-menu { + align-items: center; display: flex; flex-direction: column; - align-items: center; text-align: center; } #smoke-spawn-menu>button { - width: 100%; - text-align: left; - display: flex; - flex-wrap: wrap; align-items: center; column-gap: 10px; + display: flex; + flex-wrap: wrap; + text-align: left; + width: 100%; } #smoke-spawn-menu>button::before { - display: block; - width: 10px; - height: 10px; border-radius: 999px; content: ""; + display: block; + height: 10px; + width: 10px; } -[data-smoke-color="red"]::before{ background-color: red; } -[data-smoke-color="white"]::before{ background-color: white; } -[data-smoke-color="blue"]::before{ background-color: blue; } -[data-smoke-color="green"]::before{ background-color: green; } -[data-smoke-color="orange"]::before{ background-color: orange; } +[data-smoke-color="red"]::before { + background-color: red; +} + +[data-smoke-color="white"]::before { + background-color: white; +} + +[data-smoke-color="blue"]::before { + background-color: blue; +} + +[data-smoke-color="green"]::before { + background-color: green; +} + +[data-smoke-color="orange"]::before { + background-color: orange; +} /* Unit context menu */ #unit-contextmenu { display: flex; flex-direction: column; height: fit-content; - width: fit-content; + padding: 15px; position: absolute; row-gap: 5px; + width: fit-content; z-index: 1000; - padding: 15px; } #unit-contextmenu button { border: 1px solid var(--background-offwhite); border-radius: var(--border-radius-sm); - padding: 12px; font-weight: normal; + padding: 12px; } #unit-contextmenu div { + align-content: center; display: flex; flex-direction: row; - align-content: center; } #unit-contextmenu div:before { - display: inline-block; - filter: invert(100%); - height: 16px; - margin-right: 15px; - width: 16px; + display: inline-block; + filter: invert(100%); + height: 16px; + margin-right: 15px; + width: 16px; } #center-map::before { - content: url( /images/icons/arrows-to-eye-solid.svg ); + content: url("/resources/theme/images/icons/arrows-to-eye-solid.svg"); } #refuel::before { - content: url( /images/icons/fuel.svg ); + content: url("/resources/theme/images/icons/fuel.svg"); } #attack::before { - content: url( /images/icons/sword.svg ); + content: url("/resources/theme/images/icons/sword.svg"); } #follow::before { - content: url( /images/icons/follow.svg ); + content: url("/resources/theme/images/icons/follow.svg"); } #trail::before { - content: url( /images/icons/trail.svg ); + content: url("/resources/theme/images/icons/trail.svg"); } #echelon-lh::before { - content: url( /images/icons/echelon-lh.svg ); + content: url("/resources/theme/images/icons/echelon-lh.svg"); } #echelon-rh::before { - content: url( /images/icons/echelon-rh.svg ); + content: url("/resources/theme/images/icons/echelon-rh.svg"); } #line-abreast::before { - content: url( /images/icons/line-abreast.svg ); + content: url("/resources/theme/images/icons/line-abreast.svg"); } #front::before { - content: url( /images/icons/front.svg ); + content: url("/resources/theme/images/icons/front.svg"); } #custom::before { - content: url( /images/icons/custom.svg ); + content: url("/resources/theme/images/icons/custom.svg"); } #custom-formation-dialog { - width: 250px; + width: 250px; } -#custom-formation-dialog > .ol-dialog-content { - margin-top: 10px; - margin-bottom: 10px; - display: flex; - flex-direction: column; - flex-wrap: nowrap; - row-gap: 10px; +#custom-formation-dialog>.ol-dialog-content { align-items: center; + display: flex; + flex-direction: column; + flex-wrap: nowrap; + margin-bottom: 10px; + margin-top: 10px; + row-gap: 10px; } -#custom-formation-dialog > .ol-dialog-content > .ol-group { +#custom-formation-dialog>.ol-dialog-content>.ol-group { + justify-content: space-between; width: 100%; - justify-content: space-between; } #reference-system { - content: url( /images/reference-system.svg ); + content: url("/images/reference-system.svg"); display: inline-block; - filter: invert(100%); - width: 50px; - transform: translate(-50%, -50%); + filter: invert(100%); position: absolute; + transform: translate(-50%, -50%); + width: 50px; } .formation-position-clock { - position: relative; height: 100px; - width: 100px; margin: 15px; + position: relative; + width: 100px; } -.formation-position-clock > .clock-hand { - transform: translate(-50%, -50%); - display: flex; - position: absolute; - align-items: center; - justify-content: center; - height: 20px; +.formation-position-clock>.clock-hand { + align-items: center; + display: flex; + height: 20px; + justify-content: center; + position: absolute; + transform: translate(-50%, -50%); width: 20px; } @@ -362,9 +379,9 @@ --width: 40px; --height: calc(var(--width) / 2); --border-radius: calc(var(--height) / 2); + cursor: pointer; display: inline-block; - cursor: pointer; } .toggle-input { @@ -372,23 +389,22 @@ } .toggle-fill { - position: relative; - width: var(--width); - height: var(--height); border-radius: var(--border-radius); + height: var(--height); + position: relative; transition: background-color 0.2s; + width: var(--width); } .toggle-fill::after { + background-color: #ffffff; + border-radius: var(--border-radius); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.25); content: ""; + height: calc(var(--height) - 4px); + left: 2; position: absolute; top: 2; - left: 2; - height: calc(var(--height) - 4px); - width: calc(var(--height) - 4px); - background-color: #ffffff; - box-shadow: 0 0 10px rgba(0, 0, 0, 0.25); - border-radius: var(--border-radius); transition: transform 0.2s; -} - + width: calc(var(--height) - 4px); +} \ No newline at end of file diff --git a/client/public/stylesheets/other/popup.css b/client/public/stylesheets/other/popup.css index fb514460..521e4cde 100644 --- a/client/public/stylesheets/other/popup.css +++ b/client/public/stylesheets/other/popup.css @@ -1,13 +1,3 @@ -#info-popup { - position: absolute; - width: fit-content; - height: fit-content; - top: 100px; - left: 50%; - translate: -50% 0%; - z-index: 9999; -} - .ol-popup > div { padding-left: 15px; padding-right: 15px; diff --git a/client/public/stylesheets/panels/connectionstatus.css b/client/public/stylesheets/panels/connectionstatus.css index 362657cf..2b7dcec4 100644 --- a/client/public/stylesheets/panels/connectionstatus.css +++ b/client/public/stylesheets/panels/connectionstatus.css @@ -1,12 +1,3 @@ -#connection-status-panel { - bottom: 20px; - font-size:12px; - position: absolute; - right: 10px; - width: 160px; - z-index: 1000; -} - #connection-status-panel dt::before { content: "No connection"; } @@ -15,15 +6,14 @@ border-radius: 50%; background: red; content: " "; - height:12px; - width:12px; + height: 12px; + width: 12px; } - #connection-status-panel[data-is-connected] dt::before { content: "Connected"; } #connection-status-panel[data-is-connected] dd::after { - background:var( --accent-green ); + background: var(--accent-green); } \ No newline at end of file diff --git a/client/public/stylesheets/panels/mouseinfo.css b/client/public/stylesheets/panels/mouseinfo.css index fe8030a3..cfbb70c6 100644 --- a/client/public/stylesheets/panels/mouseinfo.css +++ b/client/public/stylesheets/panels/mouseinfo.css @@ -1,91 +1,75 @@ -#mouse-info-panel { - bottom: 60px; - display:flex; - flex-direction: column; - height: fit-content; - position: absolute; - right: 10px; - row-gap: 10px; - width: 160px; - z-index: 1000; +#mouse-info-panel>* { + background-color: var(--background-grey); + border-radius: var(--border-radius-sm); + padding: 6px; } - -#mouse-info-panel > * { - background-color: var( --background-grey ); - border-radius: var( --border-radius-sm ); - padding:6px; -} - - #mouse-info-panel dl { - margin-bottom:4px; + margin-bottom: 4px; row-gap: 8px; } #mouse-info-panel dt { - height:20px; - width:30%; + height: 20px; + width: 30%; } #mouse-info-panel dt::after { align-items: center; background-color: white; - border-radius: var( --border-radius-sm ); - color: var( --background-steel ); - display:flex; - font-size:15.6px; + border-radius: var(--border-radius-sm); + color: var(--background-steel); + display: flex; + font-size: 15.6px; font-weight: bolder; - height:16px; + height: 16px; justify-content: center; line-height: 16px; - padding:4px; + padding: 4px; text-transform: uppercase; - width:16px; + width: 16px; } #mouse-info-panel dt#ref-unit-position::after { - background-image: url( "/images/icons/ruler.svg" ); + background-image: url("/resources/theme/images/icons/ruler.svg"); background-position: 50% 50%; background-repeat: no-repeat; - background-size:16px 16px; + background-size: 16px 16px; content: ""; } #mouse-info-panel dt#ref-measure-position::after { - background-image: url( "/images/pin.png" ); + background-image: url("/resources/theme/images/pin.png"); background-position: 50% 50%; background-repeat: no-repeat; - background-size:16px 16px; + background-size: 16px 16px; content: ""; } - #mouse-info-panel dt[data-label]::after { - content: attr( data-label ); + content: attr(data-label); } #mouse-info-panel dt[data-coalition="blue"]::after { - background-color: var( --primary-blue ); + background-color: var(--primary-blue); } #mouse-info-panel dt[data-coalition="red"]::after { - background-color: var( --primary-red ); + background-color: var(--primary-red); } - #mouse-info-panel dt[data-tooltip]:hover::before { - background-color: var( --background-grey ); + background-color: var(--background-grey); border-radius: 5px; - content: attr( data-tooltip ); - display:flex; + content: attr(data-tooltip); + display: flex; flex-wrap: nowrap; padding: 5px; position: absolute; - translate: calc( -100% - 15px ) 0; + translate: calc(-100% - 15px) 0; white-space: nowrap; } #mouse-info-panel dd { - width:70%; + width: 70%; } \ No newline at end of file diff --git a/client/public/stylesheets/panels/unitcontrol.css b/client/public/stylesheets/panels/unitcontrol.css index cb314504..521ec15f 100644 --- a/client/public/stylesheets/panels/unitcontrol.css +++ b/client/public/stylesheets/panels/unitcontrol.css @@ -2,16 +2,6 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { display: block !important; } - -#unit-control-panel { - height: fit-content; - left: 10px; - position: absolute; - top: 80px; - width: 240px; - z-index: 1000; -} - #unit-control-panel h3 { margin-bottom: 8px; } @@ -33,31 +23,31 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { display: flex; font-size: 11px; height: 32px; + margin-right: 5px; padding: 8px 0; position: relative; width: calc(100% - 5px); - margin-right: 5px; } #unit-control-panel #selected-units-container button::before { - background-color: var( --primary-neutral ); + background-color: var(--primary-neutral); border-radius: 999px; content: attr(data-short-label); + font-size: 10px; margin: 2px 4px; + max-width: 30px; + min-width: 20px; + overflow: hidden; padding: 4px 6px; + text-overflow: ellipsis; white-space: nowrap; width: fit-content; - min-width: 20px; - max-width: 30px; - text-overflow: ellipsis; - overflow: hidden; - font-size: 10px; } #unit-control-panel #selected-units-container button:hover::before { + background-color: black; max-width: 100%; text-overflow: unset; - background-color: black; } #unit-control-panel #selected-units-container button[data-coalition="blue"]::before { @@ -120,13 +110,12 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { display: none; } - #advanced-settings-dialog>.ol-dialog-content { - margin-top: 10px; - margin-bottom: 10px; display: flex; flex-direction: column; flex-wrap: nowrap; + margin-bottom: 10px; + margin-top: 10px; row-gap: 10px; } @@ -146,8 +135,8 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { } #advanced-settings-dialog hr { - margin-top: 15px; margin-bottom: 10px; + margin-top: 15px; } #general-settings-grid { diff --git a/client/public/stylesheets/panels/unitinfo.css b/client/public/stylesheets/panels/unitinfo.css index 1fa1e3ee..8e20e07d 100644 --- a/client/public/stylesheets/panels/unitinfo.css +++ b/client/public/stylesheets/panels/unitinfo.css @@ -1,119 +1,106 @@ - -#unit-info-panel { - bottom: 20px; - font-size:12px; - position: absolute; - left: 10px; - width: fit-content; - z-index: 1000; -} - - #unit-info-panel #unit-name { - padding:0px 0; - margin-bottom:4px; + padding: 0px 0; + margin-bottom: 4px; } #unit-info-panel #current-task { - border-radius: var( --border-radius-lg ); - margin-top:8px; - padding:6px 16px; + border-radius: var(--border-radius-lg); + margin-top: 8px; + padding: 6px 16px; } #unit-info-panel #current-task::after { - content: attr( data-current-task ); - display:block; + content: attr(data-current-task); + display: block; } - #loadout { - display:flex; + display: flex; overflow: visible; } #loadout-silhouette { align-items: center; - display:flex; + display: flex; justify-content: center; - width:100px; + width: 100px; } #loadout-silhouette::before { - background-image: var( --loadout-background-image ); + 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; + background-size: 75px 75px; + content: ""; + display: block; + filter: invert(100%); + height: 75px; + translate: -10px 0; + width: 75px; } #loadout-items { align-self: center; - display:flex; + display: flex; flex-flow: column nowrap; row-gap: 8px; } -#loadout-items > * { +#loadout-items>* { align-items: center; column-gap: 8px; - display:flex; + display: flex; justify-content: flex-end; white-space: nowrap; } -#loadout-items > *::before { +#loadout-items>*::before { align-items: center; - background-color: var( --secondary-light-grey ); - border-radius: var( --border-radius-sm ); - content: attr( data-qty ); - display:flex; - font-weight: var( --font-weight-bolder ); - padding:1px 4px; + background-color: var(--secondary-light-grey); + border-radius: var(--border-radius-sm); + content: attr(data-qty); + display: flex; + font-weight: var(--font-weight-bolder); + padding: 1px 4px; } -#loadout-items > *::after { - content: attr( data-item ); +#loadout-items>*::after { + content: attr(data-item); overflow: hidden; - position:relative; + position: relative; text-overflow: ellipsis; - width:80px; + width: 80px; } #fuel-percentage { align-items: center; - display:flex; + display: flex; } #fuel-percentage::before { - content: url( /images/icons/fuel.svg ); - display:inline-block; - filter:invert(100%); - height:16px; - margin-right:6px; - width:16px; + content: url("/resources/theme/images/icons/fuel.svg"); + display: inline-block; + filter: invert(100%); + height: 16px; + margin-right: 6px; + width: 16px; } #fuel-percentage::after { - content: attr( data-percentage ) "%"; + content: attr(data-percentage) "%"; } #fuel-display { - background-color: var( --background-grey ); - border-radius: var( --border-radius-md ); - height:6px; - margin-top:4px; + background-color: var(--background-grey); + border-radius: var(--border-radius-md); + height: 6px; + margin-top: 4px; overflow: hidden; } #fuel-display #fuel-bar { - border-radius: var( --border-radius-md ); - height:100%; -} - + border-radius: var(--border-radius-md); + height: 100%; +} \ No newline at end of file diff --git a/client/public/themes/olympus/images/actions/180.svg b/client/public/themes/olympus/images/actions/180.svg deleted file mode 100644 index 2db2cb63..00000000 --- a/client/public/themes/olympus/images/actions/180.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/client/public/themes/olympus/images/actions/cancel.svg b/client/public/themes/olympus/images/actions/cancel.svg deleted file mode 100644 index dc3335a8..00000000 --- a/client/public/themes/olympus/images/actions/cancel.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/actions/gas.svg b/client/public/themes/olympus/images/actions/gas.svg deleted file mode 100644 index 14bbef19..00000000 --- a/client/public/themes/olympus/images/actions/gas.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/actions/nothing.svg b/client/public/themes/olympus/images/actions/nothing.svg deleted file mode 100644 index 1ea95812..00000000 --- a/client/public/themes/olympus/images/actions/nothing.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/actions/rtb.svg b/client/public/themes/olympus/images/actions/rtb.svg deleted file mode 100644 index 9147bcc5..00000000 --- a/client/public/themes/olympus/images/actions/rtb.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/actions/search.svg b/client/public/themes/olympus/images/actions/search.svg deleted file mode 100644 index 2ca59cbb..00000000 --- a/client/public/themes/olympus/images/actions/search.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/buttons/emissions/attack.svg b/client/public/themes/olympus/images/buttons/emissions/attack.svg new file mode 100644 index 00000000..0846e4e6 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/emissions/attack.svg @@ -0,0 +1,44 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/emissions/defend.svg b/client/public/themes/olympus/images/buttons/emissions/defend.svg new file mode 100644 index 00000000..5c884188 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/emissions/defend.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/emissions/free.svg b/client/public/themes/olympus/images/buttons/emissions/free.svg new file mode 100644 index 00000000..f07b2db6 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/emissions/free.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/emissions/silent.svg b/client/public/themes/olympus/images/buttons/emissions/silent.svg new file mode 100644 index 00000000..f735bc21 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/emissions/silent.svg @@ -0,0 +1,43 @@ + + + + + + diff --git a/client/public/themes/olympus/images/buttons/other/spawn_aircraft.svg b/client/public/themes/olympus/images/buttons/spawn/aircraft.svg similarity index 100% rename from client/public/themes/olympus/images/buttons/other/spawn_aircraft.svg rename to client/public/themes/olympus/images/buttons/spawn/aircraft.svg diff --git a/client/public/themes/olympus/images/buttons/other/spawn_ground.svg b/client/public/themes/olympus/images/buttons/spawn/ground.svg similarity index 100% rename from client/public/themes/olympus/images/buttons/other/spawn_ground.svg rename to client/public/themes/olympus/images/buttons/spawn/ground.svg diff --git a/client/public/themes/olympus/images/buttons/other/spawn_smoke.svg b/client/public/themes/olympus/images/buttons/spawn/smoke.svg similarity index 100% rename from client/public/themes/olympus/images/buttons/other/spawn_smoke.svg rename to client/public/themes/olympus/images/buttons/spawn/smoke.svg diff --git a/client/public/themes/olympus/images/buttons/threat/evade.svg b/client/public/themes/olympus/images/buttons/threat/evade.svg index 687f852e..54d7b49f 100644 --- a/client/public/themes/olympus/images/buttons/threat/evade.svg +++ b/client/public/themes/olympus/images/buttons/threat/evade.svg @@ -45,7 +45,8 @@ stroke="#5ca7ff" stroke-linecap="round" stroke-dasharray="2, 2" - id="path4" /> + id="path4" + style="fill:none" /> + + + + + + + diff --git a/client/public/images/marker-icon.png b/client/public/themes/olympus/images/markers/marker-icon.png similarity index 100% rename from client/public/images/marker-icon.png rename to client/public/themes/olympus/images/markers/marker-icon.png diff --git a/client/public/images/marker-shadow.png b/client/public/themes/olympus/images/markers/marker-shadow.png similarity index 100% rename from client/public/images/marker-shadow.png rename to client/public/themes/olympus/images/markers/marker-shadow.png diff --git a/client/public/images/icon-temporary.png b/client/public/themes/olympus/images/markers/temporary-icon.png similarity index 100% rename from client/public/images/icon-temporary.png rename to client/public/themes/olympus/images/markers/temporary-icon.png diff --git a/client/public/themes/olympus/images/other/icons_misc_brush_blue.svg b/client/public/themes/olympus/images/other/icons_misc_brush_blue.svg deleted file mode 100644 index 7ecf9816..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_brush_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_brush_dark.svg b/client/public/themes/olympus/images/other/icons_misc_brush_dark.svg deleted file mode 100644 index ed925a5a..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_brush_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_brush_light.svg b/client/public/themes/olympus/images/other/icons_misc_brush_light.svg deleted file mode 100644 index 74d2a691..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_brush_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_gas_blue.svg b/client/public/themes/olympus/images/other/icons_misc_gas_blue.svg deleted file mode 100644 index 13afd5e5..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_gas_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_gas_dark.svg b/client/public/themes/olympus/images/other/icons_misc_gas_dark.svg deleted file mode 100644 index 53bb4a3f..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_gas_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_gas_light.svg b/client/public/themes/olympus/images/other/icons_misc_gas_light.svg deleted file mode 100644 index 8530905f..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_gas_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_map_blue.svg b/client/public/themes/olympus/images/other/icons_misc_map_blue.svg deleted file mode 100644 index 3e233196..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_map_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_map_dark.svg b/client/public/themes/olympus/images/other/icons_misc_map_dark.svg deleted file mode 100644 index 6b9a5219..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_map_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_map_light.svg b/client/public/themes/olympus/images/other/icons_misc_map_light.svg deleted file mode 100644 index 8bec4777..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_map_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_plane_blue.svg b/client/public/themes/olympus/images/other/icons_misc_plane_blue.svg deleted file mode 100644 index 7bf3bcd8..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_plane_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_plane_dark.svg b/client/public/themes/olympus/images/other/icons_misc_plane_dark.svg deleted file mode 100644 index cec512c5..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_plane_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_plane_light.svg b/client/public/themes/olympus/images/other/icons_misc_plane_light.svg deleted file mode 100644 index cf34b9c8..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_plane_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_settings_blue.svg b/client/public/themes/olympus/images/other/icons_misc_settings_blue.svg deleted file mode 100644 index 2d7004c6..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_settings_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_settings_dark.svg b/client/public/themes/olympus/images/other/icons_misc_settings_dark.svg deleted file mode 100644 index 04b375e4..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_settings_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_settings_light.svg b/client/public/themes/olympus/images/other/icons_misc_settings_light.svg deleted file mode 100644 index 19270b5c..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_settings_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_visible_blue.svg b/client/public/themes/olympus/images/other/icons_misc_visible_blue.svg deleted file mode 100644 index 66f1ae9a..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_visible_blue.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_visible_dark.svg b/client/public/themes/olympus/images/other/icons_misc_visible_dark.svg deleted file mode 100644 index 0023c2c1..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_visible_dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/themes/olympus/images/other/icons_misc_visible_light.svg b/client/public/themes/olympus/images/other/icons_misc_visible_light.svg deleted file mode 100644 index 46f1b080..00000000 --- a/client/public/themes/olympus/images/other/icons_misc_visible_light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/client/public/images/splash/splash_pic_ship.png b/client/public/themes/olympus/images/splash/1.png similarity index 100% rename from client/public/images/splash/splash_pic_ship.png rename to client/public/themes/olympus/images/splash/1.png diff --git a/client/src/@types/server.d.ts b/client/src/@types/server.d.ts index e46d5f8a..e1a9c3e9 100644 --- a/client/src/@types/server.d.ts +++ b/client/src/@types/server.d.ts @@ -8,7 +8,7 @@ interface AirbasesData { } interface BullseyesData { - bullseyes: {[key: string]: any}, + bullseyes: {[key: string]: {latitude: number, longitude: number, coalition: string}}, } interface LogData { diff --git a/client/src/controls/airbasecontextmenu.ts b/client/src/controls/airbasecontextmenu.ts index d94f17db..bb5e7f29 100644 --- a/client/src/controls/airbasecontextmenu.ts +++ b/client/src/controls/airbasecontextmenu.ts @@ -5,8 +5,7 @@ import { ContextMenu } from "./contextmenu"; export class AirbaseContextMenu extends ContextMenu { #airbase: Airbase | null = null; - constructor(id: string) - { + constructor(id: string) { super(id); document.addEventListener("contextMenuSpawnAirbase", (e: any) => { this.showSpawnMenu(); @@ -19,8 +18,7 @@ export class AirbaseContextMenu extends ContextMenu { }) } - setAirbase(airbase: Airbase) - { + setAirbase(airbase: Airbase) { this.#airbase = airbase; this.setName(airbase.getName()); this.setProperties(airbase.getProperties()); @@ -29,24 +27,21 @@ export class AirbaseContextMenu extends ContextMenu { this.enableLandButton(getUnitsManager().getSelectedUnitsType() === "Aircraft" && (getUnitsManager().getSelectedUnitsCoalition() === airbase.getCoalition() || airbase.getCoalition() === "neutral")) } - setName(airbaseName: string) - { + setName(airbaseName: string) { var nameDiv = this.getContainer()?.querySelector("#airbase-name"); if (nameDiv != null) - nameDiv.innerText = airbaseName; + nameDiv.innerText = airbaseName; } - setProperties(airbaseProperties: string[]) - { + setProperties(airbaseProperties: string[]) { this.getContainer()?.querySelector("#airbase-properties")?.replaceChildren(...airbaseProperties.map((property: string) => { var div = document.createElement("div"); div.innerText = property; return div; - }), ); + }),); } - setParkings(airbaseParkings: string[]) - { + setParkings(airbaseParkings: string[]) { this.getContainer()?.querySelector("#airbase-parking")?.replaceChildren(...airbaseParkings.map((parking: string) => { var div = document.createElement("div"); div.innerText = parking; @@ -54,22 +49,18 @@ export class AirbaseContextMenu extends ContextMenu { })); } - setCoalition(coalition: string) - { + setCoalition(coalition: string) { (this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")).dataset.activeCoalition = coalition; } - enableLandButton(enableLandButton: boolean) - { + enableLandButton(enableLandButton: boolean) { this.getContainer()?.querySelector("#land-here-button")?.classList.toggle("hide", !enableLandButton); } - showSpawnMenu() - { - if (this.#airbase != null) - { + showSpawnMenu() { + if (this.#airbase != null) { setActiveCoalition(this.#airbase.getCoalition()); - getMap().showMapContextMenu({originalEvent: {x: this.getX(), y: this.getY(), latlng: this.getLatLng()}}); + getMap().showMapContextMenu({ originalEvent: { x: this.getX(), y: this.getY(), latlng: this.getLatLng() } }); getMap().getMapContextMenu().hideUpperBar(); getMap().getMapContextMenu().showSubMenu("aircraft"); getMap().getMapContextMenu().setAirbaseName(this.#airbase.getName()); diff --git a/client/src/controls/contextmenu.ts b/client/src/controls/contextmenu.ts index 9a19562f..58cf371f 100644 --- a/client/src/controls/contextmenu.ts +++ b/client/src/controls/contextmenu.ts @@ -23,28 +23,23 @@ export class ContextMenu { this.#container?.classList.toggle("hide", true); } - getContainer() - { + getContainer() { return this.#container; } - getLatLng() - { + getLatLng() { return this.#latlng; } - getX() - { + getX() { return this.#x; } - getY() - { + getY() { return this.#y; } - clip() - { + clip() { if (this.#container != null) { if (this.#x + this.#container.offsetWidth < window.innerWidth) this.#container.style.left = this.#x + "px"; diff --git a/client/src/controls/dropdown.ts b/client/src/controls/dropdown.ts index cb96e4c0..641c405f 100644 --- a/client/src/controls/dropdown.ts +++ b/client/src/controls/dropdown.ts @@ -7,17 +7,16 @@ export class Dropdown { #optionsList: string[] = []; #index: number = 0; - constructor(ID: string, callback: CallableFunction, options: string[] | null = null) - { - this.#element = document.getElementById(ID); - this.#options = this.#element.querySelector(".ol-select-options"); - this.#value = this.#element.querySelector(".ol-select-value"); + constructor(ID: string, callback: CallableFunction, options: string[] | null = null) { + this.#element = document.getElementById(ID); + this.#options = this.#element.querySelector(".ol-select-options"); + this.#value = this.#element.querySelector(".ol-select-value"); this.#defaultValue = this.#value.innerText; - this.#callback = callback; + this.#callback = callback; if (options != null) { this.setOptions(options); - } + } this.#value.addEventListener("click", (ev) => { this.#element.classList.toggle("is-open"); @@ -31,11 +30,10 @@ export class Dropdown { } }); - this.#options.classList.add( "ol-scrollable" ); + this.#options.classList.add("ol-scrollable"); } - setOptions(optionsList: string[]) - { + setOptions(optionsList: string[]) { this.#optionsList = optionsList; this.#options.replaceChildren(...optionsList.map((option: string, idx: number) => { var div = document.createElement("div"); @@ -48,7 +46,9 @@ export class Dropdown { button.addEventListener("click", (e: MouseEvent) => { e.stopPropagation(); - this.#value.innerHTML = `
${option}
`; + this.#value = document.createElement("div"); + this.#value.classList.add("ol-ellipsed"); + this.#value.innerText = option; this.#close(); this.#callback(option, e); this.#index = idx; @@ -57,19 +57,15 @@ export class Dropdown { })); } - selectText( text:string ) { - - const index = [].slice.call( this.#options.children ).findIndex( ( opt:Element ) => opt.querySelector( "button" )?.innerText === text ); - if ( index > -1 ) { - this.selectValue( index ); + selectText(text: string) { + const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text); + if (index > -1) { + this.selectValue(index); } - } - selectValue(idx: number) - { - if (idx < this.#optionsList.length) - { + selectValue(idx: number) { + if (idx < this.#optionsList.length) { var option = this.#optionsList[idx]; this.#value.innerHTML = `
${option}
`; this.#index = idx; @@ -91,8 +87,8 @@ export class Dropdown { } setValue(value: string) { - var index = this.#optionsList.findIndex((option) => {return option === value}); - if (index > -1) + var index = this.#optionsList.findIndex((option) => { return option === value }); + if (index > -1) this.selectValue(index); } @@ -102,21 +98,21 @@ export class Dropdown { #clip() { const options = this.#options; - const bounds = options.getBoundingClientRect(); - this.#element.dataset.position = ( bounds.bottom > window.innerHeight ) ? "top" : ""; + const bounds = options.getBoundingClientRect(); + this.#element.dataset.position = (bounds.bottom > window.innerHeight) ? "top" : ""; } #close() { - this.#element.classList.remove( "is-open" ); + this.#element.classList.remove("is-open"); this.#element.dataset.position = ""; } #open() { - this.#element.classList.add( "is-open" ); + this.#element.classList.add("is-open"); } #toggle() { - if ( this.#element.classList.contains( "is-open" ) ) { + if (this.#element.classList.contains("is-open")) { this.#close(); } else { this.#open(); diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index 929dad05..c8c98b40 100644 --- a/client/src/controls/mapcontextmenu.ts +++ b/client/src/controls/mapcontextmenu.ts @@ -41,8 +41,7 @@ export class MapContextMenu extends ContextMenu { document.addEventListener("contextMenuDeployAircraft", () => { this.hide(); this.#spawnOptions.coalition = getActiveCoalition(); - if (this.#spawnOptions) - { + if (this.#spawnOptions) { getMap().addTemporaryMarker(this.#spawnOptions.latlng); spawnAircraft(this.#spawnOptions); } @@ -51,8 +50,7 @@ export class MapContextMenu extends ContextMenu { document.addEventListener("contextMenuDeployGroundUnit", () => { this.hide(); this.#spawnOptions.coalition = getActiveCoalition(); - if (this.#spawnOptions) - { + if (this.#spawnOptions) { getMap().addTemporaryMarker(this.#spawnOptions.latlng); spawnGroundUnit(this.#spawnOptions); } @@ -189,10 +187,10 @@ export class MapContextMenu extends ContextMenu { this.#spawnOptions.role = role; this.#resetGroundUnitType(); - const types = groundUnitsDatabase.getByRole(role).map((blueprint) => { return blueprint.label } ); + const types = groundUnitsDatabase.getByRole(role).map((blueprint) => { return blueprint.label }); types.sort(); - this.#groundUnitTypeDropdown.setOptions( types ); + this.#groundUnitTypeDropdown.setOptions(types); this.#groundUnitTypeDropdown.selectValue(0); this.clip(); } @@ -205,8 +203,8 @@ export class MapContextMenu extends ContextMenu { const roles = groundUnitsDatabase.getRoles(); roles.sort(); - - this.#groundUnitRoleDropdown.setOptions( roles ); + + this.#groundUnitRoleDropdown.setOptions(roles); this.clip(); } diff --git a/client/src/controls/slider.ts b/client/src/controls/slider.ts index 7cf185cc..7fab7782 100644 --- a/client/src/controls/slider.ts +++ b/client/src/controls/slider.ts @@ -23,8 +23,7 @@ export class Slider { if (this.#container != null) { this.#display = this.#container.style.display; this.#slider = this.#container.querySelector("input"); - if (this.#slider != null) - { + if (this.#slider != null) { this.#slider.addEventListener("input", (e: any) => this.#onInput()); this.#slider.addEventListener("mousedown", (e: any) => this.#onStart()); this.#slider.addEventListener("mouseup", (e: any) => this.#onFinalize()); @@ -33,93 +32,77 @@ export class Slider { } } - show() - { + show() { if (this.#container != null) this.#container.style.display = this.#display; } - hide() - { + hide() { if (this.#container != null) this.#container.style.display = 'none'; } - setActive(newActive: boolean) - { - if (this.#container && !this.#dragged) - { + setActive(newActive: boolean) { + if (this.#container && !this.#dragged) { this.#container.classList.toggle("active", newActive); if (!newActive && this.#valueText != null) this.#valueText.innerText = "Mixed values"; } } - setMinMax(newMinValue: number, newMaxValue: number) - { + setMinMax(newMinValue: number, newMaxValue: number) { this.#minValue = newMinValue; this.#maxValue = newMaxValue; this.#updateMax(); } - setIncrement(newIncrement: number) - { + setIncrement(newIncrement: number) { this.#increment = newIncrement; this.#updateMax(); } - setValue(newValue: number) - { + setValue(newValue: number) { // Disable value setting if the user is dragging the element - if (!this.#dragged) - { + if (!this.#dragged) { this.#value = newValue; if (this.#slider != null) - this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max)); + this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max)); this.#onValue() } } - getValue() - { + getValue() { return this.#value; } - getDragged() - { + getDragged() { return this.#dragged; } - #updateMax() - { + #updateMax() { var oldValue = this.getValue(); if (this.#slider != null) this.#slider.max = String((this.#maxValue - this.#minValue) / this.#increment); this.setValue(oldValue); } - #onValue() - { + #onValue() { if (this.#valueText != null && this.#slider != null) - this.#valueText.innerHTML = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue)) + this.#unit + this.#valueText.innerHTML = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue)) + this.#unit this.setActive(true); } - #onInput() - { + #onInput() { this.#onValue(); } - #onStart() - { + #onStart() { this.#dragged = true; } - #onFinalize() - { + #onFinalize() { this.#dragged = false; - if (this.#slider != null) - { + if (this.#slider != null) { this.#value = this.#minValue + parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue); this.#callback(this.getValue()); } diff --git a/client/src/controls/unitcontextmenu.ts b/client/src/controls/unitcontextmenu.ts index aaa1b205..86250e6d 100644 --- a/client/src/controls/unitcontextmenu.ts +++ b/client/src/controls/unitcontextmenu.ts @@ -1,4 +1,3 @@ -import { getUnitsManager } from ".."; import { deg2rad } from "../other/utils"; import { ContextMenu } from "./contextmenu"; @@ -10,21 +9,19 @@ export class UnitContextMenu extends ContextMenu { document.addEventListener("applyCustomFormation", () => { var dialog = document.getElementById("custom-formation-dialog"); - if (dialog) - { + if (dialog) { dialog.classList.add("hide"); var clock = 1; - while (clock < 8) - { - if (( dialog.querySelector(`#formation-${clock}`)).checked) + while (clock < 8) { + if ((dialog.querySelector(`#formation-${clock}`)).checked) break clock++; } var angleDeg = 360 - (clock - 1) * 45; var angleRad = deg2rad(angleDeg); - var distance = parseInt(( dialog.querySelector(`#distance`)?.querySelector("input")).value) * 0.3048; - var upDown = parseInt(( dialog.querySelector(`#up-down`)?.querySelector("input")).value) * 0.3048; - + var distance = parseInt((dialog.querySelector(`#distance`)?.querySelector("input")).value) * 0.3048; + var upDown = parseInt((dialog.querySelector(`#up-down`)?.querySelector("input")).value) * 0.3048; + // X: front-rear, positive front // Y: top-bottom, positive top // Z: left-right, positive right @@ -34,7 +31,7 @@ export class UnitContextMenu extends ContextMenu { var z = distance * Math.sin(angleRad); if (this.#customFormationCallback) - this.#customFormationCallback({"x": x, "y": y, "z": z}) + this.#customFormationCallback({ "x": x, "y": y, "z": z }) } }) } @@ -43,10 +40,8 @@ export class UnitContextMenu extends ContextMenu { this.#customFormationCallback = callback; } - setOptions(options: {[key: string]: string}, callback: CallableFunction) - { - this.getContainer()?.replaceChildren(...Object.keys(options).map((option: string, idx: number) => - { + setOptions(options: { [key: string]: string }, callback: CallableFunction) { + this.getContainer()?.replaceChildren(...Object.keys(options).map((option: string, idx: number) => { var button = document.createElement("button"); button.innerHTML = options[option]; button.addEventListener("click", () => callback(option)); diff --git a/client/src/features/featureswitches.ts b/client/src/features/featureswitches.ts index c5776683..ee237b01 100644 --- a/client/src/features/featureswitches.ts +++ b/client/src/features/featureswitches.ts @@ -23,13 +23,13 @@ class FeatureSwitch { userPreference; - constructor( config:FeatureSwitchInterface ) { + constructor(config: FeatureSwitchInterface) { this.defaultEnabled = config.defaultEnabled; - this.label = config.label; - this.masterSwitch = config.masterSwitch; - this.name = config.name; - this.onEnabled = config.onEnabled; + this.label = config.label; + this.masterSwitch = config.masterSwitch; + this.name = config.name; + this.onEnabled = config.onEnabled; this.userPreference = this.getUserPreference(); @@ -38,16 +38,16 @@ class FeatureSwitch { getUserPreference() { - let preferences = JSON.parse( localStorage.getItem( "featureSwitches" ) || "{}" ); + let preferences = JSON.parse(localStorage.getItem("featureSwitches") || "{}"); - return ( preferences.hasOwnProperty( this.name ) ) ? preferences[ this.name ] : this.defaultEnabled; + return (preferences.hasOwnProperty(this.name)) ? preferences[this.name] : this.defaultEnabled; } isEnabled() { - if ( !this.masterSwitch ) { + if (!this.masterSwitch) { return false; } @@ -58,7 +58,7 @@ class FeatureSwitch { export class FeatureSwitches { - #featureSwitches:FeatureSwitch[] = [ + #featureSwitches: FeatureSwitch[] = [ new FeatureSwitch({ "defaultEnabled": false, @@ -66,7 +66,7 @@ export class FeatureSwitches { "masterSwitch": true, "name": "aic" }), - + new FeatureSwitch({ "defaultEnabled": false, "label": "AI Formations", @@ -74,21 +74,21 @@ export class FeatureSwitches { "name": "ai-formations", "removeArtifactsIfDisabled": false }), - + new FeatureSwitch({ "defaultEnabled": false, "label": "ATC", "masterSwitch": true, "name": "atc" }), - + new FeatureSwitch({ "defaultEnabled": false, "label": "Force show unit control panel", "masterSwitch": true, "name": "forceShowUnitControlPanel" }), - + new FeatureSwitch({ "defaultEnabled": true, "label": "Show splash screen", @@ -108,41 +108,41 @@ export class FeatureSwitches { } - getSwitch( switchName:string ) { + getSwitch(switchName: string) { - return this.#featureSwitches.find( featureSwitch => featureSwitch.name === switchName ); + return this.#featureSwitches.find(featureSwitch => featureSwitch.name === switchName); } #testSwitches() { - for ( const featureSwitch of this.#featureSwitches ) { - if ( featureSwitch.isEnabled() ) { - if ( typeof featureSwitch.onEnabled === "function" ) { + for (const featureSwitch of this.#featureSwitches) { + if (featureSwitch.isEnabled()) { + if (typeof featureSwitch.onEnabled === "function") { featureSwitch.onEnabled(); } } else { - document.querySelectorAll( "[data-feature-switch='" + featureSwitch.name + "']" ).forEach( el => { - if ( featureSwitch.removeArtifactsIfDisabled === false ) { + document.querySelectorAll("[data-feature-switch='" + featureSwitch.name + "']").forEach(el => { + if (featureSwitch.removeArtifactsIfDisabled === false) { el.remove(); } else { - el.classList.add( "hide" ); + el.classList.add("hide"); } }); } - document.body.classList.toggle( "feature-" + featureSwitch.name, featureSwitch.isEnabled() ); + document.body.classList.toggle("feature-" + featureSwitch.name, featureSwitch.isEnabled()); } } savePreferences() { - let preferences:any = {}; + let preferences: any = {}; - for ( const featureSwitch of this.#featureSwitches ) { - preferences[ featureSwitch.name ] = featureSwitch.isEnabled(); + for (const featureSwitch of this.#featureSwitches) { + preferences[featureSwitch.name] = featureSwitch.isEnabled(); } - localStorage.setItem( "featureSwitches", JSON.stringify( preferences ) ); + localStorage.setItem("featureSwitches", JSON.stringify(preferences)); } diff --git a/client/src/features/toggleablefeature.ts b/client/src/features/toggleablefeature.ts index 5873fb6e..09f1723e 100644 --- a/client/src/features/toggleablefeature.ts +++ b/client/src/features/toggleablefeature.ts @@ -1,9 +1,9 @@ export abstract class ToggleableFeature { - #status:boolean = false; + #status: boolean = false; - constructor( defaultStatus:boolean ) { + constructor(defaultStatus: boolean) { this.#status = defaultStatus; @@ -12,17 +12,17 @@ export abstract class ToggleableFeature { } - getStatus() : boolean { + getStatus(): boolean { return this.#status; } - protected onStatusUpdate() {} + protected onStatusUpdate() { } - toggleStatus( force?:boolean ) : void { + toggleStatus(force?: boolean): void { - if ( force ) { + if (force) { this.#status = force; } else { this.#status = !this.#status; diff --git a/client/src/map/boxselect.ts b/client/src/map/boxselect.ts index b285c10a..2ea92fd8 100644 --- a/client/src/map/boxselect.ts +++ b/client/src/map/boxselect.ts @@ -1,10 +1,10 @@ import { Map } from 'leaflet'; -import { Handler} from 'leaflet'; +import { Handler } from 'leaflet'; import { Util } from 'leaflet'; import { DomUtil } from 'leaflet'; import { DomEvent } from 'leaflet'; import { LatLngBounds } from 'leaflet'; -import { Bounds } from 'leaflet'; +import { Bounds } from 'leaflet'; export var BoxSelect = Handler.extend({ initialize: function (map: Map) { @@ -82,12 +82,12 @@ export var BoxSelect = Handler.extend({ this._point = this._map.mouseEventToContainerPoint(e); var bounds = new Bounds(this._point, this._startPoint), - size = bounds.getSize(); + size = bounds.getSize(); if (bounds.min != undefined) DomUtil.setPosition(this._box, bounds.min); - this._box.style.width = size.x + 'px'; + this._box.style.width = size.x + 'px'; this._box.style.height = size.y + 'px'; }, @@ -113,7 +113,7 @@ export var BoxSelect = Handler.extend({ if ((e.which !== 1) && (e.button !== 0)) { return; } this._finish(); - + if (!this._moved) { return; } // Postpone to next JS tick so internal click event handling // still see it as "moved". @@ -121,8 +121,8 @@ export var BoxSelect = Handler.extend({ var bounds = new LatLngBounds( this._map.containerPointToLatLng(this._startPoint), this._map.containerPointToLatLng(this._point)); - - this._map.fire('selectionend', {selectionBounds: bounds}); + + this._map.fire('selectionend', { selectionBounds: bounds }); }, _onKeyDown: function (e: any) { diff --git a/client/src/map/clickableminimap.ts b/client/src/map/clickableminimap.ts new file mode 100644 index 00000000..00104f7e --- /dev/null +++ b/client/src/map/clickableminimap.ts @@ -0,0 +1,12 @@ +import { MiniMap, MiniMapOptions } from "leaflet-control-mini-map"; + +export class ClickableMiniMap extends MiniMap { + constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) { + super(layer, options); + } + + getMap() { + //@ts-ignore needed to access not exported member. A bit of a hack, required to access click events //TODO: fix me + return this._miniMap; + } +} \ No newline at end of file diff --git a/client/src/map/custommarker.ts b/client/src/map/custommarker.ts new file mode 100644 index 00000000..41d79c14 --- /dev/null +++ b/client/src/map/custommarker.ts @@ -0,0 +1,24 @@ +import { Map, Marker } from "leaflet"; +import { MarkerOptions } from "leaflet"; +import { LatLngExpression } from "leaflet"; + +export class CustomMarker extends Marker { + constructor(latlng: LatLngExpression, options?: MarkerOptions) { + super(latlng, options); + } + + onAdd(map: Map): this { + super.onAdd(map); + this.createIcon(); + return this; + } + + onRemove(map: Map): this { + super.onRemove(map); + return this; + } + + createIcon() { + /* Overloaded by child classes */ + } +} \ No newline at end of file diff --git a/client/src/map/destinationpreviewmarker.ts b/client/src/map/destinationpreviewmarker.ts new file mode 100644 index 00000000..1146992d --- /dev/null +++ b/client/src/map/destinationpreviewmarker.ts @@ -0,0 +1,15 @@ +import { DivIcon } from "leaflet"; +import { CustomMarker } from "./custommarker"; + +export class DestinationPreviewMarker extends CustomMarker { + createIcon() { + this.setIcon(new DivIcon({ + iconSize: [52, 52], + iconAnchor: [26, 26], + className: "leaflet-destination-preview" + })); + var el = document.createElement("div"); + el.classList.add("ol-destination-preview-icon"); + this.getElement()?.appendChild(el); + } +} diff --git a/client/src/map/map.ts b/client/src/map/map.ts index f98a58c9..a36ae834 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -1,6 +1,4 @@ import * as L from "leaflet" -import { MiniMap, MiniMapOptions } from "leaflet-control-mini-map"; - import { getUnitsManager } from ".."; import { BoxSelect } from "./boxselect"; import { MapContextMenu, SpawnOptions } from "../controls/mapcontextmenu"; @@ -10,40 +8,20 @@ import { Dropdown } from "../controls/dropdown"; import { Airbase } from "../missionhandler/airbase"; import { Unit } from "../units/unit"; import { bearing } from "../other/utils"; - -// TODO a bit of a hack, this module is provided as pure javascript only -require("../../node_modules/leaflet.nauticscale/dist/leaflet.nauticscale.js") - -export const IDLE = "IDLE"; -export const MOVE_UNIT = "MOVE_UNIT"; +import { DestinationPreviewMarker } from "./destinationpreviewmarker"; +import { TemporaryUnitMarker } from "./temporaryunitmarker"; +import { ClickableMiniMap } from "./clickableminimap"; +import { SVGInjector } from '@tanem/svg-injector' L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); -var temporaryIcon = new L.Icon({ - iconUrl: 'images/icon-temporary.png', - iconSize: [52, 52], - iconAnchor: [26, 26] -}); +// TODO would be nice to convert to ts +require("../../public/javascripts/leaflet.nauticscale.js") -var destinationPreviewIcon = new L.DivIcon({ - html: `
`, - iconSize: [52, 52], - iconAnchor: [26, 26], - 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); - } - - getMap() { - //@ts-ignore needed to access not exported member. A bit of a hack, required to access click events - return this._miniMap; - } -} +/* Map constants */ +export const IDLE = "IDLE"; +export const MOVE_UNIT = "MOVE_UNIT"; +export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; export class Map extends L.Map { #state: string; @@ -107,19 +85,21 @@ export class Map extends L.Map { this.on('mousedown', (e: any) => this.#onMouseDown(e)); this.on('mouseup', (e: any) => this.#onMouseUp(e)); this.on('mousemove', (e: any) => this.#onMouseMove(e)); - this.on('keydown', (e: any) => this.#updateDestinationPreview(e)); - this.on('keyup', (e: any) => this.#updateDestinationPreview(e)); - + this.on('keydown', (e: any) => this.#updateDestinationPreview(e)); + this.on('keyup', (e: any) => this.#updateDestinationPreview(e)); + /* Event listeners */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { - ev.detail._element.classList.toggle("off"); - document.body.toggleAttribute("data-hide-" + ev.detail.coalition); + const el = ev.detail._element; + el?.classList.toggle("off"); + getUnitsManager().setHiddenType(ev.detail.coalition, (el?.currentTarget as HTMLElement)?.classList.contains("off")); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { - ev.detail._element.classList.toggle("off"); - document.body.toggleAttribute("data-hide-" + ev.detail.category); + const el = ev.detail._element; + el?.classList.toggle("off"); + getUnitsManager().setHiddenType(ev.detail.type, !el?.classList.contains("off")); Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); @@ -129,17 +109,14 @@ export class Map extends L.Map { }); /* Pan interval */ - this.#panInterval = window.setInterval(() => { - 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)); + this.#panInterval = window.setInterval(() => { + 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"); - }); + return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, "", "toggleUnitVisibility", `{"type": "${option}"}`); }); document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]); } @@ -217,10 +194,10 @@ export class Map extends L.Map { }) this.#destinationPreviewMarkers = []; - if (getUnitsManager().getSelectedUnits({excludeHumans: true}).length < 20) { + if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length < 20) { /* Create the unit destination preview markers */ - this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({excludeHumans: true}).map((unit: Unit) => { - var marker = new L.Marker(this.getMouseCoordinates(), {icon: destinationPreviewIcon, interactive: false}); + this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true }).map((unit: Unit) => { + var marker = new DestinationPreviewMarker(this.getMouseCoordinates()); marker.addTo(this); return marker; }) @@ -352,7 +329,7 @@ export class Map extends L.Map { } handleMapPanning(e: any) { - if (e.type === "keyup"){ + if (e.type === "keyup") { switch (e.code) { case "KeyA": case "ArrowLeft": @@ -372,9 +349,8 @@ export class Map extends L.Map { break; } } - else { - switch (e.code) - { + else { + switch (e.code) { case 'KeyA': case 'ArrowLeft': this.#panLeft = true; @@ -396,7 +372,7 @@ export class Map extends L.Map { } addTemporaryMarker(latlng: L.LatLng) { - var marker = new L.Marker(latlng, {icon: temporaryIcon}); + var marker = new TemporaryUnitMarker(latlng); marker.addTo(this); this.#temporaryMarkers.push(marker); } @@ -413,8 +389,7 @@ export class Map extends L.Map { i = idx; } }); - if (closest) - { + if (closest) { this.removeLayer(closest); delete this.#temporaryMarkers[i]; } @@ -449,7 +424,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) } } @@ -465,16 +440,14 @@ export class Map extends L.Map { #onMouseDown(e: any) { this.hideAllContextMenus(); - if (this.#state == MOVE_UNIT && e.originalEvent.button == 2) - { + if (this.#state == MOVE_UNIT && e.originalEvent.button == 2) { this.#computeDestinationRotation = true; this.#destinationRotationCenter = this.getMouseCoordinates(); } } #onMouseUp(e: any) { - if (this.#state == MOVE_UNIT) - { + if (this.#state == MOVE_UNIT) { this.#computeDestinationRotation = false; this.#destinationRotationCenter = null; this.#destinationGroupRotation = 0; @@ -488,7 +461,7 @@ export class Map extends L.Map { if (this.#computeDestinationRotation && this.#destinationRotationCenter != null) this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng); - this.#updateDestinationPreview(e); + this.#updateDestinationPreview(e); } #onZoom(e: any) { @@ -542,18 +515,23 @@ export class Map extends L.Map { } #updateDestinationPreview(e: any) { - Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null? this.#destinationRotationCenter: this.getMouseCoordinates(), this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { + 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()); + }) } - #createOptionButton(value: string, url: string, title: string, callback: EventListenerOrEventListenerObject) { + #createOptionButton(value: string, url: string, title: string, callback: string, argument: string) { var button = document.createElement("button"); + const img = document.createElement("img"); + img.src = `/resources/theme/images/buttons/${url}`; + img.onload = () => SVGInjector(img); button.title = title; button.value = value; - button.innerHTML = `` - button.addEventListener("click", callback); + button.appendChild(img); + button.setAttribute("data-on-click", callback); + button.setAttribute("data-on-click-params", argument); return button; } } + diff --git a/client/src/map/temporaryunitmarker.ts b/client/src/map/temporaryunitmarker.ts new file mode 100644 index 00000000..904ee83d --- /dev/null +++ b/client/src/map/temporaryunitmarker.ts @@ -0,0 +1,13 @@ +import { Icon } from "leaflet"; +import { CustomMarker } from "./custommarker"; + +export class TemporaryUnitMarker extends CustomMarker { + createIcon() { + var icon = new Icon({ + iconUrl: '/resources/theme/images/markers/temporary-icon.png', + iconSize: [52, 52], + iconAnchor: [26, 26] + }); + this.setIcon(icon); + } +} \ No newline at end of file diff --git a/client/src/missionhandler/airbase.ts b/client/src/missionhandler/airbase.ts index a3ca42dc..4128001b 100644 --- a/client/src/missionhandler/airbase.ts +++ b/client/src/missionhandler/airbase.ts @@ -1,13 +1,14 @@ -import * as L from 'leaflet' +import { DivIcon } from 'leaflet'; +import { CustomMarker } from '../map/custommarker'; +import { SVGInjector } from '@tanem/svg-injector'; export interface AirbaseOptions { name: string, - position: L.LatLng, - src: string + position: L.LatLng } -export class Airbase extends L.Marker +export class Airbase extends CustomMarker { #name: string = ""; #coalition: string = ""; @@ -19,21 +20,30 @@ export class Airbase extends L.Marker super(options.position, { riseOnHover: true }); this.#name = options.name; - var icon = new L.DivIcon({ - html: `
-
-
`, + } + + createIcon() { + var icon = new DivIcon({ className: 'leaflet-airbase-marker', iconSize: [40, 40], iconAnchor: [20, 20] }); // Set the marker, className must be set to avoid white square this.setIcon(icon); + + var el = document.createElement("div"); + el.classList.add("airbase-icon"); + el.setAttribute("data-object", "airbase"); + var img = document.createElement("img"); + img.src = "/resources/theme/images/markers/airbase.svg"; + img.onload = () => SVGInjector(img); + el.appendChild(img); + this.getElement()?.appendChild(el); } setCoalition(coalition: string) { this.#coalition = coalition; - ( this.getElement()?.querySelector(".airbase")).dataset.coalition = this.#coalition; + ( this.getElement()?.querySelector(".airbase-icon")).dataset.coalition = this.#coalition; } getCoalition() diff --git a/client/src/missionhandler/bullseye.ts b/client/src/missionhandler/bullseye.ts new file mode 100644 index 00000000..b50b1a7b --- /dev/null +++ b/client/src/missionhandler/bullseye.ts @@ -0,0 +1,36 @@ +import { DivIcon } from "leaflet"; +import { CustomMarker } from "../map/custommarker"; +import { SVGInjector } from "@tanem/svg-injector"; + +export class Bullseye extends CustomMarker { + #coalition: string = ""; + + createIcon() { + var icon = new DivIcon({ + className: 'leaflet-bullseye-marker', + iconSize: [40, 40], + iconAnchor: [20, 20] + }); // Set the marker, className must be set to avoid white square + this.setIcon(icon); + + var el = document.createElement("div"); + el.classList.add("bullseye-icon"); + el.setAttribute("data-object", "bullseye"); + var img = document.createElement("img"); + img.src = "/resources/theme/images/markers/bullseye.svg"; + img.onload = () => SVGInjector(img); + el.appendChild(img); + this.getElement()?.appendChild(el); + } + + setCoalition(coalition: string) + { + this.#coalition = coalition; + ( this.getElement()?.querySelector(".bullseye-icon")).dataset.coalition = this.#coalition; + } + + getCoalition() + { + return this.#coalition; + } +} \ No newline at end of file diff --git a/client/src/missionhandler/missionhandler.ts b/client/src/missionhandler/missionhandler.ts index 66233f0a..93b13192 100644 --- a/client/src/missionhandler/missionhandler.ts +++ b/client/src/missionhandler/missionhandler.ts @@ -1,44 +1,58 @@ -import { Marker, LatLng, Icon } from "leaflet"; +import { LatLng } from "leaflet"; import { getInfoPopup, getMap } from ".."; import { Airbase } from "./airbase"; - -var bullseyeIcons = [ - new Icon({ iconUrl: 'images/bullseye0.png', iconAnchor: [30, 30]}), - new Icon({ iconUrl: 'images/bullseye1.png', iconAnchor: [30, 30]}), - new Icon({ iconUrl: 'images/bullseye2.png', iconAnchor: [30, 30]}) -] +import { Bullseye } from "./bullseye"; export class MissionHandler { - #bullseyes : any; //TODO declare interface - #bullseyeMarkers: any; - #airbases : any; //TODO declare interface - #airbasesMarkers: {[name: string]: Airbase}; + #bullseyes : {[name: string]: Bullseye} = {}; + #airbases : {[name: string]: Airbase} = {}; #theatre : string = ""; constructor() { - this.#bullseyes = undefined; - this.#bullseyeMarkers = [ - new Marker([0, 0], {icon: bullseyeIcons[0]}).addTo(getMap()), - new Marker([0, 0], {icon: bullseyeIcons[1]}).addTo(getMap()), - new Marker([0, 0], {icon: bullseyeIcons[2]}).addTo(getMap()) - ] - this.#airbasesMarkers = {}; + } update(data: BullseyesData | AirbasesData | any) { if ("bullseyes" in data) { - this.#bullseyes = data.bullseyes; - this.#drawBullseyes(); + for (let idx in data.bullseyes) + { + const bullseye = data.bullseyes[idx]; + if (!(idx in this.#bullseyes)) + this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getMap()); + + if (bullseye.latitude && bullseye.longitude && bullseye.coalition) + { + this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude)); + this.#bullseyes[idx].setCoalition(bullseye.coalition); + } + } } if ("airbases" in data) { - this.#airbases = data.airbases; - this.#drawAirbases(); + for (let idx in data.airbases) + { + var airbase = data.airbases[idx] + if (this.#airbases[idx] === undefined) + { + this.#airbases[idx] = new Airbase({ + position: new LatLng(airbase.latitude, airbase.longitude), + name: airbase.callsign + }).addTo(getMap()); + this.#airbases[idx].on('contextmenu', (e) => this.#onAirbaseClick(e)); + } + if (airbase.latitude && airbase.longitude && airbase.coalition) + { + this.#airbases[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude)); + this.#airbases[idx].setCoalition(airbase.coalition); + } + //this.#airbases[idx].setProperties(["Runway 1: 31L / 13R", "Runway 2: 31R / 13L", "TCN: 17X", "ILS: ---" ]); + //this.#airbases[idx].setParkings(["2x big", "5x small"]); + } } if ("mission" in data) @@ -58,38 +72,6 @@ export class MissionHandler return this.#bullseyes; } - #drawBullseyes() - { - for (let idx in this.#bullseyes) - { - var bullseye = this.#bullseyes[idx]; - this.#bullseyeMarkers[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude)); - } - } - - #drawAirbases() - { - for (let idx in this.#airbases) - { - var airbase = this.#airbases[idx] - if (this.#airbasesMarkers[idx] === undefined) - { - this.#airbasesMarkers[idx] = new Airbase({ - position: new LatLng(airbase.latitude, airbase.longitude), - name: airbase.callsign, - src: "images/airbase.png"}).addTo(getMap()); - this.#airbasesMarkers[idx].on('contextmenu', (e) => this.#onAirbaseClick(e)); - } - else - { - this.#airbasesMarkers[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude)); - this.#airbasesMarkers[idx].setCoalition(airbase.coalition); - //this.#airbasesMarkers[idx].setProperties(["Runway 1: 31L / 13R", "Runway 2: 31R / 13L", "TCN: 17X", "ILS: ---" ]); - //this.#airbasesMarkers[idx].setParkings(["2x big", "5x small"]); - } - } - } - #onAirbaseClick(e: any) { getMap().showAirbaseContextMenu(e, e.sourceTarget); diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index b3899d41..ce1ecc1f 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -154,4 +154,10 @@ export function mercatorToLatLng(x: number, y: number) { lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0); return { lng: lng, lat: lat }; +} + +export function createDivWithClass(className: string) { + var el = document.createElement("div"); + el.classList.add(className); + return el; } \ No newline at end of file diff --git a/client/src/panels/mouseinfopanel.ts b/client/src/panels/mouseinfopanel.ts index 7cfdd710..bdd38fb3 100644 --- a/client/src/panels/mouseinfopanel.ts +++ b/client/src/panels/mouseinfopanel.ts @@ -37,8 +37,8 @@ export class MouseInfoPanel extends Panel { if ( el != null ) { - var dist = distance(bullseyes[idx].latitude, bullseyes[idx].longitude, mousePosition.lat, mousePosition.lng); - var bear = bearing(bullseyes[idx].latitude, bullseyes[idx].longitude, mousePosition.lat, mousePosition.lng); + var dist = distance(bullseyes[idx].getLatLng().lat, bullseyes[idx].getLatLng().lng, mousePosition.lat, mousePosition.lng); + var bear = bearing(bullseyes[idx].getLatLng().lat, bullseyes[idx].getLatLng().lng, mousePosition.lat, mousePosition.lng); let bng = zeroAppend(Math.floor(bear), 3); diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 9f6ef22e..7b1967d4 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -346,7 +346,7 @@ export class UnitControlPanel extends Panel { var button = document.createElement("button"); button.title = title; button.value = value; - button.innerHTML = `` + button.innerHTML = `` button.addEventListener("click", callback); return button; } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index a7a7ec09..3338c095 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -4,14 +4,16 @@ 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 { aircraftDatabase } from './aircraftdatabase'; import { groundUnitsDatabase } from './groundunitsdatabase'; +import { CustomMarker } from '../map/custommarker'; +import { SVGInjector } from '@tanem/svg-injector'; var pathIcon = new Icon({ - iconUrl: 'images/marker-icon.png', - shadowUrl: 'images/marker-shadow.png', + iconUrl: '/resources/theme/images/markers/marker-icon.png', + shadowUrl: '/resources/theme/images/markers/marker-shadow.png', iconAnchor: [13, 41] }); -export class Unit extends Marker { +export class Unit extends CustomMarker { ID: number; #data: UnitData = { @@ -114,22 +116,7 @@ export class Unit extends Marker { /* Set the unit data */ this.setData(data); - /* Set the icon */ - var icon = new DivIcon({ - html: this.getMarkerHTML(), - className: 'leaflet-unit-marker', - iconAnchor: [25, 25], - iconSize: [50, 50], - }); - this.setIcon(icon); - } - - getMarkerHTML() { - return `
-
-
-
-
` + } getMarkerCategory() { @@ -137,6 +124,20 @@ export class Unit extends Marker { return ""; } + getActiveMarkerElements() { + // Default values + return { + state: false, + vvi: false, + hotgroup: false, + unitIcon: true, + shortLabel: false, + fuel: false, + ammo: false, + summary: false + } + } + setSelected(selected: boolean) { /* Only alive units can be selected. Some units are not selectable (weapons) */ if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { @@ -285,6 +286,104 @@ export class Unit extends Marker { return this.getData().optionsData; } + /********************** Icon *************************/ + createIcon(): void { + /* Set the icon */ + var icon = new DivIcon({ + className: 'leaflet-unit-icon', + iconAnchor: [25, 25], + iconSize: [50, 50], + }); + this.setIcon(icon); + + var el = document.createElement("div"); + el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`); + el.setAttribute("data-coalition", this.getMissionData().coalition); + + // Generate and append elements depending on active options + // State icon + if (this.getActiveMarkerElements().state){ + var state = document.createElement("div"); + state.classList.add("unit-state"); + el.appendChild(state); + } + + // Velocity vector + if (this.getActiveMarkerElements().vvi) { + var vvi = document.createElement("div"); + vvi.classList.add("unit-vvi"); + vvi.toggleAttribute("data-rotate-to-heading"); + el.append(vvi); + } + + // Hotgroup indicator + if (this.getActiveMarkerElements().hotgroup) { + var hotgroup = document.createElement("div"); + hotgroup.classList.add("unit-hotgroup"); + var hotgroupId = document.createElement("div"); + hotgroupId.classList.add("unit-hotgroup-id"); + hotgroup.appendChild(hotgroupId); + el.append(hotgroup); + } + + // Main icon + if (this.getActiveMarkerElements().unitIcon) { + var unitIcon = document.createElement("div"); + unitIcon.classList.add("unit-icon"); + var img = document.createElement("img"); + img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`; + img.onload = () => SVGInjector(img); + unitIcon.appendChild(img); + el.append(unitIcon); + } + + // Short label + if (this.getActiveMarkerElements().shortLabel) { + var shortLabel = document.createElement("div"); + shortLabel.classList.add("unit-short-label"); + shortLabel.innerText = aircraftDatabase.getByName(this.getBaseData().name)?.shortLabel || ""; //TODO: fix, use correct database + el.append(shortLabel); + } + + // Fuel indicator + if (this.getActiveMarkerElements().fuel) { + var fuelIndicator = document.createElement("div"); + fuelIndicator.classList.add("unit-fuel"); + var fuelLevel = document.createElement("div"); + fuelLevel.classList.add("unit-fuel-level"); + fuelIndicator.appendChild(fuelLevel); + el.append(fuelIndicator); + } + + // Ammo indicator + if (this.getActiveMarkerElements().ammo){ + var ammoIndicator = document.createElement("div"); + ammoIndicator.classList.add("unit-ammo"); + for (let i = 0; i <= 3; i++) + ammoIndicator.appendChild(document.createElement("div")); + el.append(ammoIndicator); + } + + // Unit summary + if (this.getActiveMarkerElements().summary) { + var summary = document.createElement("div"); + summary.classList.add("unit-summary"); + var callsign = document.createElement("div"); + callsign.classList.add("unit-callsign"); + callsign.innerText = this.getBaseData().unitName; + var altitude = document.createElement("div"); + altitude.classList.add("unit-altitude"); + var speed = document.createElement("div"); + speed.classList.add("unit-speed"); + summary.appendChild(callsign); + summary.appendChild(altitude); + summary.appendChild(speed); + el.appendChild(summary); + } + + this.getElement()?.appendChild(el); + } + /********************** Visibility *************************/ updateVisibility() { var hidden = false; @@ -295,7 +394,9 @@ export class Unit extends Marker { 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); + else if (hiddenUnits.includes(this.getMissionData().coalition)) + hidden = true; + this.setHidden(hidden || !this.getBaseData().alive); } setHidden(hidden: boolean) { @@ -576,7 +677,7 @@ export class Unit extends Marker { el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`); }); - /* Turn on ordnance indicators */ + /* Turn on ammo indicators */ var hasFox1 = element.querySelector(".unit")?.hasAttribute("data-has-fox-1"); var hasFox2 = element.querySelector(".unit")?.hasAttribute("data-has-fox-2"); var hasFox3 = element.querySelector(".unit")?.hasAttribute("data-has-fox-3"); @@ -690,7 +791,18 @@ export class Unit extends Marker { } export class AirUnit extends Unit { - + getActiveMarkerElements() { + return { + state: true, + vvi: true, + hotgroup: true, + unitIcon: true, + shortLabel: true, + fuel: true, + ammo: true, + summary: true + }; + } } export class Aircraft extends AirUnit { @@ -698,30 +810,6 @@ export class Aircraft extends AirUnit { super(ID, data); } - getMarkerHTML() { - return `
-
-
-
-
-
${aircraftDatabase.getByName(this.getBaseData().name)?.shortLabel || ""}
-
-
-
-
-
-
-
-
-
-
-
${this.getBaseData().unitName}
-
-
-
-
` - } - getMarkerCategory() { return "aircraft"; } @@ -732,7 +820,7 @@ export class Helicopter extends AirUnit { super(ID, data); } - getVisibilityCategory() { + getMarkerCategory() { return "helicopter"; } } @@ -742,15 +830,17 @@ export class GroundUnit extends Unit { super(ID, data); } - getMarkerHTML() { - var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; - return `
-
-
${role?.substring(0, 1)?.toUpperCase() || ""}
-
-
-
-
` + getActiveMarkerElements() { + return { + state: true, + vvi: false, + hotgroup: true, + unitIcon: true, + shortLabel: true, + fuel: false, + ammo: false, + summary: false + }; } getMarkerCategory() { @@ -766,6 +856,19 @@ export class NavyUnit extends Unit { super(ID, data); } + getActiveMarkerElements() { + return { + state: true, + vvi: false, + hotgroup: true, + unitIcon: true, + shortLabel: true, + fuel: false, + ammo: false, + summary: false + }; + } + getMarkerCategory() { return "navyunit"; } @@ -776,14 +879,6 @@ export class Weapon extends Unit { super(ID, data); this.setSelectable(false); } - - getMarkerHTML(): string { - return `
-
-
-
` - } - } export class Missile extends Weapon { diff --git a/client/views/index.ejs b/client/views/index.ejs index 863572aa..558d6b67 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -9,9 +9,7 @@ - - - + diff --git a/client/views/panels/navbar.ejs b/client/views/panels/navbar.ejs index 06aeeeb9..3ae2453a 100644 --- a/client/views/panels/navbar.ejs +++ b/client/views/panels/navbar.ejs @@ -4,7 +4,7 @@
-
+

DCS Olympus

version v0.2.1
diff --git a/client/views/panels/unitcontrol.ejs b/client/views/panels/unitcontrol.ejs index d27223f5..fe67082c 100644 --- a/client/views/panels/unitcontrol.ejs +++ b/client/views/panels/unitcontrol.ejs @@ -40,21 +40,21 @@

Rules of engagement

-
+

Reaction to threat

-
+

Radar & ECM

-
+
diff --git a/client/views/uikit/uikit.ejs b/client/views/uikit/uikit.ejs index 9bcc7e2c..e4c0a2f4 100644 --- a/client/views/uikit/uikit.ejs +++ b/client/views/uikit/uikit.ejs @@ -2,7 +2,7 @@ Olympus UI Kit - + @@ -172,7 +172,7 @@
-
+
Z
@@ -184,7 +184,7 @@
-
+
Y
@@ -196,7 +196,7 @@
-
+
X
@@ -218,7 +218,7 @@
-
+
Z
@@ -230,7 +230,7 @@
-
+
Y
@@ -242,7 +242,7 @@
-
+
X
@@ -263,7 +263,7 @@
-
+
Z
@@ -275,7 +275,7 @@
-
+
Y
@@ -287,7 +287,7 @@
-
+
X
@@ -308,7 +308,7 @@
-
+
J
@@ -320,7 +320,7 @@
-
+
K
@@ -332,7 +332,7 @@
-
+
L
@@ -360,7 +360,7 @@
4
-
+
18
@@ -384,7 +384,7 @@
4
-
+
18
@@ -407,7 +407,7 @@
4
-
+
18
@@ -430,7 +430,7 @@
4
-
+
18
@@ -453,7 +453,7 @@
4
-
+
18
@@ -476,7 +476,7 @@
4
-
+
18
@@ -509,7 +509,7 @@
4
-
+
18
@@ -539,7 +539,7 @@
4
-
+
18
@@ -568,7 +568,7 @@
4
-
+
18
@@ -598,7 +598,7 @@
4
-
+
18
@@ -628,7 +628,7 @@
4
-
+
18
@@ -657,7 +657,7 @@
4
-
+
18
@@ -695,7 +695,7 @@
4
-
+
18
@@ -725,7 +725,7 @@
4
-
+
18
@@ -754,7 +754,7 @@
4
-
+
18
@@ -791,7 +791,7 @@
-
+
@@ -800,7 +800,7 @@
-
+
@@ -808,7 +808,7 @@
-
+
@@ -1183,19 +1183,19 @@
Actions
- + icons_actions_gas
- + icons_actions_nothing
- + icons_actions_rtb
- + icons_actions_search
@@ -1205,19 +1205,19 @@
RoE
- + icons_roe_free
- + icons_roe_return
- + icons_roe_stop
- + icons_roe_target
@@ -1227,11 +1227,11 @@
Threat
- + icons_threat_protect
- + icons_threat_retreat