mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Split client into frontend website and server
This commit is contained in:
8
frontend/website/plugins/_boilerplate/copy.bat
Normal file
8
frontend/website/plugins/_boilerplate/copy.bat
Normal file
@@ -0,0 +1,8 @@
|
||||
mkdir .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin
|
||||
|
||||
copy .\\index.js .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin\\index.js
|
||||
copy .\\plugin.json .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin\\plugin.json
|
||||
copy .\\style.css .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin\\style.css
|
||||
|
||||
mkdir .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin\\images
|
||||
copy .\\images\\*.* .\\..\\DCSOlympus\\client\\public\\plugins\\boilerplateplugin\\images\\
|
||||
BIN
frontend/website/plugins/_boilerplate/images/placeholder1x1.png
Normal file
BIN
frontend/website/plugins/_boilerplate/images/placeholder1x1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 928 B |
14
frontend/website/plugins/_boilerplate/package.json
Normal file
14
frontend/website/plugins/_boilerplate/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "boilerplateplugin",
|
||||
"version": "v0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/sortablejs": "^1.15.4",
|
||||
"browserify": "^17.0.0",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tsify": "^5.0.4"
|
||||
}
|
||||
}
|
||||
7
frontend/website/plugins/_boilerplate/plugin.json
Normal file
7
frontend/website/plugins/_boilerplate/plugin.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "Boilerplate",
|
||||
"version": "0.0.1",
|
||||
"description": "Base plugin starter",
|
||||
"authorName": "",
|
||||
"authorContact": ""
|
||||
}
|
||||
3
frontend/website/plugins/_boilerplate/readme.md
Normal file
3
frontend/website/plugins/_boilerplate/readme.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Boilerplate plugin
|
||||
|
||||
See: https://github.com/Pax1601/DCSOlympus/wiki/Developer-Guide
|
||||
27
frontend/website/plugins/_boilerplate/src/boilerplate.ts
Normal file
27
frontend/website/plugins/_boilerplate/src/boilerplate.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { OlympusPlugin } from "interfaces";
|
||||
import { OlympusApp } from "olympusapp";
|
||||
|
||||
|
||||
export class BoilerplatePlugin implements OlympusPlugin {
|
||||
#app!: OlympusApp;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param app <OlympusApp>
|
||||
*
|
||||
* @returns boolean on success/fail
|
||||
*/
|
||||
|
||||
initialize(app: OlympusApp) : boolean {
|
||||
this.#app = app;
|
||||
|
||||
return true; // Return true on success
|
||||
}
|
||||
|
||||
getName() {
|
||||
return "Boilerplate";
|
||||
}
|
||||
|
||||
}
|
||||
5
frontend/website/plugins/_boilerplate/src/index.ts
Normal file
5
frontend/website/plugins/_boilerplate/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { BoilerplatePlugin } from "./boilerplate";
|
||||
|
||||
globalThis.getOlympusPlugin = () => {
|
||||
return new BoilerplatePlugin();
|
||||
}
|
||||
0
frontend/website/plugins/_boilerplate/style.css
Normal file
0
frontend/website/plugins/_boilerplate/style.css
Normal file
104
frontend/website/plugins/_boilerplate/tsconfig.json
Normal file
104
frontend/website/plugins/_boilerplate/tsconfig.json
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
/* Language and Environment */
|
||||
"target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"rootDirs": ["./src", "./@types"], /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"@types"
|
||||
], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"types": [
|
||||
"olympus"
|
||||
], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
/* JavaScript Support */
|
||||
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": [
|
||||
"src/*.ts",
|
||||
"../DCSOlympus/client/@types/olympus/index.d.ts"
|
||||
]
|
||||
}
|
||||
5
frontend/website/plugins/controltips/copy.bat
Normal file
5
frontend/website/plugins/controltips/copy.bat
Normal file
@@ -0,0 +1,5 @@
|
||||
mkdir .\\..\\..\\public\\plugins\\controltipsplugin
|
||||
|
||||
copy .\\index.js .\\..\\..\\public\\plugins\\controltipsplugin\\index.js
|
||||
copy .\\plugin.json .\\..\\..\\public\\plugins\\controltipsplugin\\plugin.json
|
||||
copy .\\style.css .\\..\\..\\public\\plugins\\controltipsplugin\\style.css
|
||||
377
frontend/website/plugins/controltips/index.js
Normal file
377
frontend/website/plugins/controltips/index.js
Normal file
@@ -0,0 +1,377 @@
|
||||
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
"use strict";
|
||||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
||||
if (kind === "m") throw new TypeError("Private method is not writable");
|
||||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
||||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
||||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
||||
};
|
||||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
||||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
||||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
||||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
||||
};
|
||||
var _ControlTipsPlugin_instances, _ControlTipsPlugin_element, _ControlTipsPlugin_app, _ControlTipsPlugin_shortcutManager, _ControlTipsPlugin_cursorIsHoveringOverUnit, _ControlTipsPlugin_cursorIsHoveringOverAirbase, _ControlTipsPlugin_mouseoverElement, _ControlTipsPlugin_updateTips;
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ControlTipsPlugin = void 0;
|
||||
const SHOW_CONTROL_TIPS = "Show control tips";
|
||||
class ControlTipsPlugin {
|
||||
constructor() {
|
||||
_ControlTipsPlugin_instances.add(this);
|
||||
_ControlTipsPlugin_element.set(this, void 0);
|
||||
_ControlTipsPlugin_app.set(this, void 0);
|
||||
_ControlTipsPlugin_shortcutManager.set(this, void 0);
|
||||
_ControlTipsPlugin_cursorIsHoveringOverUnit.set(this, false);
|
||||
_ControlTipsPlugin_cursorIsHoveringOverAirbase.set(this, false);
|
||||
_ControlTipsPlugin_mouseoverElement.set(this, void 0);
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_element, document.createElement("div"), "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_element, "f").id = "control-tips-panel";
|
||||
document.body.appendChild(__classPrivateFieldGet(this, _ControlTipsPlugin_element, "f"));
|
||||
}
|
||||
getName() {
|
||||
return "Control Tips Plugin";
|
||||
}
|
||||
initialize(app) {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_app, app, "f");
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_shortcutManager, __classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getShortcutManager(), "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").onKeyDown(() => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").onKeyUp(() => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("airbaseMouseover", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, true, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("airbaseMouseout", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, false, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitDeselection", (ev) => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitMouseover", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, true, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitMouseout", (ev) => {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, false, "f");
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("unitsSelection", (ev) => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("mapOptionsChanged", () => {
|
||||
this.toggle(!__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getMap().getVisibilityOptions()[SHOW_CONTROL_TIPS]);
|
||||
});
|
||||
document.addEventListener("mouseover", (ev) => {
|
||||
if (ev.target instanceof HTMLElement) {
|
||||
__classPrivateFieldSet(this, _ControlTipsPlugin_mouseoverElement, ev.target, "f");
|
||||
}
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
document.addEventListener("mouseup", (ev) => {
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
});
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_instances, "m", _ControlTipsPlugin_updateTips).call(this);
|
||||
__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getMap().addVisibilityOption(SHOW_CONTROL_TIPS, true);
|
||||
return true;
|
||||
}
|
||||
getElement() {
|
||||
return __classPrivateFieldGet(this, _ControlTipsPlugin_element, "f");
|
||||
}
|
||||
toggle(bool) {
|
||||
this.getElement().classList.toggle("hide", bool);
|
||||
}
|
||||
}
|
||||
exports.ControlTipsPlugin = ControlTipsPlugin;
|
||||
_ControlTipsPlugin_element = new WeakMap(), _ControlTipsPlugin_app = new WeakMap(), _ControlTipsPlugin_shortcutManager = new WeakMap(), _ControlTipsPlugin_cursorIsHoveringOverUnit = new WeakMap(), _ControlTipsPlugin_cursorIsHoveringOverAirbase = new WeakMap(), _ControlTipsPlugin_mouseoverElement = new WeakMap(), _ControlTipsPlugin_instances = new WeakSet(), _ControlTipsPlugin_updateTips = function _ControlTipsPlugin_updateTips() {
|
||||
const combos = [
|
||||
{
|
||||
"keys": [],
|
||||
"tips": [
|
||||
{
|
||||
"key": `SHIFT`,
|
||||
"action": `Box select`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"showIfUnitSelected": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": `Deselect`,
|
||||
"showIfUnitSelected": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse1+drag`,
|
||||
"action": `Move map`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"showIfUnitSelected": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Spawn menu`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Airbase menu`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Set first waypoint`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2 (hold)`,
|
||||
"action": `Interact (ground)`,
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Shift`,
|
||||
"action": "<em> in formation...</em>",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2
|
||||
},
|
||||
{
|
||||
"key": "CTRL",
|
||||
"action": "<em> ... more</em>",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL",
|
||||
"action": " Pin tool",
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL+Mouse2",
|
||||
"action": " Airbase menu",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle Blue/Red",
|
||||
"mouseoverSelector": "#coalition-switch .ol-switch-fill"
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": "Set Neutral",
|
||||
"mouseoverSelector": "#coalition-switch .ol-switch-fill"
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle time display",
|
||||
"mouseoverSelector": "#connection-status-panel[data-is-connected] #connection-status-message abbr"
|
||||
},
|
||||
{
|
||||
"key": `Mouse1 or Z`,
|
||||
"action": "Change location system",
|
||||
"mouseoverSelector": "#coordinates-tool, #coordinates-tool *"
|
||||
},
|
||||
{
|
||||
"key": `Comma`,
|
||||
"action": "Decrease precision",
|
||||
"mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *`
|
||||
},
|
||||
{
|
||||
"key": `Period`,
|
||||
"action": "Increase precision",
|
||||
"mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ControlLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle pin",
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle selection",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Add waypoint`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Interact (airbase)`,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Interact (unit)`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": true,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Shift`,
|
||||
"action": "<em> in formation...</em>",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2
|
||||
},
|
||||
{
|
||||
"key": `[Num 1-9]`,
|
||||
"action": "Set hotgroup",
|
||||
"showIfUnitSelected": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ShiftLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `Mouse1+drag`,
|
||||
"action": "Box select",
|
||||
"showIfUnitSelected": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": "Set first formation waypoint",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2
|
||||
},
|
||||
{
|
||||
"key": `[Num 1-9]`,
|
||||
"action": "Add to hotgroup",
|
||||
"showIfUnitSelected": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL",
|
||||
"action": "<em> ... more</em>",
|
||||
"minSelectedUnits": 2,
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"unitsMustBeControlled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ControlLeft", "ShiftLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": "Add formation waypoint",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2,
|
||||
"unitsMustBeControlled": true
|
||||
}, {
|
||||
"key": `[Num 1-9]`,
|
||||
"action": "Add hotgroup to selection",
|
||||
"callback": (tip) => {
|
||||
return (Object.values(__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getUnitsManager().getUnits()).some((unit) => {
|
||||
return unit.getAlive() && unit.getControlled() && unit.getHotgroup();
|
||||
}));
|
||||
},
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
const currentCombo = combos.find((combo) => __classPrivateFieldGet(this, _ControlTipsPlugin_shortcutManager, "f").keyComboMatches(combo.keys)) || combos[0];
|
||||
const element = this.getElement();
|
||||
element.innerHTML = "";
|
||||
let numSelectedUnits = 0;
|
||||
let numSelectedControlledUnits = 0;
|
||||
let unitSelectionContainsControlled = false;
|
||||
if (__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getUnitsManager()) {
|
||||
let selectedUnits = Object.values(__classPrivateFieldGet(this, _ControlTipsPlugin_app, "f").getUnitsManager().getSelectedUnits());
|
||||
numSelectedUnits = selectedUnits.length;
|
||||
numSelectedControlledUnits = selectedUnits.filter((unit) => unit.getControlled()).length;
|
||||
unitSelectionContainsControlled = numSelectedControlledUnits > 0;
|
||||
}
|
||||
const tipsIncludesActiveMouseover = (currentCombo.tips.some((tip) => {
|
||||
if (!tip.mouseoverSelector) {
|
||||
return false;
|
||||
}
|
||||
if (__classPrivateFieldGet(this, _ControlTipsPlugin_mouseoverElement, "f") instanceof HTMLElement === false) {
|
||||
return false;
|
||||
}
|
||||
if (!__classPrivateFieldGet(this, _ControlTipsPlugin_mouseoverElement, "f").matches(tip.mouseoverSelector)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
currentCombo.tips.filter((tip) => {
|
||||
if (numSelectedUnits > 0) {
|
||||
if (tip.showIfUnitSelected === false) {
|
||||
return false;
|
||||
}
|
||||
if (tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false) {
|
||||
return false;
|
||||
}
|
||||
if (typeof tip.minSelectedUnits === "number" && numSelectedControlledUnits < tip.minSelectedUnits) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (numSelectedUnits === 0 && tip.showIfUnitSelected === true) {
|
||||
return false;
|
||||
}
|
||||
if (typeof tip.showIfHoveringOverAirbase === "boolean") {
|
||||
if (tip.showIfHoveringOverAirbase !== __classPrivateFieldGet(this, _ControlTipsPlugin_cursorIsHoveringOverAirbase, "f")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (typeof tip.showIfHoveringOverUnit === "boolean") {
|
||||
if (tip.showIfHoveringOverUnit !== __classPrivateFieldGet(this, _ControlTipsPlugin_cursorIsHoveringOverUnit, "f")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (tipsIncludesActiveMouseover && (typeof tip.mouseoverSelector !== "string" || !__classPrivateFieldGet(this, _ControlTipsPlugin_mouseoverElement, "f").matches(tip.mouseoverSelector))) {
|
||||
return false;
|
||||
}
|
||||
if (!tipsIncludesActiveMouseover && typeof tip.mouseoverSelector === "string") {
|
||||
return false;
|
||||
}
|
||||
if (typeof tip.callback === "function" && !tip.callback(tip)) {
|
||||
return false;
|
||||
}
|
||||
element.innerHTML += `<div><span class="key">${tip.key}</span><span class="action">${tip.action}</span></div>`;
|
||||
});
|
||||
};
|
||||
|
||||
},{}],2:[function(require,module,exports){
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const controltipsplugin_1 = require("./controltipsplugin");
|
||||
globalThis.getOlympusPlugin = () => {
|
||||
return new controltipsplugin_1.ControlTipsPlugin();
|
||||
};
|
||||
|
||||
},{"./controltipsplugin":1}]},{},[2]);
|
||||
29
frontend/website/plugins/controltips/package.json
Normal file
29
frontend/website/plugins/controltips/package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "ControlTipsPlugin",
|
||||
"version": "v0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat",
|
||||
"build-release": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.21.4",
|
||||
"@types/node": "^18.16.1",
|
||||
"@types/sortablejs": "^1.15.0",
|
||||
"babelify": "^10.0.0",
|
||||
"browserify": "^17.0.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"cp": "^0.2.0",
|
||||
"esmify": "^2.1.1",
|
||||
"nodemon": "^2.0.20",
|
||||
"requirejs": "^2.3.6",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tinyify": "^4.0.0",
|
||||
"tsify": "^5.0.4",
|
||||
"tslib": "latest",
|
||||
"typescript": "^4.9.4",
|
||||
"usng.js": "^0.4.5",
|
||||
"watchify": "^4.0.0"
|
||||
}
|
||||
}
|
||||
6
frontend/website/plugins/controltips/plugin.json
Normal file
6
frontend/website/plugins/controltips/plugin.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Control Tip Plugin",
|
||||
"version": "0.0.1",
|
||||
"description": "This plugin shows useful control tips on the right side of the screen. The tips change dynamically depending on what the user does",
|
||||
"author": "Peekaboo"
|
||||
}
|
||||
396
frontend/website/plugins/controltips/src/controltipsplugin.ts
Normal file
396
frontend/website/plugins/controltips/src/controltipsplugin.ts
Normal file
@@ -0,0 +1,396 @@
|
||||
import { OlympusPlugin } from "interfaces";
|
||||
import { OlympusApp } from "olympusapp";
|
||||
import { Unit } from "unit/unit";
|
||||
|
||||
|
||||
const SHOW_CONTROL_TIPS = "Show control tips"
|
||||
|
||||
export class ControlTipsPlugin implements OlympusPlugin {
|
||||
#element: HTMLElement;
|
||||
#app: any;
|
||||
#shortcutManager: any;
|
||||
#cursorIsHoveringOverUnit: boolean = false;
|
||||
#cursorIsHoveringOverAirbase: boolean = false;
|
||||
#mouseoverElement!: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
this.#element = document.createElement("div");
|
||||
this.#element.id = "control-tips-panel";
|
||||
document.body.appendChild(this.#element);
|
||||
}
|
||||
|
||||
getName() {
|
||||
return "Control Tips Plugin"
|
||||
}
|
||||
|
||||
initialize(app: any) {
|
||||
this.#app = app;
|
||||
|
||||
this.#shortcutManager = this.#app.getShortcutManager();
|
||||
|
||||
this.#shortcutManager.onKeyDown(() => {
|
||||
this.#updateTips()
|
||||
});
|
||||
|
||||
this.#shortcutManager.onKeyUp(() => {
|
||||
this.#updateTips()
|
||||
});
|
||||
|
||||
document.addEventListener("airbaseMouseover", (ev: CustomEventInit) => {
|
||||
this.#cursorIsHoveringOverAirbase = true;
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
document.addEventListener("airbaseMouseout", (ev: CustomEventInit) => {
|
||||
this.#cursorIsHoveringOverAirbase = false;
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
document.addEventListener("unitDeselection", (ev: CustomEventInit ) => {
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
document.addEventListener("unitMouseover", (ev: CustomEventInit) => {
|
||||
this.#cursorIsHoveringOverUnit = true;
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
document.addEventListener("unitMouseout", (ev: CustomEventInit) => {
|
||||
this.#cursorIsHoveringOverUnit = false;
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
document.addEventListener("unitsSelection", (ev: CustomEventInit ) => {
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
document.addEventListener("mapOptionsChanged", () => {
|
||||
this.toggle( !this.#app.getMap().getVisibilityOptions()[SHOW_CONTROL_TIPS] );
|
||||
});
|
||||
|
||||
document.addEventListener( "mouseover", ( ev: MouseEvent ) => {
|
||||
if ( ev.target instanceof HTMLElement ) {
|
||||
this.#mouseoverElement = <HTMLElement>ev.target;
|
||||
}
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
document.addEventListener( "mouseup", ( ev: MouseEvent ) => {
|
||||
this.#updateTips();
|
||||
});
|
||||
|
||||
this.#updateTips();
|
||||
|
||||
this.#app.getMap().addVisibilityOption(SHOW_CONTROL_TIPS, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getElement() {
|
||||
return this.#element;
|
||||
}
|
||||
|
||||
toggle(bool?: boolean) {
|
||||
this.getElement().classList.toggle("hide", bool);
|
||||
}
|
||||
|
||||
#updateTips() {
|
||||
const combos: Array<object> = [
|
||||
{
|
||||
"keys": [],
|
||||
"tips": [
|
||||
{
|
||||
"key": `SHIFT`,
|
||||
"action": `Box select`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"showIfUnitSelected": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": `Deselect`,
|
||||
"showIfUnitSelected": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse1+drag`,
|
||||
"action": `Move map`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"showIfUnitSelected": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Spawn menu`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Airbase menu`,
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Set first waypoint`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2 (hold)`,
|
||||
"action": `Interact (ground)`,
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Shift`,
|
||||
"action": "<em> in formation...</em>",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2
|
||||
},
|
||||
{
|
||||
"key": "CTRL",
|
||||
"action": "<em> ... more</em>",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL",
|
||||
"action": " Pin tool",
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL+Mouse2",
|
||||
"action": " Airbase menu",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle Blue/Red",
|
||||
"mouseoverSelector": "#coalition-switch .ol-switch-fill"
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": "Set Neutral",
|
||||
"mouseoverSelector": "#coalition-switch .ol-switch-fill"
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle time display",
|
||||
"mouseoverSelector": "#connection-status-panel[data-is-connected] #connection-status-message abbr"
|
||||
},
|
||||
{
|
||||
"key": `Mouse1 or Z`,
|
||||
"action": "Change location system",
|
||||
"mouseoverSelector": "#coordinates-tool, #coordinates-tool *"
|
||||
},
|
||||
{
|
||||
"key": `Comma`,
|
||||
"action": "Decrease precision",
|
||||
"mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *`
|
||||
},
|
||||
{
|
||||
"key": `Period`,
|
||||
"action": "Increase precision",
|
||||
"mouseoverSelector": `#coordinates-tool[data-location-system="MGRS"], #coordinates-tool[data-location-system="MGRS"] *`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ControlLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle pin",
|
||||
"showIfUnitSelected": false,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse1`,
|
||||
"action": "Toggle selection",
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Add waypoint`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": false,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Interact (airbase)`,
|
||||
"showIfHoveringOverAirbase": true,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": `Interact (unit)`,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"showIfHoveringOverUnit": true,
|
||||
"showIfUnitSelected": true,
|
||||
"unitsMustBeControlled": true
|
||||
},
|
||||
{
|
||||
"key": `Shift`,
|
||||
"action": "<em> in formation...</em>",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2
|
||||
},
|
||||
{
|
||||
"key": `[Num 1-9]`,
|
||||
"action": "Set hotgroup",
|
||||
"showIfUnitSelected": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ShiftLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `Mouse1+drag`,
|
||||
"action": "Box select",
|
||||
"showIfUnitSelected": false
|
||||
},
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": "Set first formation waypoint",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2
|
||||
},
|
||||
{
|
||||
"key": `[Num 1-9]`,
|
||||
"action": "Add to hotgroup",
|
||||
"showIfUnitSelected": true
|
||||
},
|
||||
{
|
||||
"key": "CTRL",
|
||||
"action": "<em> ... more</em>",
|
||||
"minSelectedUnits": 2,
|
||||
"showIfUnitSelected": true,
|
||||
"showIfHoveringOverAirbase": false,
|
||||
"unitsMustBeControlled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"keys": ["ControlLeft", "ShiftLeft"],
|
||||
"tips": [
|
||||
{
|
||||
"key": `Mouse2`,
|
||||
"action": "Add formation waypoint",
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 2,
|
||||
"unitsMustBeControlled": true
|
||||
}, {
|
||||
"key": `[Num 1-9]`,
|
||||
"action": "Add hotgroup to selection",
|
||||
"callback": ( tip:object ) => {
|
||||
return (Object.values<Unit>( this.#app.getUnitsManager().getUnits() ).some( ( unit:Unit ) => {
|
||||
return unit.getAlive() && unit.getControlled() && unit.getHotgroup();
|
||||
}));
|
||||
},
|
||||
"showIfUnitSelected": true,
|
||||
"minSelectedUnits": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const currentCombo: any = combos.find((combo: any) => this.#shortcutManager.keyComboMatches(combo.keys)) || combos[0];
|
||||
|
||||
const element = this.getElement();
|
||||
|
||||
element.innerHTML = "";
|
||||
|
||||
let numSelectedUnits = 0;
|
||||
let numSelectedControlledUnits = 0;
|
||||
let unitSelectionContainsControlled = false;
|
||||
|
||||
if (this.#app.getUnitsManager()) {
|
||||
let selectedUnits = Object.values(this.#app.getUnitsManager().getSelectedUnits());
|
||||
numSelectedUnits = selectedUnits.length;
|
||||
numSelectedControlledUnits = selectedUnits.filter((unit: any) => unit.getControlled()).length;
|
||||
unitSelectionContainsControlled = numSelectedControlledUnits > 0;
|
||||
}
|
||||
|
||||
const tipsIncludesActiveMouseover = ( currentCombo.tips.some( ( tip:any ) => {
|
||||
if ( !tip.mouseoverSelector ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( this.#mouseoverElement instanceof HTMLElement === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !this.#mouseoverElement.matches( tip.mouseoverSelector ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}));
|
||||
|
||||
currentCombo.tips.filter((tip: any) => {
|
||||
if (numSelectedUnits > 0) {
|
||||
if (tip.showIfUnitSelected === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tip.unitsMustBeControlled === true && unitSelectionContainsControlled === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( typeof tip.minSelectedUnits === "number" && numSelectedControlledUnits < tip.minSelectedUnits ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (numSelectedUnits === 0 && tip.showIfUnitSelected === true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof tip.showIfHoveringOverAirbase === "boolean") {
|
||||
if (tip.showIfHoveringOverAirbase !== this.#cursorIsHoveringOverAirbase) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof tip.showIfHoveringOverUnit === "boolean") {
|
||||
if (tip.showIfHoveringOverUnit !== this.#cursorIsHoveringOverUnit) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ( tipsIncludesActiveMouseover && ( typeof tip.mouseoverSelector !== "string" || !this.#mouseoverElement.matches( tip.mouseoverSelector ) ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !tipsIncludesActiveMouseover && typeof tip.mouseoverSelector === "string" ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( typeof tip.callback === "function" && !tip.callback( tip ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
element.innerHTML += `<div><span class="key">${tip.key}</span><span class="action">${tip.action}</span></div>`;
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
5
frontend/website/plugins/controltips/src/index.ts
Normal file
5
frontend/website/plugins/controltips/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { ControlTipsPlugin } from "./controltipsplugin";
|
||||
|
||||
globalThis.getOlympusPlugin = () => {
|
||||
return new ControlTipsPlugin();
|
||||
}
|
||||
33
frontend/website/plugins/controltips/style.css
Normal file
33
frontend/website/plugins/controltips/style.css
Normal 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: .9;
|
||||
padding: 5px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
#control-tips-panel>*>.key {
|
||||
background-color: var(--background-grey);
|
||||
border-radius: var(--border-radius-sm);
|
||||
color: white;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
104
frontend/website/plugins/controltips/tsconfig.json
Normal file
104
frontend/website/plugins/controltips/tsconfig.json
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
/* Language and Environment */
|
||||
"target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"rootDirs": ["./src", "../../@types"], /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"../../@types"
|
||||
], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"types": [
|
||||
"olympus"
|
||||
], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
/* JavaScript Support */
|
||||
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": [
|
||||
"src/*.ts",
|
||||
"../../@types/olympus/*.d.ts"
|
||||
]
|
||||
}
|
||||
20
frontend/website/plugins/databasemanager/.vscode/launch.json
vendored
Normal file
20
frontend/website/plugins/databasemanager/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Attach to Chrome",
|
||||
"port": 9222,
|
||||
"urlFilter": "http://localhost:3000/*",
|
||||
"request": "attach",
|
||||
"type": "chrome",
|
||||
"webRoot": "${workspaceFolder}../../public/",
|
||||
"sourceMapPathOverrides": {
|
||||
"src/*": "src/*"
|
||||
},
|
||||
"preLaunchTask": "start"
|
||||
}
|
||||
]
|
||||
}
|
||||
13
frontend/website/plugins/databasemanager/.vscode/tasks.json
vendored
Normal file
13
frontend/website/plugins/databasemanager/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
// See https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
// for the documentation about the tasks.json format
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "start",
|
||||
"type": "shell",
|
||||
"command": "npm run start",
|
||||
"isBackground": true
|
||||
}
|
||||
]
|
||||
}
|
||||
5
frontend/website/plugins/databasemanager/copy.bat
Normal file
5
frontend/website/plugins/databasemanager/copy.bat
Normal file
@@ -0,0 +1,5 @@
|
||||
mkdir .\\..\\..\\public\\plugins\\databasemanager
|
||||
|
||||
copy .\\index.js .\\..\\..\\public\\plugins\\databasemanager\\index.js
|
||||
copy .\\plugin.json .\\..\\..\\public\\plugins\\databasemanager\\plugin.json
|
||||
copy .\\style.css .\\..\\..\\public\\plugins\\databasemanager\\style.css
|
||||
1
frontend/website/plugins/databasemanager/index.js
Normal file
1
frontend/website/plugins/databasemanager/index.js
Normal file
File diff suppressed because one or more lines are too long
32
frontend/website/plugins/databasemanager/package.json
Normal file
32
frontend/website/plugins/databasemanager/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "DatabaseManagerPlugin",
|
||||
"version": "v0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "browserify ./src/index.ts -p [ tsify --noImplicitAny] > index.js && copy.bat",
|
||||
"build-release": "browserify ./src/index.ts -p [ tsify --noImplicitAny] -p [ tinyify ] > index.js && copy.bat",
|
||||
"start": "npm run copy & concurrently --kill-others \"npm run watch\"",
|
||||
"copy": "copy.bat",
|
||||
"watch": "watchify ./src/index.ts --debug -o ../../public/plugins/databasemanager/index.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js']"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@babel/preset-env": "^7.21.4",
|
||||
"@types/node": "^18.16.1",
|
||||
"@types/sortablejs": "^1.15.0",
|
||||
"babelify": "^10.0.0",
|
||||
"browserify": "^17.0.0",
|
||||
"concurrently": "^7.6.0",
|
||||
"cp": "^0.2.0",
|
||||
"esmify": "^2.1.1",
|
||||
"nodemon": "^2.0.20",
|
||||
"requirejs": "^2.3.6",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tinyify": "^4.0.0",
|
||||
"tsify": "^5.0.4",
|
||||
"tslib": "latest",
|
||||
"typescript": "^4.9.4",
|
||||
"usng.js": "^0.4.5",
|
||||
"watchify": "^4.0.0"
|
||||
}
|
||||
}
|
||||
6
frontend/website/plugins/databasemanager/plugin.json
Normal file
6
frontend/website/plugins/databasemanager/plugin.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "Control Tip Plugin",
|
||||
"version": "0.0.1",
|
||||
"description": "This plugin shows useful control tips on the right side of the screen. The tips change dynamically depending on what the user does",
|
||||
"author": "Peekaboo"
|
||||
}
|
||||
110
frontend/website/plugins/databasemanager/src/airuniteditor.ts
Normal file
110
frontend/website/plugins/databasemanager/src/airuniteditor.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { LoadoutBlueprint, UnitBlueprint } from "interfaces";
|
||||
import { UnitEditor } from "./uniteditor";
|
||||
import { LoadoutEditor } from "./loadouteditor";
|
||||
import { addCheckboxInput, addDropdownInput, addLoadoutsScroll, addNewElementInput, addStringInput } from "./utils";
|
||||
|
||||
/** Database editor for Air Units, both Aircraft and Helicopter since they are identical in terms of datbase entries.
|
||||
*
|
||||
*/
|
||||
export class AirUnitEditor extends UnitEditor {
|
||||
|
||||
#loadoutEditor: LoadoutEditor | null = null;
|
||||
|
||||
constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) {
|
||||
super(contentDiv1, contentDiv2, contentDiv3);
|
||||
|
||||
/* The loadout editor allows to edit the loadout (who could have thought eh?) */
|
||||
this.#loadoutEditor = new LoadoutEditor(this.contentDiv3);
|
||||
|
||||
/* Refresh the loadout editor if needed */
|
||||
this.contentDiv3.addEventListener("refresh", () => {
|
||||
if (this.visible)
|
||||
this.#loadoutEditor?.show();
|
||||
});
|
||||
}
|
||||
|
||||
/** Sets a unit blueprint as the currently active one
|
||||
*
|
||||
* @param blueprint The blueprint to edit
|
||||
*/
|
||||
setBlueprint(blueprint: UnitBlueprint) {
|
||||
this.blueprint = blueprint;
|
||||
|
||||
if (this.blueprint !== null) {
|
||||
this.contentDiv2.replaceChildren();
|
||||
|
||||
var title = document.createElement("label");
|
||||
title.innerText = "Unit properties";
|
||||
this.contentDiv2.appendChild(title);
|
||||
|
||||
addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => { blueprint.name = value; }, true);
|
||||
addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => { blueprint.label = value; });
|
||||
addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => { blueprint.shortLabel = value; });
|
||||
addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value: string) => {blueprint.coalition = value; });
|
||||
addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value: string) => {blueprint.era = value; });
|
||||
addStringInput(this.contentDiv2, "Filename", blueprint.filename ?? "", "text", (value: string) => { blueprint.filename = value; });
|
||||
addStringInput(this.contentDiv2, "Cost", String(blueprint.cost) ?? "", "number", (value: string) => { blueprint.cost = parseFloat(value); });
|
||||
addCheckboxInput(this.contentDiv2, "Can target point", blueprint.canTargetPoint ?? false, (value: boolean) => {blueprint.canTargetPoint = value;})
|
||||
addStringInput(this.contentDiv2, "Description", blueprint.description ?? "", "text", (value: string) => {blueprint.description = value; });
|
||||
addStringInput(this.contentDiv2, "Tags", blueprint.tags ?? "", "text", (value: string) => {blueprint.tags = value; });
|
||||
|
||||
/* Add a scrollable list of loadouts that the user can edit */
|
||||
var title = document.createElement("label");
|
||||
title.innerText = "Loadouts";
|
||||
this.contentDiv2.appendChild(title);
|
||||
addLoadoutsScroll(this.contentDiv2, blueprint.loadouts ?? [], (loadout: LoadoutBlueprint) => {
|
||||
this.#loadoutEditor?.setLoadout(loadout);
|
||||
this.#loadoutEditor?.show();
|
||||
});
|
||||
addNewElementInput(this.contentDiv2, (ev: MouseEvent, input: HTMLInputElement) => { this.addLoadout(input.value); });
|
||||
|
||||
this.#loadoutEditor?.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/** Add a new empty blueprint
|
||||
*
|
||||
* @param key Blueprint key
|
||||
*/
|
||||
addBlueprint(key: string) {
|
||||
if (this.database != null) {
|
||||
this.database.blueprints[key] = {
|
||||
name: key,
|
||||
coalition: "",
|
||||
label: "",
|
||||
shortLabel: "",
|
||||
era: "",
|
||||
loadouts: [],
|
||||
enabled: true
|
||||
}
|
||||
this.show();
|
||||
this.setBlueprint(this.database.blueprints[key]);
|
||||
}
|
||||
}
|
||||
|
||||
/** Add a new empty loadout to the currently active blueprint
|
||||
*
|
||||
* @param loadoutName The name of the new loadout
|
||||
*/
|
||||
addLoadout(loadoutName: string) {
|
||||
if (loadoutName && this.blueprint !== null) {
|
||||
this.blueprint.loadouts?.push({
|
||||
name: loadoutName,
|
||||
code: "",
|
||||
fuel: 1,
|
||||
items: [],
|
||||
roles: [],
|
||||
enabled: true
|
||||
})
|
||||
this.setBlueprint(this.blueprint);
|
||||
}
|
||||
}
|
||||
|
||||
/** Hide the editor
|
||||
*
|
||||
*/
|
||||
hide() {
|
||||
super.hide();
|
||||
this.#loadoutEditor?.hide();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
import { OlympusPlugin, UnitBlueprint } from "interfaces";
|
||||
import { AirUnitEditor } from "./airuniteditor";
|
||||
import { OlympusApp } from "olympusapp";
|
||||
import { GroundUnitEditor } from "./grounduniteditor";
|
||||
import { PrimaryToolbar } from "toolbars/primarytoolbar";
|
||||
import { NavyUnitEditor } from "./navyuniteditor";
|
||||
|
||||
/** Database Manager
|
||||
*
|
||||
* This database provides a user interface to allow easier and convenient unit databases manipulation. It allows to edit all the fields of the units databases, save them
|
||||
* on the server, and restore the defaults.
|
||||
*
|
||||
* TODO:
|
||||
* Add ability to manage liveries
|
||||
*
|
||||
*/
|
||||
|
||||
export class DatabaseManagerPlugin implements OlympusPlugin {
|
||||
#app!: OlympusApp;
|
||||
|
||||
#element: HTMLElement;
|
||||
#mainContentContainer: HTMLElement;
|
||||
#contentDiv1: HTMLElement;
|
||||
#contentDiv2: HTMLElement;
|
||||
#contentDiv3: HTMLElement;
|
||||
|
||||
/* Upper tab buttons */
|
||||
#button1: HTMLButtonElement;
|
||||
#button2: HTMLButtonElement;
|
||||
#button3: HTMLButtonElement;
|
||||
#button4: HTMLButtonElement;
|
||||
|
||||
/* Lower operation buttons */
|
||||
#button5: HTMLButtonElement;
|
||||
#button6: HTMLButtonElement;
|
||||
#button7: HTMLButtonElement;
|
||||
#button8: HTMLButtonElement;
|
||||
#button9: HTMLButtonElement;
|
||||
|
||||
/* Database editors */
|
||||
#aircraftEditor: AirUnitEditor;
|
||||
#helicopterEditor: AirUnitEditor;
|
||||
#groundUnitEditor: GroundUnitEditor;
|
||||
#navyUnitEditor: NavyUnitEditor;
|
||||
|
||||
constructor() {
|
||||
/* Create main HTML element */
|
||||
this.#element = document.createElement("div");
|
||||
this.#element.id = "database-manager-panel";
|
||||
this.#element.oncontextmenu = () => { return false; }
|
||||
this.#element.classList.add("ol-dialog");
|
||||
document.body.appendChild(this.#element);
|
||||
|
||||
/* Start hidden */
|
||||
this.toggle(false);
|
||||
|
||||
/* Create the top tab buttons container and buttons */
|
||||
let topButtonContainer = document.createElement("div");
|
||||
|
||||
this.#button1 = document.createElement("button");
|
||||
this.#button1.classList.add("tab-button");
|
||||
this.#button1.textContent = "Aircraft database";
|
||||
this.#button1.onclick = () => { this.#hideAll(); this.#aircraftEditor.show(); this.#button1.classList.add("selected"); };
|
||||
topButtonContainer.appendChild(this.#button1);
|
||||
|
||||
this.#button2 = document.createElement("button");
|
||||
this.#button2.classList.add("tab-button");
|
||||
this.#button2.textContent = "Helicopter database";
|
||||
this.#button2.onclick = () => { this.#hideAll(); this.#helicopterEditor.show(); this.#button2.classList.add("selected"); };
|
||||
topButtonContainer.appendChild(this.#button2);
|
||||
|
||||
this.#button3 = document.createElement("button");
|
||||
this.#button3.classList.add("tab-button");
|
||||
this.#button3.textContent = "Ground Unit database";
|
||||
this.#button3.onclick = () => { this.#hideAll(); this.#groundUnitEditor.show(); this.#button3.classList.add("selected"); };
|
||||
topButtonContainer.appendChild(this.#button3);
|
||||
|
||||
this.#button4 = document.createElement("button");
|
||||
this.#button4.classList.add("tab-button");
|
||||
this.#button4.textContent = "Navy Unit database";
|
||||
this.#button4.onclick = () => { this.#hideAll(); this.#navyUnitEditor.show(); this.#button4.classList.add("selected"); };
|
||||
topButtonContainer.appendChild(this.#button4);
|
||||
|
||||
this.#element.appendChild(topButtonContainer);
|
||||
|
||||
/* Create the container for the database editor elements and the elements themselves */
|
||||
this.#mainContentContainer = document.createElement("div");
|
||||
this.#mainContentContainer.classList.add("dm-container");
|
||||
this.#element.appendChild(this.#mainContentContainer);
|
||||
|
||||
this.#contentDiv1 = document.createElement("div");
|
||||
this.#contentDiv1.classList.add("dm-content-container", "ol-scrollable");
|
||||
this.#mainContentContainer.appendChild(this.#contentDiv1);
|
||||
|
||||
this.#contentDiv2 = document.createElement("div");
|
||||
this.#contentDiv2.classList.add("dm-content-container", "ol-scrollable");
|
||||
this.#mainContentContainer.appendChild(this.#contentDiv2);
|
||||
|
||||
this.#contentDiv3 = document.createElement("div");
|
||||
this.#contentDiv3.classList.add("dm-content-container", "ol-scrollable");
|
||||
this.#mainContentContainer.appendChild(this.#contentDiv3);
|
||||
|
||||
/* Create the database editors, which use the three divs created before */
|
||||
this.#aircraftEditor = new AirUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
|
||||
this.#helicopterEditor = new AirUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
|
||||
this.#groundUnitEditor = new GroundUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
|
||||
this.#navyUnitEditor = new NavyUnitEditor(this.#contentDiv1, this.#contentDiv2, this.#contentDiv3);
|
||||
|
||||
/* Create the bottom buttons container. These buttons allow to save, restore, reset, and discard the changes */
|
||||
let bottomButtonContainer = document.createElement("div");
|
||||
|
||||
this.#button5 = document.createElement("button");
|
||||
this.#button5.textContent = "Save";
|
||||
this.#button5.title = "Save the changes on the server"
|
||||
this.#button5.onclick = () => { this.#saveDatabases();};
|
||||
bottomButtonContainer.appendChild(this.#button5);
|
||||
|
||||
this.#button6 = document.createElement("button");
|
||||
this.#button6.textContent = "Discard";
|
||||
this.#button6.title = "Discard all changes and reload the database from the server";
|
||||
this.#button6.onclick = () => { this.#loadDatabases(); };
|
||||
bottomButtonContainer.appendChild(this.#button6);
|
||||
|
||||
this.#button7 = document.createElement("button");
|
||||
this.#button7.textContent = "Reset defaults";
|
||||
this.#button7.onclick = () => { this.#resetToDefaultDatabases(); };
|
||||
this.#button7.title = "Reset the databases to the default values";
|
||||
bottomButtonContainer.appendChild(this.#button7);
|
||||
|
||||
this.#button8 = document.createElement("button");
|
||||
this.#button8.textContent = "Restore previous";
|
||||
this.#button8.onclick = () => { this.#restoreToPreviousDatabases(); };
|
||||
this.#button8.title = "Restore the previously saved databases. Use this if you saved a database by mistake.";
|
||||
bottomButtonContainer.appendChild(this.#button8);
|
||||
|
||||
this.#button9 = document.createElement("button");
|
||||
this.#button9.textContent = "Close";
|
||||
this.#button9.title = "Close the Database Manager"
|
||||
this.#button9.onclick = () => { this.toggle(false); };
|
||||
bottomButtonContainer.appendChild(this.#button9);
|
||||
|
||||
this.#element.appendChild(bottomButtonContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The name of the plugin
|
||||
*/
|
||||
getName() {
|
||||
return "Database Control Plugin"
|
||||
}
|
||||
|
||||
/** Initialize the plugin
|
||||
*
|
||||
* @param app The OlympusApp singleton
|
||||
* @returns True if successfull
|
||||
*/
|
||||
initialize(app: any) {
|
||||
this.#app = app;
|
||||
|
||||
const contextManager = this.#app.getContextManager();
|
||||
contextManager.add( "databaseManager", {
|
||||
"allowUnitCopying": false,
|
||||
"allowUnitPasting": false,
|
||||
"useSpawnMenu": false,
|
||||
"useUnitControlPanel": false,
|
||||
"useUnitInfoPanel": false
|
||||
});
|
||||
|
||||
/* Load the databases and initialize the editors */
|
||||
this.#loadDatabases();
|
||||
|
||||
/* Add a button to the main Olympus App to allow the users to open the dialog */
|
||||
var mainButtonDiv = document.createElement("div");
|
||||
var mainButton = document.createElement("button");
|
||||
mainButton.textContent = "Database manager";
|
||||
mainButtonDiv.appendChild(mainButton);
|
||||
var toolbar: PrimaryToolbar = this.#app?.getToolbarsManager().get("primaryToolbar") as PrimaryToolbar;
|
||||
var elements = toolbar.getMainDropdown().getOptionElements();
|
||||
var arr = Array.prototype.slice.call(elements);
|
||||
arr.splice(arr.length - 3, 0, mainButtonDiv);
|
||||
toolbar.getMainDropdown().setOptionsElements(arr);
|
||||
mainButton.onclick = () => {
|
||||
toolbar.getMainDropdown().close();
|
||||
if (this.#app?.getMissionManager().getCommandModeOptions().commandMode === "Game master")
|
||||
this.toggle();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The main container element
|
||||
*/
|
||||
getElement() {
|
||||
return this.#element;
|
||||
}
|
||||
|
||||
/** Toggles the visibility of the dialog
|
||||
*
|
||||
* @param bool Force a specific visibility state
|
||||
*/
|
||||
toggle(bool?: boolean) {
|
||||
if (bool)
|
||||
this.getElement().classList.toggle("hide", !bool);
|
||||
else
|
||||
this.getElement().classList.toggle("hide");
|
||||
|
||||
if ( this.#app )
|
||||
this.#app.getContextManager().setContext( this.getElement().classList.contains("hide") ? "olympus" : "databaseManager" );
|
||||
}
|
||||
|
||||
/** Hide all the editors
|
||||
*
|
||||
*/
|
||||
#hideAll() {
|
||||
this.#aircraftEditor.hide();
|
||||
this.#helicopterEditor.hide();
|
||||
this.#groundUnitEditor.hide();
|
||||
this.#navyUnitEditor.hide();
|
||||
|
||||
this.#button1.classList.remove("selected");
|
||||
this.#button2.classList.remove("selected");
|
||||
this.#button3.classList.remove("selected");
|
||||
this.#button4.classList.remove("selected");
|
||||
}
|
||||
|
||||
/** Load the databases from the app to the editor. Note, this does not reload the databases from the server to the app
|
||||
*
|
||||
*/
|
||||
#loadDatabases() {
|
||||
var aircraftDatabase = this.#app?.getAircraftDatabase();
|
||||
if (aircraftDatabase != null)
|
||||
this.#aircraftEditor.setDatabase(aircraftDatabase);
|
||||
|
||||
var helicopterDatabase = this.#app?.getHelicopterDatabase();
|
||||
if (helicopterDatabase != null)
|
||||
this.#helicopterEditor.setDatabase(helicopterDatabase);
|
||||
|
||||
var groundUnitDatabase = this.#app?.getGroundUnitDatabase();
|
||||
if (groundUnitDatabase != null)
|
||||
this.#groundUnitEditor.setDatabase(groundUnitDatabase);
|
||||
|
||||
var navyUnitDatabase = this.#app?.getNavyUnitDatabase();
|
||||
if (navyUnitDatabase != null)
|
||||
this.#navyUnitEditor.setDatabase(navyUnitDatabase);
|
||||
|
||||
this.#hideAll();
|
||||
this.#aircraftEditor.show();
|
||||
this.#button1.classList.add("selected");
|
||||
}
|
||||
|
||||
/** Save the databases on the server and reloads it to apply the changes
|
||||
*
|
||||
*/
|
||||
#saveDatabases() {
|
||||
var aircraftDatabase = this.#aircraftEditor.getDatabase();
|
||||
if (aircraftDatabase){
|
||||
this.#uploadDatabase(aircraftDatabase, "aircraftdatabase", "Aircraft database", () => {
|
||||
var helicopterDatabase = this.#helicopterEditor.getDatabase();
|
||||
if (helicopterDatabase) {
|
||||
this.#uploadDatabase(helicopterDatabase, "helicopterDatabase", "Helicopter database", () => {
|
||||
var groundUnitDatabase = this.#groundUnitEditor.getDatabase();
|
||||
if (groundUnitDatabase) {
|
||||
this.#uploadDatabase(groundUnitDatabase, "groundUnitDatabase", "Ground Unit database", () => {
|
||||
var navyUnitDatabase = this.#navyUnitEditor.getDatabase();
|
||||
if (navyUnitDatabase) {
|
||||
this.#uploadDatabase(navyUnitDatabase, "navyUnitDatabase", "Navy Unit database", () => {
|
||||
this.#app?.getAircraftDatabase().load(() => {});
|
||||
this.#app?.getHelicopterDatabase().load(() => {});
|
||||
this.#app?.getGroundUnitDatabase().load(() => {});
|
||||
this.#app?.getNavyUnitDatabase().load(() => {});
|
||||
|
||||
this.#app?.getServerManager().reloadDatabases(() => {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText("Olympus core databases reloaded");
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Resets the databases to the default values
|
||||
*
|
||||
*/
|
||||
#resetToDefaultDatabases() {
|
||||
this.#resetToDefaultDatabase("aircraftdatabase", "Aircraft database", () => {
|
||||
this.#app?.getAircraftDatabase().load(() => {
|
||||
this.#resetToDefaultDatabase("helicopterdatabase", "Helicopter database", () => {
|
||||
this.#app?.getHelicopterDatabase().load(() => {
|
||||
this.#resetToDefaultDatabase("groundunitdatabase", "Ground Unit database", () => {
|
||||
this.#app?.getGroundUnitDatabase().load(() => {
|
||||
this.#resetToDefaultDatabase("navyunitdatabase", "Navy Unit database", () => {
|
||||
this.#app?.getNavyUnitDatabase().load(() => {
|
||||
this.#loadDatabases();
|
||||
|
||||
this.#app?.getServerManager().reloadDatabases(() => {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText("Olympus core databases reloaded");
|
||||
})
|
||||
|
||||
this.#hideAll();
|
||||
this.#aircraftEditor.show();
|
||||
this.#button1.classList.add("selected");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Restores the databases to the previous saved values. This is useful if you saved the databases by mistake and want to undo the error.
|
||||
*
|
||||
*/
|
||||
#restoreToPreviousDatabases() {
|
||||
this.#restoreToPreviousDatabase("aircraftdatabase", "Aircraft database", () => {
|
||||
this.#app?.getAircraftDatabase().load(() => {
|
||||
this.#restoreToPreviousDatabase("helicopterdatabase", "Helicopter database", () => {
|
||||
this.#app?.getHelicopterDatabase().load(() => {
|
||||
this.#restoreToPreviousDatabase("groundunitdatabase", "Ground Unit database", () => {
|
||||
this.#app?.getGroundUnitDatabase().load(() => {
|
||||
this.#restoreToPreviousDatabase("navyunitdatabase", "Navy Unit database", () => {
|
||||
this.#app?.getNavyUnitDatabase().load(() => {
|
||||
this.#loadDatabases();
|
||||
|
||||
this.#app?.getServerManager().reloadDatabases(() => {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText("Olympus core databases reloaded");
|
||||
})
|
||||
|
||||
this.#hideAll();
|
||||
this.#aircraftEditor.show();
|
||||
this.#button1.classList.add("selected");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Upload a single database to the server
|
||||
*
|
||||
* @param database The database
|
||||
* @param name The name of the database as it will be saved on the server
|
||||
* @param label A label used in the info popup
|
||||
*/
|
||||
#uploadDatabase(database: { blueprints: { [key: string]: UnitBlueprint } }, name: string, label: string, callback: CallableFunction) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open("PUT", "/api/databases/save/units/" + name);
|
||||
xmlHttp.setRequestHeader("Content-Type", "application/json");
|
||||
xmlHttp.onload = (res: any) => {
|
||||
if (xmlHttp.status == 200) {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " saved successfully");
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while saving the " + label);
|
||||
}
|
||||
};
|
||||
xmlHttp.onerror = (res: any) => {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while saving the " + label);
|
||||
}
|
||||
xmlHttp.send(JSON.stringify(database));
|
||||
}
|
||||
|
||||
/** Resets a database to its default values on the server. NOTE: this only resets the database on the server, it will not reload it in the app.
|
||||
*
|
||||
* @param name The name of the database as it is saved on the server
|
||||
* @param label A label used in the info popup
|
||||
* @param callback Called when the operation is completed
|
||||
*/
|
||||
#resetToDefaultDatabase(name: string, label: string, callback: CallableFunction) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open("PUT", "/api/databases/reset/units/" + name);
|
||||
xmlHttp.setRequestHeader("Content-Type", "application/json");
|
||||
xmlHttp.onload = (res: any) => {
|
||||
if (xmlHttp.status == 200) {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " reset successfully");
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while resetting the " + label);
|
||||
}
|
||||
};
|
||||
xmlHttp.onerror = (res: any) => {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while resetting the " + label)
|
||||
}
|
||||
xmlHttp.send("");
|
||||
}
|
||||
|
||||
/** Restores a database to its previously saved values on the server. NOTE: this only restores the database on the server, it will not reload it in the app.
|
||||
*
|
||||
* @param name The name of the database as it is saved on the server
|
||||
* @param label A label used in the info popup
|
||||
* @param callback Called when the operation is completed
|
||||
*/
|
||||
#restoreToPreviousDatabase(name: string, label: string, callback: CallableFunction) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.open("PUT", "/api/databases/restore/units/" + name);
|
||||
xmlHttp.setRequestHeader("Content-Type", "application/json");
|
||||
xmlHttp.onload = (res: any) => {
|
||||
if (xmlHttp.status == 200) {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText(label + " restored successfully");
|
||||
callback();
|
||||
}
|
||||
else {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while restoring the " + label);
|
||||
}
|
||||
};
|
||||
xmlHttp.onerror = (res: any) => {
|
||||
this.#app?.getPopupsManager().get("infoPopup")?.setText("An error has occurred while restoring the " + label)
|
||||
}
|
||||
xmlHttp.send("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { UnitBlueprint } from "interfaces";
|
||||
import { UnitEditor } from "./uniteditor";
|
||||
import { addCheckboxInput, addDropdownInput, addStringInput } from "./utils";
|
||||
|
||||
/** Database editor for ground units
|
||||
*
|
||||
*/
|
||||
export class GroundUnitEditor extends UnitEditor {
|
||||
#blueprint: UnitBlueprint | null = null;
|
||||
|
||||
constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) {
|
||||
super(contentDiv1, contentDiv2, contentDiv3);
|
||||
}
|
||||
|
||||
/** Sets a unit blueprint as the currently active one
|
||||
*
|
||||
* @param blueprint The blueprint to edit
|
||||
*/
|
||||
setBlueprint(blueprint: UnitBlueprint) {
|
||||
this.#blueprint = blueprint;
|
||||
|
||||
if (this.#blueprint !== null) {
|
||||
this.contentDiv2.replaceChildren();
|
||||
|
||||
var title = document.createElement("label");
|
||||
title.innerText = "Unit properties";
|
||||
this.contentDiv2.appendChild(title);
|
||||
|
||||
addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => {blueprint.name = value; }, true);
|
||||
addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => {blueprint.label = value; });
|
||||
addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => {blueprint.shortLabel = value; });
|
||||
addStringInput(this.contentDiv2, "Type", blueprint.type?? "", "text", (value: string) => {blueprint.type = value; });
|
||||
addStringInput(this.contentDiv2, "Unit when grouped", blueprint.unitWhenGrouped?? "", "text", (value: string) => {blueprint.unitWhenGrouped = value; });
|
||||
addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value: string) => {blueprint.coalition = value; });
|
||||
addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value: string) => {blueprint.era = value; });
|
||||
//addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; });
|
||||
addStringInput(this.contentDiv2, "Cost", String(blueprint.cost)?? "", "number", (value: string) => {blueprint.cost = parseFloat(value); });
|
||||
addStringInput(this.contentDiv2, "Acquisition range [m]", String(blueprint.acquisitionRange)?? "", "number", (value: string) => {blueprint.acquisitionRange = parseFloat(value); });
|
||||
addStringInput(this.contentDiv2, "Engagement range [m]", String(blueprint.engagementRange)?? "", "number", (value: string) => {blueprint.engagementRange = parseFloat(value); });
|
||||
addStringInput(this.contentDiv2, "Targeting range [m]", String(blueprint.targetingRange)?? "", "number", (value: string) => {blueprint.targetingRange = parseFloat(value); });
|
||||
addStringInput(this.contentDiv2, "Aim method range [m]", String(blueprint.aimMethodRange)?? "", "number", (value: string) => {blueprint.aimMethodRange = parseFloat(value); });
|
||||
addStringInput(this.contentDiv2, "Barrel height [m]", String(blueprint.barrelHeight)?? "", "number", (value: string) => {blueprint.barrelHeight = parseFloat(value); });
|
||||
addStringInput(this.contentDiv2, "Muzzle velocity [m/s]", String(blueprint.muzzleVelocity)?? "", "number", (value: string) => {blueprint.muzzleVelocity = parseFloat(value); });
|
||||
addStringInput(this.contentDiv2, "Aim time [s]", String(blueprint.aimTime)?? "", "number", (value: string) => {blueprint.aimTime = parseFloat(value); });
|
||||
addStringInput(this.contentDiv2, "Shots to fire", String(blueprint.shotsToFire)?? "", "number", (value: string) => {blueprint.shotsToFire = Math.round(parseFloat(value)); });
|
||||
addStringInput(this.contentDiv2, "Shots base interval [s]", String(blueprint.shotsBaseInterval)?? "", "number", (value: string) => {blueprint.shotsBaseInterval = Math.round(parseFloat(value)); });
|
||||
addStringInput(this.contentDiv2, "Shots base scatter [°]", String(blueprint.shotsBaseScatter)?? "", "number", (value: string) => {blueprint.shotsBaseScatter = Math.round(parseFloat(value)); });
|
||||
addStringInput(this.contentDiv2, "Alertness time constant [s]", String(blueprint.alertnessTimeConstant)?? "", "number", (value: string) => {blueprint.alertnessTimeConstant = Math.round(parseFloat(value)); });
|
||||
addCheckboxInput(this.contentDiv2, "Can target point", blueprint.canTargetPoint ?? false, (value: boolean) => {blueprint.canTargetPoint = value;})
|
||||
addCheckboxInput(this.contentDiv2, "Can rearm", blueprint.canRearm ?? false, (value: boolean) => {blueprint.canRearm = value;})
|
||||
addCheckboxInput(this.contentDiv2, "Can operate as AAA", blueprint.canAAA ?? false, (value: boolean) => {blueprint.canAAA = value;})
|
||||
addCheckboxInput(this.contentDiv2, "Indirect fire (e.g. mortar)", blueprint.indirectFire ?? false, (value: boolean) => {blueprint.indirectFire = value;})
|
||||
addStringInput(this.contentDiv2, "Description", blueprint.description ?? "", "text", (value: string) => {blueprint.description = value; });
|
||||
addStringInput(this.contentDiv2, "Tags", blueprint.tags ?? "", "text", (value: string) => {blueprint.tags = value; });
|
||||
addStringInput(this.contentDiv2, "Marker file", blueprint.markerFile ?? "", "text", (value: string) => {blueprint.markerFile = value; });
|
||||
}
|
||||
}
|
||||
|
||||
/** Add a new empty blueprint
|
||||
*
|
||||
* @param key Blueprint key
|
||||
*/
|
||||
addBlueprint(key: string) {
|
||||
if (this.database != null) {
|
||||
this.database.blueprints[key] = {
|
||||
name: key,
|
||||
coalition: "",
|
||||
label: "",
|
||||
shortLabel: "",
|
||||
era: "",
|
||||
enabled: true
|
||||
}
|
||||
this.show();
|
||||
this.setBlueprint(this.database.blueprints[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
5
frontend/website/plugins/databasemanager/src/index.ts
Normal file
5
frontend/website/plugins/databasemanager/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { DatabaseManagerPlugin } from "./databasemanagerplugin";
|
||||
|
||||
globalThis.getOlympusPlugin = () => {
|
||||
return new DatabaseManagerPlugin();
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { LoadoutBlueprint } from "interfaces";
|
||||
import { addLoadoutItemsEditor, addStringInput, arrayToString, stringToArray } from "./utils";
|
||||
|
||||
/** The LoadoutEditor allows the user to edit a loadout
|
||||
*
|
||||
*/
|
||||
export class LoadoutEditor {
|
||||
#contentDiv: HTMLElement;
|
||||
#loadout: LoadoutBlueprint | null = null;
|
||||
#visible: boolean = false;
|
||||
|
||||
constructor(contentDiv: HTMLElement) {
|
||||
this.#contentDiv = contentDiv;
|
||||
this.#contentDiv.addEventListener("refresh", () => {
|
||||
if (this.#visible)
|
||||
this.show();
|
||||
})
|
||||
}
|
||||
|
||||
/** Set the loadout to edit
|
||||
*
|
||||
* @param loadout The loadout to edit
|
||||
*/
|
||||
setLoadout(loadout: LoadoutBlueprint) {
|
||||
this.#loadout = loadout;
|
||||
}
|
||||
|
||||
/** Show the editor
|
||||
*
|
||||
*/
|
||||
show() {
|
||||
this.#visible = true;
|
||||
this.#contentDiv.replaceChildren();
|
||||
|
||||
var title = document.createElement("label");
|
||||
title.innerText = "Loadout properties";
|
||||
this.#contentDiv.appendChild(title);
|
||||
|
||||
if (this.#loadout) {
|
||||
var loadout = this.#loadout;
|
||||
addStringInput(this.#contentDiv, "Name", loadout.name, "text", (value: string) => {loadout.name = value; this.#contentDiv.dispatchEvent(new Event("refresh"));});
|
||||
addStringInput(this.#contentDiv, "Code", loadout.code, "text", (value: string) => {loadout.code = value; });
|
||||
addStringInput(this.#contentDiv, "Roles", arrayToString(loadout.roles), "text", (value: string) => {loadout.roles = stringToArray(value);});
|
||||
addLoadoutItemsEditor(this.#contentDiv, this.#loadout);
|
||||
}
|
||||
}
|
||||
|
||||
/** Hide the editor
|
||||
*
|
||||
*/
|
||||
hide() {
|
||||
this.#visible = false;
|
||||
this.#contentDiv.replaceChildren();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { UnitBlueprint } from "interfaces";
|
||||
import { UnitEditor } from "./uniteditor";
|
||||
import { addDropdownInput, addStringInput } from "./utils";
|
||||
|
||||
/** Database editor for navy units
|
||||
*
|
||||
*/
|
||||
export class NavyUnitEditor extends UnitEditor {
|
||||
#blueprint: UnitBlueprint | null = null;
|
||||
|
||||
constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) {
|
||||
super(contentDiv1, contentDiv2, contentDiv3);
|
||||
}
|
||||
|
||||
/** Sets a unit blueprint as the currently active one
|
||||
*
|
||||
* @param blueprint The blueprint to edit
|
||||
*/
|
||||
setBlueprint(blueprint: UnitBlueprint) {
|
||||
this.#blueprint = blueprint;
|
||||
|
||||
if (this.#blueprint !== null) {
|
||||
this.contentDiv2.replaceChildren();
|
||||
|
||||
var title = document.createElement("label");
|
||||
title.innerText = "Unit properties";
|
||||
this.contentDiv2.appendChild(title);
|
||||
|
||||
addStringInput(this.contentDiv2, "Name", blueprint.name, "text", (value: string) => {blueprint.name = value; }, true);
|
||||
addStringInput(this.contentDiv2, "Label", blueprint.label, "text", (value: string) => {blueprint.label = value; });
|
||||
addStringInput(this.contentDiv2, "Short label", blueprint.shortLabel, "text", (value: string) => {blueprint.shortLabel = value; });
|
||||
addStringInput(this.contentDiv2, "Type", blueprint.type?? "", "text", (value: string) => {blueprint.type = value; });
|
||||
addDropdownInput(this.contentDiv2, "Coalition", blueprint.coalition, ["", "blue", "red"], (value: string) => {blueprint.coalition = value; });
|
||||
addDropdownInput(this.contentDiv2, "Era", blueprint.era, ["WW2", "Early Cold War", "Mid Cold War", "Late Cold War", "Modern"], (value: string) => {blueprint.era = value; });
|
||||
//addStringInput(this.contentDiv2, "Filename", blueprint.filename?? "", "text", (value: string) => {blueprint.filename = value; });
|
||||
addStringInput(this.contentDiv2, "Cost", String(blueprint.cost)?? "", "number", (value: string) => {blueprint.cost = parseFloat(value); });
|
||||
addStringInput(this.contentDiv2, "Barrel height [m]", String(blueprint.barrelHeight)?? "", "number", (value: string) => {blueprint.barrelHeight = parseFloat(value); });
|
||||
addStringInput(this.contentDiv2, "Muzzle velocity [m/s]", String(blueprint.muzzleVelocity)?? "", "number", (value: string) => {blueprint.muzzleVelocity = parseFloat(value); });
|
||||
}
|
||||
}
|
||||
|
||||
/** Add a new empty blueprint
|
||||
*
|
||||
* @param key Blueprint key
|
||||
*/
|
||||
addBlueprint(key: string) {
|
||||
if (this.database != null) {
|
||||
this.database.blueprints[key] = {
|
||||
name: key,
|
||||
coalition: "",
|
||||
label: "",
|
||||
shortLabel: "",
|
||||
era: "",
|
||||
enabled: true
|
||||
}
|
||||
this.show();
|
||||
this.setBlueprint(this.database.blueprints[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
117
frontend/website/plugins/databasemanager/src/uniteditor.ts
Normal file
117
frontend/website/plugins/databasemanager/src/uniteditor.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { UnitBlueprint } from "interfaces";
|
||||
import { UnitDatabase } from "unit/databases/unitdatabase";
|
||||
import { addBlueprintsScroll, addNewElementInput } from "./utils";
|
||||
|
||||
/** Base abstract class of Unit database editors
|
||||
*
|
||||
*/
|
||||
export abstract class UnitEditor {
|
||||
blueprint: UnitBlueprint | null = null;
|
||||
database: {blueprints: {[key: string]: UnitBlueprint}} | null = null;
|
||||
visible: boolean = false;
|
||||
contentDiv1: HTMLElement;
|
||||
contentDiv2: HTMLElement;
|
||||
contentDiv3: HTMLElement;
|
||||
|
||||
constructor(contentDiv1: HTMLElement, contentDiv2: HTMLElement, contentDiv3: HTMLElement) {
|
||||
this.contentDiv1 = contentDiv1;
|
||||
this.contentDiv2 = contentDiv2;
|
||||
this.contentDiv3 = contentDiv3;
|
||||
|
||||
/* Refresh the list of units if it changes */
|
||||
this.contentDiv1.addEventListener("refresh", () => {
|
||||
if (this.visible)
|
||||
this.show();
|
||||
})
|
||||
|
||||
/* If the unit properties or loadout are edited, reload the editor */
|
||||
this.contentDiv2.addEventListener("refresh", () => {
|
||||
if (this.visible) {
|
||||
if (this.blueprint !== null)
|
||||
this.setBlueprint(this.blueprint);
|
||||
}
|
||||
});
|
||||
|
||||
this.contentDiv3.addEventListener("refresh", () => {
|
||||
if (this.visible) {
|
||||
if (this.blueprint !== null)
|
||||
this.setBlueprint(this.blueprint);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param database The database that the editor will operate on
|
||||
*/
|
||||
setDatabase(database: UnitDatabase) {
|
||||
this.database = JSON.parse(JSON.stringify({blueprints: database.getBlueprints(true)}));
|
||||
}
|
||||
|
||||
/** Show the editor
|
||||
* @param filter String filter
|
||||
*/
|
||||
show(filter: string = "") {
|
||||
this.visible = true;
|
||||
this.contentDiv1.replaceChildren();
|
||||
this.contentDiv2.replaceChildren();
|
||||
this.contentDiv3.replaceChildren();
|
||||
|
||||
/* Create the list of units. Each unit is clickable to activate the editor on it */
|
||||
if (this.database != null) {
|
||||
var title = document.createElement("label");
|
||||
title.innerText = "Units list";
|
||||
this.contentDiv1.appendChild(title);
|
||||
|
||||
var filterInput = document.createElement("input");
|
||||
filterInput.value = filter;
|
||||
this.contentDiv1.appendChild(filterInput);
|
||||
|
||||
filterInput.onchange = (e: Event) => {
|
||||
this.show((e.target as HTMLInputElement).value);
|
||||
}
|
||||
|
||||
this.addBlueprints(filter);
|
||||
}
|
||||
}
|
||||
|
||||
/** Hide the editor
|
||||
*
|
||||
*/
|
||||
hide() {
|
||||
this.visible = false;
|
||||
this.contentDiv1.replaceChildren();
|
||||
this.contentDiv2.replaceChildren();
|
||||
this.contentDiv3.replaceChildren();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns The edited database
|
||||
*/
|
||||
getDatabase() {
|
||||
return this.database;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param filter String filter
|
||||
*/
|
||||
addBlueprints(filter: string = "") {
|
||||
if (this.database) {
|
||||
addBlueprintsScroll(this.contentDiv1, this.database, filter, (key: string) => {
|
||||
if (this.database != null)
|
||||
this.setBlueprint(this.database.blueprints[key])
|
||||
});
|
||||
|
||||
addNewElementInput(this.contentDiv1, (ev: MouseEvent, input: HTMLInputElement) => {
|
||||
if (input.value != "")
|
||||
this.addBlueprint((input).value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* Abstract methods which will depend on the specific type of units */
|
||||
abstract setBlueprint(blueprint: UnitBlueprint): void;
|
||||
abstract addBlueprint(key: string): void;
|
||||
}
|
||||
297
frontend/website/plugins/databasemanager/src/utils.ts
Normal file
297
frontend/website/plugins/databasemanager/src/utils.ts
Normal file
@@ -0,0 +1,297 @@
|
||||
import { LoadoutBlueprint, LoadoutItemBlueprint, UnitBlueprint } from "interfaces";
|
||||
|
||||
/** This file contains a set of utility functions that are reused in the various editors and allows to declutter the classes
|
||||
*
|
||||
*/
|
||||
|
||||
/** Add a string input in the form of String: [ value ]
|
||||
*
|
||||
* @param div The HTMLElement that will contain the input
|
||||
* @param key The key of the input, which will be used as label
|
||||
* @param value The initial value of the input
|
||||
* @param type The type of the input, e.g. "Text" or "Number" as per html standard
|
||||
* @param callback Callback called when the user enters a new value
|
||||
* @param disabled If true, the input will be disabled and read only
|
||||
*/
|
||||
export function addStringInput(div: HTMLElement, key: string, value: string, type: string, callback: CallableFunction, disabled?: boolean) {
|
||||
var row = document.createElement("div");
|
||||
var dt = document.createElement("dt");
|
||||
var dd = document.createElement("dd");
|
||||
dt.innerText = key;
|
||||
var input = document.createElement("input");
|
||||
input.value = value;
|
||||
input.textContent = value;
|
||||
input.type = type?? "text";
|
||||
input.disabled = disabled?? false;
|
||||
input.onchange = () => callback(input.value);
|
||||
dd.appendChild(input);
|
||||
row.appendChild(dt);
|
||||
row.appendChild(dd);
|
||||
row.classList.add("input-row");
|
||||
div.appendChild(row);
|
||||
}
|
||||
|
||||
/** Add a dropdown (select) input
|
||||
*
|
||||
* @param div The HTMLElement that will contain the input
|
||||
* @param key The key of the input, which will be used as label
|
||||
* @param value The initial value of the input
|
||||
* @param options The dropdown options
|
||||
*/
|
||||
export function addDropdownInput(div: HTMLElement, key: string, value: string, options: string[], callback: CallableFunction, disabled?: boolean) {
|
||||
var row = document.createElement("div");
|
||||
var dt = document.createElement("dt");
|
||||
var dd = document.createElement("dd");
|
||||
dt.innerText = key;
|
||||
var select = document.createElement("select");
|
||||
options.forEach((option: string) => {
|
||||
var el = document.createElement("option");
|
||||
el.value = option;
|
||||
el.innerText = option;
|
||||
select.appendChild(el);
|
||||
});
|
||||
select.value = value;
|
||||
select.disabled = disabled?? false;
|
||||
select.onchange = () => callback(select.value);
|
||||
dd.appendChild(select);
|
||||
row.appendChild(dt);
|
||||
row.appendChild(dd);
|
||||
row.classList.add("input-row");
|
||||
div.appendChild(row);
|
||||
}
|
||||
|
||||
/** Add a checkbox input in the form of String: [ value ]
|
||||
*
|
||||
* @param div The HTMLElement that will contain the input
|
||||
* @param key The key of the input, which will be used as label
|
||||
* @param value The initial value of the input
|
||||
* @param callback Callback called when the user enters a new value
|
||||
* @param disabled If true, the input will be disabled and read only
|
||||
*/
|
||||
export function addCheckboxInput(div: HTMLElement, key: string, value: boolean, callback: CallableFunction, disabled?: boolean) {
|
||||
var row = document.createElement("div");
|
||||
var dt = document.createElement("dt");
|
||||
var dd = document.createElement("dd");
|
||||
dt.innerText = key;
|
||||
var input = document.createElement("input");
|
||||
input.checked = value;
|
||||
input.type = "checkbox";
|
||||
input.disabled = disabled?? false;
|
||||
input.onchange = () => callback(input.checked);
|
||||
dd.appendChild(input);
|
||||
row.appendChild(dt);
|
||||
row.appendChild(dd);
|
||||
row.classList.add("input-row");
|
||||
div.appendChild(row);
|
||||
}
|
||||
|
||||
/** Create a loadout items editor. This editor allows to add or remove loadout items, as well as changing their name and quantity
|
||||
*
|
||||
* @param div The HTMLElement that will contain the editor
|
||||
* @param loadout The loadout to edit
|
||||
*/
|
||||
export function addLoadoutItemsEditor(div: HTMLElement, loadout: LoadoutBlueprint) {
|
||||
var itemsEl = document.createElement("div");
|
||||
itemsEl.classList.add("dm-scroll-container", "dm-items-container");
|
||||
|
||||
/* Create a row for each loadout item to allow and change the name and quantity of the item itself */
|
||||
loadout.items.sort((a: LoadoutItemBlueprint, b: LoadoutItemBlueprint) => a.name.localeCompare(b.name, undefined, {sensitivity: 'base'}));
|
||||
loadout.items.forEach((item: LoadoutItemBlueprint, index: number) => {
|
||||
var rowDiv = document.createElement("div");
|
||||
|
||||
var nameLabel = document.createElement("label");
|
||||
nameLabel.innerText = "Name"
|
||||
rowDiv.appendChild(nameLabel);
|
||||
|
||||
var nameInput = document.createElement("input");
|
||||
rowDiv.appendChild(nameInput);
|
||||
nameInput.textContent = item.name;
|
||||
nameInput.value = item.name
|
||||
nameInput.onchange = () => { loadout.items[index].name = nameInput.value; }
|
||||
|
||||
var quantityLabel = document.createElement("label");
|
||||
quantityLabel.innerText = "Quantity"
|
||||
rowDiv.appendChild(quantityLabel);
|
||||
|
||||
var quantityInput = document.createElement("input");
|
||||
rowDiv.appendChild(quantityInput);
|
||||
quantityInput.textContent = String(item.quantity);
|
||||
quantityInput.value = String(item.quantity);
|
||||
quantityInput.type = "number";
|
||||
quantityInput.step = "1";
|
||||
quantityInput.onchange = () => { loadout.items[index].quantity = parseInt(quantityInput.value); }
|
||||
|
||||
/* This button allows to remove the item */
|
||||
var button = document.createElement("button");
|
||||
button.innerText = "X";
|
||||
button.onclick = () => {
|
||||
loadout.items.splice(index, 1);
|
||||
div.dispatchEvent(new Event("refresh"));
|
||||
}
|
||||
rowDiv.appendChild(button);
|
||||
|
||||
itemsEl.appendChild(rowDiv);
|
||||
})
|
||||
div.appendChild(itemsEl);
|
||||
|
||||
/* Button to add a new item to the loadout */
|
||||
var inputDiv = document.createElement("div");
|
||||
inputDiv.classList.add("dm-new-item-input");
|
||||
var button = document.createElement("button");
|
||||
button.innerText = "Add";
|
||||
inputDiv.appendChild(button);
|
||||
div.appendChild(inputDiv);
|
||||
|
||||
button.addEventListener("click", (ev: MouseEvent) => {
|
||||
loadout?.items.push({
|
||||
name: "",
|
||||
quantity: 1
|
||||
})
|
||||
div.dispatchEvent(new Event("refresh"));
|
||||
});
|
||||
}
|
||||
|
||||
/** Add a input and button to create a new element in a list. It uses a generic callback to actually add the element.
|
||||
*
|
||||
* @param div The HTMLElement that will contain the input and button
|
||||
* @param callback Callback called when the user clicks on "Add"
|
||||
*/
|
||||
export function addNewElementInput(div: HTMLElement, callback: CallableFunction) {
|
||||
var inputDiv = document.createElement("div");
|
||||
inputDiv.classList.add("dm-new-element-input");
|
||||
|
||||
var input = document.createElement("input");
|
||||
inputDiv.appendChild(input);
|
||||
|
||||
var button = document.createElement("button");
|
||||
button.innerText = "Add";
|
||||
button.addEventListener("click", (ev: MouseEvent) => callback(ev, input));
|
||||
inputDiv.appendChild(button);
|
||||
div.appendChild(inputDiv);
|
||||
}
|
||||
|
||||
/** Add a scrollable list of blueprints
|
||||
*
|
||||
* @param div The HTMLElement that will contain the list
|
||||
* @param database The database that will be used to fill the list of blueprints
|
||||
* @param filter A string filter that will be executed to filter the blueprints to add
|
||||
* @param callback Callback called when the user clicks on one of the elements
|
||||
*/
|
||||
export function addBlueprintsScroll(div: HTMLElement, database: {blueprints: {[key: string]: UnitBlueprint}}, filter: string, callback: CallableFunction) {
|
||||
var scrollDiv = document.createElement("div");
|
||||
scrollDiv.classList.add("dm-scroll-container");
|
||||
if (database !== null) {
|
||||
var blueprints: {[key: string]: UnitBlueprint} = database.blueprints;
|
||||
|
||||
for (let key of Object.keys(blueprints).sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}))) {
|
||||
var addKey = true;
|
||||
if (filter !== "") {
|
||||
try {
|
||||
var blueprint = blueprints[key];
|
||||
addKey = eval(filter);
|
||||
} catch {
|
||||
console.error("An error has occurred evaluating the blueprint filter")
|
||||
}
|
||||
}
|
||||
|
||||
if (addKey) {
|
||||
var rowDiv = document.createElement("div");
|
||||
scrollDiv.appendChild(rowDiv);
|
||||
|
||||
let text = document.createElement("div");
|
||||
text.innerHTML = `<div>${key}</div> <div>${blueprints[key].label}</div>`;
|
||||
text.onclick = () => {
|
||||
callback(key);
|
||||
const collection = document.getElementsByClassName("blueprint-selected");
|
||||
for (let i = 0; i < collection.length; i++) {
|
||||
collection[i].classList.remove("blueprint-selected");
|
||||
}
|
||||
text.classList.add("blueprint-selected");
|
||||
}
|
||||
rowDiv.appendChild(text);
|
||||
|
||||
let checkbox = document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.checked = blueprints[key].enabled;
|
||||
checkbox.onclick = () => {
|
||||
console.log(checkbox.checked);
|
||||
blueprints[key].enabled = checkbox.checked;
|
||||
}
|
||||
rowDiv.appendChild(checkbox);
|
||||
|
||||
/* This button allows to remove an element from the list. It requires a refresh. */
|
||||
var button = document.createElement("button");
|
||||
button.innerText = "X";
|
||||
button.onclick = () => {
|
||||
delete blueprints[key];
|
||||
div.dispatchEvent(new Event("refresh"));
|
||||
}
|
||||
rowDiv.appendChild(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
div.appendChild(scrollDiv);
|
||||
}
|
||||
|
||||
/** Add a scrollable list of loadouts
|
||||
*
|
||||
* @param div The HTMLElement that will contain the list
|
||||
* @param loadouts The loadouts that will be used to fill the list
|
||||
* @param callback Callback called when the user clicks on one of the elements
|
||||
*/
|
||||
export function addLoadoutsScroll(div: HTMLElement, loadouts: LoadoutBlueprint[], callback: CallableFunction) {
|
||||
var loadoutsEl = document.createElement("div");
|
||||
loadoutsEl.classList.add("dm-scroll-container", "dm-loadout-container")
|
||||
|
||||
loadouts.sort((a: LoadoutBlueprint, b: LoadoutBlueprint) => a.name.localeCompare(b.name, undefined, {sensitivity: 'base'}));
|
||||
loadouts.forEach((loadout: LoadoutBlueprint, index: number) => {
|
||||
var rowDiv = document.createElement("div");
|
||||
loadoutsEl.appendChild(rowDiv);
|
||||
|
||||
var text = document.createElement("label");
|
||||
text.textContent = loadout.name;
|
||||
text.onclick = () => { callback(loadout) };
|
||||
rowDiv.appendChild(text);
|
||||
|
||||
/* The "Empty loadout" can not be removed */
|
||||
if (loadout.name !== "Empty loadout") {
|
||||
let checkbox = document.createElement("input");
|
||||
checkbox.type = "checkbox";
|
||||
checkbox.checked = loadout.enabled;
|
||||
checkbox.onclick = () => {
|
||||
console.log(checkbox.checked);
|
||||
loadout.enabled = checkbox.checked;
|
||||
}
|
||||
rowDiv.appendChild(checkbox);
|
||||
|
||||
/* This button allows to remove an element from the list. It requires a refresh. */
|
||||
var button = document.createElement("button");
|
||||
button.innerText = "X";
|
||||
button.onclick = () => {
|
||||
loadouts.splice(index, 1);
|
||||
div.dispatchEvent(new Event("refresh"));
|
||||
}
|
||||
rowDiv.appendChild(button);
|
||||
}
|
||||
});
|
||||
|
||||
div.appendChild(loadoutsEl);
|
||||
}
|
||||
|
||||
/** Converts an array of string into a single string like [val1, val2, val3]
|
||||
*
|
||||
* @param array The input array of strings
|
||||
* @returns The string
|
||||
*/
|
||||
export function arrayToString(array: string[]) {
|
||||
return "[" + array.join( ", " ) + "]";
|
||||
}
|
||||
|
||||
/** Converts an a single string like [val1, val2, val3] into an array
|
||||
*
|
||||
* @param input The input string
|
||||
* @returns The array
|
||||
*/
|
||||
export function stringToArray(input: string) {
|
||||
return input.match( /(\w)+/g ) ?? [];
|
||||
}
|
||||
295
frontend/website/plugins/databasemanager/style.css
Normal file
295
frontend/website/plugins/databasemanager/style.css
Normal file
@@ -0,0 +1,295 @@
|
||||
#database-manager-panel {
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
background-color: var(--background-steel) !important;
|
||||
z-index: 9999999;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.dm-container {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.dm-container {
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#database-manager-panel * {
|
||||
font-size: 13;
|
||||
font-family: 'Open Sans', sans-serif !important;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#database-manager-panel>div:first-child {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#database-manager-panel>div:last-child {
|
||||
display: flex;
|
||||
column-gap: 5px;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
justify-items: end;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#database-manager-panel>div:last-child>button {
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.dm-container {
|
||||
background-color: var(--background-grey);
|
||||
border: 2px solid #777777;
|
||||
position: relative;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
height: calc(100% - 64px - 5px);
|
||||
border-radius: 0px 5px 5px 5px;
|
||||
}
|
||||
|
||||
.dm-content-container {
|
||||
position: relative;
|
||||
margin: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 5px;
|
||||
max-height: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.dm-content-container {
|
||||
height: calc(100% - 20px);
|
||||
}
|
||||
|
||||
.dm-content-container:nth-of-type(1) {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.dm-content-container:nth-of-type(2) {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.dm-content-container:nth-of-type(3) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.dm-content-container {
|
||||
width: calc(100% - 20px);
|
||||
}
|
||||
|
||||
.dm-content-container:nth-of-type(1) {
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
.dm-content-container:nth-of-type(2) {
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.dm-content-container:nth-of-type(3) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.dm-content-container>label {
|
||||
font-size: 18px !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dm-scroll-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: scroll;
|
||||
max-height: 100%;
|
||||
color: black;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#database-manager-panel input {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div:nth-child(even) {
|
||||
background-color: gainsboro;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div:nth-child(odd) {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div *:nth-child(1) {
|
||||
height: 100%;
|
||||
width: calc(100% - 25px);
|
||||
padding: 2px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div *:nth-child(1):hover {
|
||||
background-color: var(--accent-dark-blue);
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.blueprint-selected {
|
||||
background-color: var(--accent-light-blue) !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div>div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div>button {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div>div>div:nth-child(1) {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.dm-scroll-container>div>div>div:nth-child(2) {
|
||||
overflow: hidden;
|
||||
text-wrap: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.input-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.dm-content-container label {
|
||||
width: 100%;
|
||||
}
|
||||
.input-row {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.input-row>dt {
|
||||
width: 250px;
|
||||
}
|
||||
|
||||
.input-row>dd {
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.input-row>dd>* {
|
||||
width: 100%;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input-row>dd>*[type="checkbox"] {
|
||||
width: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dm-loadout-container {
|
||||
max-height: 100%;
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dm-items-container {
|
||||
max-height: 100%;
|
||||
height: fit-content;
|
||||
}
|
||||
|
||||
.dm-items-container>div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
column-gap: 2px;
|
||||
}
|
||||
|
||||
.dm-items-container>div>label {
|
||||
width: 80px !important;
|
||||
}
|
||||
|
||||
.dm-items-container div>input:nth-of-type(1) {
|
||||
flex: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dm-items-container div>input:nth-of-type(2) {
|
||||
width: 40px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.dm-new-element-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
column-gap: 2px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dm-new-element-input>input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dm-new-element-input>button {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.dm-new-item-input {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.dm-new-item-input>button {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
transform: translateY(+3px);
|
||||
background-color: var(--background-steel);
|
||||
border-radius: 0;
|
||||
border-bottom: 2px solid transparent !important;
|
||||
border-top: 2px solid #777777 !important;
|
||||
border-left: 2px solid #777777 !important;
|
||||
border-right: 0px solid #777777 !important;
|
||||
}
|
||||
|
||||
.tab-button.selected {
|
||||
background-color: var(--background-grey);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tab-button:first-of-type {
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
|
||||
.tab-button:last-of-type {
|
||||
border-top-right-radius: 5px;
|
||||
border-right: 2px solid #777777 !important;
|
||||
}
|
||||
|
||||
#database-manager-panel button :not(.dm-scroll-container>div) {
|
||||
border: 1px solid white;
|
||||
}
|
||||
103
frontend/website/plugins/databasemanager/tsconfig.json
Normal file
103
frontend/website/plugins/databasemanager/tsconfig.json
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
/* Language and Environment */
|
||||
"target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
"rootDirs": ["./src"], /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
"typeRoots": [
|
||||
"./node_modules/@types",
|
||||
"../../@types"
|
||||
], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"types": [
|
||||
"olympus"
|
||||
], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
/* JavaScript Support */
|
||||
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"include": [
|
||||
"src/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user