diff --git a/client/public/stylesheets/contextmenu.css b/client/public/stylesheets/contextmenu.css index 17cbd8de..762bd936 100644 --- a/client/public/stylesheets/contextmenu.css +++ b/client/public/stylesheets/contextmenu.css @@ -6,6 +6,10 @@ position: relative; } +#aircraft-spawn-menu { + height: 191px; +} + .ol-contextmenu { display: flex; flex-direction: column; @@ -42,18 +46,42 @@ text-align: left; } -.ol-contextmenu>ul::-webkit-scrollbar { - width: 10px; -} - -.ol-contextmenu>ul::-webkit-scrollbar-track { - background-color: transparent; - border-radius: 100px; +.ol-contextmenu>div:nth-child(2){ + display: flex; + flex-direction: column; + + justify-content: space-between; + align-items: center; + row-gap: 5px; } -.ol-contextmenu>ul::-webkit-scrollbar-thumb { - background-color: white; - border-radius: 100px; - opacity: 0.8; - margin-top: 10px; +.ol-contextmenu .ol-select-container{ + width: 100%; + flex:0 0 auto; + align-self: stretch; } + +#deploy-unit-button { + width: 100%; + text-align: center; +} + +#unit-spawn-aircraft { + background-image: var( --spawn-aircraft-url ); +} + +#unit-spawn-ground { + background-image: var( --spawn-ground-url ); +} + +#unit-spawn button { + border: none; + height: 32px; + width: 32px; +} + +#unit-spawn button:not(:hover){ + background-color: transparent !important; +} + + diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 39ebdb41..0995cf05 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -5,40 +5,40 @@ /* Variables definitions */ :root { - --accent-green : #8bff63; - --accent-light-blue : #5ca7ff; - --background-grey : #3d4651; - --background-offwhite : #f2f2f3; - --background-steel : #202831; - --primary-blue : #247be2; - --primary-grey : #CFD9E8; - --primary-red : #ff5858; - --secondary-blue-outline : #082e44; - --secondary-dark-steel : #181e25; - --secondary-gunmetal-grey : #2f2f2f; - --secondary-light-grey : #797e83; - --secondary-neutral : #111111; - --secondary-red-outline : #262222; - --secondary-yellow : #ffd46893; - --nav-text : #ECECEC; + --accent-green: #8bff63; + --accent-light-blue: #5ca7ff; + --background-grey: #3d4651; + --background-offwhite: #f2f2f3; + --background-steel: #202831; + --primary-blue: #247be2; + --primary-grey: #CFD9E8; + --primary-red: #ff5858; + --secondary-blue-outline: #082e44; + --secondary-dark-steel: #181e25; + --secondary-gunmetal-grey: #2f2f2f; + --secondary-light-grey: #797e83; + --secondary-neutral: #111111; + --secondary-red-outline: #262222; + --secondary-yellow: #ffd46893; + --nav-text: #ECECEC; - --ol-select-secondary : #545F6C; - - --border-radius-xs : 2px; - --border-radius-sm : 5px; - --border-radius-md : 10px; - --border-radius-lg : 15px; - - --font-weight-bolder : 600; + --ol-select-secondary: #545F6C; + + --border-radius-xs: 2px; + --border-radius-sm: 5px; + --border-radius-md: 10px; + --border-radius-lg: 15px; + + --font-weight-bolder: 600; } -* { - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; +* { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; } @@ -48,25 +48,25 @@ html { button { - background-color:var(--background-steel); - border:1px solid var( --background-steel ); - border-radius: var( --border-radius-sm ); - color:whitesmoke; - cursor:pointer; - font-weight: var( --font-weight-bolder ); - padding:8px; + background-color: var(--background-steel); + border: 1px solid var(--background-steel); + border-radius: var(--border-radius-sm); + color: whitesmoke; + cursor: pointer; + font-weight: var(--font-weight-bolder); + padding: 8px; } button[disabled="disabled"] { - color: var( --highlight-color ); - cursor:not-allowed; + color: var(--highlight-color); + cursor: not-allowed; } .pill { - border-radius: var( --border-radius-sm ); - padding:2px 6px; - width:fit-content; + border-radius: var(--border-radius-sm); + padding: 2px 6px; + width: fit-content; } @@ -75,248 +75,282 @@ button[disabled="disabled"] { background-color: var(--background-steel); border-radius: 15px; box-shadow: 0px 2px 5px #000A; - color:white; + color: white; font-size: 12px; - height:fit-content; - padding:10px; - width:fit-content; + height: fit-content; + padding: 10px; + width: fit-content; } .ol-panel hr { - background-color: var( --secondary-light-grey ); - border:none; - height:1px; - margin:20px 0; - width:100%; + background-color: var(--secondary-light-grey); + border: none; + height: 1px; + margin: 20px 0; + width: 100%; } .ol-panel-padding-lg { - padding:24px 30px; + padding: 24px 30px; +} + +.ol-select-container { + width: 100%; } .ol-select { - color: var( --nav-text ); + position: relative; + color: var(--nav-text); } -.ol-select > .ol-select-value { +.ol-select>.ol-select-value { align-content: center; box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); - cursor:pointer; - display:flex; + cursor: pointer; + display: flex; justify-content: left; text-align: center; white-space: nowrap; + width: 100%; +} + +.ol-select:not(.ol-select-image)>.ol-select-value { + align-items: center; + background-color: var(--background-grey); + border-radius: var(--border-radius-sm); + padding: 1em; + width: 100%; + padding-left: 20px; + padding-right: 30px; + overflow: hidden; + text-overflow: ellipsis; +} + +.ol-select:not(.ol-select-image)>.ol-select-value svg { + margin-right: 10px; +} + +.ol-select:not(.ol-select-image)>.ol-select-value:after { + position: absolute; + content: url("/themes/olympus/images/chevron-down.svg"); + right: 10px; +} + +.ol-select>.ol-select-options { + position: absolute; + overflow: hidden; + max-height: 0; + translate: 0 -2px; + z-index: 1000; +} + +.ol-select.ol-select-image>.ol-select-options { + position: absolute; +} + + +.ol-select.is-open>.ol-select-options { + max-height: fit-content; + overflow: visible; + overflow-y: auto; + padding: 8px 0; + max-height: 300px; + min-width: 100%; +} + +.ol-select>.ol-select-options>div { + background-color: var(--background-grey); + box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25); + display: flex; + justify-content: left; + padding: 6px 25px; + width: 100%; +} + +.ol-select>.ol-select-options>div:first-of-type { + border-top-left-radius: var(--border-radius-md); + border-top-right-radius: var(--border-radius-md); + padding-top: 16px; +} + +.ol-select>.ol-select-options>div:last-of-type { + border-bottom-left-radius: var(--border-radius-md); + border-bottom-right-radius: var(--border-radius-md); + padding-bottom: 16px; +} + +.ol-select>.ol-select-options div hr { + background-color: white; + height: 1px; + width: 100%; +} + +.ol-select>.ol-select-options div button { + background-color: transparent; + border: none; + border-radius: 0; + font-size: 14px; + font-weight: normal; + padding: 6px 2px; + text-align: left; + white-space: nowrap; width: fit-content; } -.ol-select:not( .ol-select-image ) > .ol-select-value { - align-items: center; - background-color: var( --background-grey ); - border-radius: var( --border-radius-sm ); - justify-content: center; - padding:1em; - width:100%; -} - -.ol-select:not( .ol-select-image ) > .ol-select-value svg { - margin-right: 10px; -} - -.ol-select:not( .ol-select-image ) > .ol-select-value:after { - content: url( "/themes/olympus/images/chevron-down.svg" ); - margin-left:10px; -} - -.ol-select > .ol-select-options { - overflow:hidden; - max-height: 0; - translate:0 -2px; -} - -.ol-select.ol-select-image > .ol-select-options { - position:absolute; -} - - -.ol-select.is-open > .ol-select-options { - max-height: fit-content; - overflow: visible; - padding:8px 0; -} - -.ol-select > .ol-select-options > div { - background-color: var( --background-grey ); - box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25); - display:flex; - justify-content: left; - padding:6px 25px; - width:100%; -} - -.ol-select > .ol-select-options > div:first-of-type { - border-top-left-radius: var( --border-radius-md ); - border-top-right-radius: var( --border-radius-md ); - padding-top:16px; -} - -.ol-select > .ol-select-options > div:last-of-type { - border-bottom-left-radius: var( --border-radius-md ); - border-bottom-right-radius: var( --border-radius-md ); - padding-bottom:16px; -} - -.ol-select > .ol-select-options div hr { - background-color: white; - height:1px; - width:100%; -} - -.ol-select > .ol-select-options div button { - background-color: transparent; - border:none; - border-radius: 0; - font-size:14px; - font-weight: normal; - padding:6px 2px; - text-align: left; - white-space: nowrap; - width:fit-content; -} - -.ol-select > .ol-select-options > div button:hover { +.ol-select>.ol-select-options>div button:hover { text-decoration: underline; } +.ol-select>.ol-select-options::-webkit-scrollbar { + width: 10px; +} + +.ol-select>.ol-select-options::-webkit-scrollbar-track { + background-color: transparent; + border-radius: 100px; +} +.ol-select>.ol-select-options::-webkit-scrollbar-thumb { + background-color: white; + border-radius: 100px; + opacity: 0.8; + margin-top: 10px; +} .ol-panel-list { - border-radius: var( --border-radius-sm ); + border-radius: var(--border-radius-sm); display: flex; flex-direction: column; - height: fit-content; + height: fit-content; row-gap: 5px; - text-align: center; - width: fit-content; + text-align: center; + width: fit-content; } .ol-panel-list .list-item { - border-radius: var( --border-radius-md ); - display:flex; + border-radius: var(--border-radius-md); + display: flex; justify-content: space-between; - padding: 6px 10px; + padding: 6px 10px; } -.ol-panel-list.sortable > .sortable-item { +.ol-panel-list.sortable>.sortable-item { align-items: center; column-gap: 5px; - display:flex; + display: flex; flex-direction: row; } -.ol-panel-list.sortable > .sortable-item > .handle { - cursor:grab; - filter:invert(100); +.ol-panel-list.sortable>.sortable-item>.handle { + cursor: grab; + filter: invert(100); } -.ol-panel-list.sortable > .sortable-item > .handle img { +.ol-panel-list.sortable>.sortable-item>.handle img { max-width: 16px; } .ol-panel-board { - display:flex; + display: flex; flex-direction: row; justify-content: space-evenly; } -.ol-panel-board > .panel-section { +.ol-panel-board>.panel-section { border-right: 1px solid #555; - padding:10px; + padding: 10px; } -.ol-panel-board > .panel-section:last-of-type { +.ol-panel-board>.panel-section:last-of-type { border-right-width: 0; } -.ol-panel-board h1, .ol-panel-board h2 { - font-weight: var( --font-weight-bolder ); +.ol-panel-board h1, +.ol-panel-board h2 { + font-weight: var(--font-weight-bolder); margin: 0; - padding:0 0 5px 0; + padding: 0 0 5px 0; } .ol-panel-board h2 { - font-size:14px; + font-size: 14px; } -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { margin: 0px; } h1 { - font-size:36px; + font-size: 36px; font-weight: 800; } h2 { - font-size:24px; + font-size: 24px; font-weight: bold; } h3 { - font-size:18px; + font-size: 18px; font-weight: bold; } h4 { - font-size:14px; + font-size: 14px; font-weight: normal; } button.ol-button-warning { - border: 1px solid var( --primary-red ); - color: var( --primary-red ); + border: 1px solid var(--primary-red); + color: var(--primary-red); } nav.ol-panel { column-gap: 20px; - display:flex; + display: flex; flex-direction: row; - height:58px; + height: 58px; } -nav.ol-panel > :last-child { - margin-right:5px; +nav.ol-panel> :last-child { + margin-right: 5px; } .ol-panel .ol-group { column-gap: 10px; - display:flex; + display: flex; flex-direction: row; - flex-wrap:nowrap; + flex-wrap: nowrap; } -.ol-panel .ol-group { - border-radius: var( --border-radius-sm ); +.ol-panel .ol-group { + border-radius: var(--border-radius-sm); } .ol-panel .ol-group-button-toggle { align-items: center; column-gap: 15px; - display:flex; + display: flex; flex-wrap: nowrap; white-space: nowrap; - width:fit-content; + width: fit-content; } .ol-panel .ol-group-button-toggle button { align-items: center; - background-image: url( "/themes/olympus/images/check_square.svg"); + background-image: url("/themes/olympus/images/check_square.svg"); background-position: 5px 50%; background-repeat: no-repeat; - border:0; + border: 0; text-indent: 15px; } @@ -324,46 +358,47 @@ nav.ol-panel > :last-child { .highlight-primary { - background-color: var(--secondary-light-grey); + background-color: var(--secondary-light-grey); } .highlight-bluefor { - background-color: var(--primary-blue); - color: var(--background-steel ) + background-color: var(--primary-blue); + color: var(--background-steel) } .highlight-redfor { - background-color: var(--primary-red); + background-color: var(--primary-red); } .highlight-neutral { - background-color: var(--primary-grey); + background-color: var(--primary-grey); color: var(--secondary-gunmetal-grey) } .accent-green { color: var(--accent-green); - font-weight: var( --font-weight-bolder ); + font-weight: var(--font-weight-bolder); } + .accent-light-blue { color: var(--accent-light-blue); - font-weight: var( --font-weight-bolder ); + font-weight: var(--font-weight-bolder); } .accent-bluefor { color: var(--primary-blue); - font-weight: var( --font-weight-bolder ); + font-weight: var(--font-weight-bolder); } .accent-redfor { color: var(--primary-red); - font-weight: var( --font-weight-bolder ); + font-weight: var(--font-weight-bolder); } .accent-neutral { - color: var( --primary-grey ); - font-weight: var( --font-weight-bolder ); + color: var(--primary-grey); + font-weight: var(--font-weight-bolder); } @@ -391,32 +426,32 @@ nav.ol-panel > :last-child { column-gap: 2px; } -.data-row>*:nth-child(2){ +.data-row>*:nth-child(2) { width: 100px; } -.data-row>*:last-child{ +.data-row>*:last-child { width: 30px; text-align: right; } -.data-row>.icon-small{ +.data-row>.icon-small { margin: 2px; } .slider-container { - width: 100%; + width: 100%; } .slider { width: 100%; - -webkit-appearance: none; + -webkit-appearance: none; appearance: none; - height: 2px; - background: #d3d3d3; - outline: none; - opacity: 0.7; - -webkit-transition: .2s; + height: 2px; + background: #d3d3d3; + outline: none; + opacity: 0.7; + -webkit-transition: .2s; transition: opacity .2s; margin-top: 10px; margin-bottom: 10px; @@ -431,20 +466,20 @@ nav.ol-panel > :last-child { appearance: none; width: 20px; height: 20px; - background: gray; - cursor: pointer; + background: gray; + cursor: pointer; border-radius: 999px; } .active .slider::-webkit-slider-thumb { - background: #5ca7ff; + background: #5ca7ff; } .slider::-moz-range-thumb { - width: 20px; + width: 20px; height: 20px; - background: gray; - cursor: pointer; + background: gray; + cursor: pointer; border-radius: 999px; } @@ -458,20 +493,20 @@ nav.ol-panel > :last-child { } .ol-measure-box { - position: absolute; - padding-left: 0.5em; - padding-right: 0.5em; - padding-top: 0.2em; - padding-bottom: 0.2em; - background-color: var(--background-steel); - border-radius: 999px; - width: fit-content; - height: fit-content; - text-align: center; - color: var(--primary-grey); + position: absolute; + padding-left: 0.5em; + padding-right: 0.5em; + padding-top: 0.2em; + padding-bottom: 0.2em; + background-color: var(--background-steel); + border-radius: 999px; + width: fit-content; + height: fit-content; + text-align: center; + color: var(--primary-grey); font-size: 12px; - z-index: 2000; - font-weight: var(--font-weight-bolder); + z-index: 2000; + font-weight: var(--font-weight-bolder); } @@ -483,44 +518,44 @@ nav.ol-panel > :last-child { #unit-info-panel #unit-identification { align-items: center; - display:flex; - margin-bottom:11px; + display: flex; + margin-bottom: 11px; } #unit-info-panel #unit-identification .unit { - height:28px; - margin-right:6px; - width:28px; + height: 28px; + margin-right: 6px; + width: 28px; } #unit-info-panel #unit-identification .unit .unit-marker { background-size: 28px 28px; - height:28px; - width:28px; + height: 28px; + width: 28px; } #unit-info-panel #unit-identification .unit .unit-short-label { - font-size:12px; + font-size: 12px; } #unit-info-panel #unit-identification #unit-name { - background-color:transparent; - border:none; - color:white; - font-size:16px; - font-weight: var( --font-weight-bolder ); - outline:none; + background-color: transparent; + border: none; + color: white; + font-size: 16px; + font-weight: var(--font-weight-bolder); + outline: none; overflow: hidden; white-space: nowrap; width: 150px; } #edit-unit-name { - background-image: url( "/images/buttons/edit.svg" ); + background-image: url("/images/buttons/edit.svg"); background-repeat: no-repeat; - height:14px; - margin-left:10px; - width:15px; + height: 14px; + margin-left: 10px; + width: 15px; } @@ -530,54 +565,95 @@ nav.ol-panel > :last-child { } #unit-visibility-control button { - border:none; - height:32px; - width:32px; + border: none; + height: 32px; + width: 32px; } #unit-visibility-control-aircraft { - background-image: var( --visibility-control-aircraft-visible-url ); + background-image: var(--visibility-control-aircraft-visible-url); } body[data-hide-aircraft] #unit-visibility-control-aircraft { - background-image: var( --visibility-control-aircraft-hidden-url ); + background-image: var(--visibility-control-aircraft-hidden-url); } #unit-visibility-control-ground { - background-image: var( --visibility-control-ground-visible-url ); + background-image: var(--visibility-control-ground-visible-url); } body[data-hide-ground] #unit-visibility-control-ground { - background-image: var( --visibility-control-ground-hidden-url ); + background-image: var(--visibility-control-ground-hidden-url); } #unit-visibility-control-sam { - background-image: var( --visibility-control-sam-visible-url ); + background-image: var(--visibility-control-sam-visible-url); } body[data-hide-sam] #unit-visibility-control-sam { - background-image: var( --visibility-control-sam-hidden-url ); + background-image: var(--visibility-control-sam-hidden-url); } #unit-visibility-control-threat { - background-image: var( --visibility-control-threat-visible-url ); + background-image: var(--visibility-control-threat-visible-url); } body[data-hide-threat] #unit-visibility-control-threat { - background-image: var( --visibility-control-threat-hidden-url ); + background-image: var(--visibility-control-threat-hidden-url); } #unit-visibility-control-naval { - background-image: var( --visibility-control-naval-visible-url ); + background-image: var(--visibility-control-naval-visible-url); } body[data-hide-naval] #unit-visibility-control-naval { - background-image: var( --visibility-control-naval-hidden-url ); + background-image: var(--visibility-control-naval-hidden-url); } [data-hide-blue] #coalition-visibility-control #coalition-visibility-control-blue, [data-hide-red] #coalition-visibility-control #coalition-visibility-control-red, [data-hide-neutral] #coalition-visibility-control #coalition-visibility-control-neutral { - background-image:none; -} \ No newline at end of file + background-image: none; +} + +.toggle { + --width: 40px; + --height: calc(var(--width) / 2); + --border-radius: calc(var(--height) / 2); + + display: inline-block; + cursor: pointer; +} + +.toggle-input { + display: none; +} + +.toggle-fill { + position: relative; + width: var(--width); + height: var(--height); + border-radius: var(--border-radius); + transition: background-color 0.2s; +} + +.toggle-fill::after { + content: ""; + position: absolute; + top: 2; + left: 2; + height: calc(var(--height) - 4px); + width: calc(var(--height) - 4px); + background-color: #ffffff; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.25); + border-radius: var(--border-radius); + transition: transform 0.2s; +} + +.toggle-input:checked ~ .toggle-fill::after { + transform: translateX(var(--height)); +} + +[data-coalition=blue] {background-color: var(--primary-blue) !important} +[data-coalition=red] {background-color:var(--primary-red)!important} \ No newline at end of file diff --git a/client/public/themes/olympus/images/spawn_aircraft.svg b/client/public/themes/olympus/images/spawn_aircraft.svg new file mode 100644 index 00000000..e8793e30 --- /dev/null +++ b/client/public/themes/olympus/images/spawn_aircraft.svg @@ -0,0 +1,75 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/spawn_ground.svg b/client/public/themes/olympus/images/spawn_ground.svg new file mode 100644 index 00000000..f0d8bf1c --- /dev/null +++ b/client/public/themes/olympus/images/spawn_ground.svg @@ -0,0 +1,83 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/olympus.css b/client/public/themes/olympus/olympus.css index 603bb88f..42e78949 100644 --- a/client/public/themes/olympus/olympus.css +++ b/client/public/themes/olympus/olympus.css @@ -162,4 +162,10 @@ --unit-building-marker-neutral-url: url( "/themes/olympus/images/icon_building_neutral.svg" ); --unit-building-marker-red-url: url( "/themes/olympus/images/icon_building_red.svg" ); + + /*** Context menu ***/ + + --spawn-aircraft-url: url( "/themes/olympus/images/spawn_aircraft.svg" ); + --spawn-ground-url: url( "/themes/olympus/images/spawn_ground.svg" ); + } \ No newline at end of file diff --git a/client/src/controls/contextmenu.ts b/client/src/controls/contextmenu.ts index ab51f0bd..0786e0e6 100644 --- a/client/src/controls/contextmenu.ts +++ b/client/src/controls/contextmenu.ts @@ -1,41 +1,63 @@ +import { LatLng } from "leaflet"; import { getActiveCoalition, setActiveCoalition } from ".."; import { ContextMenuOption } from "../@types/dom"; +import { ClickEvent } from "../map/map"; +import { spawnAircraft } from "../server/server"; +import { aircraftDatabase } from "../units/aircraftdatabase"; +import { Dropdown } from "./dropdown"; + +export interface SpawnOptions { + role: string; + type: string; + latlng: LatLng; + coalition: string; + loadout: string | null; + airbaseName: string | null; +} export class ContextMenu { #container: HTMLElement | null; + #latlng: LatLng = new LatLng(0, 0); + #aircraftRoleDropdown: Dropdown; + #aircraftTypeDropdown: Dropdown; + #aircraftLoadoutDropdown: Dropdown; + //#unitsNumberDropdown: Dropdown; + #spawnOptions: SpawnOptions = {role: "", type: "", latlng: this.#latlng, loadout: null, coalition: "blue", airbaseName: null}; constructor(id: string,) { this.#container = document.getElementById(id); - this.#container?.querySelector("#switch")?.addEventListener('change', (e) => this.#onSwitch(e)) + this.#container?.querySelector("#context-menu-switch")?.addEventListener('change', (e) => this.#onSwitch(e)); + + this.#aircraftRoleDropdown = new Dropdown("role-options", (role: string) => this.#setAircraftRole(role), aircraftDatabase.getRoles()); + this.#aircraftTypeDropdown = new Dropdown("aircraft-options", (type: string) => this.#setAircraftType(type)); + this.#aircraftLoadoutDropdown = new Dropdown("loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout)); + //this.#unitsNumberDropdown = new Dropdown("#units-options", this.#setAircraftType, [""]); + + document.addEventListener("contextMenuShow", (e: any) => { + this.#container?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", e.detail.unitType !== "aircraft"); + }) + + document.addEventListener("contextMenuDeployAircraft", () => { + this.hide(); + this.#spawnOptions.coalition = getActiveCoalition(); + if (this.#spawnOptions) + spawnAircraft(this.#spawnOptions); + }) + this.hide(); } - show(x: number, y: number, title: string, options: ContextMenuOption[], showCoalition: boolean) { + show(x: number, y: number, latlng: LatLng) { + this.#spawnOptions.latlng = latlng; this.#container?.classList.toggle("hide", false); - - this.#container?.querySelector("#list")?.replaceChildren(...options.map((option: ContextMenuOption) => { - var li = document.createElement("li"); - var button = document.createElement("button"); - button.textContent = option.tooltip; - li.appendChild(button); - button.addEventListener("click", (e: MouseEvent) => option.callback((e.target as HTMLButtonElement).innerText)); - return button; - })); - - this.#container?.querySelector("#switch")?.classList.toggle("hide", !showCoalition); - - if (this.#container != null && options.length >= 1) { - var titleDiv = this.#container.querySelector("#title"); - if (titleDiv) - titleDiv.textContent = title; - - if (x - this.#container.offsetWidth / 2 + this.#container.offsetWidth < window.innerWidth) - this.#container.style.left = x - this.#container.offsetWidth / 2 + "px"; + if (this.#container != null) { + if (x + this.#container.offsetWidth < window.innerWidth) + this.#container.style.left = x + "px"; else this.#container.style.left = window.innerWidth - this.#container.offsetWidth + "px"; - if (y - 20 + this.#container.offsetHeight < window.innerHeight) - this.#container.style.top = y - 20 + "px"; + if (y + this.#container.offsetHeight < window.innerHeight) + this.#container.style.top = y + "px"; else this.#container.style.top = window.innerHeight - this.#container.offsetHeight + "px"; } @@ -47,10 +69,43 @@ export class ContextMenu { #onSwitch(e: any) { if (this.#container != null) { - if (e.currentTarget.checked) + if (e.srcElement.checked) setActiveCoalition("red"); else setActiveCoalition("blue"); } } + + #setAircraftRole(role: string) + { + if (this.#spawnOptions != null) + { + this.#spawnOptions.role = role; + this.#aircraftTypeDropdown.setOptions(aircraftDatabase.getLabelsByRole(role)); + } + } + + #setAircraftType(type: string) + { + if (this.#spawnOptions != null) + { + this.#spawnOptions.type = type; + this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(type, this.#spawnOptions.role)); + } + } + + #setAircraftLoadout(loadoutName: string) + { + if (this.#spawnOptions != null) + { + var loadout = aircraftDatabase.getLoadoutsByName(this.#spawnOptions.type, loadoutName); + if (loadout) + this.#spawnOptions.loadout = loadout.code; + } + } + + #setUnitsNumber(unitsNumber: string) + { + + } } \ No newline at end of file diff --git a/client/src/controls/dropdown.ts b/client/src/controls/dropdown.ts new file mode 100644 index 00000000..5861c3e5 --- /dev/null +++ b/client/src/controls/dropdown.ts @@ -0,0 +1,32 @@ +export class Dropdown { + #element: HTMLElement; + #options: HTMLElement; + #value: HTMLElement; + #callback: CallableFunction; + + constructor(ID: string, callback: CallableFunction, options: string[] | null = null) + { + this.#element = document.getElementById(ID); + var element = this.#element; + this.#options = this.#element.querySelector(".ol-select-options"); + this.#value = this.#element.querySelector(".ol-select-value"); + this.#callback = callback; + if (options != null) + this.setOptions(options); + } + + setOptions(options: string[]) + { + this.#options.replaceChildren(...options.map((option: string) => { + var div = document.createElement("div"); + var button = document.createElement("button"); + button.textContent = option; + div.appendChild(button); + button.addEventListener("click", (e: MouseEvent) => { + this.#value.innerText = option; + this.#callback(option); + }); + return div; + })); + } +} \ No newline at end of file diff --git a/client/src/index.ts b/client/src/index.ts index d58ad7a5..d86d691a 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -248,6 +248,7 @@ export function getConnectionStatusPanel() { export function setActiveCoalition(newActiveCoalition: string) { activeCoalition = newActiveCoalition; + document.querySelectorAll('[data-coalition]').forEach((element: any) => {element.setAttribute("data-coalition", activeCoalition)}); } export function getActiveCoalition() { diff --git a/client/src/map/map.ts b/client/src/map/map.ts index cc24b02d..fd0e3b12 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -5,6 +5,7 @@ import { aircraftDatabase } from "../units/aircraftdatabase"; import { unitTypes } from "../units/unittypes"; import { BoxSelect } from "./boxselect"; import { ContextMenuOption } from "../@types/dom"; +import { SpawnOptions } from "../controls/contextmenu"; export const IDLE = "IDLE"; export const MOVE_UNIT = "MOVE_UNIT"; @@ -116,10 +117,10 @@ export class Map extends L.Map { } /* Context Menu */ - showContextMenu(e: ClickEvent | SpawnEvent, title: string, options: ContextMenuOption[], showCoalition: boolean = false) { - var x = e.x; - var y = e.y; - getContextMenu()?.show(x, y, title, options, showCoalition); + showContextMenu(e: any, spawnOptions: SpawnOptions | null = null) { + var x = e.originalEvent.x; + var y = e.originalEvent.y; + getContextMenu()?.show(x, y, e.latlng); document.dispatchEvent(new CustomEvent("mapContextMenu")); } @@ -139,7 +140,7 @@ export class Map extends L.Map { /* Spawn from air base */ spawnFromAirbase(e: SpawnEvent) { - this.#aircraftSpawnMenu(e); + //this.#aircraftSpawnMenu(e); } /* Event handlers */ @@ -164,15 +165,8 @@ export class Map extends L.Map { #onContextMenu(e: any) { this.hideContextMenu(); if (this.#state === IDLE) { - var spawnEvent: SpawnEvent = {x: e.originalEvent.x, y: e.originalEvent.y, latlng: e.latlng, airbaseName: null, coalitionID: null}; if (this.#state == IDLE) { - var options = [ - { "tooltip": "Spawn air unit", "src": "spawnAir.png", "callback": () => this.#aircraftSpawnMenu(spawnEvent) }, - { "tooltip": "Spawn ground unit", "src": "spawnGround.png", "callback": () => this.#groundUnitSpawnMenu(spawnEvent) }, - { "tooltip": "Smoke", "src": "spawnSmoke.png", "callback": () => this.#smokeSpawnMenu(spawnEvent) }, - //{ "tooltip": "Explosion", "src": "spawnExplosion.png", "callback": () => this.#explosionSpawnMenu(e) } - ] - this.showContextMenu(spawnEvent, "Action", options, false); + this.showContextMenu(e); } } else if (this.#state === MOVE_UNIT) { @@ -215,99 +209,4 @@ export class Map extends L.Map { this.#lastMousePosition.x = e.originalEvent.x; this.#lastMousePosition.y = e.originalEvent.y; } - - /* Spawning menus */ - #aircraftSpawnMenu(e: SpawnEvent) { - var options = [ - { 'coalition': true, 'tooltip': 'CAP', 'src': 'spawnCAP.png', 'callback': () => this.#selectAircraft(e, "cap") }, - { 'coalition': true, 'tooltip': 'CAS', 'src': 'spawnCAS.png', 'callback': () => this.#selectAircraft(e, "cas") }, - { 'coalition': true, 'tooltip': 'Strike', 'src': 'spawnStrike.png', 'callback': () => this.#selectAircraft(e, "strike") }, - { 'coalition': true, 'tooltip': 'Recce', 'src': 'spawnStrike.png', 'callback': () => this.#selectAircraft(e, "reconnaissance") }, - { 'coalition': true, 'tooltip': 'Tanker', 'src': 'spawnTanker.png', 'callback': () => this.#selectAircraft(e, "tanker") }, - { 'coalition': true, 'tooltip': 'AWACS', 'src': 'spawnAWACS.png', 'callback': () => this.#selectAircraft(e, "awacs") }, - { 'coalition': true, 'tooltip': 'Drone', 'src': 'spawnDrone.png', 'callback': () => this.#selectAircraft(e, "drone") }, - { 'coalition': true, 'tooltip': 'Transport', 'src': 'spawnTransport.png', 'callback': () => this.#selectAircraft(e, "transport") }, - ] - if (e.airbaseName != null) - this.showContextMenu(e, "Spawn at " + e.airbaseName, options, true); - else - this.showContextMenu(e, "Spawn air unit", options, true); - } - - #groundUnitSpawnMenu(e: SpawnEvent) { - var options = [ - {'coalition': true, 'tooltip': 'Howitzer', 'src': 'spawnHowitzer.png', 'callback': () => this.#selectGroundUnit(e, "Howitzers")}, - {'coalition': true, 'tooltip': 'SAM', 'src': 'spawnSAM.png', 'callback': () => this.#selectGroundUnit(e, "SAM")}, - {'coalition': true, 'tooltip': 'IFV', 'src': 'spawnIFV.png', 'callback': () => this.#selectGroundUnit(e, "IFV")}, - {'coalition': true, 'tooltip': 'Tank', 'src': 'spawnTank.png', 'callback': () => this.#selectGroundUnit(e, "Tanks")}, - {'coalition': true, 'tooltip': 'MLRS', 'src': 'spawnMLRS.png', 'callback': () => this.#selectGroundUnit(e, "MLRS")}, - {'coalition': true, 'tooltip': 'Radar', 'src': 'spawnRadar.png', 'callback': () => this.#selectGroundUnit(e, "Radar")}, - {'coalition': true, 'tooltip': 'Unarmed', 'src': 'spawnUnarmed.png', 'callback': () => this.#selectGroundUnit(e, "Unarmed")} - ] - this.showContextMenu(e, "Spawn ground unit", options, true); - } - - #smokeSpawnMenu(e: SpawnEvent) { - this.hideContextMenu(); - var options = [ - {'tooltip': 'Red smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideContextMenu(); spawnSmoke('red', e.latlng)}, 'tint': 'red'}, - {'tooltip': 'White smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideContextMenu(); spawnSmoke('white', e.latlng)}, 'tint': 'white'}, - {'tooltip': 'Blue smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideContextMenu(); spawnSmoke('blue', e.latlng)}, 'tint': 'blue'}, - {'tooltip': 'Green smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideContextMenu(); spawnSmoke('green', e.latlng)}, 'tint': 'green'}, - {'tooltip': 'Orange smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideContextMenu(); spawnSmoke('orange', e.latlng)}, 'tint': 'orange'}, - ] - this.showContextMenu(e, "Spawn smoke", options, false); - } - - #explosionSpawnMenu(e: SpawnEvent) { - - } - - /* Show unit selection for air units */ - #selectAircraft(e: SpawnEvent, role: string) { - this.hideContextMenu(); - var options = aircraftDatabase.getLabelsByRole(role); - this.showContextMenu(e, "Select aircraft", - options.map((option: string) => { - return {tooltip: option, src: "", callback: (label: string) => { - this.hideContextMenu(); - var name = aircraftDatabase.getNameByLabel(label); - if (name != null) - this.#unitSelectPayload(e, name, role); - }}}), true); - } - - /* Show weapon selection for air units */ - #unitSelectPayload(e: SpawnEvent, unitType: string, role: string) { - this.hideContextMenu(); - var options = aircraftDatabase.getLoadoutNamesByRole(unitType, role); - //options = payloadNames[unitType] - if (options != undefined && options.length > 0) { - options.sort(); - this.showContextMenu({x: e.x, y: e.y, latlng: e.latlng}, "Select loadout", - options.map((option: string) => { - return {tooltip: option, src: "", callback: (loadoutName: string) => { - this.hideContextMenu(); - var loadout = aircraftDatabase.getLoadoutsByName(unitType, loadoutName); - spawnAircraft(unitType, e.latlng, getActiveCoalition(), loadout != null? loadout.code: "", e.airbaseName); - }}}), true); - } - else { - spawnAircraft(unitType, e.latlng, getActiveCoalition()); - } - } - - /* Show unit selection for ground units */ - #selectGroundUnit(e: any, group: string) - { - this.hideContextMenu(); - var options = unitTypes.vehicles[group]; - options.sort(); - this.showContextMenu(e, "Select ground unit", - options.map((option: string) => { - return {tooltip: option, src: "", callback: (unitType: string) => { - this.hideContextMenu(); - spawnGroundUnit(unitType, e.latlng, getActiveCoalition()); - }}}), true); - } } diff --git a/client/src/missionhandler/missionhandler.ts b/client/src/missionhandler/missionhandler.ts index 67f7e9ae..193b69a9 100644 --- a/client/src/missionhandler/missionhandler.ts +++ b/client/src/missionhandler/missionhandler.ts @@ -84,9 +84,9 @@ export class MissionHandler else options = ["Spawn unit"]; - getMap().showContextMenu(e.originalEvent, e.sourceTarget.getName(), - options.map((option) => {return {tooltip: option, src: "", callback: (label: string) => {this.#onAirbaseOptionSelection(e, label)}}}, false) - ) + //getMap().showContextMenu(e.originalEvent, e.sourceTarget.getName(), + // options.map((option) => {return {tooltip: option, src: "", callback: (label: string) => {this.#onAirbaseOptionSelection(e, label)}}}, false) + //) } #onAirbaseOptionSelection(e: any, option: string) { diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 9768bb18..7f66425a 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -1,5 +1,6 @@ import * as L from 'leaflet' import { setConnected } from '..'; +import { SpawnOptions } from '../controls/contextmenu'; /* Edit here to change server address */ const REST_ADDRESS = "http://localhost:30000/olympus"; @@ -69,8 +70,8 @@ export function spawnGroundUnit(type: string, latlng: L.LatLng, coalition: strin POST(data, () => { }); } -export function spawnAircraft(type: string, latlng: L.LatLng, coalition: string, payloadName: string | null = null, airbaseName: string | null = null) { - var command = { "type": type, "location": latlng, "coalition": coalition, "payloadName": payloadName != null? payloadName: "", "airbaseName": airbaseName != null? airbaseName: ""}; +export function spawnAircraft(spawnOptions: SpawnOptions) { + var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "payloadName": spawnOptions.loadout != null? spawnOptions.loadout: "", "airbaseName": spawnOptions.airbaseName != null? spawnOptions.airbaseName: ""}; var data = { "spawnAir": command } POST(data, () => { }); } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index c91bd337..f7a82c0e 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -290,7 +290,7 @@ export class Unit extends Marker { 'Attack', 'Follow' ] - getMap().showContextMenu(e.originalEvent, "Action: " + this.getData().unitName, options.map((option: string) => {return {tooltip: option, src: "", callback: (action: string) => this.#executeAction(action)}})); + //getMap().showContextMenu(e.originalEvent, "Action: " + this.getData().unitName, options.map((option: string) => {return {tooltip: option, src: "", callback: (action: string) => this.#executeAction(action)}})); } #executeAction(action: string) { diff --git a/client/src/units/unitdatabase.ts b/client/src/units/unitdatabase.ts index 1359d491..97d23035 100644 --- a/client/src/units/unitdatabase.ts +++ b/client/src/units/unitdatabase.ts @@ -6,6 +6,24 @@ export class UnitDatabase { } + getRoles() + { + var roles: string[] = []; + for (let unit in this.units) + { + for (let loadout of this.units[unit].loadouts) + { + for (let role of loadout.roles) + { + role = role.toUpperCase(); + if (role !== "" && !roles.includes(role)) + roles.push(role); + } + } + } + return roles; + } + getLabelsByRole(role: string) { var units = []; @@ -13,7 +31,7 @@ export class UnitDatabase { { for (let loadout of this.units[unit].loadouts) { - if (loadout.roles.includes(role)) + if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase())) { units.push(this.units[unit].label) break; @@ -28,7 +46,7 @@ export class UnitDatabase { var loadouts = []; for (let loadout of this.units[unit].loadouts) { - if (loadout.roles.includes(role) || loadout.roles.includes("")) + if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase()) || loadout.roles.includes("")) { loadouts.push(loadout.name) } diff --git a/client/views/contextmenu.ejs b/client/views/contextmenu.ejs index 2997ad88..cead9fcc 100644 --- a/client/views/contextmenu.ejs +++ b/client/views/contextmenu.ejs @@ -1,19 +1,44 @@
-
Title
-
- + + +
+ +
-
    -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
  • -
+
+
+
+
Aircraft role
+
+ +
+
+
+
+
+
Aircraft type
+
+
Select role first
+ +
+
+
+
+
+
Loadout
+
+
Select type first
+ +
+
+
+ +
\ No newline at end of file