diff --git a/client/src/atc/unitdatatable.ts b/client/src/atc/unitdatatable.ts index ea28c44e..7ad9d34d 100644 --- a/client/src/atc/unitdatatable.ts +++ b/client/src/atc/unitdatatable.ts @@ -4,7 +4,7 @@ import { Unit } from "../unit/unit"; export class UnitDataTable extends Panel { constructor(id: string) { - super(id); + super( id ); this.hide(); } diff --git a/client/src/index.ts b/client/src/index.ts index e16f8562..3a925873 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -19,6 +19,7 @@ import { SVGInjector } from "@tanem/svg-injector"; import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants"; import { ServerStatusPanel } from "./panels/serverstatuspanel"; import { WeaponsManager } from "./weapon/weaponsmanager"; +import { IndexApp } from "./indexapp"; var map: Map; @@ -90,6 +91,32 @@ function setup() { /* Load the config file */ getConfig(readConfig); + + /* + This is done like this for now as a way to make it work in the new and old world. + Over time/at some point, we'll need to start migrating the pre-existing code to an "app" format + */ + + const indexApp = new IndexApp({ + "featureSwitches": featureSwitches, + "map": map, + "missionHandler": missionHandler, + "panels": { + "connectionStatus": connectionStatusPanel, + "hotgroup": hotgroupPanel, + "infoPopup": infoPopup, + "log": logPanel, + "mouseInfo": mouseInfoPanel, + "serverStatus": serverStatusPanel, + "unitControl": unitControlPanel, + "unitInfo": unitInfoPanel + }, + "unitDataTable": unitDataTable, + "unitsManager": unitsManager + }); + + indexApp.start(); + } function readConfig(config: any) { diff --git a/client/src/indexapp.ts b/client/src/indexapp.ts new file mode 100644 index 00000000..30c20287 --- /dev/null +++ b/client/src/indexapp.ts @@ -0,0 +1,78 @@ +import { FeatureSwitches } from "./features/featureswitches"; +import { Map } from "./map/map"; +import { MissionHandler } from "./mission/missionhandler"; +import { IOlympusApp, OlympusApp } from "./olympusapp"; +import { ConnectionStatusPanel } from "./panels/connectionstatuspanel"; +import { HotgroupPanel } from "./panels/hotgrouppanel"; +import { LogPanel } from "./panels/logpanel"; +import { MouseInfoPanel } from "./panels/mouseinfopanel"; +import { Panel } from "./panels/panel"; +import { ServerStatusPanel } from "./panels/serverstatuspanel"; +import { UnitControlPanel } from "./panels/unitcontrolpanel"; +import { UnitInfoPanel } from "./panels/unitinfopanel"; +import { PluginManager } from "./plugin/pluginmanager"; +import { PluginHelloWorld } from "./plugins/helloworld/pluginhelloworld"; +import { Popup } from "./popups/popup"; +import { UnitsManager } from "./unit/unitsmanager"; + +export interface IIndexApp extends IOlympusApp { + "featureSwitches": FeatureSwitches, + "map": Map, + "missionHandler": MissionHandler, + "panels": IIndexAppPanels, + "unitsManager": UnitsManager +} + +export interface IIndexAppPanels { + "connectionStatus": ConnectionStatusPanel, + "hotgroup": HotgroupPanel, + "infoPopup": Popup, + "log": LogPanel, + "mouseInfo": MouseInfoPanel, + "serverStatus": ServerStatusPanel, + "unitControl": UnitControlPanel, + "unitInfo": UnitInfoPanel +} + +export class IndexApp extends OlympusApp { + + #pluginManager!: PluginManager; + + constructor( config:IIndexApp ) { + + super( config ); + + // this.setMap( config.map ); + + // Panels + this.getPanelsManager().add( "connectionStatus", config.panels.connectionStatus ); + this.getPanelsManager().add( "hotgroup", config.panels.hotgroup ); + this.getPanelsManager().add( "log", config.panels.log ); + this.getPanelsManager().add( "mouseInfo", config.panels.mouseInfo ); + this.getPanelsManager().add( "serverStatus", config.panels.serverStatus ); + this.getPanelsManager().add( "unitControl", config.panels.unitControl ); + this.getPanelsManager().add( "unitInfo", config.panels.unitInfo ); + + // Popup + this.getPanelsManager().add( "unitPopup", config.panels.infoPopup ); + + // Retrofitting + Object.values( this.getPanelsManager().getAll() ).forEach( ( panel:Panel ) => { + panel.setOlympusApp( this ); + }); + + // Plugins + this.#pluginManager = new PluginManager( this ); + + // Manual loading for now + this.#pluginManager.add( "helloWorld", new PluginHelloWorld( this ) ); + } + + + start() { + + super.start(); + + } + +} \ No newline at end of file diff --git a/client/src/map/map.ts b/client/src/map/map.ts index f809a388..c496e6a8 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -12,7 +12,7 @@ import { DestinationPreviewMarker } from "./destinationpreviewmarker"; import { TemporaryUnitMarker } from "./temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; import { SVGInjector } from '@tanem/svg-injector' -import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, 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 } from "../constants/constants"; import { TargetMarker } from "./targetmarker"; import { CoalitionArea } from "./coalitionarea"; import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu"; @@ -20,7 +20,7 @@ import { DrawingCursor } from "./drawingcursor"; L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); -// TODO would be nice to convert to ts +// TODO would be nice to convert to ts - yes require("../../public/javascripts/leaflet.nauticscale.js") require("../../public/javascripts/L.Path.Drag.js") diff --git a/client/src/olympusapp.ts b/client/src/olympusapp.ts new file mode 100644 index 00000000..aa031d7f --- /dev/null +++ b/client/src/olympusapp.ts @@ -0,0 +1,67 @@ +import { UnitDataTable } from "./atc/unitdatatable"; +import { FeatureSwitches } from "./features/featureswitches"; +import { Map } from "./map/map"; +import { MissionHandler } from "./mission/missionhandler"; +import { PanelsManager } from "./panels/panelsmanager"; +import { UnitsManager } from "./unit/unitsmanager"; + +export interface IOlympusApp { + featureSwitches: FeatureSwitches; + missionHandler: MissionHandler; + unitDataTable: UnitDataTable; + unitsManager: UnitsManager; +} + +export abstract class OlympusApp { + + #featureSwitches: FeatureSwitches; + #map!: Map; + #missionHandler: MissionHandler; + #panelsManager: PanelsManager = new PanelsManager( this ); + #unitDataTable: UnitDataTable; + #unitsManager: UnitsManager; + + constructor( config:IOlympusApp ) { + + this.#featureSwitches = config.featureSwitches; + this.#missionHandler = config.missionHandler; + this.#unitDataTable = config.unitDataTable; + this.#unitsManager = config.unitsManager; + + } + + getFeatureSwitches() { + return this.#featureSwitches; + } + + getMap() { + return this.#map; + } + + getPanelsManager() { + return this.#panelsManager; + } + + getUnitDataTable() { + return this.#unitDataTable; + } + + getUnitsManager() { + return this.#unitsManager; + } + + getWeaponsManager() { + return this.getWeaponsManager; + } + + setMap( map:Map ) { + this.#map = map; + } + + start() { + + // Start the app + + } + +} \ No newline at end of file diff --git a/client/src/other/eventsmanager.ts b/client/src/other/eventsmanager.ts new file mode 100644 index 00000000..6bdc504c --- /dev/null +++ b/client/src/other/eventsmanager.ts @@ -0,0 +1,10 @@ +import { OlympusApp } from "../olympusapp"; +import { Manager } from "./manager"; + +export abstract class EventsManager extends Manager { + + constructor( olympusApp:OlympusApp ) { + super( olympusApp ); + } + +} \ No newline at end of file diff --git a/client/src/other/manager.ts b/client/src/other/manager.ts new file mode 100644 index 00000000..80d2bddd --- /dev/null +++ b/client/src/other/manager.ts @@ -0,0 +1,52 @@ +import { OlympusApp } from "../olympusapp"; + +export interface IManager { + add:CallableFunction; +} + +export abstract class Manager { + + #items: {[key:string]: any } = {}; + #olympusApp: OlympusApp; + + constructor( olympusApp:OlympusApp ) { + + this.#olympusApp = olympusApp; + + } + + add( name:string, item:any ) { + + const regex = new RegExp( "^[a-z][a-z0-9]{2,}$", "i" ); + + if ( regex.test( name ) === false ) { + throw new Error( `Item name "${name}" does not match regex: ${regex.toString()}.` ); + } + + if ( this.#items.hasOwnProperty( name ) ) { + throw new Error( `Item with name "${name}" already exists.` ); + } + + this.#items[ name ] = item; + + } + + get( name:string ) { + + if ( this.#items.hasOwnProperty( name ) ) { + return this.#items[ name ]; + } else { + return false; + } + + } + + getAll() { + return this.#items; + } + + getOlympusApp() { + return this.#olympusApp; + } + +} \ No newline at end of file diff --git a/client/src/panels/connectionstatuspanel.ts b/client/src/panels/connectionstatuspanel.ts index 54a3f7f6..73fad262 100644 --- a/client/src/panels/connectionstatuspanel.ts +++ b/client/src/panels/connectionstatuspanel.ts @@ -2,7 +2,7 @@ import { Panel } from "./panel"; export class ConnectionStatusPanel extends Panel { constructor(ID: string) { - super(ID); + super( ID ); } update(connected: boolean) { diff --git a/client/src/panels/hotgrouppanel.ts b/client/src/panels/hotgrouppanel.ts index 5af828e6..120899bc 100644 --- a/client/src/panels/hotgrouppanel.ts +++ b/client/src/panels/hotgrouppanel.ts @@ -4,7 +4,7 @@ import { Panel } from "./panel"; export class HotgroupPanel extends Panel { constructor(ID: string) { - super(ID); + super( ID ); document.addEventListener("unitDeath", () => this.refreshHotgroups()); } diff --git a/client/src/panels/logpanel.ts b/client/src/panels/logpanel.ts index 92fbb702..68b2ea6d 100644 --- a/client/src/panels/logpanel.ts +++ b/client/src/panels/logpanel.ts @@ -8,7 +8,7 @@ export class LogPanel extends Panel { #logs: {[key: string]: string} = {}; constructor(ID: string) { - super(ID); + super( ID ); document.addEventListener("toggleLogPanel", () => { this.getElement().classList.toggle("open"); diff --git a/client/src/panels/mouseinfopanel.ts b/client/src/panels/mouseinfopanel.ts index 923cca9e..682384d0 100644 --- a/client/src/panels/mouseinfopanel.ts +++ b/client/src/panels/mouseinfopanel.ts @@ -13,7 +13,7 @@ export class MouseInfoPanel extends Panel { #measureBox: HTMLElement; constructor(ID: string) { - super(ID); + super( ID ); this.#measureIcon = new Icon({ iconUrl: 'resources/theme/images/icons/pin.svg', iconAnchor: [16, 32] }); this.#measureMarker = new Marker([0, 0], { icon: this.#measureIcon, interactive: false }); diff --git a/client/src/panels/panel.ts b/client/src/panels/panel.ts index df0a2f48..47ac9c3d 100644 --- a/client/src/panels/panel.ts +++ b/client/src/panels/panel.ts @@ -1,6 +1,11 @@ -export class Panel { +import { OlympusApp } from "../olympusapp"; +import { PanelEventsManager } from "./paneleventsmanager"; + +export abstract class Panel { + #element: HTMLElement - #visible: boolean = true; + #eventsManager!: PanelEventsManager; + #olympusApp!: OlympusApp; constructor(ID: string) { this.#element = document.getElementById(ID); @@ -8,17 +13,17 @@ export class Panel { show() { this.#element.classList.toggle("hide", false); - this.#visible = true; + this.getEventsManager()?.trigger( "show", {} ); } hide() { this.#element.classList.toggle("hide", true); - this.#visible = false; + this.getEventsManager()?.trigger( "hide", {} ); } toggle() { // Simple way to track if currently visible - if (this.#visible) + if (this.getVisible()) this.hide(); else this.show(); @@ -29,6 +34,20 @@ export class Panel { } getVisible(){ - return this.#visible; + return (!this.getElement().classList.contains( "hide" ) ); } + + getEventsManager() { + return this.#eventsManager; + } + + getOlympusApp() { + return this.#olympusApp; + } + + setOlympusApp( olympusApp:OlympusApp ) { + this.#olympusApp = olympusApp; + this.#eventsManager = new PanelEventsManager( this.getOlympusApp() ); + } + } \ No newline at end of file diff --git a/client/src/panels/paneleventsmanager.ts b/client/src/panels/paneleventsmanager.ts new file mode 100644 index 00000000..d98cf10a --- /dev/null +++ b/client/src/panels/paneleventsmanager.ts @@ -0,0 +1,50 @@ +import { OlympusApp } from "../olympusapp"; +import { EventsManager } from "../other/eventsmanager"; + +interface IListener { + callback: CallableFunction; + name?: string +} + +export class PanelEventsManager extends EventsManager { + + constructor( olympusApp:OlympusApp ) { + + super( olympusApp ); + + this.add( "hide", [] ); + this.add( "show", [] ); + + } + + on( eventName:string, listener:IListener ) { + + const event = this.get( eventName ); + + if ( !event ) { + throw new Error( `Event name "${eventName}" is not valid.` ); + } + + this.get( eventName ).push({ + "callback": listener.callback + }); + + } + + trigger( eventName:string, contextData:object ) { + + const listeners = this.get( eventName ); + + if ( listeners ) { + + listeners.forEach( ( listener:IListener ) => { + + listener.callback( contextData ); + + }); + + } + + } + +} \ No newline at end of file diff --git a/client/src/panels/panelsmanager.ts b/client/src/panels/panelsmanager.ts new file mode 100644 index 00000000..9153753b --- /dev/null +++ b/client/src/panels/panelsmanager.ts @@ -0,0 +1,17 @@ +import { OlympusApp } from "../olympusapp"; +import { Manager } from "../other/manager"; +import { Panel } from "./panel"; + +export class PanelsManager extends Manager { + + #panels: { [key:string]: Panel } = {} + + constructor( olympusApp:OlympusApp ) { + super( olympusApp ); + } + + get( name:string ): Panel { + return super.get( name ); + } + +} \ No newline at end of file diff --git a/client/src/panels/serverstatuspanel.ts b/client/src/panels/serverstatuspanel.ts index 33cb1b7a..74a87a3f 100644 --- a/client/src/panels/serverstatuspanel.ts +++ b/client/src/panels/serverstatuspanel.ts @@ -2,7 +2,7 @@ import { Panel } from "./panel"; export class ServerStatusPanel extends Panel { constructor(ID: string) { - super(ID); + super( ID ); } update(frameRate: number, load: number) { diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 0f276367..3e2ff92a 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -26,7 +26,7 @@ export class UnitControlPanel extends Panel { #selectedUnitsTypes: string[] = []; constructor(ID: string) { - super(ID); + super( ID ); /* Unit control sliders */ this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getUnitsManager().selectedUnitsSetAltitude(ftToM(value)); }); diff --git a/client/src/panels/unitinfopanel.ts b/client/src/panels/unitinfopanel.ts index 2672bce2..aa39de2a 100644 --- a/client/src/panels/unitinfopanel.ts +++ b/client/src/panels/unitinfopanel.ts @@ -1,6 +1,4 @@ -import { getUnitsManager } from ".."; import { Ammo } from "../@types/unit"; -import { ConvertDDToDMS, rad2deg } from "../other/utils"; import { aircraftDatabase } from "../unit/aircraftdatabase"; import { Unit } from "../unit/unit"; import { Panel } from "./panel"; @@ -23,7 +21,7 @@ export class UnitInfoPanel extends Panel { #unitName: HTMLElement; constructor(ID: string) { - super(ID); + super( ID ); this.#altitude = (this.getElement().querySelector("#altitude")) as HTMLElement; this.#currentTask = (this.getElement().querySelector("#current-task")) as HTMLElement; diff --git a/client/src/plugin/plugin.ts b/client/src/plugin/plugin.ts new file mode 100644 index 00000000..130ade43 --- /dev/null +++ b/client/src/plugin/plugin.ts @@ -0,0 +1,32 @@ +import { OlympusApp } from "../olympusapp"; + +export interface PluginInterface { +} + +export abstract class Plugin { + + #olympusApp!:OlympusApp; + protected name = ""; + + constructor( olympusApp:OlympusApp, pluginName:string ) { + + const regex = "^[a-zA-Z][a-zA-Z\d]{4,}" + + if ( new RegExp( regex ).test( pluginName ) === false ) { + throw new Error( `Plugin names must match regex: ${regex}` ); + } + + this.name = pluginName; + this.#olympusApp = olympusApp; + + } + + getName() { + return this.name; + } + + getOlympusApp() { + return this.#olympusApp; + } + +} \ No newline at end of file diff --git a/client/src/plugin/pluginmanager.ts b/client/src/plugin/pluginmanager.ts new file mode 100644 index 00000000..7e017d5a --- /dev/null +++ b/client/src/plugin/pluginmanager.ts @@ -0,0 +1,13 @@ +import { OlympusApp } from "../olympusapp"; +import { Manager } from "../other/manager"; + + +export class PluginManager extends Manager { + + constructor( olympusApp:OlympusApp ) { + + super( olympusApp ); + + } + +} \ No newline at end of file diff --git a/client/src/plugins/helloworld/plugin.json b/client/src/plugins/helloworld/plugin.json new file mode 100644 index 00000000..c9024809 --- /dev/null +++ b/client/src/plugins/helloworld/plugin.json @@ -0,0 +1,6 @@ +{ + "author": "J. R. Hartley", + "exportedClassName": "PluginHelloWorld", + "name": "Hello World", + "version": "1.2.3" +} \ No newline at end of file diff --git a/client/src/plugins/helloworld/pluginhelloworld.ts b/client/src/plugins/helloworld/pluginhelloworld.ts new file mode 100644 index 00000000..a7126650 --- /dev/null +++ b/client/src/plugins/helloworld/pluginhelloworld.ts @@ -0,0 +1,29 @@ +import { OlympusApp } from "../../olympusapp"; +import { Plugin, PluginInterface } from "../../plugin/plugin"; + + +export class PluginHelloWorld extends Plugin implements PluginInterface { + + constructor( olympusApp:OlympusApp ) { + + super( olympusApp, "HelloWorld" ); + + const panel = this.getOlympusApp().getPanelsManager().get( "unitControl" ); + const em = panel.getEventsManager(); + + em.on( "show", { + "callback": () => { + console.log( "Showing unit control panel" ); + } + }); + + em.on( "hide", { + "callback": () => { + console.log( "Hiding unit control panel" ); + } + }); + + //const tpl = new ejs + + } +} \ No newline at end of file diff --git a/client/src/popups/popup.ts b/client/src/popups/popup.ts index 55f828ac..37af5df8 100644 --- a/client/src/popups/popup.ts +++ b/client/src/popups/popup.ts @@ -1,6 +1,11 @@ import { Panel } from "../panels/panel"; export class Popup extends Panel { + + constructor( elementId:string ) { + super( elementId ); + } + #fadeTime: number = 2000; // Milliseconds #hideTimer: number | undefined = undefined; #visibilityTimer: number | undefined = undefined;