mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Completed plugins framework
More work required to define all interfaces for the useful classes in the @types folder
This commit is contained in:
@@ -17,6 +17,9 @@ import { WeaponsManager } from "./weapon/weaponsmanager";
|
||||
|
||||
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "./constants/constants";
|
||||
import { Manager } from "./other/manager";
|
||||
import { ShortcutKeyboard } from "./shortcut/shortcut";
|
||||
import { getPaused, setCredentials, setPaused, startUpdate, toggleDemoEnabled } from "./server/server";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
|
||||
export class OlympusApp {
|
||||
/* Global data */
|
||||
@@ -36,11 +39,11 @@ export class OlympusApp {
|
||||
#shortcutManager: ShortcutManager | null = null;
|
||||
|
||||
/* UI Toolbars */
|
||||
#primaryToolbar: PrimaryToolbar| null = null;
|
||||
#commandModeToolbar: CommandModeToolbar| null = null;
|
||||
#primaryToolbar: PrimaryToolbar | null = null;
|
||||
#commandModeToolbar: CommandModeToolbar | null = null;
|
||||
|
||||
constructor() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
getMap() {
|
||||
@@ -122,7 +125,7 @@ export class OlympusApp {
|
||||
this.#unitsManager = new UnitsManager();
|
||||
this.#weaponsManager = new WeaponsManager();
|
||||
this.#missionManager = new MissionManager();
|
||||
|
||||
|
||||
this.#shortcutManager = new ShortcutManager();
|
||||
|
||||
this.#panelsManager = new Manager();
|
||||
@@ -136,16 +139,130 @@ export class OlympusApp {
|
||||
.add("mouseInfo", new MouseInfoPanel("mouse-info-panel"))
|
||||
.add("log", new LogPanel("log-panel"))
|
||||
.add("serverStatus", new ServerStatusPanel("server-status-panel"))
|
||||
.add("unitControl", new UnitControlPanel("unit-control-panel"))
|
||||
.add("unitControl", new UnitControlPanel("unit-control-panel"))
|
||||
.add("unitInfo", new UnitInfoPanel("unit-info-panel"))
|
||||
|
||||
|
||||
// Popups
|
||||
this.getPopupsManager().add("infoPopup", new Popup("info-popup"));
|
||||
|
||||
// Toolbars
|
||||
this.getToolbarsManager().add("primaryToolbar", new PrimaryToolbar("primary-toolbar"))
|
||||
.add("commandModeToolbar", new PrimaryToolbar("command-mode-toolbar"));
|
||||
.add("commandModeToolbar", new PrimaryToolbar("command-mode-toolbar"));
|
||||
|
||||
this.#pluginsManager = new PluginsManager();
|
||||
|
||||
this.#setupEvents();
|
||||
}
|
||||
|
||||
#setupEvents() {
|
||||
/* Generic clicks */
|
||||
document.addEventListener("click", (ev) => {
|
||||
if (ev instanceof MouseEvent && ev.target instanceof HTMLElement) {
|
||||
const target = ev.target;
|
||||
|
||||
if (target.classList.contains("olympus-dialog-close")) {
|
||||
target.closest("div.olympus-dialog")?.classList.add("hide");
|
||||
}
|
||||
|
||||
const triggerElement = target.closest("[data-on-click]");
|
||||
|
||||
if (triggerElement instanceof HTMLElement) {
|
||||
const eventName: string = triggerElement.dataset.onClick || "";
|
||||
let params = JSON.parse(triggerElement.dataset.onClickParams || "{}");
|
||||
params._element = triggerElement;
|
||||
|
||||
if (eventName) {
|
||||
document.dispatchEvent(new CustomEvent(eventName, {
|
||||
detail: params
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const shortcutManager = this.getShortcutManager();
|
||||
shortcutManager.add("toggleDemo", new ShortcutKeyboard({
|
||||
"callback": () => {
|
||||
toggleDemoEnabled();
|
||||
},
|
||||
"code": "KeyT"
|
||||
})).add("togglePause", new ShortcutKeyboard({
|
||||
"altKey": false,
|
||||
"callback": () => {
|
||||
setPaused(!getPaused());
|
||||
},
|
||||
"code": "Space",
|
||||
"ctrlKey": false
|
||||
}));
|
||||
|
||||
["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => {
|
||||
shortcutManager.add(`pan${code}keydown`, new ShortcutKeyboard({
|
||||
"altKey": false,
|
||||
"callback": (ev: KeyboardEvent) => {
|
||||
this.getMap().handleMapPanning(ev);
|
||||
},
|
||||
"code": code,
|
||||
"ctrlKey": false,
|
||||
"event": "keydown"
|
||||
}));
|
||||
});
|
||||
|
||||
["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => {
|
||||
shortcutManager.add(`pan${code}keyup`, new ShortcutKeyboard({
|
||||
"callback": (ev: KeyboardEvent) => {
|
||||
this.getMap().handleMapPanning(ev);
|
||||
},
|
||||
"code": code
|
||||
}));
|
||||
});
|
||||
|
||||
["Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9"].forEach(code => {
|
||||
shortcutManager.add(`hotgroup${code}`, new ShortcutKeyboard({
|
||||
"callback": (ev: KeyboardEvent) => {
|
||||
if (ev.ctrlKey && ev.shiftKey)
|
||||
this.getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5)));
|
||||
else if (ev.ctrlKey && !ev.shiftKey)
|
||||
this.getUnitsManager().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5)));
|
||||
else
|
||||
this.getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5)));
|
||||
},
|
||||
"code": code
|
||||
}));
|
||||
});
|
||||
|
||||
// TODO: move from here in dedicated class
|
||||
document.addEventListener("closeDialog", (ev: CustomEventInit) => {
|
||||
ev.detail._element.closest(".ol-dialog").classList.add("hide");
|
||||
});
|
||||
|
||||
/* Try and connect with the Olympus REST server */
|
||||
document.addEventListener("tryConnection", () => {
|
||||
const form = document.querySelector("#splash-content")?.querySelector("#authentication-form");
|
||||
const username = (form?.querySelector("#username") as HTMLInputElement).value;
|
||||
const password = (form?.querySelector("#password") as HTMLInputElement).value;
|
||||
|
||||
/* Update the user credentials */
|
||||
setCredentials(username, password);
|
||||
|
||||
/* Start periodically requesting updates */
|
||||
startUpdate();
|
||||
|
||||
this.setLoginStatus("connecting");
|
||||
})
|
||||
|
||||
/* Reload the page, used to mimic a restart of the app */
|
||||
document.addEventListener("reloadPage", () => {
|
||||
location.reload();
|
||||
})
|
||||
|
||||
/* Inject the svgs with the corresponding svg code. This allows to dynamically manipulate the svg, like changing colors */
|
||||
document.querySelectorAll("[inject-svg]").forEach((el: Element) => {
|
||||
var img = el as HTMLImageElement;
|
||||
var isLoaded = img.complete;
|
||||
if (isLoaded)
|
||||
SVGInjector(img);
|
||||
else
|
||||
img.addEventListener("load", () => { SVGInjector(img); });
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -153,7 +153,6 @@ 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";
|
||||
|
||||
@@ -68,6 +68,10 @@ export class Dropdown {
|
||||
return this.#options.children;
|
||||
}
|
||||
|
||||
addOptionElement(optionElement: HTMLElement) {
|
||||
this.#options.appendChild(optionElement);
|
||||
}
|
||||
|
||||
selectText(text: string) {
|
||||
const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text);
|
||||
if (index > -1) {
|
||||
|
||||
@@ -1,156 +1,30 @@
|
||||
import { getConfig, getPaused, setAddress, setCredentials, setPaused, startUpdate, toggleDemoEnabled } from "./server/server";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getConfig, setAddress } from "./server/server";
|
||||
import { OlympusApp } from "./app";
|
||||
import { ShortcutKeyboard } from "./shortcut/shortcut";
|
||||
|
||||
var app: OlympusApp;
|
||||
|
||||
function setup() {
|
||||
/* Load the config file from the app server*/
|
||||
getConfig((config: ConfigurationOptions) => readConfig(config));
|
||||
getConfig((config: ConfigurationOptions) => {
|
||||
if (config && config.address != undefined && config.port != undefined) {
|
||||
const address = config.address;
|
||||
const port = config.port;
|
||||
if (typeof address === 'string' && typeof port == 'number') {
|
||||
setAddress(address == "*" ? window.location.hostname : address, <number>port);
|
||||
|
||||
app = new OlympusApp();
|
||||
app.start();
|
||||
|
||||
/* Setup event handlers */
|
||||
setupEvents(app);
|
||||
/* If the configuration file was successfully loaded, start the app */
|
||||
app = new OlympusApp();
|
||||
app.start();
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error('Could not read configuration file');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getApp() {
|
||||
return app;
|
||||
}
|
||||
|
||||
/** Loads the configuration parameters
|
||||
*
|
||||
* @param config ConfigParameters, defines the address and port of the Olympus REST server
|
||||
*/
|
||||
function readConfig(config: ConfigurationOptions) {
|
||||
if (config && config.address != undefined && config.port != undefined) {
|
||||
const address = config.address;
|
||||
const port = config.port;
|
||||
if (typeof address === 'string' && typeof port == 'number')
|
||||
setAddress(address == "*" ? window.location.hostname : address, <number>port);
|
||||
}
|
||||
else {
|
||||
throw new Error('Could not read configuration file');
|
||||
}
|
||||
}
|
||||
|
||||
function setupEvents(app: OlympusApp) {
|
||||
/* Generic clicks */
|
||||
document.addEventListener("click", (ev) => {
|
||||
if (ev instanceof MouseEvent && ev.target instanceof HTMLElement) {
|
||||
const target = ev.target;
|
||||
|
||||
if (target.classList.contains("olympus-dialog-close")) {
|
||||
target.closest("div.olympus-dialog")?.classList.add("hide");
|
||||
}
|
||||
|
||||
const triggerElement = target.closest("[data-on-click]");
|
||||
|
||||
if (triggerElement instanceof HTMLElement) {
|
||||
const eventName: string = triggerElement.dataset.onClick || "";
|
||||
let params = JSON.parse(triggerElement.dataset.onClickParams || "{}");
|
||||
params._element = triggerElement;
|
||||
|
||||
if (eventName) {
|
||||
document.dispatchEvent(new CustomEvent(eventName, {
|
||||
detail: params
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const shortcutManager = app.getShortcutManager();
|
||||
shortcutManager.add("toggleDemo", new ShortcutKeyboard({
|
||||
"callback": () => {
|
||||
toggleDemoEnabled();
|
||||
},
|
||||
"code": "KeyT"
|
||||
})
|
||||
)
|
||||
.add("togglePause", new ShortcutKeyboard({
|
||||
"altKey": false,
|
||||
"callback": () => {
|
||||
setPaused(!getPaused());
|
||||
},
|
||||
"code": "Space",
|
||||
"ctrlKey": false
|
||||
})
|
||||
);
|
||||
|
||||
["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => {
|
||||
shortcutManager.add(`pan${code}keydown`, new ShortcutKeyboard({
|
||||
"altKey": false,
|
||||
"callback": (ev: KeyboardEvent) => {
|
||||
getApp().getMap().handleMapPanning(ev);
|
||||
},
|
||||
"code": code,
|
||||
"ctrlKey": false,
|
||||
"event": "keydown"
|
||||
}));
|
||||
});
|
||||
|
||||
["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => {
|
||||
shortcutManager.add(`pan${code}keyup`, new ShortcutKeyboard({
|
||||
"callback": (ev: KeyboardEvent) => {
|
||||
getApp().getMap().handleMapPanning(ev);
|
||||
},
|
||||
"code": code
|
||||
}));
|
||||
});
|
||||
|
||||
["Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9"].forEach(code => {
|
||||
shortcutManager.add(`hotgroup${code}`, new ShortcutKeyboard({
|
||||
"callback": (ev: KeyboardEvent) => {
|
||||
if (ev.ctrlKey && ev.shiftKey)
|
||||
getApp().getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5)));
|
||||
else if (ev.ctrlKey && !ev.shiftKey)
|
||||
getApp().getUnitsManager().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5)));
|
||||
else
|
||||
getApp().getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5)));
|
||||
},
|
||||
"code": code
|
||||
}));
|
||||
});
|
||||
|
||||
// TODO: move from here in dedicated class
|
||||
document.addEventListener("closeDialog", (ev: CustomEventInit) => {
|
||||
ev.detail._element.closest(".ol-dialog").classList.add("hide");
|
||||
});
|
||||
|
||||
/* Try and connect with the Olympus REST server */
|
||||
document.addEventListener("tryConnection", () => {
|
||||
const form = document.querySelector("#splash-content")?.querySelector("#authentication-form");
|
||||
const username = (form?.querySelector("#username") as HTMLInputElement).value;
|
||||
const password = (form?.querySelector("#password") as HTMLInputElement).value;
|
||||
|
||||
/* Update the user credentials */
|
||||
setCredentials(username, password);
|
||||
|
||||
/* Start periodically requesting updates */
|
||||
startUpdate();
|
||||
|
||||
getApp().setLoginStatus("connecting");
|
||||
})
|
||||
|
||||
/* Reload the page, used to mimic a restart of the app */
|
||||
document.addEventListener("reloadPage", () => {
|
||||
location.reload();
|
||||
})
|
||||
|
||||
/* Inject the svgs with the corresponding svg code. This allows to dynamically manipulate the svg, like changing colors */
|
||||
document.querySelectorAll("[inject-svg]").forEach((el: Element) => {
|
||||
var img = el as HTMLImageElement;
|
||||
var isLoaded = img.complete;
|
||||
if (isLoaded)
|
||||
SVGInjector(img);
|
||||
else
|
||||
img.addEventListener("load", () => {
|
||||
SVGInjector(img);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
window.onload = setup;
|
||||
@@ -12,7 +12,7 @@ import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
|
||||
import { TemporaryUnitMarker } from "./markers/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, SHOW_CONTROL_TIPS } 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 "./markers/targetmarker";
|
||||
import { CoalitionArea } from "./coalitionarea/coalitionarea";
|
||||
import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
|
||||
@@ -161,7 +161,6 @@ export class Map extends L.Map {
|
||||
|
||||
document.addEventListener("mapVisibilityOptionsChanged", () => {
|
||||
this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]);
|
||||
// TODO this.getControlTips().toggle( !this.getVisibilityOptions()[SHOW_CONTROL_TIPS] );
|
||||
});
|
||||
|
||||
/* Pan interval */
|
||||
@@ -180,20 +179,16 @@ export class Map extends L.Map {
|
||||
document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]);
|
||||
|
||||
/* Create the checkboxes to select the advanced visibility options */
|
||||
this.#visibilityOptions[SHOW_CONTACT_LINES] = false;
|
||||
this.#visibilityOptions[HIDE_GROUP_MEMBERS] = true;
|
||||
this.#visibilityOptions[SHOW_UNIT_PATHS] = true;
|
||||
this.#visibilityOptions[SHOW_UNIT_TARGETS] = true;
|
||||
this.#visibilityOptions[SHOW_UNIT_LABELS] = true;
|
||||
|
||||
// Manual until we use the App approach
|
||||
this.#visibilityOptions[SHOW_CONTROL_TIPS] = JSON.parse( localStorage.getItem( "featureSwitches" ) || "{}" )?.controlTips || true;
|
||||
this.addVisibilityOption(SHOW_CONTACT_LINES, false);
|
||||
this.addVisibilityOption(HIDE_GROUP_MEMBERS, true);
|
||||
this.addVisibilityOption(SHOW_UNIT_PATHS, true);
|
||||
this.addVisibilityOption(SHOW_UNIT_TARGETS, true);
|
||||
this.addVisibilityOption(SHOW_UNIT_LABELS, true);
|
||||
}
|
||||
|
||||
this.#mapVisibilityOptionsDropdown.setOptionsElements(Object.keys(this.#visibilityOptions).map((option: string) => {
|
||||
return createCheckboxOption(option, option, this.#visibilityOptions[option], (ev: any) => {
|
||||
this.#setVisibilityOption(option, ev);
|
||||
});
|
||||
}));
|
||||
addVisibilityOption(option: string, defaultValue: boolean) {
|
||||
this.#visibilityOptions[option] = defaultValue;
|
||||
this.#mapVisibilityOptionsDropdown.addOptionElement(createCheckboxOption(option, option, defaultValue, (ev: any) => { this.#setVisibilityOption(option, ev); }));
|
||||
}
|
||||
|
||||
setLayer(layerName: string) {
|
||||
|
||||
@@ -2,6 +2,11 @@ import path from "path";
|
||||
import { Manager } from "../other/manager";
|
||||
import { getApp } from "..";
|
||||
|
||||
/** The plugins manager is responsible for loading and initializing all the plugins. Plugins are located in the public/plugins folder.
|
||||
* Each plugin must be comprised of a single folder containing a index.js file. Each plugin must set the globalThis.getOlympusPlugin variable to
|
||||
* return a valid class implementing the OlympusPlugin interface.
|
||||
*/
|
||||
|
||||
export class PluginsManager extends Manager {
|
||||
constructor() {
|
||||
super();
|
||||
@@ -36,15 +41,28 @@ export class PluginsManager extends Manager {
|
||||
document.getElementsByTagName("head")[0].appendChild(link);
|
||||
|
||||
/* Evaluate the plugin javascript */
|
||||
eval(xhr.response);
|
||||
const plugin = globalThis.getOlympusPlugin() as OlympusPlugin;
|
||||
console.log(plugin.getName() + " loaded correctly");
|
||||
|
||||
if (plugin.initialize(getApp())) {
|
||||
console.log(plugin.getName() + " initialized correctly");
|
||||
this.add(pluginName, plugin);
|
||||
var plugin: OlympusPlugin | null = null;
|
||||
try {
|
||||
eval(xhr.response);
|
||||
plugin = globalThis.getOlympusPlugin() as OlympusPlugin;
|
||||
console.log(plugin.getName() + " loaded correctly");
|
||||
} catch (error: any) {
|
||||
console.log("An error occured while loading a plugin from " + pluginName);
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
/* If the plugin was loaded, try to initialize it */
|
||||
if (plugin != null) {
|
||||
try {
|
||||
if (plugin.initialize(getApp())) {
|
||||
console.log(plugin.getName() + " initialized correctly");
|
||||
this.add(pluginName, plugin);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log("An error occured while initializing a plugin from " + pluginName);
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
console.error(`Error retrieving plugin from ${pluginName}`)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user