Nearly final commit of control tips' v1

This commit is contained in:
PeekabooSteam
2023-09-14 17:55:01 +01:00
parent d2e162edbf
commit 798856c649
20 changed files with 1612 additions and 7244 deletions

8407
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -43,6 +43,7 @@
"requirejs": "^2.3.6",
"sortablejs": "^1.15.0",
"tsify": "^5.0.4",
"tslib": "latest",
"typescript": "^4.9.4",
"watchify": "^4.0.0"
}

View File

@@ -60,6 +60,11 @@
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
@@ -646,7 +651,7 @@ svg.leaflet-image-layer.leaflet-interactive path {
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {

View File

@@ -2,6 +2,7 @@
@import url("atc/atc.css");
@import url("atc/unitdatatable.css");
@import url("aic/aic.css");
@import url("other/controltips.css");
@import url("panels/connectionstatus.css");
@import url("panels/serverstatus.css");
@import url("panels/mouseinfo.css");

View File

@@ -0,0 +1,33 @@
#control-tips-panel {
align-self: center;
display: flex;
flex-flow: column wrap;
font-size: 13px;
justify-self: flex-end;
position: absolute;
right: 10px;
row-gap: 20px;
text-align: right;
z-index: 999;
}
#control-tips-panel > * {
align-items: center;
align-self: end;
background-color: var( --background-steel );
border-radius: var(--border-radius-md);
color: white;
column-gap: 8px;
display:flex;
justify-items: right;
opacity: .85;
padding:5px;
width:fit-content;
}
#control-tips-panel > * > .key {
background-color: white;
border-radius: var( --border-radius-sm );
color: var( --background-steel );
padding:1px 4px;
}

View File

@@ -145,6 +145,7 @@ export const IADSDensities: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3
export const SHOW_CONTACT_LINES = "Show unit contact lines";
export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out";
export const SHOW_CONTROL_TIPS = "Show control tips";
export const SHOW_UNIT_LABELS = "Show unit labels";
export const SHOW_UNIT_PATHS = "Show unit paths";
export const SHOW_UNIT_TARGETS = "Show unit targets";

View File

@@ -45,7 +45,7 @@ class FeatureSwitch {
}
isEnabled() {
isEnabled():boolean {
if ( this.forceState === 0 ) {
return false;
@@ -81,11 +81,18 @@ export class FeatureSwitches {
new FeatureSwitch({
"defaultEnabled": false,
"forceState": 1,
"forceState": -1,
"label": "ATC",
"name": "atc"
}),
new FeatureSwitch({
"defaultEnabled": false,
"forceState": -1,
"label": "Control tips",
"name": "controlTips"
}),
new FeatureSwitch({
"defaultEnabled": false,
"forceState": -1,
@@ -150,4 +157,16 @@ export class FeatureSwitches {
}
savePreference( featureSwitchName:string, value:boolean ) {
const preferences = JSON.parse( localStorage.getItem( "featureSwitches" ) || "{}" );
if ( preferences.hasOwnProperty( featureSwitchName ) ) {
preferences[ featureSwitchName ] = value;
}
localStorage.setItem("featureSwitches", JSON.stringify(preferences));
}
}

View File

@@ -179,10 +179,12 @@ function setupEvents( indexApp:OlympusApp ) {
})
)
.add( "togglePause", new ShortcutKeyboard({
"altKey": false,
"callback": () => {
setPaused(!getPaused());
},
"code": "space"
"code": "Space",
"ctrlKey": false
})
);

View File

@@ -10,6 +10,7 @@ import { ServerStatusPanel } from "./panels/serverstatuspanel";
import { UnitControlPanel } from "./panels/unitcontrolpanel";
import { UnitInfoPanel } from "./panels/unitinfopanel";
import { Popup } from "./popups/popup";
import { ControlTips } from "./shortcut/controltips";
import { UnitsManager } from "./unit/unitsmanager";
export interface IIndexApp extends IOlympusApp {
@@ -60,6 +61,8 @@ export class IndexApp extends OlympusApp {
super.start();
new ControlTips( "control-tips-panel", this );
}
}

View File

@@ -12,11 +12,12 @@ import { DestinationPreviewMarker } from "./destinationpreviewmarker";
import { TemporaryUnitMarker } from "./temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector'
import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTooltips, MOVE_UNIT, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS } from "../constants/constants";
import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTooltips, MOVE_UNIT, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS, SHOW_CONTROL_TIPS } from "../constants/constants";
import { TargetMarker } from "./targetmarker";
import { CoalitionArea } from "./coalitionarea";
import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu";
import { DrawingCursor } from "./drawingcursor";
import { OlympusApp } from "../olympusapp";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
@@ -67,6 +68,8 @@ export class Map extends L.Map {
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
#visibilityOptions: { [key: string]: boolean } = {}
#olympusApp!:OlympusApp;
constructor(ID: string) {
/* Init the leaflet map */
//@ts-ignore Needed because the boxSelect option is non-standard
@@ -153,6 +156,7 @@ export class Map extends L.Map {
document.addEventListener("mapVisibilityOptionsChanged", () => {
this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]);
this.getOlympusApp().getControlTips().toggle( !this.getVisibilityOptions()[SHOW_CONTROL_TIPS] );
});
/* Pan interval */
@@ -176,6 +180,10 @@ export class Map extends L.Map {
this.#visibilityOptions[SHOW_UNIT_PATHS] = true;
this.#visibilityOptions[SHOW_UNIT_TARGETS] = true;
this.#visibilityOptions[SHOW_UNIT_LABELS] = true;
// Manual until we use the OlympusApp approach
this.#visibilityOptions[SHOW_CONTROL_TIPS] = JSON.parse( localStorage.getItem( "featureSwitches" ) || "{}" )?.controlTips || true;
this.#mapVisibilityOptionsDropdown.setOptionsElements(Object.keys(this.#visibilityOptions).map((option: string) => {
return createCheckboxOption(option, option, this.#visibilityOptions[option], (ev: any) => {
this.#setVisibilityOption(option, ev);
@@ -776,5 +784,26 @@ export class Map extends L.Map {
this.#visibilityOptions[option] = ev.currentTarget.checked;
document.dispatchEvent(new CustomEvent("mapVisibilityOptionsChanged"));
}
getOlympusApp() {
return this.#olympusApp;
}
setOlympusApp( olympusApp:OlympusApp ) {
this.#olympusApp = olympusApp;
// Bit crappy until we move to a more structured set of code
let controlTipsBoolean = this.getOlympusApp().getFeatureSwitches().getSwitch( "controlTips" )?.isEnabled();
controlTipsBoolean = ( typeof controlTipsBoolean === "boolean" ) ? controlTipsBoolean : true;
this.#visibilityOptions[SHOW_CONTROL_TIPS] = controlTipsBoolean;
document.dispatchEvent(new CustomEvent("mapVisibilityOptionsChanged"));
}
}

View File

@@ -3,6 +3,7 @@ import { FeatureSwitches } from "./features/featureswitches";
import { Map } from "./map/map";
import { MissionHandler } from "./mission/missionhandler";
import { PanelsManager } from "./panels/panelsmanager";
import { ControlTips } from "./shortcut/controltips";
import { ShortcutManager } from "./shortcut/shortcutmanager";
import { UnitsManager } from "./unit/unitsmanager";
@@ -17,6 +18,7 @@ export interface IOlympusApp {
export abstract class OlympusApp {
#controlTips: ControlTips;
#featureSwitches: FeatureSwitches;
#map: Map;
#missionHandler: MissionHandler;
@@ -27,12 +29,19 @@ export abstract class OlympusApp {
constructor( config:IOlympusApp ) {
this.#controlTips = new ControlTips( "control-tips-panel", this );
this.#featureSwitches = config.featureSwitches;
this.#map = config.map;
this.#missionHandler = config.missionHandler;
this.#unitDataTable = config.unitDataTable;
this.#unitsManager = config.unitsManager;
this.getMap().setOlympusApp( this );
}
getControlTips() {
return this.#controlTips;
}
getFeatureSwitches() {
@@ -71,6 +80,7 @@ export abstract class OlympusApp {
// Start the app
}
}

View File

@@ -7,8 +7,12 @@ export abstract class Panel {
#eventsManager!: PanelEventsManager;
#olympusApp!: OlympusApp;
constructor(ID: string) {
constructor(ID: string, olympusApp?:OlympusApp ) {
this.#element = <HTMLElement>document.getElementById(ID);
if ( olympusApp ) {
this.setOlympusApp( olympusApp );
}
}
show() {
@@ -46,7 +50,7 @@ export abstract class Panel {
}
setOlympusApp( olympusApp:OlympusApp ) {
this.#olympusApp = olympusApp;
this.#olympusApp = olympusApp;
this.#eventsManager = new PanelEventsManager( this.getOlympusApp() );
}

View File

@@ -1,6 +0,0 @@
{
"author": "J. R. Hartley",
"exportedClassName": "PluginHelloWorld",
"name": "Hello World",
"version": "1.2.3"
}

View File

@@ -1,89 +0,0 @@
import { OlympusApp } from "../../olympusapp";
import { Plugin } from "../../plugin/plugin";
import { ShortcutManager } from "../../shortcut/shortcutmanager";
export class PluginHelloWorld extends Plugin {
#element:HTMLElement;
#shortcutManager:ShortcutManager;
constructor( olympusApp:OlympusApp ) {
super( olympusApp, "HelloWorld" );
const templates = {
bar: `<div id="shortcut-bar"
style="
background-color:var( --background-steel );
border-radius:var( --border-radius-md );
bottom:100px;
color:white;
display:flex;
font-size:12px;
justify-self:center;
line-height:28px;
padding:5px;
position:absolute;
z-index:999;"></div>`
}
document.body.insertAdjacentHTML( "beforeend", templates.bar );
this.#element = <HTMLElement>document.getElementById( "shortcut-bar" );
this.#shortcutManager = this.getOlympusApp().getShortcutManager();
this.#shortcutManager.onKeyDown( () => {
this.#updateText()
});
this.#shortcutManager.onKeyUp( () => {
this.#updateText()
});
this.#updateText();
}
#matches( combo:string[], heldKeys:string[] ) {
if ( combo.length !== heldKeys.length ) {
return false;
}
return combo.every( key => heldKeys.indexOf( key ) > -1 );
}
#updateText() {
const heldKeys = this.#shortcutManager.getKeysBeingHeld();
const combos:Array<object> = [
{
"keys": [],
"text": `[CTRL]: Pin tool | [SHIFT]: box select tool<br />[Mouse1+drag]: Move map | [Mouse2]: Spawn menu `
},
{
"keys": [ "ControlLeft" ],
"text": "Mouse1: drop pin"
},
{
"keys": [ "ShiftLeft" ],
"text": "Mouse1+drag: select units"
}
];
const currentCombo:any = combos.find( (combo:any) => this.#matches( combo.keys, heldKeys ) );
if ( currentCombo ) {
this.#element.innerHTML = currentCombo.text;
this.#element.classList.remove( "hide" );
} else {
this.#element.classList.add( "hide" );
}
}
}

View File

@@ -0,0 +1,193 @@
import { OlympusApp } from "../olympusapp";
import { ShortcutManager } from "../shortcut/shortcutmanager";
import { Unit } from "../unit/unit";
export class ControlTips {
#element:HTMLElement;
#cursorIsHoveringOverUnit:boolean = false;
#olympusApp:OlympusApp;
#shortcutManager:ShortcutManager;
constructor( ID:string, olympusApp:OlympusApp ) {
this.#element = <HTMLElement>document.getElementById( ID );
this.#olympusApp = olympusApp;
this.#shortcutManager = this.#olympusApp.getShortcutManager();
this.#shortcutManager.onKeyDown( () => {
this.#updateTips()
});
this.#shortcutManager.onKeyUp( () => {
this.#updateTips()
});
document.addEventListener( "unitDeselection", ( ev:CustomEvent ) => {
this.#updateTips();
});
document.addEventListener( "unitMouseover", ( ev:CustomEventInit ) => {
this.#cursorIsHoveringOverUnit = true;
this.#updateTips();
});
document.addEventListener( "unitMouseout", ( ev:CustomEventInit ) => {
this.#cursorIsHoveringOverUnit = false;
this.#updateTips();
});
document.addEventListener( "unitSelection", ( ev:CustomEvent ) => {
this.#updateTips()
});
this.#updateTips();
}
getElement() {
return this.#element;
}
#getOlympusApp() {
return this.#olympusApp;
}
toggle( bool?:boolean ) {
this.getElement().classList.toggle( "hide", bool );
this.#olympusApp.getFeatureSwitches().savePreference( "controlTips", !this.getElement().classList.contains( "hide" ) );
}
#updateTips() {
const combos:Array<object> = [
{
"keys": [],
"tips": [
{
"key": `W/A/S/D`,
"action": `Pan map`,
"showIfUnitSelected": false
},
{
"key": `SHIFT`,
"action": `Box select`,
"showIfUnitSelected": false
},
{
"key": `Mouse1`,
"action": `Deselect`,
"showIfUnitSelected": true
},
{
"key": `Mouse1+drag`,
"action": `Move map`,
"showIfUnitSelected": false
},
{
"key": `Mouse2`,
"action": `Spawn menu`,
"showIfUnitSelected": false
},
{
"key": `Mouse2`,
"action": `Set first waypoint`,
"showIfUnitSelected": true,
"unitsMustBeControlled": true
},
{
"key": `Delete`,
"action": `Delete unit`,
"showIfUnitSelected": true
},
{
"key": "CTRL",
"action": " (more...)"
}
]
},
{
"keys": [ "ControlLeft" ],
"tips": [
{
"key": `Mouse1`,
"action": "Toggle pin",
"showIfUnitSelected": false,
"showIfHoveringOverUnit": false
},
{
"key": `Mouse1`,
"action": "Toggle selection",
"showIfUnitSelected": true,
"showIfHoveringOverUnit": true
},
{
"key": `Mouse2`,
"action": `Add waypoint`,
"showIfUnitSelected": true
}
]
},
{
"keys": [ "ShiftLeft" ],
"tips": [
{
"key": `mouse1+drag`,
"action": "Box select"
}
]
}
];
const currentCombo:any = combos.find( (combo:any) => this.#shortcutManager.keyComboMatches( combo.keys ) ) || combos[0];
const element = this.getElement();
element.innerHTML = "";
const a = this.#getOlympusApp();
let numSelectedUnits = 0;
let unitSelectionContainsControlled = false;
if ( this.#getOlympusApp().getUnitsManager() ) {
let selectedUnits = Object.values( this.#getOlympusApp().getUnitsManager().getSelectedUnits() );
numSelectedUnits = selectedUnits.length;
unitSelectionContainsControlled = selectedUnits.some( (unit:Unit) => unit.getControlled() );
}
currentCombo.tips.forEach( ( tip:any ) => {
if ( numSelectedUnits > 0 ) {
if ( tip.showIfUnitSelected === false ) {
return;
}
if ( tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false ) {
return;
}
}
if ( numSelectedUnits === 0 && tip.showIfUnitSelected === true ) {
return;
}
// console.log( tip.action, "state:", this.#cursorIsHoveringOverUnit, "typeof", typeof tip.showIfHoveringOverUnit, "logic", tip.showIfHoveringOverUnit !== this.#cursorIsHoveringOverUnit );
if ( typeof tip.showIfHoveringOverUnit === "boolean" && tip.showIfHoveringOverUnit !== this.#cursorIsHoveringOverUnit ) {
return;
}
element.innerHTML += `<div><span class="key">${tip.key}</span><span class="action">${tip.action}</span></div>`
});
// console.log( "----" );
}
}

View File

@@ -35,6 +35,18 @@ export class ShortcutManager extends Manager {
return this.#keysBeingHeld;
}
keyComboMatches( combo:string[] ) {
const heldKeys = this.getKeysBeingHeld();
if ( combo.length !== heldKeys.length ) {
return false;
}
return combo.every( key => heldKeys.indexOf( key ) > -1 );
}
onKeyDown( callback:CallableFunction ) {
this.#keyDownCallbacks.push( callback );
}

View File

@@ -154,8 +154,16 @@ export class Unit extends CustomMarker {
this.on('click', (e) => this.#onClick(e));
this.on('dblclick', (e) => this.#onDoubleClick(e));
this.on('contextmenu', (e) => this.#onContextMenu(e));
this.on('mouseover', () => { if (this.belongsToCommandedCoalition()) this.setHighlighted(true); })
this.on('mouseout', () => { this.setHighlighted(false); })
this.on('mouseover', () => {
if (this.belongsToCommandedCoalition()) {
this.setHighlighted(true);
document.dispatchEvent(new CustomEvent("unitMouseover", { detail: this }));
}
});
this.on('mouseout', () => {
this.setHighlighted(false);
document.dispatchEvent(new CustomEvent("unitMouseout", { detail: this }));
});
getMap().on("zoomend", () => { this.#onZoom(); })
/* Deselect units if they are hidden */
@@ -329,11 +337,9 @@ export class Unit extends CustomMarker {
this.#selected = selected;
if (selected) {
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
this.#updateMarker();
}
else {
document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this }));
this.#clearContacts();
this.#clearPath();
this.#clearTarget();
@@ -347,6 +353,13 @@ export class Unit extends CustomMarker {
this.#updateMarker();
}
// Trigger events after all (de-)selecting has been done
if (selected) {
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
} else {
document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this }));
}
}
}

View File

@@ -20,6 +20,8 @@
<%- include('atc/atc.ejs') %>
<%- include('atc/unitdatatable.ejs') %>
<%- include('other/controltips.ejs') %>
<%- include('panels/unitcontrol.ejs') %>
<%- include('panels/unitinfo.ejs') %>
<%- include('panels/mouseinfo.ejs') %>

View File

@@ -0,0 +1 @@
<div id="control-tips-panel"></div>

View File

@@ -63,7 +63,6 @@
</div>
</div>
<!--
<div id="atc-navbar-control" class="ol-group-container ol-navbar-buttons-group" data-feature-switch="atc">
<div class="ol-group">
<button data-on-click="toggleElements"
@@ -71,5 +70,5 @@
<button data-on-click="toggleElements"
data-on-click-params='{"selector": "#strip-board-tower"}' class="off"><img src="resources/theme/images/buttons/tools/tower.svg" inject-svg></button>
</div>
</div> -->
</div>
</nav>