diff --git a/client/demo.js b/client/demo.js index ca65e0c8..5589ac23 100644 --- a/client/demo.js +++ b/client/demo.js @@ -138,6 +138,7 @@ class DemoDataGenerator { DEMO_UNIT_DATA[idx].position.lat += idx / 100; DEMO_UNIT_DATA[idx].category = "GroundUnit"; DEMO_UNIT_DATA[idx].isLeader = true; + DEMO_UNIT_DATA[idx].coalition = 0; idx += 1; DEMO_UNIT_DATA[idx] = JSON.parse(JSON.stringify(baseData)); diff --git a/client/public/stylesheets/panels/unitcontrol.css b/client/public/stylesheets/panels/unitcontrol.css index 0a5fa330..c7ada90f 100644 --- a/client/public/stylesheets/panels/unitcontrol.css +++ b/client/public/stylesheets/panels/unitcontrol.css @@ -2,6 +2,69 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { display: block !important; } + +#roe-buttons-container button, +#reaction-to-threat-buttons-container button, +#emissions-countermeasures-buttons-container button, +#shots-scatter-buttons-container button +#shots-intensity-buttons-container button { + align-items: center; + background-color: transparent; + border: 1px solid var(--accent-light-blue); + display: flex; + height: 30px; + justify-content: center; + width: 30px; +} + +#reaction-to-threat-buttons-container button:not(:first-child) svg { + width: 150%; + margin: -5px; +} + +#unit-control-panel .ol-option-button button.selected { + background-color: white; + border-color: white; +} + +#unit-control-panel .ol-option-button button.selected svg * { + fill: var(--background-steel); + stroke: var(--background-steel); +} + +#rapid-controls { + display: flex; + flex-direction: column; + row-gap: 5px; + height: fit-content; + width: fit-content; +} + +#rapid-controls button { + padding: 4px; +} + +#rapid-controls button.pulse { + animation: pulse 1.5s linear infinite; +} + +#rapid-controls svg { + height: 20px; + width: 20px; + fill: white; + stroke: white; +} + +#rapid-controls button:before { + display: inline-block; + filter: invert(100%); + height: 20px; + width: 20px; +} + + + + #unit-control-panel { display: flex; flex-direction: row; @@ -55,10 +118,6 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { } } -#unit-control-panel h3 { - margin-bottom: 8px; -} - #unit-control-panel #selected-units-container { align-items: left; border-radius: var(--border-radius-md); @@ -72,9 +131,9 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { #unit-control-panel #selected-units-container button { align-items: center; - border-radius: var(--border-radius-md); + border-radius: 20px; display: flex; - font-size: 11px; + font-size: 13px; height: 32px; justify-content: space-between; margin-right: 5px; @@ -88,6 +147,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { content: attr(data-label); font-size: 10px; padding: 4px 6px; + padding-right: 7px; white-space: nowrap; width: fit-content; } @@ -103,7 +163,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { display: block; overflow: hidden; padding: 4px; - padding-left: 0; + padding-left: 7px; text-align: left; text-overflow: ellipsis; white-space: nowrap; @@ -232,56 +292,59 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { opacity: 80%; } -#unit-control-panel .switch-control .ol-switch { - height: 25px; - width: 60px; -} - -#unit-control-panel .switch-control .ol-switch-fill { - background-color: var(--accent-light-blue); -} - -#unit-control-panel .switch-control .ol-switch-fill::after { - background-color: white; -} - -#unit-control-panel .switch-control .ol-switch[data-value="true"]>.ol-switch-fill::before { - content: "YES"; -} - -#unit-control-panel .switch-control .ol-switch[data-value="false"]>.ol-switch-fill::before { - content: "NO"; -} - -#operate-as-switch[data-value="true"] .ol-switch-fill { - background-color: var(--accent-light-blue); -} - -#operate-as-switch[data-value="false"] .ol-switch-fill { - background-color: var(--primary-red); -} - -#operate-as-switch[data-value="true"]>.ol-switch-fill::before { - content: "BLUE" !important; -} - -#operate-as-switch[data-value="false"]>.ol-switch-fill::before { - content: "RED" !important; -} - #advanced-settings-div { - position: relative; - column-gap: 5px; + align-items: center; + column-gap: 8px; display: flex; height: fit-content; + position: relative; } -#advanced-settings-div>*:nth-child(2) { - margin-left: auto; -} - -#advanced-settings-div>button { +#advanced-settings-div > button { + background-color: var(--background-grey); + font-size:13px; height: 40px; + padding:0 20px; +} + +#delete-options { + font-size:13px; +} + +#delete-options.ol-select > .ol-select-value:after { + content: ""; +} + +#delete-options.ol-select > .ol-select-value svg { + background-color: transparent; + position: absolute; + right:2px; + translate:0 1px; +} + +#delete-options.ol-select > .ol-select-value svg * { + fill: var(--primary-red); +} + +#delete-options * { + background-color: var(--background-steel); +} + +#delete-options.ol-select > .ol-select-value:hover, +#delete-options .ol-select-options > div:not(.hr):hover, +#delete-options .ol-select-options > div:not(.hr):hover button, +#delete-options .ol-select-options > div hr { + background-color: var(--background-grey); +} + +#delete-options .ol-select-options > div:first-of-type { + margin-top:12px; + padding-top:0; +} + +#delete-options .ol-select-options > div:last-of-type { + margin-bottom:12px; + padding-bottom:0; } #delete-options button { @@ -291,11 +354,16 @@ body.feature-forceShowUnitControlPanel #unit-control-panel { } #delete-options button svg { + background-color: transparent; margin-right: 10px; width: 18px; max-height: 18px; } +#delete-options button svg * { + stroke: red; +} + /* Element visibility control */ #unit-control-panel:not([data-show-categories-tooltip]) #categories-tooltip, #unit-control-panel:not([data-show-speed-slider]) #speed-slider, diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index f8f1a1be..53d8768c 100644 --- a/client/public/stylesheets/style/style.css +++ b/client/public/stylesheets/style/style.css @@ -67,6 +67,10 @@ button>img:first-child { pointer-events: none; } +.ol-box-shadow { + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); +} + form { margin: 0; padding: 0; @@ -226,13 +230,13 @@ form { } .ol-select.is-open[data-position="top"]>.ol-select-options { + box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25); top: 0; translate: 0 -100%; } .ol-select>.ol-select-options>div { background-color: var(--background-grey); - box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25); display: flex; justify-content: left; padding: 2px 15px; @@ -262,7 +266,6 @@ form { color: white; display: block; font-size: 13px; - font-weight: normal; height: 32px; padding: 6px 2px; padding: 5px; @@ -715,62 +718,6 @@ nav.ol-panel> :last-child { } - -#roe-buttons-container button, -#reaction-to-threat-buttons-container button, -#emissions-countermeasures-buttons-container button, -#shots-scatter-buttons-container button -#shots-intensity-buttons-container button { - align-items: center; - background-color: transparent; - border: 1px solid var(--accent-light-blue); - display: flex; - height: 30px; - justify-content: center; - width: 30px; -} - -#reaction-to-threat-buttons-container button:not(:first-child) svg { - width: 150%; - margin: -5px; -} - -#unit-control-panel .ol-option-button button.selected { - background-color: white; - border-color: white; -} - -#unit-control-panel .ol-option-button button.selected svg * { - fill: var(--background-steel); - stroke: var(--background-steel); -} - -#rapid-controls { - display: flex; - flex-direction: column; - row-gap: 5px; - height: fit-content; - width: fit-content; -} - -#rapid-controls button { - padding: 4px; -} - -#rapid-controls svg { - height: 20px; - width: 20px; - fill: white; - stroke: white; -} - -#rapid-controls button:before { - display: inline-block; - filter: invert(100%); - height: 20px; - width: 20px; -} - /****************************************************************************************/ #splash-screen { border-radius: var(--border-radius-md); @@ -1325,13 +1272,12 @@ input[type=number]::-webkit-outer-spin-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; + margin-right: 4px; } .ol-button-close { @@ -1368,10 +1314,12 @@ input[type=number]::-webkit-outer-spin-button { } .ol-switch { + align-items: center; cursor: pointer; display: flex; - align-items: center; + height: 25px; justify-content: center; + width: 40px; } .ol-switch-input { @@ -1407,7 +1355,7 @@ input[type=number]::-webkit-outer-spin-button { display: flex; font-size: 11px; height: 100%; - padding: 0px 7px; + padding: 0px 6px; position: absolute; transition: transform 0.2s; } @@ -1424,6 +1372,29 @@ input[type=number]::-webkit-outer-spin-button { transform: translateX(calc((var(--width) - var(--height)) * 0.5)); } +.switch-control.yes-no .ol-switch[data-value="true"] .ol-switch-fill { + background-color: var(--accent-light-blue); +} + +.switch-control.yes-no .ol-switch[data-value="false"] .ol-switch-fill { + background-color: var(--ol-switch-off); +} + +.switch-control.coalition .ol-switch>.ol-switch-fill::before, +.switch-control.yes-no .ol-switch>.ol-switch-fill::before { + translate:-100% 0; + transform: none; +} + +.switch-control.yes-no .ol-switch[data-value="true"]>.ol-switch-fill::before { + content: "YES"; +} + +.switch-control.yes-no .ol-switch[data-value="false"]>.ol-switch-fill::before { + content: "NO"; +} + + .ol-contexmenu-panel { padding: 20px; } @@ -1439,6 +1410,26 @@ input[type=number]::-webkit-outer-spin-button { .ol-coalition-switch[data-value="undefined"]>.ol-switch-fill { background-color: var(--primary-neutral); } +/* +#unit-control-panel .switch-control .ol-switch-fill::after { + background-color: white; +} +*/ +.switch-control.coalition [data-value="true"] .ol-switch-fill { + background-color: var(--accent-light-blue); +} + +.switch-control.coalition [data-value="false"] .ol-switch-fill { + background-color: var(--primary-red); +} + +.switch-control.coalition [data-value="true"]>.ol-switch-fill::before { + content: "BLUE" !important; +} + +.switch-control.coalition [data-value="false"]>.ol-switch-fill::before { + content: "RED" !important; +} .ol-context-menu>ul { max-height: 200px; diff --git a/client/public/themes/olympus/theme.css b/client/public/themes/olympus/theme.css index 6e559ea9..b086467c 100644 --- a/client/public/themes/olympus/theme.css +++ b/client/public/themes/olympus/theme.css @@ -45,6 +45,7 @@ --nav-text: #ECECEC; --ol-select-secondary: #545F6C; + --ol-switch-off:#686868; /*** General border radii **/ --border-radius-xs: 2px; @@ -88,3 +89,7 @@ --unit-fuel-y: 22px; --unit-vvi-width: 4px; } + +* { + font-weight:600; +} diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 6fbb87ec..6100b84a 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -107,6 +107,19 @@ export class UnitControlPanel extends Panel { getApp().getUnitsManager().setOperateAs(value); }); + /* Mouseover of (?) highlights activation buttons */ + const operateAsQuestionMark = this.getElement().querySelector("#operate-as h4 img"); + operateAsQuestionMark.addEventListener("mouseover", () => { + document.querySelectorAll(`#rapid-controls button.scenic-action`).forEach((btn:Element) => { + btn.classList.add(`pulse`); + }); + }); + operateAsQuestionMark.addEventListener("mouseout", () => { + document.querySelectorAll(`#rapid-controls button.scenic-action.pulse`).forEach((btn:Element) => { + btn.classList.remove(`pulse`); + }); + }); + /* Advanced settings dialog */ this.#advancedSettingsDialog = document.querySelector("#advanced-settings-dialog"); @@ -324,6 +337,8 @@ export class UnitControlPanel extends Panel { let button = document.createElement("button"); button.title = contextAction.getDescription(); button.classList.add("ol-button", "unit-action-button"); + if (contextAction.getOptions().isScenic) + button.classList.add("scenic-action"); button.id = key; rapidControlsContainer.appendChild(button); button.onclick = () => { diff --git a/client/src/unit/contextaction.ts b/client/src/unit/contextaction.ts index 32da1dc1..dd3a4b6b 100644 --- a/client/src/unit/contextaction.ts +++ b/client/src/unit/contextaction.ts @@ -1,5 +1,9 @@ import { Unit } from "./unit"; +export interface ContextActionOptions { + isScenic?: boolean +} + export class ContextAction { #id: string = ""; #label: string = ""; @@ -7,13 +11,18 @@ export class ContextAction { #callback: CallableFunction | null = null; #units: Unit[] = []; #hideContextAfterExecution: boolean = true + #options: ContextActionOptions; - constructor(id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true) { - this.#id = id; + constructor(id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true, options: ContextActionOptions) { + this.#id = id; this.#label = label; this.#description = description; this.#callback = callback; this.#hideContextAfterExecution = hideContextAfterExecution; + this.#options = { + "isScenic": false, + ...options + } } addUnit(unit: Unit) { @@ -28,6 +37,10 @@ export class ContextAction { return this.#label; } + getOptions() { + return this.#options; + } + getDescription() { return this.#description; } diff --git a/client/src/unit/contextactionset.ts b/client/src/unit/contextactionset.ts index afea52ed..d061cd1d 100644 --- a/client/src/unit/contextactionset.ts +++ b/client/src/unit/contextactionset.ts @@ -1,4 +1,4 @@ -import { ContextAction } from "./contextaction"; +import { ContextAction, ContextActionOptions } from "./contextaction"; import { Unit } from "./unit"; export class ContextActionSet { @@ -8,9 +8,11 @@ export class ContextActionSet { } - addContextAction(unit: Unit, id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true) { + addContextAction(unit: Unit, id: string, label: string, description: string, callback: CallableFunction, hideContextAfterExecution: boolean = true, options?:ContextActionOptions) { + options = options || {}; + if (!(id in this.#contextActions)) { - this.#contextActions[id] = new ContextAction(id, label, description, callback, hideContextAfterExecution); + this.#contextActions[id] = new ContextAction(id, label, description, callback, hideContextAfterExecution, options); } this.#contextActions[id].addUnit(unit); } diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index 28b3f2d3..274b08e2 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -1549,13 +1549,17 @@ export class GroundUnit extends Unit { if (targetPosition !== null) { if (this.canTargetPoint()) { contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", (units: Unit[]) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) }); - contextActionSet.addContextAction(this, "simulate-fire-fight", "Simulate fire fight", "Simulate a fire fight by shooting randomly in a certain large area. WARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) }); + contextActionSet.addContextAction(this, "simulate-fire-fight", "Simulate fire fight", "Simulate a fire fight by shooting randomly in a certain large area.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) }); } } else { if (this.canAAA()) { - contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when a enemy unit gets close enough. WARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().scenicAAA(units) }); - contextActionSet.addContextAction(this, "miss-aaa", "Misson on purpose", "Shoot AAA towards the closest enemy unit, but don't aim precisely. WARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().missOnPurpose(units) }); + contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when a enemy unit gets close enough.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().scenicAAA(units) }, undefined, { + "isScenic": true + }); + contextActionSet.addContextAction(this, "miss-aaa", "Miss on purpose", "Shoot AAA towards the closest enemy unit, but don't aim precisely.\nWARNING: works correctly only on neutral units, blue or red units will aim", (units: Unit[]) => { getApp().getUnitsManager().missOnPurpose(units) }, undefined, { + "isScenic": true + }); } } } diff --git a/client/views/panels/unitcontrol.ejs b/client/views/panels/unitcontrol.ejs index 740c7909..946037de 100644 --- a/client/views/panels/unitcontrol.ejs +++ b/client/views/panels/unitcontrol.ejs @@ -16,7 +16,6 @@
-

Controls

Speed
@@ -43,6 +42,8 @@
Multiple categories selected
+
+

Rules of engagement

@@ -78,27 +79,27 @@
-
+

Enable tanker

-
+

Airborne Early Warning

-
+

Operate as

-
+

Unit active

-
+

Follow roads

@@ -107,14 +108,15 @@
- +
Delete unit +
-

+