Added installation/deletion of camera control plugin from manager

This commit is contained in:
Davide Passoni 2024-02-29 10:54:52 +01:00
parent 832568aa00
commit 4782596e3c
12 changed files with 175 additions and 42 deletions

View File

@ -176,7 +176,7 @@ declare module "constants/constants" {
zoom: number;
};
};
export const mapLayers: {
export const defaultMapLayers: {
"ArcGIS Satellite": {
urlTemplate: string;
minZoom: number;
@ -235,6 +235,7 @@ declare module "constants/constants" {
export const SHOW_UNIT_CONTACTS = "Show selected units contact lines";
export const SHOW_UNIT_PATHS = "Show selected unit paths";
export const SHOW_UNIT_TARGETS = "Show selected unit targets";
export const DCS_LINK_PORT = "DCS Camera link port";
export enum DataIndexes {
startOfData = 0,
category = 1,
@ -362,6 +363,7 @@ declare module "controls/dropdown" {
setOptionsElements(optionsElements: HTMLElement[]): void;
getOptionElements(): HTMLCollection;
addOptionElement(optionElement: HTMLElement): void;
addHorizontalDivider(): void;
/** Select the active value of the dropdown
*
* @param idx The index of the element to select
@ -851,10 +853,11 @@ declare module "other/utils" {
export function enumToCoalition(coalitionID: number): "" | "blue" | "red" | "neutral";
export function coalitionToEnum(coalition: string): 0 | 1 | 2;
export function convertDateAndTimeToDate(dateAndTime: DateAndTime): Date;
export function createCheckboxOption(value: string, text: string, checked?: boolean, callback?: CallableFunction, options?: any): HTMLElement;
export function createCheckboxOption(text: string, description: string, checked?: boolean, callback?: CallableFunction, options?: any): HTMLElement;
export function getCheckboxOptions(dropdown: Dropdown): {
[key: string]: boolean;
};
export function createTextInputOption(text: string, description: string, initialValue: string, type: string, callback?: CallableFunction, options?: any): HTMLElement;
export function getGroundElevation(latlng: LatLng, callback: CallableFunction): void;
}
declare module "controls/slider" {
@ -1624,7 +1627,9 @@ declare module "map/map" {
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string);
addVisibilityOption(option: string, defaultValue: boolean): void;
addVisibilityOption(option: string, defaultValue: boolean | number | string, options?: {
[key: string]: any;
}): void;
setLayer(layerName: string): void;
getLayers(): string[];
setState(state: string): void;
@ -1660,12 +1665,14 @@ declare module "map/map" {
getSelectedCoalitionArea(): CoalitionArea | undefined;
bringCoalitionAreaToBack(coalitionArea: CoalitionArea): void;
getVisibilityOptions(): {
[key: string]: boolean;
[key: string]: string | number | boolean;
};
isZooming(): boolean;
getPreviousZoom(): number;
getIsUnitProtected(unit: Unit): boolean;
getMapMarkerVisibilityControls(): MapMarkerVisibilityControl[];
setSlaveDCSCamera(newSlaveDCSCamera: boolean): void;
setCameraControlMode(newCameraControlMode: string): void;
}
}
declare module "mission/bullseye" {
@ -2612,6 +2619,7 @@ declare module "olympusapp" {
*/
setLoginStatus(status: string): void;
start(): void;
getConfig(): any;
}
}
declare module "index" {

25
manager/ejs/camera.ejs Normal file
View File

@ -0,0 +1,25 @@
<style>
</style>
<div>
<div class="instructions">
<div class="step">
Step <%= instances.length === 1? "5": "6" %> of <%= instances.length === 1? "5": "6" %>
</div>
<div class="title">
Do you want to install the camera control plugin?
</div>
<div class="description">
The camera control plugin allows you to control the camera position in DCS from Olympus. <br>
It is necessary to install it in DCS even if you plan to use Olympus on a remote machine via your browser.
</div>
</div>
<div class="wizard-inputs">
<div class="button radio install selected" onclick="signal('onInstallCameraControlClicked', 'install')">
Install
</div>
<div class="button radio no-install" onclick="signal('onInstallCameraControlClicked', 'no-install')">
Do not install
</div>
</div>
</div>

View File

@ -4,7 +4,7 @@
<div id="connections-page">
<div class="instructions">
<div class="step">
Step <%= instances.length === 1? "3": "4" %> of <%= instances.length === 1? "4": "5" %>
Step <%= instances.length === 1? "3": "4" %> of <%= instances.length === 1? "5": "6" %>
</div>
<div class="title">
Manually set Olympus port and address settings

View File

@ -4,7 +4,7 @@
<div>
<div class="instructions">
<div class="step">
Step <%= instances.length === 1? "2": "3" %> of <%= instances.length === 1? "4": "5" %>
Step <%= instances.length === 1? "2": "3" %> of <%= instances.length === 1? "5": "6" %>
</div>
<div class="title">
Do you want to set port and address settings?

View File

@ -73,6 +73,13 @@
title="Allows services to connect to Olympus directly. This is NOT NEEDED for normal Olympus operation, even for dedicated servers. Leave it unchecked if in doubt.">
</span>
</div>
<div class="input-group camera-plugin">
<span onclick="signal('onEnableCameraPluginClicked')">
<div class="checkbox checked"></div> Enable camera control plugin
<img src="./icons/circle-info-solid.svg"
title="Install the camera control plugin, which allows direct control of the DCS camera from Olympus. It is necessary even to control the camera even if Olympus is being used remotely using a browser.">
</span>
</div>
</div>
</div>
</div>

View File

@ -5,7 +5,7 @@
<div class="instructions">
<% if (instances.length > 0) { %>
<div class="step">
Step 1 of <%= instances.length === 1? "4": "5" %>
Step 1 of <%= instances.length === 1? "5": "6" %>
</div>
<div class="title">
Which DCS instance you want to add Olympus to?

View File

@ -4,7 +4,7 @@
<div id="passwords-page">
<div class="instructions">
<div class="step">
Step <%= instances.length === 1? "4": "5" %> of <%= instances.length === 1? "4": "5" %>
Step <%= instances.length === 1? "4": "5" %> of <%= instances.length === 1? "5": "6" %>
</div>
<div class="title">
Enter your passwords for Olympus

View File

@ -4,7 +4,7 @@
<div>
<div class="instructions">
<div class="step">
Step <%= instances.length === 1? "1": "2" %> of <%= instances.length === 1? "4": "5" %>
Step <%= instances.length === 1? "1": "2" %> of <%= instances.length === 1? "5": "6" %>
</div>
<div class="title">
Do you want to add Olympus for singleplayer or multiplayer?

View File

@ -6,7 +6,7 @@ const { checkPort, fetchWithTimeout, getFreePort } = require('./net')
const dircompare = require('dir-compare');
const { spawn } = require('child_process');
const find = require('find-process');
const { installHooks, installMod, installJSON, applyConfiguration, installShortCuts, deleteMod, deleteHooks, deleteJSON, deleteShortCuts } = require('./filesystem')
const { installHooks, installMod, installJSON, applyConfiguration, installShortCuts, deleteMod, deleteHooks, deleteJSON, deleteShortCuts, installCameraPlugin, deleteCameraPlugin } = require('./filesystem')
const { showErrorPopup, showConfirmPopup, showWaitLoadingPopup, setPopupLoadingProgress } = require('./popup')
const { logger } = require("./filesystem")
const { hidePopup } = require('./popup');
@ -129,6 +129,7 @@ class DCSInstance {
fps = 0;
installationType = 'singleplayer';
connectionsType = 'auto';
installCameraPlugin = 'install';
gameMasterPasswordEdited = false;
blueCommanderPasswordEdited = false;
redCommanderPasswordEdited = false;
@ -154,6 +155,7 @@ class DCSInstance {
this.error = false;
this.installationType = 'singleplayer';
this.connectionsType = 'auto';
this.installCameraPlugin = 'install';
/* Check if the olympus.json file is detected. If true, Olympus is considered to be installed */
if (fs.existsSync(path.join(this.folder, "Config", "olympus.json"))) {
@ -518,22 +520,28 @@ class DCSInstance {
await sleep(100);
await installHooks(getManager().getActiveInstance().folder);
setPopupLoadingProgress("Installing mod folder...", 20);
setPopupLoadingProgress("Installing mod folder...", 16);
await sleep(100);
await installMod(getManager().getActiveInstance().folder, getManager().getActiveInstance().name);
setPopupLoadingProgress("Installing JSON file...", 40);
setPopupLoadingProgress("Installing JSON file...", 33);
await sleep(100);
await installJSON(getManager().getActiveInstance().folder);
setPopupLoadingProgress("Applying configuration...", 60);
setPopupLoadingProgress("Applying configuration...", 50);
await sleep(100);
await applyConfiguration(getManager().getActiveInstance().folder, getManager().getActiveInstance());
setPopupLoadingProgress("Creating shortcuts...", 80);
setPopupLoadingProgress("Creating shortcuts...", 67);
await sleep(100);
await installShortCuts(getManager().getActiveInstance().folder, getManager().getActiveInstance().name);
if (getManager().getActiveInstance().installCameraPlugin === 'install') {
setPopupLoadingProgress("Installing camera plugin...", 83);
await sleep(100);
await installCameraPlugin(getManager().getActiveInstance().folder);
}
setPopupLoadingProgress("Installation completed!", 100);
await sleep(500);
logger.log(`Installation completed successfully`);
@ -575,18 +583,22 @@ class DCSInstance {
await sleep(100);
await deleteMod(this.folder, this.name);
setPopupLoadingProgress("Deleting hook scripts...", 25);
setPopupLoadingProgress("Deleting hook scripts...", 20);
await sleep(100);
await deleteHooks(this.folder);
setPopupLoadingProgress("Deleting JSON...", 50);
setPopupLoadingProgress("Deleting JSON...", 40);
await sleep(100);
await deleteJSON(this.folder);
setPopupLoadingProgress("Deleting shortcuts...", 75);
setPopupLoadingProgress("Deleting shortcuts...", 60);
await sleep(100);
await deleteShortCuts(this.folder, this.name);
setPopupLoadingProgress("Deleting camera plugin...", 80);
await sleep(100);
await deleteCameraPlugin(this.folder);
await sleep(500);
setPopupLoadingProgress("Instance removed!", 100);
logger.log(`Olympus removed from ${this.folder}`)

View File

@ -11,6 +11,8 @@ var logger = new Console(output, output);
const date = new Date();
output.write(` ======================= New log starting at ${date.toString()} =======================\n`);
var EXPORT_STRING = "pcall(function() local olympusLFS=require('lfs');dofile(olympusLFS.writedir()..[[Mods\\Services\\Olympus\\Scripts\\OlympusCameraControl.lua]]); end,nil) ";
/** Conveniency function to asynchronously delete a single file, with error catching
*
* @param {String} filePath The path to the file to delete
@ -172,6 +174,29 @@ async function applyConfiguration(folder, instance) {
}
}
/** Asynchronously install the camera control plugin
*
* @param {String} folder The base Saved Games folder where Olympus is installed
*/
async function installCameraPlugin(folder) {
logger.log(`Installing camera support plugin to DCS in ${folder}`);
/* If the export file doesn't exist, create it */
if (!(await exists(path.join(folder, "Scripts", "export.lua")))) {
await fsp.writeFile(path.join(folder, "Scripts", "export.lua"), EXPORT_STRING);
} else {
let content = await fsp.readFile(path.join(folder, "Scripts", "export.lua"), { encoding: 'utf8' });
if (content.indexOf(EXPORT_STRING) != -1) {
/* Looks like the export string is already installed, nothing to do */
}
else {
/* Append the export string at the end of the file */
content += ("\n" + EXPORT_STRING);
}
/* Write the content of the file */
await fsp.writeFile(path.join(folder, "Scripts", "export.lua"), content)
}
}
/** Asynchronously deletes the Hooks script
*
* @param {String} folder The base Saved Games folder where Olympus is installed
@ -231,15 +256,40 @@ async function deleteShortCuts(folder, name) {
logger.log(`ShortCuts deleted from ${folder} and desktop`);
}
/** Asynchronously removes the camera plugin string from the export lua file
*
* @param {String} folder The base Saved Games folder where Olympus is installed
*/
async function deleteCameraPlugin(folder) {
logger.log(`Deleting camera support plugin to DCS in ${folder}`);
if (!(await exists(path.join(folder, "Scripts", "export.lua")))) {
/* If the export file doesn't exist, nothing to do */
} else {
let content = await fsp.readFile(path.join(folder, "Scripts", "export.lua"), { encoding: 'utf8' });
if (content.indexOf(EXPORT_STRING) ==+ -1) {
/* Looks like the export string is not installed, nothing to do */
}
else {
/* Remove the export string from the file */
content = content.replace(EXPORT_STRING, "")
/* Write the content of the file */
await fsp.writeFile(path.join(folder, "Scripts", "export.lua"), content)
}
}
}
module.exports = {
applyConfiguration: applyConfiguration,
installJSON: installJSON,
installHooks: installHooks,
installMod: installMod,
installShortCuts, installShortCuts,
installShortCuts: installShortCuts,
installCameraPlugin: installCameraPlugin,
deleteHooks: deleteHooks,
deleteJSON: deleteJSON,
deleteMod: deleteMod,
deleteShortCuts: deleteShortCuts,
deleteCameraPlugin: deleteCameraPlugin,
logger: logger
}

View File

@ -32,6 +32,7 @@ class Manager {
connectionsTypePage = null;
connectionsPage = null;
passwordsPage = null;
cameraPage = null;
resultPage = null;
instancesPage = null;
expertSettingsPage = null;
@ -103,9 +104,9 @@ class Manager {
/* Get my public IP */
this.getPublicIP().then(
(IP) => { this.setIP(IP); },
(err) => {
(err) => {
logger.log(err)
this.setIP(undefined);
this.setIP(undefined);
}
)
@ -142,6 +143,7 @@ class Manager {
this.connectionsTypePage = new WizardPage(this, "./ejs/connectionsType.ejs");
this.connectionsPage = new WizardPage(this, "./ejs/connections.ejs");
this.passwordsPage = new WizardPage(this, "./ejs/passwords.ejs");
this.cameraPage = new WizardPage(this, "./ejs/camera.ejs");
this.resultPage = new ManagerPage(this, "./ejs/result.ejs");
this.instancesPage = new ManagerPage(this, "./ejs/instances.ejs");
this.expertSettingsPage = new WizardPage(this, "./ejs/expertsettings.ejs");
@ -159,7 +161,7 @@ class Manager {
this.setPort('backend', this.getActiveInstance().backendPort);
}
}
/* Always force the IDLE state when reaching the menu page */
this.menuPage.options.onShow = async () => {
await this.setState('IDLE');
@ -337,6 +339,17 @@ class Manager {
}
}
/* When the camera control installation is selected */
async onInstallCameraControlClicked(type) {
this.connectionsTypePage.getElement().querySelector(`.install`).classList.toggle("selected", type === 'install');
this.connectionsTypePage.getElement().querySelector(`.no-install`).classList.toggle("selected", type === 'no-install');
if (this.getActiveInstance())
this.getActiveInstance().installCameraPlugin = type;
else {
showErrorPopup(`<div class='main-message'>A critical error occurred! </div><div class='sub-message'> Check ${this.getLogLocation()} for more info. </div>`);
}
}
/* When the next button of a wizard page is clicked */
async onNextClicked() {
/* Choose which page to show depending on the active page */
@ -360,11 +373,11 @@ class Manager {
this.activePage.hide();
this.typePage.show();
}
/* Installation type page */
/* Installation type page */
} else if (this.activePage == this.typePage) {
this.activePage.hide();
this.connectionsTypePage.show();
/* Connection type page */
/* Connection type page */
} else if (this.activePage == this.connectionsTypePage) {
if (this.getActiveInstance()) {
if (this.getActiveInstance().connectionsType === 'auto') {
@ -374,24 +387,28 @@ class Manager {
else {
this.activePage.hide();
this.connectionsPage.show();
(this.getMode() === 'basic'? this.connectionsPage: this.expertSettingsPage).getElement().querySelector(".backend-address .checkbox").classList.toggle("checked", this.getActiveInstance().backendAddress === '*')
(this.getMode() === 'basic' ? this.connectionsPage : this.expertSettingsPage).getElement().querySelector(".backend-address .checkbox").classList.toggle("checked", this.getActiveInstance().backendAddress === '*')
}
} else {
showErrorPopup(`<div class='main-message'>A critical error occurred! </div><div class='sub-message'> Check ${this.getLogLocation()} for more info. </div>`)
}
/* Connection page */
/* Connection page */
} else if (this.activePage == this.connectionsPage) {
if (await this.checkPorts()) {
this.activePage.hide();
this.passwordsPage.show();
}
/* Passwords page */
}
/* Passwords page */
} else if (this.activePage == this.passwordsPage) {
if (await this.checkPasswords()) {
this.activePage.hide();
this.getState() === 'INSTALL' ? this.getActiveInstance().install() : this.getActiveInstance().edit();
this.cameraPage.show()
}
/* Expert settings page */
/* Installation type page */
} else if (this.activePage == this.cameraPage) {
this.activePage.hide();
this.getState() === 'INSTALL' ? this.getActiveInstance().install() : this.getActiveInstance().edit();
/* Expert settings page */
} else if (this.activePage == this.expertSettingsPage) {
if (await this.checkPorts() && await this.checkPasswords()) {
this.activePage.hide();
@ -416,7 +433,7 @@ class Manager {
async onCancelClicked() {
this.activePage.hide();
await this.setState('IDLE');
if (this.getMode() === "basic")
if (this.getMode() === "basic")
this.menuPage.show(true);
else
this.instancesPage.show(true);
@ -441,7 +458,7 @@ class Manager {
if (this.getActiveInstance())
this.getActiveInstance().setBlueCommanderPassword(value);
else
else
showErrorPopup(`<div class='main-message'>A critical error occurred! </div><div class='sub-message'> Check ${this.getLogLocation()} for more info. </div>`);
}
@ -450,9 +467,9 @@ class Manager {
input.placeholder = "";
}
if (this.getActiveInstance())
if (this.getActiveInstance())
this.getActiveInstance().setRedCommanderPassword(value);
else
else
showErrorPopup(`<div class='main-message'>A critical error occurred! </div><div class='sub-message'> Check ${this.getLogLocation()} for more info. </div>`);
}
@ -485,6 +502,20 @@ class Manager {
}
}
/* When the "Enable camera control plugin" checkbox is clicked */
async onEnableCameraPluginClicked() {
if (this.getActiveInstance()) {
if (this.getActiveInstance().installCameraPlugin === 'install') {
this.getActiveInstance().installCameraPlugin = 'no-install';
} else {
this.getActiveInstance().installCameraPlugin = 'install';
}
this.expertSettingsPage.getElement().querySelector(".camera-plugin .checkbox").classList.toggle("checked", this.getActiveInstance().installCameraPlugin === 'install')
} else {
showErrorPopup(`<div class='main-message'>A critical error occurred! </div><div class='sub-message'> Check ${this.getLogLocation()} for more info. </div>`)
}
}
/* When the "Return to manager" button is pressed */
async onReturnClicked() {
await this.reload();
@ -562,7 +593,7 @@ class Manager {
this.setActiveInstance(instance);
await this.setState('EDIT');
this.activePage.hide();
(this.getMode() === 'basic'? this.typePage: this.expertSettingsPage).show();
(this.getMode() === 'basic' ? this.typePage : this.expertSettingsPage).show();
}
}
@ -571,7 +602,7 @@ class Manager {
this.setActiveInstance(instance);
await this.setState('INSTALL');
this.activePage.hide();
(this.getMode() === 'basic'? this.typePage: this.expertSettingsPage).show();
(this.getMode() === 'basic' ? this.typePage : this.expertSettingsPage).show();
}
async onUninstallClicked(name) {
@ -579,7 +610,7 @@ class Manager {
this.setActiveInstance(instance);
await this.setState('UNINSTALL');
if (instance.webserverOnline || instance.backendOnline)
showErrorPopup("<div class='main-message'>The selected Olympus instance is currently active </div><div class='sub-message'> Please stop DCS and Olympus Server/Client before removing it! </div>")
showErrorPopup("<div class='main-message'>The selected Olympus instance is currently active </div><div class='sub-message'> Please stop DCS and Olympus Server/Client before removing it! </div>")
else
await instance.uninstall();
}
@ -620,11 +651,11 @@ class Manager {
this.getActiveInstance().setBackendPort(value);
}
var successEls = (this.getMode() === 'basic'? this.connectionsPage: this.expertSettingsPage).getElement().querySelector(`.${port}-port`).querySelectorAll(".success");
var successEls = (this.getMode() === 'basic' ? this.connectionsPage : this.expertSettingsPage).getElement().querySelector(`.${port}-port`).querySelectorAll(".success");
for (let i = 0; i < successEls.length; i++) {
successEls[i].classList.toggle("hide", !success);
}
var errorEls = (this.getMode() === 'basic'? this.connectionsPage: this.expertSettingsPage).getElement().querySelector(`.${port}-port`).querySelectorAll(".error");
var errorEls = (this.getMode() === 'basic' ? this.connectionsPage : this.expertSettingsPage).getElement().querySelector(`.${port}-port`).querySelectorAll(".error");
for (let i = 0; i < errorEls.length; i++) {
errorEls[i].classList.toggle("hide", success);
}
@ -693,7 +724,7 @@ class Manager {
document.getElementById("loader").style.opacity = "0%";
window.setTimeout(() => {
document.getElementById("loader").classList.add("hide");
}, 250);
}, 250);
}
async setActiveInstance(newActiveInstance) {
@ -718,12 +749,12 @@ class Manager {
async setLogLocation(newLogLocation) {
this.options.logLocation = newLogLocation;
}
}
async setState(newState) {
this.options.state = newState;
await DCSInstance.reloadInstances();
if (newState === 'IDLE')
if (newState === 'IDLE')
this.setActiveInstance(undefined);
}