Reformatted project using prettier plugin

This commit is contained in:
Davide Passoni
2024-07-01 17:43:46 +02:00
parent 1acb7d6762
commit 00e2da2aab
524 changed files with 36320 additions and 24305 deletions

View File

@@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32"
preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228">
<path fill="#00D8FF"
d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -23,138 +23,234 @@ export const IRST = 8;
export const RWR = 16;
export const DLINK = 32;
export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area", "simulate-fire-fight", "scenic-aaa", "miss-on-purpose", "land-at-point"];
export const states: string[] = [
"none",
"idle",
"reach-destination",
"attack",
"follow",
"land",
"refuel",
"AWACS",
"tanker",
"bomb-point",
"carpet-bomb",
"bomb-building",
"fire-at-area",
"simulate-fire-fight",
"scenic-aaa",
"miss-on-purpose",
"land-at-point",
];
export const ROEs: string[] = ["free", "designated", "", "return", "hold"];
export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"];
export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"];
export const reactionsToThreat: string[] = [
"none",
"manoeuvre",
"passive",
"evade",
];
export const emissionsCountermeasures: string[] = [
"silent",
"attack",
"defend",
"free",
];
export const ERAS = [{
"name": "Early Cold War",
"chronologicalOrder": 2
}, {
"name": "Late Cold War",
"chronologicalOrder": 4
}, {
"name": "Mid Cold War",
"chronologicalOrder": 3
}, {
"name": "Modern",
"chronologicalOrder": 5
}, {
"name": "WW2",
"chronologicalOrder": 1
}];
export const ERAS = [
{
name: "Early Cold War",
chronologicalOrder: 2,
},
{
name: "Late Cold War",
chronologicalOrder: 4,
},
{
name: "Mid Cold War",
chronologicalOrder: 3,
},
{
name: "Modern",
chronologicalOrder: 5,
},
{
name: "WW2",
chronologicalOrder: 1,
},
];
export const ROEDescriptions: string[] = [
"Free (Attack anyone)",
"Designated (Attack the designated target only) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.",
"",
"Return (Only fire if fired upon) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.",
"Hold (Never fire)"
"Free (Attack anyone)",
"Designated (Attack the designated target only) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.",
"",
"Return (Only fire if fired upon) \nWARNING: Ground and Navy units don't respect this ROE, it will be equivalent to weapons FREE.",
"Hold (Never fire)",
];
export const reactionsToThreatDescriptions: string[] = [
"None (No reaction)",
"Manoeuvre (no countermeasures)",
"Passive (Countermeasures only, no manoeuvre)",
"Evade (Countermeasures and manoeuvers)"
"None (No reaction)",
"Manoeuvre (no countermeasures)",
"Passive (Countermeasures only, no manoeuvre)",
"Evade (Countermeasures and manoeuvers)",
];
export const emissionsCountermeasuresDescriptions: string[] = [
"Silent (Radar OFF, no ECM)",
"Attack (Radar only for targeting, ECM only if locked)",
"Defend (Radar for searching, ECM if locked)",
"Always on (Radar and ECM always on)"
"Silent (Radar OFF, no ECM)",
"Attack (Radar only for targeting, ECM only if locked)",
"Defend (Radar for searching, ECM if locked)",
"Always on (Radar and ECM always on)",
];
export const shotsScatterDescriptions: string[] = [
"When performing scenic shooting tasks like simulated firefights, will shoot with a large scatter",
"When performing scenic shooting tasks like simulated firefights, will shoot with a medium scatter",
"When performing scenic shooting tasks like simulated firefights, will shoot with a small scatter (Radar guided units will track shots when the enemy unit is close)"
"When performing scenic shooting tasks like simulated firefights, will shoot with a large scatter",
"When performing scenic shooting tasks like simulated firefights, will shoot with a medium scatter",
"When performing scenic shooting tasks like simulated firefights, will shoot with a small scatter (Radar guided units will track shots when the enemy unit is close)",
];
export const shotsIntensityDescriptions: string[] = [
"When performing scenic shooting tasks like simulated firefights, will shoot with a low rate of fire",
"When performing scenic shooting tasks like simulated firefights, will shoot with a medium rate of fire",
"When performing scenic shooting tasks like simulated firefights, will shoot with a high rate of fire"
"When performing scenic shooting tasks like simulated firefights, will shoot with a low rate of fire",
"When performing scenic shooting tasks like simulated firefights, will shoot with a medium rate of fire",
"When performing scenic shooting tasks like simulated firefights, will shoot with a high rate of fire",
];
export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
export const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 };
export const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 };
export const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 };
export const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 };
export const minSpeedValues: { [key: string]: number } = {
Aircraft: 100,
Helicopter: 0,
NavyUnit: 0,
GroundUnit: 0,
};
export const maxSpeedValues: { [key: string]: number } = {
Aircraft: 800,
Helicopter: 300,
NavyUnit: 60,
GroundUnit: 60,
};
export const speedIncrements: { [key: string]: number } = {
Aircraft: 25,
Helicopter: 10,
NavyUnit: 5,
GroundUnit: 5,
};
export const minAltitudeValues: { [key: string]: number } = {
Aircraft: 0,
Helicopter: 0,
};
export const maxAltitudeValues: { [key: string]: number } = {
Aircraft: 50000,
Helicopter: 10000,
};
export const altitudeIncrements: { [key: string]: number } = {
Aircraft: 500,
Helicopter: 100,
};
export const minimapBoundaries = {
"Nevada": [ // NTTR
new LatLng(39.7982463, -119.985425),
new LatLng(34.4037128, -119.7806729),
new LatLng(34.3483316, -112.4529351),
new LatLng(39.7372411, -112.1130805),
new LatLng(39.7982463, -119.985425)
],
"Syria": [ // Syria
new LatLng(37.3630556, 29.2686111),
new LatLng(31.8472222, 29.8975),
new LatLng(32.1358333, 42.1502778),
new LatLng(37.7177778, 42.3716667),
new LatLng(37.3630556, 29.2686111)
],
"Caucasus": [ // Caucasus
new LatLng(39.6170191, 27.634935),
new LatLng(38.8735863, 47.1423108),
new LatLng(47.3907982, 49.3101946),
new LatLng(48.3955879, 26.7753625),
new LatLng(39.6170191, 27.634935)
],
"PersianGulf": [ // Persian Gulf
new LatLng(32.9355285, 46.5623682),
new LatLng(21.729393, 47.572675),
new LatLng(21.8501348, 63.9734737),
new LatLng(33.131584, 64.7313594),
new LatLng(32.9355285, 46.5623682)
],
"MarianaIslands": [ // Marianas
new LatLng(22.09, 135.0572222),
new LatLng(10.5777778, 135.7477778),
new LatLng(10.7725, 149.3918333),
new LatLng(22.5127778, 149.5427778),
new LatLng(22.09, 135.0572222)
],
"Falklands": [ // South Atlantic
new LatLng(-49.097217, -79.418267),
new LatLng(-56.874517, -79.418267),
new LatLng(-56.874517, -43.316433),
new LatLng(-49.097217, -43.316433),
new LatLng(-49.097217, -79.418267)
],
"Normandy": [ // Normandy
new LatLng(50.44, -3.29),
new LatLng(48.12, -3.29),
new LatLng(48.12, 3.70),
new LatLng(50.44, 3.70),
new LatLng(50.44, -3.29)
],
"SinaiMap": [ // Sinai
new LatLng(34.312222, 28.523333),
new LatLng(25.946944, 28.523333),
new LatLng(25.946944, 36.897778),
new LatLng(34.312222, 36.897778),
new LatLng(34.312222, 28.523333)
]
Nevada: [
// NTTR
new LatLng(39.7982463, -119.985425),
new LatLng(34.4037128, -119.7806729),
new LatLng(34.3483316, -112.4529351),
new LatLng(39.7372411, -112.1130805),
new LatLng(39.7982463, -119.985425),
],
Syria: [
// Syria
new LatLng(37.3630556, 29.2686111),
new LatLng(31.8472222, 29.8975),
new LatLng(32.1358333, 42.1502778),
new LatLng(37.7177778, 42.3716667),
new LatLng(37.3630556, 29.2686111),
],
Caucasus: [
// Caucasus
new LatLng(39.6170191, 27.634935),
new LatLng(38.8735863, 47.1423108),
new LatLng(47.3907982, 49.3101946),
new LatLng(48.3955879, 26.7753625),
new LatLng(39.6170191, 27.634935),
],
PersianGulf: [
// Persian Gulf
new LatLng(32.9355285, 46.5623682),
new LatLng(21.729393, 47.572675),
new LatLng(21.8501348, 63.9734737),
new LatLng(33.131584, 64.7313594),
new LatLng(32.9355285, 46.5623682),
],
MarianaIslands: [
// Marianas
new LatLng(22.09, 135.0572222),
new LatLng(10.5777778, 135.7477778),
new LatLng(10.7725, 149.3918333),
new LatLng(22.5127778, 149.5427778),
new LatLng(22.09, 135.0572222),
],
Falklands: [
// South Atlantic
new LatLng(-49.097217, -79.418267),
new LatLng(-56.874517, -79.418267),
new LatLng(-56.874517, -43.316433),
new LatLng(-49.097217, -43.316433),
new LatLng(-49.097217, -79.418267),
],
Normandy: [
// Normandy
new LatLng(50.44, -3.29),
new LatLng(48.12, -3.29),
new LatLng(48.12, 3.7),
new LatLng(50.44, 3.7),
new LatLng(50.44, -3.29),
],
SinaiMap: [
// Sinai
new LatLng(34.312222, 28.523333),
new LatLng(25.946944, 28.523333),
new LatLng(25.946944, 36.897778),
new LatLng(34.312222, 36.897778),
new LatLng(34.312222, 28.523333),
],
};
export const mapBounds = {
"Syria": { bounds: new LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]), zoom: 5 },
"MarianaIslands": { bounds: new LatLngBounds([10.5777778, 135.7477778], [22.5127778, 149.5427778]), zoom: 5 },
"Nevada": { bounds: new LatLngBounds([34.4037128, -119.7806729], [39.7372411, -112.1130805]), zoom: 5 },
"PersianGulf": { bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]), zoom: 4 },
"Caucasus": { bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]), zoom: 4 },
"Falklands": { bounds: new LatLngBounds([-49.097217, -79.418267], [-56.874517, -43.316433]), zoom: 3 },
"Normandy": { bounds: new LatLngBounds([50.44, -3.29], [48.12, 3.70]), zoom: 5 },
"SinaiMap": { bounds: new LatLngBounds([34.312222, 28.523333], [25.946944, 36.897778]), zoom: 4 },
}
Syria: {
bounds: new LatLngBounds([31.8472222, 29.8975], [37.7177778, 42.3716667]),
zoom: 5,
},
MarianaIslands: {
bounds: new LatLngBounds(
[10.5777778, 135.7477778],
[22.5127778, 149.5427778]
),
zoom: 5,
},
Nevada: {
bounds: new LatLngBounds(
[34.4037128, -119.7806729],
[39.7372411, -112.1130805]
),
zoom: 5,
},
PersianGulf: {
bounds: new LatLngBounds([21.729393, 47.572675], [33.131584, 64.7313594]),
zoom: 4,
},
Caucasus: {
bounds: new LatLngBounds([39.6170191, 27.634935], [47.3907982, 49.3101946]),
zoom: 4,
},
Falklands: {
bounds: new LatLngBounds(
[-49.097217, -79.418267],
[-56.874517, -43.316433]
),
zoom: 3,
},
Normandy: { bounds: new LatLngBounds([50.44, -3.29], [48.12, 3.7]), zoom: 5 },
SinaiMap: {
bounds: new LatLngBounds([34.312222, 28.523333], [25.946944, 36.897778]),
zoom: 4,
},
};
export const defaultMapMirrors = {};
export const defaultMapLayers = {};
@@ -167,97 +263,102 @@ export const CONTEXT_ACTION = "Context action";
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"];
export const IADSDensities: { [key: string]: number } = { "AAA": 0.8, "SAM Site": 0.1, "Radar (EWR)": 0.05 };
export const GROUND_UNIT_AIR_DEFENCE_REGEX: RegExp = /(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/;
export const IADSDensities: { [key: string]: number } = {
AAA: 0.8,
"SAM Site": 0.1,
"Radar (EWR)": 0.05,
};
export const GROUND_UNIT_AIR_DEFENCE_REGEX: RegExp =
/(\b(AAA|SAM|MANPADS?|[mM]anpads?)|[sS]tinger\b)/;
export const MAP_OPTIONS_TOOLTIPS = {
hideGroupMembers: "Hide group members when zoomed out",
hideUnitsShortRangeRings: "Hide short range units threat range rings (R)",
showUnitContacts: "Show selected units contact lines",
showUnitPaths: "Show selected unit paths",
showUnitTargets: "Show selected unit targets",
showUnitLabels: "Show unit labels (L)",
showUnitsEngagementRings: "Show units threat range rings (Q)",
showUnitsAcquisitionRings: "Show units detection range rings (E)"
}
hideGroupMembers: "Hide group members when zoomed out",
hideUnitsShortRangeRings: "Hide short range units threat range rings (R)",
showUnitContacts: "Show selected units contact lines",
showUnitPaths: "Show selected unit paths",
showUnitTargets: "Show selected unit targets",
showUnitLabels: "Show unit labels (L)",
showUnitsEngagementRings: "Show units threat range rings (Q)",
showUnitsAcquisitionRings: "Show units detection range rings (E)",
};
export const MAP_OPTIONS_DEFAULTS = {
hideGroupMembers: true,
hideUnitsShortRangeRings: true,
showUnitContacts: true,
showUnitPaths: true,
showUnitTargets: false,
showUnitLabels: true,
showUnitsEngagementRings: true,
showUnitsAcquisitionRings: true,
fillSelectedRing: false
}
hideGroupMembers: true,
hideUnitsShortRangeRings: true,
showUnitContacts: true,
showUnitPaths: true,
showUnitTargets: false,
showUnitLabels: true,
showUnitsEngagementRings: true,
showUnitsAcquisitionRings: true,
fillSelectedRing: false,
};
export const MAP_HIDDEN_TYPES_DEFAULTS = {
'human': false,
'olympus': false,
'dcs': false,
'aircraft': false,
'helicopter': false,
'groundunit-sam': false,
'groundunit': false,
'navyunit': false,
'airbase': false,
'dead': false,
'blue': false,
'red': false,
'neutral': false
}
human: false,
olympus: false,
dcs: false,
aircraft: false,
helicopter: false,
"groundunit-sam": false,
groundunit: false,
navyunit: false,
airbase: false,
dead: false,
blue: false,
red: false,
neutral: false,
};
export enum DataIndexes {
startOfData = 0,
category,
alive,
human,
controlled,
coalition,
country,
name,
unitName,
groupName,
state,
task,
hasTask,
position,
speed,
horizontalVelocity,
verticalVelocity,
heading,
track,
isActiveTanker,
isActiveAWACS,
onOff,
followRoads,
fuel,
desiredSpeed,
desiredSpeedType,
desiredAltitude,
desiredAltitudeType,
leaderID,
formationOffset,
targetID,
targetPosition,
ROE,
reactionToThreat,
emissionsCountermeasures,
TACAN,
radio,
generalSettings,
ammo,
contacts,
activePath,
isLeader,
operateAs,
shotsScatter,
shotsIntensity,
health,
endOfData = 255
};
startOfData = 0,
category,
alive,
human,
controlled,
coalition,
country,
name,
unitName,
groupName,
state,
task,
hasTask,
position,
speed,
horizontalVelocity,
verticalVelocity,
heading,
track,
isActiveTanker,
isActiveAWACS,
onOff,
followRoads,
fuel,
desiredSpeed,
desiredSpeedType,
desiredAltitude,
desiredAltitudeType,
leaderID,
formationOffset,
targetID,
targetPosition,
ROE,
reactionToThreat,
emissionsCountermeasures,
TACAN,
radio,
generalSettings,
ammo,
contacts,
activePath,
isLeader,
operateAs,
shotsScatter,
shotsIntensity,
health,
endOfData = 255,
}
export const MGRS_PRECISION_10KM = 2;
export const MGRS_PRECISION_1KM = 3;

View File

@@ -2,33 +2,35 @@ import { ServerStatus } from "./interfaces";
import { Unit } from "./unit/unit";
interface CustomEventMap {
"unitSelection": CustomEvent<Unit>,
"unitDeselection": CustomEvent<Unit>,
"unitsSelection": CustomEvent<Unit[]>,
"unitsDeselection": CustomEvent<Unit[]>,
"clearSelection": CustomEvent<any>,
"unitDeath": CustomEvent<Unit>,
"unitUpdated": CustomEvent<Unit>,
"mapStateChanged": CustomEvent<string>,
"mapContextMenu": CustomEvent<any>,
"mapOptionChanged": CustomEvent<any>,
"mapSourceChanged": CustomEvent<string>,
"mapOptionsChanged": CustomEvent<any>, // TODO not very clear, why the two options?
"configLoaded": CustomEvent<any>,
"commandModeOptionsChanged": CustomEvent<any>,
"contactsUpdated": CustomEvent<Unit>,
"activeCoalitionChanged": CustomEvent<any>,
"serverStatusUpdated": CustomEvent<ServerStatus>
unitSelection: CustomEvent<Unit>;
unitDeselection: CustomEvent<Unit>;
unitsSelection: CustomEvent<Unit[]>;
unitsDeselection: CustomEvent<Unit[]>;
clearSelection: CustomEvent<any>;
unitDeath: CustomEvent<Unit>;
unitUpdated: CustomEvent<Unit>;
mapStateChanged: CustomEvent<string>;
mapContextMenu: CustomEvent<any>;
mapOptionChanged: CustomEvent<any>;
mapSourceChanged: CustomEvent<string>;
mapOptionsChanged: CustomEvent<any>; // TODO not very clear, why the two options?
configLoaded: CustomEvent<any>;
commandModeOptionsChanged: CustomEvent<any>;
contactsUpdated: CustomEvent<Unit>;
activeCoalitionChanged: CustomEvent<any>;
serverStatusUpdated: CustomEvent<ServerStatus>;
}
declare global {
interface Document {
addEventListener<K extends keyof CustomEventMap>(type: K,
listener: (this: Document, ev: CustomEventMap[K]) => void): void;
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void;
}
interface Document {
addEventListener<K extends keyof CustomEventMap>(
type: K,
listener: (this: Document, ev: CustomEventMap[K]) => void
): void;
dispatchEvent<K extends keyof CustomEventMap>(ev: CustomEventMap[K]): void;
}
//function getOlympusPlugin(): OlympusPlugin;
//function getOlympusPlugin(): OlympusPlugin;
}
export { };
export {};

View File

@@ -1,19 +1,19 @@
import { createContext } from "react";
export const EventsContext = createContext({
setMainMenuVisible: (e: boolean) => {},
setSpawnMenuVisible: (e: boolean) => {},
setUnitControlMenuVisible: (e: boolean) => {},
setMeasureMenuVisible: (e: boolean) => {},
setDrawingMenuVisible: (e: boolean) => {},
setOptionsMenuVisible: (e: boolean) => {},
toggleMainMenuVisible: () => {},
toggleSpawnMenuVisible: () => {},
toggleUnitControlMenuVisible: () => {},
toggleMeasureMenuVisible: () => {},
toggleDrawingMenuVisible: () => {},
toggleOptionsMenuVisible: () => {}
})
setMainMenuVisible: (e: boolean) => {},
setSpawnMenuVisible: (e: boolean) => {},
setUnitControlMenuVisible: (e: boolean) => {},
setMeasureMenuVisible: (e: boolean) => {},
setDrawingMenuVisible: (e: boolean) => {},
setOptionsMenuVisible: (e: boolean) => {},
toggleMainMenuVisible: () => {},
toggleSpawnMenuVisible: () => {},
toggleUnitControlMenuVisible: () => {},
toggleMeasureMenuVisible: () => {},
toggleDrawingMenuVisible: () => {},
toggleOptionsMenuVisible: () => {},
});
export const EventsProvider = EventsContext.Provider;
export const EventsConsumer = EventsContext.Consumer;

View File

@@ -9,8 +9,7 @@
font-family: "Inter", sans-serif;
font-optical-sizing: auto;
font-style: normal;
font-variation-settings:
"slnt" 0;
font-variation-settings: "slnt" 0;
}
.z-ui-0 {
@@ -31,4 +30,4 @@
.z-ui-4 {
z-index: 2004;
}
}

View File

@@ -1,294 +1,294 @@
import { LatLng } from "leaflet";
import { Coalition, Context } from "./types/types";
class Airbase {
}
class Airbase {}
export interface ContextMenuOption {
tooltip: string;
src: string;
callback: CallableFunction;
tooltip: string;
src: string;
callback: CallableFunction;
}
export interface AirbasesData {
airbases: { [key: string]: any },
sessionHash: string;
time: number;
airbases: { [key: string]: any };
sessionHash: string;
time: number;
}
export interface BullseyesData {
bullseyes: { [key: string]: { latitude: number, longitude: number, coalition: string } },
sessionHash: string;
time: number;
bullseyes: {
[key: string]: { latitude: number; longitude: number; coalition: string };
};
sessionHash: string;
time: number;
}
export interface MissionData {
mission: {
theatre: string,
dateAndTime: DateAndTime;
commandModeOptions: CommandModeOptions;
coalitions: { red: string[], blue: string[] };
}
time: number;
sessionHash: string;
mission: {
theatre: string;
dateAndTime: DateAndTime;
commandModeOptions: CommandModeOptions;
coalitions: { red: string[]; blue: string[] };
};
time: number;
sessionHash: string;
}
export interface CommandModeOptions {
commandMode: string;
restrictSpawns: boolean;
restrictToCoalition: boolean;
setupTime: number;
spawnPoints: {
red: number,
blue: number
},
eras: string[]
commandMode: string;
restrictSpawns: boolean;
restrictToCoalition: boolean;
setupTime: number;
spawnPoints: {
red: number;
blue: number;
};
eras: string[];
}
export interface DateAndTime {
date: { Year: number, Month: number, Day: number };
time: { h: number, m: number, s: number };
elapsedTime: number;
startTime: number;
date: { Year: number; Month: number; Day: number };
time: { h: number; m: number; s: number };
elapsedTime: number;
startTime: number;
}
export interface LogData {
logs: { [key: string]: string },
sessionHash: string;
time: number;
logs: { [key: string]: string };
sessionHash: string;
time: number;
}
export interface ServerRequestOptions {
time?: number;
commandHash?: string;
time?: number;
commandHash?: string;
}
export interface SpawnRequestTable {
category: string,
coalition: string,
unit: UnitSpawnTable
category: string;
coalition: string;
unit: UnitSpawnTable;
}
export interface UnitSpawnTable {
unitType: string,
location: LatLng,
skill: string,
liveryID: string,
altitude: number,
loadout: string
unitType: string;
location: LatLng;
skill: string;
liveryID: string;
altitude: number;
loadout: string;
}
export interface ObjectIconOptions {
showState: boolean,
showVvi: boolean,
showHealth: boolean,
showHotgroup: boolean,
showUnitIcon: boolean,
showShortLabel: boolean,
showFuel: boolean,
showAmmo: boolean,
showSummary: boolean,
showCallsign: boolean,
rotateToHeading: boolean
showState: boolean;
showVvi: boolean;
showHealth: boolean;
showHotgroup: boolean;
showUnitIcon: boolean;
showShortLabel: boolean;
showFuel: boolean;
showAmmo: boolean;
showSummary: boolean;
showCallsign: boolean;
rotateToHeading: boolean;
}
export interface GeneralSettings {
prohibitJettison: boolean;
prohibitAA: boolean;
prohibitAG: boolean;
prohibitAfterburner: boolean;
prohibitAirWpn: boolean;
prohibitJettison: boolean;
prohibitAA: boolean;
prohibitAG: boolean;
prohibitAfterburner: boolean;
prohibitAirWpn: boolean;
}
export interface TACAN {
isOn: boolean;
channel: number;
XY: string;
callsign: string;
isOn: boolean;
channel: number;
XY: string;
callsign: string;
}
export interface Radio {
frequency: number;
callsign: number;
callsignNumber: number;
frequency: number;
callsign: number;
callsignNumber: number;
}
export interface Ammo {
quantity: number,
name: string,
guidance: number,
category: number,
missileCategory: number
quantity: number;
name: string;
guidance: number;
category: number;
missileCategory: number;
}
export interface Contact {
ID: number,
detectionMethod: number
ID: number;
detectionMethod: number;
}
export interface Offset {
x: number,
y: number,
z: number
x: number;
y: number;
z: number;
}
export interface UnitData {
category: string,
categoryDisplayName: string,
ID: number;
alive: boolean;
human: boolean;
controlled: boolean;
coalition: string;
country: number;
name: string;
unitName: string;
groupName: string;
state: string;
task: string;
hasTask: boolean;
position: LatLng;
speed: number;
horizontalVelocity: number;
verticalVelocity: number;
heading: number;
track: number;
isActiveTanker: boolean;
isActiveAWACS: boolean;
onOff: boolean;
followRoads: boolean;
fuel: number;
desiredSpeed: number;
desiredSpeedType: string;
desiredAltitude: number;
desiredAltitudeType: string;
leaderID: number;
formationOffset: Offset;
targetID: number;
targetPosition: LatLng;
ROE: string;
reactionToThreat: string;
emissionsCountermeasures: string;
TACAN: TACAN;
radio: Radio;
generalSettings: GeneralSettings;
ammo: Ammo[];
contacts: Contact[];
activePath: LatLng[];
isLeader: boolean;
operateAs: string;
shotsScatter: number;
shotsIntensity: number;
health: number;
category: string;
categoryDisplayName: string;
ID: number;
alive: boolean;
human: boolean;
controlled: boolean;
coalition: string;
country: number;
name: string;
unitName: string;
groupName: string;
state: string;
task: string;
hasTask: boolean;
position: LatLng;
speed: number;
horizontalVelocity: number;
verticalVelocity: number;
heading: number;
track: number;
isActiveTanker: boolean;
isActiveAWACS: boolean;
onOff: boolean;
followRoads: boolean;
fuel: number;
desiredSpeed: number;
desiredSpeedType: string;
desiredAltitude: number;
desiredAltitudeType: string;
leaderID: number;
formationOffset: Offset;
targetID: number;
targetPosition: LatLng;
ROE: string;
reactionToThreat: string;
emissionsCountermeasures: string;
TACAN: TACAN;
radio: Radio;
generalSettings: GeneralSettings;
ammo: Ammo[];
contacts: Contact[];
activePath: LatLng[];
isLeader: boolean;
operateAs: string;
shotsScatter: number;
shotsIntensity: number;
health: number;
}
export interface LoadoutItemBlueprint {
name: string;
quantity: number;
effectiveAgainst?: string;
name: string;
quantity: number;
effectiveAgainst?: string;
}
export interface LoadoutBlueprint {
fuel: number;
items: LoadoutItemBlueprint[];
roles: string[];
code: string;
name: string;
enabled: boolean;
fuel: number;
items: LoadoutItemBlueprint[];
roles: string[];
code: string;
name: string;
enabled: boolean;
}
export interface UnitBlueprint {
name: string;
enabled: boolean;
coalition: string;
era: string;
label: string;
shortLabel: string;
type?: string;
loadouts?: LoadoutBlueprint[];
filename?: string;
liveries?: { [key: string]: { name: string, countries: string[] } };
cost?: number;
barrelHeight?: number;
muzzleVelocity?: number;
aimTime?: number;
shotsToFire?: number;
shotsBaseInterval?: number;
shotsBaseScatter?: number;
description?: string;
abilities?: string;
tags?: string;
acquisitionRange?: number;
engagementRange?: number;
targetingRange?: number;
aimMethodRange?: number;
alertnessTimeConstant?: number;
canTargetPoint?: boolean;
canRearm?: boolean;
canAAA?: boolean;
indirectFire?: boolean;
markerFile?: string;
unitWhenGrouped?: string;
name: string;
enabled: boolean;
coalition: string;
era: string;
label: string;
shortLabel: string;
type?: string;
loadouts?: LoadoutBlueprint[];
filename?: string;
liveries?: { [key: string]: { name: string; countries: string[] } };
cost?: number;
barrelHeight?: number;
muzzleVelocity?: number;
aimTime?: number;
shotsToFire?: number;
shotsBaseInterval?: number;
shotsBaseScatter?: number;
description?: string;
abilities?: string;
tags?: string;
acquisitionRange?: number;
engagementRange?: number;
targetingRange?: number;
aimMethodRange?: number;
alertnessTimeConstant?: number;
canTargetPoint?: boolean;
canRearm?: boolean;
canAAA?: boolean;
indirectFire?: boolean;
markerFile?: string;
unitWhenGrouped?: string;
}
export interface AirbaseOptions {
name: string,
position: L.LatLng
name: string;
position: L.LatLng;
}
export interface AirbaseChartData {
elevation: string,
ICAO: string,
TACAN: string,
runways: AirbaseChartRunwayData[]
elevation: string;
ICAO: string;
TACAN: string;
runways: AirbaseChartRunwayData[];
}
export interface AirbaseChartRunwayHeadingData {
[index: string]: {
magHeading: string,
ILS: string
}
[index: string]: {
magHeading: string;
ILS: string;
};
}
export interface AirbaseChartRunwayData {
headings: AirbaseChartRunwayHeadingData[],
length: string
headings: AirbaseChartRunwayHeadingData[];
length: string;
}
export interface Listener {
callback: CallableFunction;
name?: string
callback: CallableFunction;
name?: string;
}
export interface ShortcutOptions {
altKey?: boolean;
callback: CallableFunction;
context?: Context;
ctrlKey?: boolean;
name?: string;
shiftKey?: boolean;
altKey?: boolean;
callback: CallableFunction;
context?: Context;
ctrlKey?: boolean;
name?: string;
shiftKey?: boolean;
}
export interface ShortcutKeyboardOptions extends ShortcutOptions {
code: string;
event?: "keydown" | "keyup";
code: string;
event?: "keydown" | "keyup";
}
export interface ShortcutMouseOptions extends ShortcutOptions {
button: number;
event: "mousedown" | "mouseup";
button: number;
event: "mousedown" | "mouseup";
}
export interface Manager {
add: CallableFunction;
add: CallableFunction;
}
export interface ServerStatus {
frameRate: number,
load: number,
elapsedTime: number,
missionTime: DateAndTime["time"],
connected: boolean,
paused: boolean
}
frameRate: number;
load: number;
elapsedTime: number;
missionTime: DateAndTime["time"];
connected: boolean;
paused: boolean;
}

View File

@@ -1,13 +1,13 @@
/***************** UI *******************/
import React from 'react'
import ReactDOM from 'react-dom/client'
import { setupApp } from './olympusapp.js'
import { UI } from './ui/ui.js';
import React from "react";
import ReactDOM from "react-dom/client";
import { setupApp } from "./olympusapp.js";
import { UI } from "./ui/ui.js";
import './index.css'
import "./index.css";
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<UI />
</React.StrictMode>,
)
</React.StrictMode>
);

View File

@@ -1,136 +1,147 @@
import { Map } from 'leaflet';
import { Handler } from 'leaflet';
import { Util } from 'leaflet';
import { DomUtil } from 'leaflet';
import { DomEvent } from 'leaflet';
import { LatLngBounds } from 'leaflet';
import { Bounds } from 'leaflet';
import { Map } from "leaflet";
import { Handler } from "leaflet";
import { Util } from "leaflet";
import { DomUtil } from "leaflet";
import { DomEvent } from "leaflet";
import { LatLngBounds } from "leaflet";
import { Bounds } from "leaflet";
export var BoxSelect = Handler.extend({
initialize: function (map) {
this._map = map;
this._container = map.getContainer();
this._pane = map.getPanes().overlayPane;
this._resetStateTimeout = 0;
map.on('unload', this._destroy, this);
},
initialize: function (map) {
this._map = map;
this._container = map.getContainer();
this._pane = map.getPanes().overlayPane;
this._resetStateTimeout = 0;
map.on("unload", this._destroy, this);
},
addHooks: function () {
DomEvent.on(this._container, 'mousedown', this._onMouseDown, this);
},
addHooks: function () {
DomEvent.on(this._container, "mousedown", this._onMouseDown, this);
},
removeHooks: function () {
DomEvent.off(this._container, 'mousedown', this._onMouseDown, this);
},
removeHooks: function () {
DomEvent.off(this._container, "mousedown", this._onMouseDown, this);
},
moved: function () {
return this._moved;
},
moved: function () {
return this._moved;
},
_destroy: function () {
DomUtil.remove(this._pane);
delete this._pane;
},
_destroy: function () {
DomUtil.remove(this._pane);
delete this._pane;
},
_resetState: function () {
this._resetStateTimeout = 0;
this._moved = false;
},
_resetState: function () {
this._resetStateTimeout = 0;
this._moved = false;
},
_clearDeferredResetState: function () {
if (this._resetStateTimeout !== 0) {
clearTimeout(this._resetStateTimeout);
this._resetStateTimeout = 0;
}
},
_clearDeferredResetState: function () {
if (this._resetStateTimeout !== 0) {
clearTimeout(this._resetStateTimeout);
this._resetStateTimeout = 0;
}
},
_onMouseDown: function (e: any) {
if ((e.which == 1 && e.button == 0 && e.shiftKey)) {
this._map.fire('selectionstart');
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();
this._resetState();
_onMouseDown: function (e: any) {
if (e.which == 1 && e.button == 0 && e.shiftKey) {
this._map.fire("selectionstart");
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();
this._resetState();
DomUtil.disableTextSelection();
DomUtil.disableImageDrag();
DomUtil.disableTextSelection();
DomUtil.disableImageDrag();
this._startPoint = this._map.mouseEventToContainerPoint(e);
this._startPoint = this._map.mouseEventToContainerPoint(e);
//@ts-ignore
DomEvent.on(document, {
contextmenu: DomEvent.stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown
}, this);
} else {
return false;
}
},
//@ts-ignore
DomEvent.on(
document,
{
contextmenu: DomEvent.stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown,
},
this
);
} else {
return false;
}
},
_onMouseMove: function (e: any) {
if (!this._moved) {
this._moved = true;
_onMouseMove: function (e: any) {
if (!this._moved) {
this._moved = true;
this._box = DomUtil.create('div', 'leaflet-zoom-box', this._container);
DomUtil.addClass(this._container, 'leaflet-crosshair');
this._box = DomUtil.create("div", "leaflet-zoom-box", this._container);
DomUtil.addClass(this._container, "leaflet-crosshair");
this._map.fire('boxzoomstart');
}
this._map.fire("boxzoomstart");
}
this._point = this._map.mouseEventToContainerPoint(e);
this._point = this._map.mouseEventToContainerPoint(e);
var bounds = new Bounds(this._point, this._startPoint),
size = bounds.getSize();
var bounds = new Bounds(this._point, this._startPoint),
size = bounds.getSize();
if (bounds.min != undefined)
DomUtil.setPosition(this._box, bounds.min);
if (bounds.min != undefined) DomUtil.setPosition(this._box, bounds.min);
this._box.style.width = size.x + 'px';
this._box.style.height = size.y + 'px';
},
this._box.style.width = size.x + "px";
this._box.style.height = size.y + "px";
},
_finish: function () {
if (this._moved) {
DomUtil.remove(this._box);
DomUtil.removeClass(this._container, 'leaflet-crosshair');
}
_finish: function () {
if (this._moved) {
DomUtil.remove(this._box);
DomUtil.removeClass(this._container, "leaflet-crosshair");
}
DomUtil.enableTextSelection();
DomUtil.enableImageDrag();
DomUtil.enableTextSelection();
DomUtil.enableImageDrag();
//@ts-ignore
DomEvent.off(document, {
contextmenu: DomEvent.stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown
}, this);
},
//@ts-ignore
DomEvent.off(
document,
{
contextmenu: DomEvent.stop,
mousemove: this._onMouseMove,
mouseup: this._onMouseUp,
keydown: this._onKeyDown,
},
this
);
},
_onMouseUp: function (e: any) {
if ((e.which !== 1) && (e.button !== 0)) { return; }
_onMouseUp: function (e: any) {
if (e.which !== 1 && e.button !== 0) {
return;
}
this._finish();
this._finish();
if (!this._moved) { return; }
// Postpone to next JS tick so internal click event handling
// still see it as "moved".
window.setTimeout(Util.bind(this._resetState, this), 0);
var bounds = new LatLngBounds(
this._map.containerPointToLatLng(this._startPoint),
this._map.containerPointToLatLng(this._point));
if (!this._moved) {
return;
}
// Postpone to next JS tick so internal click event handling
// still see it as "moved".
window.setTimeout(Util.bind(this._resetState, this), 0);
var bounds = new LatLngBounds(
this._map.containerPointToLatLng(this._startPoint),
this._map.containerPointToLatLng(this._point)
);
this._map.fire('selectionend', { selectionBounds: bounds });
},
this._map.fire("selectionend", { selectionBounds: bounds });
},
_onKeyDown: function (e: any) {
if (e.keyCode === 27) {
this._finish();
this._clearDeferredResetState();
this._resetState();
}
}
_onKeyDown: function (e: any) {
if (e.keyCode === 27) {
this._finish();
this._clearDeferredResetState();
this._resetState();
}
},
});

View File

@@ -1,12 +1,12 @@
import { MiniMap, MiniMapOptions } from "leaflet-control-mini-map";
export class ClickableMiniMap extends MiniMap {
constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) {
super(layer, options);
}
constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) {
super(layer, options);
}
getMap() {
//@ts-ignore needed to access not exported member. A bit of a hack, required to access click events //TODO: fix me
return this._miniMap;
}
}
getMap() {
//@ts-ignore needed to access not exported member. A bit of a hack, required to access click events //TODO: fix me
return this._miniMap;
}
}

View File

@@ -1,166 +1,194 @@
import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
import {
DomUtil,
LatLng,
LatLngExpression,
Map,
Point,
Polygon,
PolylineOptions,
} from "leaflet";
import { getApp } from "../../olympusapp";
import { CoalitionAreaHandle } from "./coalitionareahandle";
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
import { BLUE_COMMANDER, RED_COMMANDER } from "../../constants/constants";
export class CoalitionArea extends Polygon {
#coalition: string = "blue";
#selected: boolean = true;
#editing: boolean = true;
#handles: CoalitionAreaHandle[] = [];
#middleHandles: CoalitionAreaMiddleHandle[] = [];
#activeIndex: number = 0;
#coalition: string = "blue";
#selected: boolean = true;
#editing: boolean = true;
#handles: CoalitionAreaHandle[] = [];
#middleHandles: CoalitionAreaMiddleHandle[] = [];
#activeIndex: number = 0;
constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: PolylineOptions) {
if (options === undefined)
options = {};
constructor(
latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][],
options?: PolylineOptions
) {
if (options === undefined) options = {};
options.bubblingMouseEvents = false;
options.interactive = false;
options.bubblingMouseEvents = false;
options.interactive = false;
super(latlngs, options);
this.#setColors();
this.#registerCallbacks();
super(latlngs, options);
this.#setColors();
this.#registerCallbacks();
if ([BLUE_COMMANDER, RED_COMMANDER].includes(getApp().getMissionManager().getCommandModeOptions().commandMode))
this.setCoalition(getApp().getMissionManager().getCommandedCoalition());
if (
[BLUE_COMMANDER, RED_COMMANDER].includes(
getApp().getMissionManager().getCommandModeOptions().commandMode
)
)
this.setCoalition(getApp().getMissionManager().getCommandedCoalition());
}
setCoalition(coalition: string) {
this.#coalition = coalition;
this.#setColors();
}
getCoalition() {
return this.#coalition;
}
setSelected(selected: boolean) {
this.#selected = selected;
this.#setColors();
this.#setHandles();
this.setOpacity(selected ? 1 : 0.5);
if (!this.getSelected() && this.getEditing()) {
/* Remove the vertex we were working on */
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.splice(this.#activeIndex, 1);
this.setLatLngs(latlngs);
this.setEditing(false);
}
}
setCoalition(coalition: string) {
this.#coalition = coalition;
this.#setColors();
}
getSelected() {
return this.#selected;
}
getCoalition() {
return this.#coalition;
}
setEditing(editing: boolean) {
this.#editing = editing;
this.#setHandles();
var latlngs = this.getLatLngs()[0] as LatLng[];
setSelected(selected: boolean) {
this.#selected = selected;
this.#setColors();
this.#setHandles();
this.setOpacity(selected? 1: 0.5);
if (!this.getSelected() && this.getEditing()) {
/* Remove the vertex we were working on */
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.splice(this.#activeIndex, 1);
this.setLatLngs(latlngs);
this.setEditing(false);
}
}
/* Remove areas with less than 2 vertexes */
if (latlngs.length <= 2) getApp().getMap().deleteCoalitionArea(this);
}
getSelected() {
return this.#selected;
}
getEditing() {
return this.#editing;
}
setEditing(editing: boolean) {
this.#editing = editing;
this.#setHandles();
var latlngs = this.getLatLngs()[0] as LatLng[];
addTemporaryLatLng(latlng: LatLng) {
this.#activeIndex++;
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.splice(this.#activeIndex, 0, latlng);
this.setLatLngs(latlngs);
this.#setHandles();
}
/* Remove areas with less than 2 vertexes */
if (latlngs.length <= 2)
getApp().getMap().deleteCoalitionArea(this);
}
moveActiveVertex(latlng: LatLng) {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs[this.#activeIndex] = latlng;
this.setLatLngs(latlngs);
this.#setHandles();
}
getEditing() {
return this.#editing;
}
setOpacity(opacity: number) {
this.setStyle({ opacity: opacity, fillOpacity: opacity * 0.25 });
}
addTemporaryLatLng(latlng: LatLng) {
this.#activeIndex++;
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.splice(this.#activeIndex, 0, latlng);
this.setLatLngs(latlngs);
this.#setHandles();
}
onRemove(map: Map): this {
super.onRemove(map);
this.#handles
.concat(this.#middleHandles)
.forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) =>
handle.removeFrom(getApp().getMap())
);
return this;
}
moveActiveVertex(latlng: LatLng) {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs[this.#activeIndex] = latlng;
this.setLatLngs(latlngs);
this.#setHandles();
}
#setColors() {
const coalitionColor =
this.getCoalition() === "blue" ? "#247be2" : "#ff5858";
this.setStyle({
color: this.getSelected() ? "white" : coalitionColor,
fillColor: coalitionColor,
});
}
setOpacity(opacity: number) {
this.setStyle({opacity: opacity, fillOpacity: opacity * 0.25});
}
onRemove(map: Map): this {
super.onRemove(map);
this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap()));
return this;
}
#setColors() {
const coalitionColor = this.getCoalition() === "blue" ? "#247be2" : "#ff5858";
this.setStyle({ color: this.getSelected() ? "white" : coalitionColor, fillColor: coalitionColor });
}
#setHandles() {
this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getApp().getMap()));
this.#handles = [];
if (this.getSelected()) {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.forEach((latlng: LatLng, idx: number) => {
/* Add the polygon vertex handle (for moving the vertex) */
const handle = new CoalitionAreaHandle(latlng);
handle.addTo(getApp().getMap());
handle.on("drag", (e: any) => {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs[idx] = e.target.getLatLng();
this.setLatLngs(latlngs);
this.#setMiddleHandles();
});
this.#handles.push(handle);
});
}
this.#setMiddleHandles();
}
#setMiddleHandles() {
this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) => handle.removeFrom(getApp().getMap()));
this.#middleHandles = [];
var latlngs = this.getLatLngs()[0] as LatLng[];
if (this.getSelected() && latlngs.length >= 2) {
var lastLatLng: LatLng | null = null;
latlngs.concat([latlngs[0]]).forEach((latlng: LatLng, idx: number) => {
/* Add the polygon middle point handle (for adding new vertexes) */
if (lastLatLng != null) {
const handle1Point = getApp().getMap().latLngToLayerPoint(latlng);
const handle2Point = getApp().getMap().latLngToLayerPoint(lastLatLng);
const middlePoint = new Point((handle1Point.x + handle2Point.x) / 2, (handle1Point.y + handle2Point.y) / 2);
const middleLatLng = getApp().getMap().layerPointToLatLng(middlePoint);
const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng);
middleHandle.addTo(getApp().getMap());
middleHandle.on("click", (e: any) => {
this.#activeIndex = idx - 1;
this.addTemporaryLatLng(middleLatLng);
});
this.#middleHandles.push(middleHandle);
}
lastLatLng = latlng;
});
}
}
#registerCallbacks() {
this.on("click", (e: any) => {
getApp().getMap().deselectAllCoalitionAreas();
if (!this.getSelected()) {
this.setSelected(true);
}
});
this.on("contextmenu", (e: any) => {
if (!this.getEditing()) {
getApp().getMap().deselectAllCoalitionAreas();
this.setSelected(true);
}
else
this.setEditing(false);
#setHandles() {
this.#handles.forEach((handle: CoalitionAreaHandle) =>
handle.removeFrom(getApp().getMap())
);
this.#handles = [];
if (this.getSelected()) {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.forEach((latlng: LatLng, idx: number) => {
/* Add the polygon vertex handle (for moving the vertex) */
const handle = new CoalitionAreaHandle(latlng);
handle.addTo(getApp().getMap());
handle.on("drag", (e: any) => {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs[idx] = e.target.getLatLng();
this.setLatLngs(latlngs);
this.#setMiddleHandles();
});
this.#handles.push(handle);
});
}
}
this.#setMiddleHandles();
}
#setMiddleHandles() {
this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) =>
handle.removeFrom(getApp().getMap())
);
this.#middleHandles = [];
var latlngs = this.getLatLngs()[0] as LatLng[];
if (this.getSelected() && latlngs.length >= 2) {
var lastLatLng: LatLng | null = null;
latlngs.concat([latlngs[0]]).forEach((latlng: LatLng, idx: number) => {
/* Add the polygon middle point handle (for adding new vertexes) */
if (lastLatLng != null) {
const handle1Point = getApp().getMap().latLngToLayerPoint(latlng);
const handle2Point = getApp().getMap().latLngToLayerPoint(lastLatLng);
const middlePoint = new Point(
(handle1Point.x + handle2Point.x) / 2,
(handle1Point.y + handle2Point.y) / 2
);
const middleLatLng = getApp()
.getMap()
.layerPointToLatLng(middlePoint);
const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng);
middleHandle.addTo(getApp().getMap());
middleHandle.on("click", (e: any) => {
this.#activeIndex = idx - 1;
this.addTemporaryLatLng(middleLatLng);
});
this.#middleHandles.push(middleHandle);
}
lastLatLng = latlng;
});
}
}
#registerCallbacks() {
this.on("click", (e: any) => {
getApp().getMap().deselectAllCoalitionAreas();
if (!this.getSelected()) {
this.setSelected(true);
}
});
this.on("contextmenu", (e: any) => {
if (!this.getEditing()) {
getApp().getMap().deselectAllCoalitionAreas();
this.setSelected(true);
} else this.setEditing(false);
});
}
}

View File

@@ -2,18 +2,20 @@ import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
export class CoalitionAreaHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, {interactive: true, draggable: true});
}
constructor(latlng: LatLng) {
super(latlng, { interactive: true, draggable: true });
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [24, 24],
iconAnchor: [12, 12],
className: "leaflet-coalitionarea-handle-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-coalitionarea-handle-icon");
this.getElement()?.appendChild(el);
}
}
createIcon() {
this.setIcon(
new DivIcon({
iconSize: [24, 24],
iconAnchor: [12, 12],
className: "leaflet-coalitionarea-handle-marker",
})
);
var el = document.createElement("div");
el.classList.add("ol-coalitionarea-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -2,18 +2,20 @@ import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
export class CoalitionAreaMiddleHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, {interactive: true, draggable: false});
}
constructor(latlng: LatLng) {
super(latlng, { interactive: true, draggable: false });
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [16, 16],
iconAnchor: [8, 8],
className: "leaflet-coalitionarea-middle-handle-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-coalitionarea-middle-handle-icon");
this.getElement()?.appendChild(el);
}
}
createIcon() {
this.setIcon(
new DivIcon({
iconSize: [16, 16],
iconAnchor: [8, 8],
className: "leaflet-coalitionarea-middle-handle-marker",
})
);
var el = document.createElement("div");
el.classList.add("ol-coalitionarea-middle-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -2,19 +2,21 @@ import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
export class DrawingCursor extends CustomMarker {
constructor() {
super(new LatLng(0, 0), {interactive: false})
this.setZIndexOffset(9999);
}
constructor() {
super(new LatLng(0, 0), { interactive: false });
this.setZIndexOffset(9999);
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [24, 24],
iconAnchor: [0, 24],
className: "leaflet-draw-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-draw-icon");
this.getElement()?.appendChild(el);
}
}
createIcon() {
this.setIcon(
new DivIcon({
iconSize: [24, 24],
iconAnchor: [0, 24],
className: "leaflet-draw-marker",
})
);
var el = document.createElement("div");
el.classList.add("ol-draw-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -1,49 +1,71 @@
import * as L from "leaflet"
import * as L from "leaflet";
export class DCSLayer extends L.TileLayer {
createTile(coords: L.Coords, done: L.DoneCallback) {
let newDone = (error?: Error, tile?: HTMLElement) => {
if (error === null && tile !== undefined && !tile.classList.contains('filtered')) {
// Create a canvas and set its width and height.
var canvas = document.createElement('canvas');
canvas.setAttribute('width', '256px');
canvas.setAttribute('height', '256px');
createTile(coords: L.Coords, done: L.DoneCallback) {
let newDone = (error?: Error, tile?: HTMLElement) => {
if (
error === null &&
tile !== undefined &&
!tile.classList.contains("filtered")
) {
// Create a canvas and set its width and height.
var canvas = document.createElement("canvas");
canvas.setAttribute("width", "256px");
canvas.setAttribute("height", "256px");
// Get the canvas drawing context, and draw the image to it.
var context = canvas.getContext('2d');
if (context) {
context.drawImage(tile as CanvasImageSource, 0, 0, canvas.width, canvas.height);
// Get the canvas drawing context, and draw the image to it.
var context = canvas.getContext("2d");
if (context) {
context.drawImage(
tile as CanvasImageSource,
0,
0,
canvas.width,
canvas.height
);
// Get the canvas image data.
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
// Get the canvas image data.
var imageData = context.getImageData(
0,
0,
canvas.width,
canvas.height
);
// Create a function for preserving a specified colour.
var makeTransparent = function(imageData: ImageData, color: {r: number, g: number, b: number}) {
// Get the pixel data from the source.
var data = imageData.data;
// Iterate through all the pixels.
for (var i = 0; i < data.length; i += 4) {
// Check if the current pixel should have preserved transparency. This simply compares whether the color we passed in is equivalent to our pixel data.
var convert = data[i] > color.r - 5 && data[i] < color.r + 5
&& data[i + 1] > color.g - 5 && data[i + 1] < color.g + 5
&& data[i + 2] > color.b - 5 && data[i + 2] < color.b + 5;
// Create a function for preserving a specified colour.
var makeTransparent = function (
imageData: ImageData,
color: { r: number; g: number; b: number }
) {
// Get the pixel data from the source.
var data = imageData.data;
// Iterate through all the pixels.
for (var i = 0; i < data.length; i += 4) {
// Check if the current pixel should have preserved transparency. This simply compares whether the color we passed in is equivalent to our pixel data.
var convert =
data[i] > color.r - 5 &&
data[i] < color.r + 5 &&
data[i + 1] > color.g - 5 &&
data[i + 1] < color.g + 5 &&
data[i + 2] > color.b - 5 &&
data[i + 2] < color.b + 5;
// Either preserve the initial transparency or set the transparency to 0.
data[i + 3] = convert ? 100: data[i + 3];
}
return imageData;
};
// Get the new pixel data and set it to the canvas context.
var newData = makeTransparent(imageData, {r: 26, g: 109, b: 127});
context.putImageData(newData, 0, 0);
(tile as HTMLImageElement).src = canvas.toDataURL();
tile.classList.add('filtered');
}
} else {
return done(error, tile);
// Either preserve the initial transparency or set the transparency to 0.
data[i + 3] = convert ? 100 : data[i + 3];
}
return imageData;
};
// Get the new pixel data and set it to the canvas context.
var newData = makeTransparent(imageData, { r: 26, g: 109, b: 127 });
context.putImageData(newData, 0, 0);
(tile as HTMLImageElement).src = canvas.toDataURL();
tile.classList.add("filtered");
}
return super.createTile(coords, newDone);
}
}
} else {
return done(error, tile);
}
};
return super.createTile(coords, newDone);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,23 +3,23 @@ import { MarkerOptions } from "leaflet";
import { LatLngExpression } from "leaflet";
export class CustomMarker extends Marker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
}
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
}
onAdd(map: Map): this {
this.setIcon(new DivIcon()); // Default empty icon
super.onAdd(map);
this.createIcon();
return this;
}
onAdd(map: Map): this {
this.setIcon(new DivIcon()); // Default empty icon
super.onAdd(map);
this.createIcon();
return this;
}
onRemove(map: Map): this {
super.onRemove(map);
return this;
}
onRemove(map: Map): this {
super.onRemove(map);
return this;
}
createIcon() {
/* Overloaded by child classes */
}
}
createIcon() {
/* Overloaded by child classes */
}
}

View File

@@ -2,18 +2,20 @@ import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
export class DestinationPreviewHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, {interactive: true, draggable: true});
}
constructor(latlng: LatLng) {
super(latlng, { interactive: true, draggable: true });
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [18, 18],
iconAnchor: [9, 9],
className: "leaflet-destination-preview-handle-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-destination-preview-handle-icon");
this.getElement()?.appendChild(el);
}
}
createIcon() {
this.setIcon(
new DivIcon({
iconSize: [18, 18],
iconAnchor: [9, 9],
className: "leaflet-destination-preview-handle-marker",
})
);
var el = document.createElement("div");
el.classList.add("ol-destination-preview-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -2,19 +2,21 @@ import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
export class DestinationPreviewMarker extends CustomMarker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
}
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 26],
className: "leaflet-destination-preview",
}));
var el = document.createElement("div");
el.classList.add("ol-destination-preview-icon");
this.getElement()?.appendChild(el);
}
createIcon() {
this.setIcon(
new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 26],
className: "leaflet-destination-preview",
})
);
var el = document.createElement("div");
el.classList.add("ol-destination-preview-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -4,28 +4,36 @@ import { SVGInjector } from "@tanem/svg-injector";
import { getApp } from "../../olympusapp";
export class SmokeMarker extends CustomMarker {
#color: string;
#color: string;
constructor(latlng: LatLngExpression, color: string, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
this.#color = color;
window.setTimeout(() => { this.removeFrom(getApp().getMap()); }, 300000) /* Remove the smoke after 5 minutes */
}
constructor(
latlng: LatLngExpression,
color: string,
options?: MarkerOptions
) {
super(latlng, options);
this.setZIndexOffset(9999);
this.#color = color;
window.setTimeout(() => {
this.removeFrom(getApp().getMap());
}, 300000); /* Remove the smoke after 5 minutes */
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 52],
className: "leaflet-smoke-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-smoke-icon");
el.setAttribute("data-color", this.#color);
var img = document.createElement("img");
img.src = "/resources/theme/images/markers/smoke.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
}
createIcon() {
this.setIcon(
new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 52],
className: "leaflet-smoke-marker",
})
);
var el = document.createElement("div");
el.classList.add("ol-smoke-icon");
el.setAttribute("data-color", this.#color);
var img = document.createElement("img");
img.src = "/resources/theme/images/markers/smoke.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
}
}

View File

@@ -1,27 +1,26 @@
.airbase-icon {
align-items: center;
cursor: pointer;
display: flex;
justify-content: center;
position: relative;
width: 40px;
height: 40px;
align-items: center;
cursor: pointer;
display: flex;
justify-content: center;
position: relative;
width: 40px;
height: 40px;
}
.airbase-icon svg {
width: 40px;
height: 40px;
width: 40px;
height: 40px;
}
.airbase-icon[data-coalition="red"] svg * {
stroke: var(--unit-background-red);
stroke: var(--unit-background-red);
}
.airbase-icon[data-coalition="blue"] svg * {
stroke: var(--unit-background-blue);
stroke: var(--unit-background-blue);
}
.airbase-icon[data-coalition="neutral"] svg * {
stroke: var(--unit-background-neutral);
stroke: var(--unit-background-neutral);
}

View File

@@ -1,24 +1,24 @@
.bullseye-icon {
align-items: center;
cursor: pointer;
display: flex;
justify-content: center;
position: relative;
width: 100%;
height: 100%;
align-items: center;
cursor: pointer;
display: flex;
justify-content: center;
position: relative;
width: 100%;
height: 100%;
}
.bullseye-icon[data-coalition="red"] svg * {
stroke: var(--unit-background-red);
fill: var(--unit-background-red);
stroke: var(--unit-background-red);
fill: var(--unit-background-red);
}
.bullseye-icon[data-coalition="blue"] svg * {
stroke: var(--unit-background-blue);
fill: var(--unit-background-blue);
stroke: var(--unit-background-blue);
fill: var(--unit-background-blue);
}
.bullseye-icon[data-coalition="neutral"] svg * {
stroke: var(--unit-background-neutral);
fill: var(--unit-background-neutral);
stroke: var(--unit-background-neutral);
fill: var(--unit-background-neutral);
}

View File

@@ -1,212 +1,213 @@
/*** Unit marker elements ***/
[data-object|="unit"] {
align-items: center;
cursor: pointer;
display: flex;
height: 100%;
justify-content: center;
position: relative;
width: 100%;
align-items: center;
cursor: pointer;
display: flex;
height: 100%;
justify-content: center;
position: relative;
width: 100%;
}
.unit-vvi {
align-self: center;
background: var(--secondary-gunmetal-grey);
display: flex;
justify-self: center;
padding-bottom: calc((var(--unit-width) / 2) + var(--unit-stroke-width));
position: absolute;
transform-origin: bottom;
translate: 0 -50%;
width: var(--unit-vvi-width);
align-self: center;
background: var(--secondary-gunmetal-grey);
display: flex;
justify-self: center;
padding-bottom: calc((var(--unit-width) / 2) + var(--unit-stroke-width));
position: absolute;
transform-origin: bottom;
translate: 0 -50%;
width: var(--unit-vvi-width);
}
.unit-hotgroup {
align-content: center;
background-color: var(--background-steel);
border-radius: var(--border-radius-xs);
display: none;
height: 15px;
justify-content: center;
position: absolute;
transform: rotate(-45deg);
translate: 0 -200%;
width: 15px;
align-content: center;
background-color: var(--background-steel);
border-radius: var(--border-radius-xs);
display: none;
height: 15px;
justify-content: center;
position: absolute;
transform: rotate(-45deg);
translate: 0 -200%;
width: 15px;
}
.unit-hotgroup-id {
background-color: transparent;
color: white;
font-size: 9px;
font-weight: bolder;
transform: rotate(45deg);
translate: -1px 1px;
background-color: transparent;
color: white;
font-size: 9px;
font-weight: bolder;
transform: rotate(45deg);
translate: -1px 1px;
}
.unit-icon {
height: var(--unit-height);
position: absolute;
transform-origin: center;
width: var(--unit-width);
height: var(--unit-height);
position: absolute;
transform-origin: center;
width: var(--unit-width);
}
.unit-icon svg {
height: 100%;
width: 100%;
height: 100%;
width: 100%;
}
[data-is-selected] .unit-icon::before {
background-color: var(--unit-spotlight-fill);
border-radius: 50%;
content: "";
height: 100%;
position: absolute;
width: 100%;
z-index: -1;
background-color: var(--unit-spotlight-fill);
border-radius: 50%;
content: "";
height: 100%;
position: absolute;
width: 100%;
z-index: -1;
}
/*** Basic colours ***/
[data-coalition="blue"] .unit-icon svg>*:first-child {
fill: var(--unit-background-blue);
[data-coalition="blue"] .unit-icon svg > *:first-child {
fill: var(--unit-background-blue);
}
[data-coalition="red"] .unit-icon svg>*:first-child {
fill: var(--unit-background-red);
[data-coalition="red"] .unit-icon svg > *:first-child {
fill: var(--unit-background-red);
}
[data-coalition="neutral"] .unit-icon svg>*:first-child {
fill: var(--unit-background-neutral);
[data-coalition="neutral"] .unit-icon svg > *:first-child {
fill: var(--unit-background-neutral);
}
[data-is-selected] .unit-icon svg>*:first-child {
fill: white;
[data-is-selected] .unit-icon svg > *:first-child {
fill: white;
}
[data-is-highlighted] .unit-icon svg>*:first-child {
stroke: white;
[data-is-highlighted] .unit-icon svg > *:first-child {
stroke: white;
}
/*** Cursors ***/
[data-is-dead],
[data-object|="unit-missile"],
[data-object|="unit-bomb"] {
cursor: default;
cursor: default;
}
/*** Labels ***/
[data-object|="unit"] .unit-short-label {
color: var(--secondary-gunmetal-grey);
font-size: var(--unit-font-size);
font-weight: var(--unit-font-weight);
line-height: normal;
position: absolute;
color: var(--secondary-gunmetal-grey);
font-size: var(--unit-font-size);
font-weight: var(--unit-font-weight);
line-height: normal;
position: absolute;
}
[data-object|="unit-groundunit"] .unit-short-label {
transform: translateY(7px);
transform: translateY(7px);
}
/*** Health indicator ***/
[data-object|="unit"] .unit-health {
background: white;
border: var(--unit-health-border-width) solid var(--secondary-dark-steel);
border-radius: var(--border-radius-sm);
display: none;
height: var(--unit-health-height);
position: absolute;
translate: var(--unit-health-x) var(--unit-health-y);
width: var(--unit-health-width);
background: white;
border: var(--unit-health-border-width) solid var(--secondary-dark-steel);
border-radius: var(--border-radius-sm);
display: none;
height: var(--unit-health-height);
position: absolute;
translate: var(--unit-health-x) var(--unit-health-y);
width: var(--unit-health-width);
}
/*** Fuel indicator ***/
[data-object|="unit"] .unit-fuel {
background: white;
border: var(--unit-fuel-border-width) solid var(--secondary-dark-steel);
border-radius: var(--border-radius-sm);
display: none;
height: var(--unit-fuel-height);
position: absolute;
translate: var(--unit-fuel-x) var(--unit-fuel-y);
width: var(--unit-fuel-width);
background: white;
border: var(--unit-fuel-border-width) solid var(--secondary-dark-steel);
border-radius: var(--border-radius-sm);
display: none;
height: var(--unit-fuel-height);
position: absolute;
translate: var(--unit-fuel-x) var(--unit-fuel-y);
width: var(--unit-fuel-width);
}
[data-object|="unit"] .unit-fuel-level,
[data-object|="unit"] .unit-health-level {
background-color: var(--secondary-light-grey);
height: 100%;
width: 100%;
background-color: var(--secondary-light-grey);
height: 100%;
width: 100%;
}
/*** Ammo indicator ***/
[data-object|="unit"] .unit-ammo {
column-gap: var(--unit-ammo-spacing);
display: none;
height: fit-content;
position: absolute;
translate: var(--unit-ammo-x) var(--unit-ammo-y);
width: fit-content;
column-gap: var(--unit-ammo-spacing);
display: none;
height: fit-content;
position: absolute;
translate: var(--unit-ammo-x) var(--unit-ammo-y);
width: fit-content;
}
[data-object|="unit"] .unit-ammo>* {
background-color: white;
border: var(--unit-ammo-border-width) solid var(--secondary-dark-steel);
border-radius: 50%;
padding: var(--unit-ammo-radius);
[data-object|="unit"] .unit-ammo > * {
background-color: white;
border: var(--unit-ammo-border-width) solid var(--secondary-dark-steel);
border-radius: 50%;
padding: var(--unit-ammo-radius);
}
/*** Unit summary ***/
[data-object|="unit"] .unit-summary {
color: white;
column-gap: 6px;
display: flex;
flex-wrap: wrap;
font-size: 11px;
font-weight: bold;
justify-content: right;
line-height: 12px;
pointer-events: none;
position: absolute;
row-gap: 1px;
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
right: 100%;
width: fit-content;
color: white;
column-gap: 6px;
display: flex;
flex-wrap: wrap;
font-size: 11px;
font-weight: bold;
justify-content: right;
line-height: 12px;
pointer-events: none;
position: absolute;
row-gap: 1px;
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000;
right: 100%;
width: fit-content;
}
[data-hide-labels] [data-object|="unit"] .unit-summary {
display: none;
display: none;
}
[data-object|="unit"] .unit-summary>* {
padding: 1px;
[data-object|="unit"] .unit-summary > * {
padding: 1px;
}
[data-object|="unit"] .unit-summary .unit-callsign {
color: white;
overflow: hidden;
text-align: right;
transform-origin: right;
white-space: nowrap;
width: 80px;
color: white;
overflow: hidden;
text-align: right;
transform-origin: right;
white-space: nowrap;
width: 80px;
}
[data-object|="unit"]:hover .unit-summary .unit-callsign{
direction: rtl;
overflow: visible;
[data-object|="unit"]:hover .unit-summary .unit-callsign {
direction: rtl;
overflow: visible;
}
/*** Common ***/
[data-object|="unit"]:hover .unit-ammo,
[data-object|="unit"]:hover .unit-health ,
[data-object|="unit"]:hover .unit-health,
[data-object|="unit"]:hover .unit-fuel {
display: flex;
display: flex;
}
[data-object|="unit"][data-has-low-fuel] .unit-fuel, [data-object|="unit"][data-has-low-health] .unit-health {
animation: pulse 1.5s linear infinite;
[data-object|="unit"][data-has-low-fuel] .unit-fuel,
[data-object|="unit"][data-has-low-health] .unit-health {
animation: pulse 1.5s linear infinite;
}
[data-object|="unit"][data-is-in-hotgroup] .unit-hotgroup,
@@ -214,147 +215,163 @@
[data-object|="unit"][data-is-selected] .unit-fuel,
[data-object|="unit"][data-is-selected] .unit-health,
[data-object|="unit"][data-is-selected] .unit-selected-spotlight {
display: flex;
display: flex;
}
[data-object|="unit"][data-has-fox-1] .unit-ammo>div:nth-child(1),
[data-object|="unit"][data-has-fox-2] .unit-ammo>div:nth-child(2),
[data-object|="unit"][data-has-fox-3] .unit-ammo>div:nth-child(3),
[data-object|="unit"][data-has-other-ammo] .unit-ammo>div:nth-child(4) {
background-color: var(--secondary-gunmetal-grey);
[data-object|="unit"][data-has-fox-1] .unit-ammo > div:nth-child(1),
[data-object|="unit"][data-has-fox-2] .unit-ammo > div:nth-child(2),
[data-object|="unit"][data-has-fox-3] .unit-ammo > div:nth-child(3),
[data-object|="unit"][data-has-other-ammo] .unit-ammo > div:nth-child(4) {
background-color: var(--secondary-gunmetal-grey);
}
[data-object|="unit"][data-coalition="blue"][data-is-selected] .unit-short-label {
color: var(--secondary-blue-text);
[data-object|="unit"][data-coalition="blue"][data-is-selected]
.unit-short-label {
color: var(--secondary-blue-text);
}
[data-object|="unit"][data-coalition="blue"] .unit-fuel-level,
[data-object|="unit"][data-coalition="blue"] .unit-health-level,
[data-object|="unit"][data-coalition="blue"][data-has-fox-1] .unit-ammo>div:nth-child(1),
[data-object|="unit"][data-coalition="blue"][data-has-fox-2] .unit-ammo>div:nth-child(2),
[data-object|="unit"][data-coalition="blue"][data-has-fox-3] .unit-ammo>div:nth-child(3),
[data-object|="unit"][data-coalition="blue"][data-has-other-ammo] .unit-ammo>div:nth-child(4) {
background-color: var(--primary-blue);
[data-object|="unit"][data-coalition="blue"][data-has-fox-1]
.unit-ammo
> div:nth-child(1),
[data-object|="unit"][data-coalition="blue"][data-has-fox-2]
.unit-ammo
> div:nth-child(2),
[data-object|="unit"][data-coalition="blue"][data-has-fox-3]
.unit-ammo
> div:nth-child(3),
[data-object|="unit"][data-coalition="blue"][data-has-other-ammo]
.unit-ammo
> div:nth-child(4) {
background-color: var(--primary-blue);
}
[data-object|="unit"][data-coalition="blue"] .unit-vvi {
background-color: var(--secondary-blue-outline);
background-color: var(--secondary-blue-outline);
}
[data-object|="unit"][data-coalition="red"][data-is-selected] .unit-short-label {
color: var(--secondary-red-text);
[data-object|="unit"][data-coalition="red"][data-is-selected]
.unit-short-label {
color: var(--secondary-red-text);
}
[data-object|="unit"][data-coalition="red"] .unit-fuel-level,
[data-object|="unit"][data-coalition="red"] .unit-health-level,
[data-object|="unit"][data-coalition="red"][data-has-fox-1] .unit-ammo>div:nth-child(1),
[data-object|="unit"][data-coalition="red"][data-has-fox-2] .unit-ammo>div:nth-child(2),
[data-object|="unit"][data-coalition="red"][data-has-fox-3] .unit-ammo>div:nth-child(3),
[data-object|="unit"][data-coalition="red"][data-has-other-ammo] .unit-ammo>div:nth-child(4) {
background-color: var(--primary-red);
[data-object|="unit"][data-coalition="red"][data-has-fox-1]
.unit-ammo
> div:nth-child(1),
[data-object|="unit"][data-coalition="red"][data-has-fox-2]
.unit-ammo
> div:nth-child(2),
[data-object|="unit"][data-coalition="red"][data-has-fox-3]
.unit-ammo
> div:nth-child(3),
[data-object|="unit"][data-coalition="red"][data-has-other-ammo]
.unit-ammo
> div:nth-child(4) {
background-color: var(--primary-red);
}
[data-object|="unit"][data-coalition="blue"] .unit-vvi {
background-color: var(--secondary-red-outline);
background-color: var(--secondary-red-outline);
}
/*** Unit state ***/
[data-object|="unit"] .unit-state {
background-repeat: no-repeat;
height: 20px;
position: absolute;
width: 20px;
left: 0px;
top: 0px;
background-repeat: no-repeat;
height: 20px;
position: absolute;
width: 20px;
left: 0px;
top: 0px;
}
[data-object|="unit"][data-state="rtb"] .unit-state {
background-image: url("/resources/theme/images/states/rtb.svg");
background-image: url("/resources/theme/images/states/rtb.svg");
}
[data-object|="unit"][data-state="land"] .unit-state {
background-image: url("/resources/theme/images/states/rtb.svg");
background-image: url("/resources/theme/images/states/rtb.svg");
}
[data-object|="unit"][data-state="idle"] .unit-state {
background-image: url("/resources/theme/images/states/idle.svg");
background-image: url("/resources/theme/images/states/idle.svg");
}
[data-object*="groundunit"][data-state="idle"] .unit-state,
[data-object*="navyunit"][data-state="idle"] .unit-state {
background-image: url(""); /* To avoid clutter, dont show the idle state for non flying units */
background-image: url(""); /* To avoid clutter, dont show the idle state for non flying units */
}
[data-object|="unit"][data-state="attack"] .unit-state,
[data-object|="unit"][data-state="bomb-point"] .unit-state,
[data-object|="unit"][data-state="carpet-bombing"] .unit-state,
[data-object|="unit"][data-state="fire-at-area"] .unit-state {
background-image: url("/resources/theme/images/states/attack.svg");
background-image: url("/resources/theme/images/states/attack.svg");
}
[data-object|="unit"][data-state="follow"] .unit-state {
background-image: url("/resources/theme/images/states/follow.svg");
background-image: url("/resources/theme/images/states/follow.svg");
}
[data-object|="unit"][data-state="refuel"] .unit-state {
background-image: url("/resources/theme/images/states/refuel.svg");
background-image: url("/resources/theme/images/states/refuel.svg");
}
[data-object|="unit"][data-state="human"] .unit-state {
background-image: url("/resources/theme/images/states/human.svg");
background-image: url("/resources/theme/images/states/human.svg");
}
[data-object|="unit"][data-state="dcs"] .unit-state {
background-image: url("/resources/theme/images/states/dcs.svg");
background-image: url("/resources/theme/images/states/dcs.svg");
}
[data-object|="unit"][data-state="land-at-point"] .unit-state {
background-image: url("/resources/theme/images/states/land-at-point.svg");
background-image: url("/resources/theme/images/states/land-at-point.svg");
}
[data-object|="unit"][data-state="no-task"] .unit-state {
background-image: url("/resources/theme/images/states/no-task.svg");
background-image: url("/resources/theme/images/states/no-task.svg");
}
[data-object|="unit"][data-state="off"] .unit-state {
background-image: url("/resources/theme/images/states/off.svg");
background-image: url("/resources/theme/images/states/off.svg");
}
[data-object|="unit"][data-state="tanker"] .unit-state {
background-image: url("/resources/theme/images/states/tanker.svg");
background-image: url("/resources/theme/images/states/tanker.svg");
}
[data-object|="unit"][data-state="AWACS"] .unit-state {
background-image: url("/resources/theme/images/states/awacs.svg");
background-image: url("/resources/theme/images/states/awacs.svg");
}
[data-object|="unit"][data-state="miss-on-purpose"] .unit-state {
background-image: url("/resources/theme/images/states/miss-on-purpose.svg");
background-image: url("/resources/theme/images/states/miss-on-purpose.svg");
}
[data-object|="unit"][data-state="scenic-aaa"] .unit-state {
background-image: url("/resources/theme/images/states/scenic-aaa.svg");
background-image: url("/resources/theme/images/states/scenic-aaa.svg");
}
[data-object|="unit"][data-state="simulate-fire-fight"] .unit-state {
background-image: url("/resources/theme/images/states/simulate-fire-fight.svg");
background-image: url("/resources/theme/images/states/simulate-fire-fight.svg");
}
[data-object|="unit"] .unit-health::before {
background-image: url("/resources/theme/images/icons/health.svg");
background-repeat: no-repeat;
background-size: contain;
content: " ";
height: 6px;
left: 0;
position: absolute;
top: 0;
translate: -10px -2px;
width: 6px;
background-image: url("/resources/theme/images/icons/health.svg");
background-repeat: no-repeat;
background-size: contain;
content: " ";
height: 6px;
left: 0;
position: absolute;
top: 0;
translate: -10px -2px;
width: 6px;
}
/*** Dead unit ***/
[data-object|="unit"][data-is-dead] .unit-selected-spotlight,
[data-object|="unit"][data-is-dead] .unit-short-label,
@@ -367,17 +384,17 @@
[data-object|="unit"][data-is-dead] .unit-ammo,
[data-object|="unit"][data-is-dead]:hover .unit-fuel,
[data-object|="unit"][data-is-dead]:hover .unit-ammo {
display: none;
display: none;
}
[data-object|="unit"][data-is-dead] .unit-summary>* {
display: none;
[data-object|="unit"][data-is-dead] .unit-summary > * {
display: none;
}
[data-object|="unit"][data-is-dead] .unit-summary .unit-callsign {
display: block;
display: block;
}
.ol-temporary-marker {
opacity: 0.5;
}
opacity: 0.5;
}

View File

@@ -2,19 +2,21 @@ import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
export class TargetMarker extends CustomMarker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
}
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 26],
className: "leaflet-target-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-target-icon");
this.getElement()?.appendChild(el);
}
createIcon() {
this.setIcon(
new DivIcon({
iconSize: [52, 52],
iconAnchor: [26, 26],
className: "leaflet-target-marker",
})
);
var el = document.createElement("div");
el.classList.add("ol-target-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -1,76 +1,87 @@
import { CustomMarker } from "./custommarker";
import { DivIcon, LatLng } from "leaflet";
import { SVGInjector } from "@tanem/svg-injector";
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../../other/utils";
import {
getMarkerCategoryByName,
getUnitDatabaseByCategory,
} from "../../other/utils";
import { getApp } from "../../olympusapp";
export class TemporaryUnitMarker extends CustomMarker {
#name: string;
#coalition: string;
#commandHash: string|undefined = undefined;
#timer: number = 0;
#name: string;
#coalition: string;
#commandHash: string | undefined = undefined;
#timer: number = 0;
constructor(latlng: LatLng, name: string, coalition: string, commandHash?: string) {
super(latlng, {interactive: false});
this.#name = name;
this.#coalition = coalition;
this.#commandHash = commandHash;
constructor(
latlng: LatLng,
name: string,
coalition: string,
commandHash?: string
) {
super(latlng, { interactive: false });
this.#name = name;
this.#coalition = coalition;
this.#commandHash = commandHash;
if (commandHash !== undefined)
this.setCommandHash(commandHash)
}
if (commandHash !== undefined) this.setCommandHash(commandHash);
}
setCommandHash(commandHash: string) {
this.#commandHash = commandHash;
this.#timer = window.setInterval(() => {
if (this.#commandHash !== undefined) {
getApp().getServerManager().isCommandExecuted((res: any) => {
if (res.commandExecuted) {
this.removeFrom(getApp().getMap());
window.clearInterval(this.#timer);
}
}, this.#commandHash)
setCommandHash(commandHash: string) {
this.#commandHash = commandHash;
this.#timer = window.setInterval(() => {
if (this.#commandHash !== undefined) {
getApp()
.getServerManager()
.isCommandExecuted((res: any) => {
if (res.commandExecuted) {
this.removeFrom(getApp().getMap());
window.clearInterval(this.#timer);
}
}, 1000);
}, this.#commandHash);
}
}, 1000);
}
createIcon() {
const category = getMarkerCategoryByName(this.#name);
const databaseEntry = getUnitDatabaseByCategory(category)?.getByName(
this.#name
);
/* Set the icon */
var icon = new DivIcon({
className: "leaflet-unit-icon",
iconAnchor: [25, 25],
iconSize: [50, 50],
});
this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("unit");
el.setAttribute("data-object", `unit-${category}`);
el.setAttribute("data-coalition", this.#coalition);
// Main icon
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
img.src = `/resources/theme/images/units/${databaseEntry?.markerFile ?? category}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", false);
el.append(unitIcon);
// Short label
if (category == "aircraft" || category == "helicopter") {
var shortLabel = document.createElement("div");
shortLabel.classList.add("unit-short-label");
shortLabel.innerText = databaseEntry?.shortLabel || "";
el.append(shortLabel);
}
createIcon() {
const category = getMarkerCategoryByName(this.#name);
const databaseEntry = getUnitDatabaseByCategory(category)?.getByName(this.#name);
/* Set the icon */
var icon = new DivIcon({
className: 'leaflet-unit-icon',
iconAnchor: [25, 25],
iconSize: [50, 50],
});
this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("unit");
el.setAttribute("data-object", `unit-${category}`);
el.setAttribute("data-coalition", this.#coalition);
// Main icon
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
img.src = `/resources/theme/images/units/${databaseEntry?.markerFile ?? category}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", false);
el.append(unitIcon);
// Short label
if (category == "aircraft" || category == "helicopter") {
var shortLabel = document.createElement("div");
shortLabel.classList.add("unit-short-label");
shortLabel.innerText = databaseEntry?.shortLabel || "";
el.append(shortLabel);
}
this.getElement()?.appendChild(el);
this.getElement()?.classList.add("ol-temporary-marker");
}
}
this.getElement()?.appendChild(el);
this.getElement()?.classList.add("ol-temporary-marker");
}
}

View File

@@ -1,7 +1,7 @@
// @ts-nocheck
// This is a horrible hack. But it is needed at the moment to ovveride a default behaviour of Leaflet. TODO please fix me the proper way.
import { Circle, Point, Polyline } from 'leaflet';
import { Circle, Point, Polyline } from "leaflet";
/**
* This custom Circle object implements a faster render method for very big circles. When zoomed in, the default ctx.arc method
@@ -10,47 +10,51 @@ import { Circle, Point, Polyline } from 'leaflet';
* A more refined version using arcs could be implemented but this works good enough.
*/
export class RangeCircle extends Circle {
_updatePath() {
if (!this._renderer._drawing || this._empty()) { return; }
var p = this._point,
ctx = this._renderer._ctx,
r = Math.max(Math.round(this._radius), 1),
s = (Math.max(Math.round(this._radiusY), 1) || r) / r;
if (s !== 1) {
ctx.save();
ctx.scale(1, s);
}
let pathBegun = false;
let dtheta = Math.PI * 2 / 120;
for (let theta = 0; theta <= Math.PI * 2; theta += dtheta) {
let p1 = new Point(p.x + r * Math.cos(theta), p.y / s + r * Math.sin(theta));
let p2 = new Point(p.x + r * Math.cos(theta + dtheta), p.y / s + r * Math.sin(theta + dtheta));
let l1 = this._map.layerPointToLatLng(p1);
let l2 = this._map.layerPointToLatLng(p2);
let line = new Polyline([l1, l2]);
if (this._map.getBounds().intersects(line.getBounds())) {
if (!pathBegun) {
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
pathBegun = true;
}
ctx.lineTo(p2.x, p2.y);
}
else {
if (pathBegun) {
this._renderer._fillStroke(ctx, this);
pathBegun = false;
}
}
}
if (pathBegun)
this._renderer._fillStroke(ctx, this);
if (s !== 1)
ctx.restore();
_updatePath() {
if (!this._renderer._drawing || this._empty()) {
return;
}
}
var p = this._point,
ctx = this._renderer._ctx,
r = Math.max(Math.round(this._radius), 1),
s = (Math.max(Math.round(this._radiusY), 1) || r) / r;
if (s !== 1) {
ctx.save();
ctx.scale(1, s);
}
let pathBegun = false;
let dtheta = (Math.PI * 2) / 120;
for (let theta = 0; theta <= Math.PI * 2; theta += dtheta) {
let p1 = new Point(
p.x + r * Math.cos(theta),
p.y / s + r * Math.sin(theta)
);
let p2 = new Point(
p.x + r * Math.cos(theta + dtheta),
p.y / s + r * Math.sin(theta + dtheta)
);
let l1 = this._map.layerPointToLatLng(p1);
let l2 = this._map.layerPointToLatLng(p2);
let line = new Polyline([l1, l2]);
if (this._map.getBounds().intersects(line.getBounds())) {
if (!pathBegun) {
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
pathBegun = true;
}
ctx.lineTo(p2.x, p2.y);
} else {
if (pathBegun) {
this._renderer._fillStroke(ctx, this);
pathBegun = false;
}
}
}
if (pathBegun) this._renderer._fillStroke(ctx, this);
if (s !== 1) ctx.restore();
}
}

View File

@@ -1,99 +1,99 @@
:root {
/** Colours **/
/** Colours **/
/*** Coalition: neutral ***/
--primary-neutral: #949ba7;
--secondary-neutral-outline: #111111;
--secondary-neutral-text: #111111;
--unit-background-neutral: #CFD9E8;
/*** Coalition: neutral ***/
--primary-neutral: #949ba7;
--secondary-neutral-outline: #111111;
--secondary-neutral-text: #111111;
--unit-background-neutral: #cfd9e8;
/*** Coalition: blue ***/
--primary-blue: #247be2;
--secondary-blue-outline: #082e44;
--secondary-blue-text: #017DC1;
--unit-background-blue: #3BB9FF;
/*** Coalition: blue ***/
--primary-blue: #247be2;
--secondary-blue-outline: #082e44;
--secondary-blue-text: #017dc1;
--unit-background-blue: #3bb9ff;
/*** Coalition: red ***/
--primary-red: #ff5858;
--secondary-red-outline: #262222;
--secondary-red-text: #D42121;
--unit-background-red: #FF5858;
/*** Coalition: red ***/
--primary-red: #ff5858;
--secondary-red-outline: #262222;
--secondary-red-text: #d42121;
--unit-background-red: #ff5858;
/*** UI Colours **/
--accent-amber: #ffd828;
--accent-green: #8bff63;
--accent-light-blue: #5ca7ff;
--accent-dark-blue: #017DC1;
--transparent-accent-light-blue: rgba(92, 167, 255, .33);
--accent-light-red: #F5B6B6;
/*** UI Colours **/
--accent-amber: #ffd828;
--accent-green: #8bff63;
--accent-light-blue: #5ca7ff;
--accent-dark-blue: #017dc1;
--transparent-accent-light-blue: rgba(92, 167, 255, 0.33);
--accent-light-red: #f5b6b6;
--background-grey: #3d4651;
--background-dark-grey: #35393d;
--background-slate-blue: #363c43;
--background-offwhite: #f2f2f3;
--background-steel: #202831;
--background-grey: #3d4651;
--background-dark-grey: #35393d;
--background-slate-blue: #363c43;
--background-offwhite: #f2f2f3;
--background-steel: #202831;
--secondary-dark-steel: #181e25;
--secondary-gunmetal-grey: #2f2f2f;
--secondary-lighter-grey: #949ba7;
--secondary-light-grey: #797e83;
--secondary-semitransparent-white: #FFFFFFAA;
--secondary-transparent-white: #FFFFFF30;
--secondary-yellow: #ffd46893;
--secondary-dark-steel: #181e25;
--secondary-gunmetal-grey: #2f2f2f;
--secondary-lighter-grey: #949ba7;
--secondary-light-grey: #797e83;
--secondary-semitransparent-white: #ffffffaa;
--secondary-transparent-white: #ffffff30;
--secondary-yellow: #ffd46893;
--background-hover: #f2f2f333;
--background-hover: #f2f2f333;
--nav-text: #ECECEC;
--nav-text: #ececec;
--ol-select-secondary: #545F6C;
--ol-switch-off:#686868;
--ol-switch-undefined:#383838;
--ol-select-secondary: #545f6c;
--ol-switch-off: #686868;
--ol-switch-undefined: #383838;
--ol-dialog-disabled-text-color: #ffffff20;
--ol-dialog-disabled-text-color: #ffffff20;
/*** General border radii **/
--border-radius-xs: 2px;
--border-radius-sm: 5px;
--border-radius-md: 10px;
--border-radius-lg: 15px;
/*** General border radii **/
--border-radius-xs: 2px;
--border-radius-sm: 5px;
--border-radius-md: 10px;
--border-radius-lg: 15px;
/*** Fonts **/
--font-weight-bolder: 600;
/*** Fonts **/
--font-weight-bolder: 600;
/*** Unit marker settings ***/
/*** All markers **/
--unit-border-radius: var(--border-radius-xs);
--unit-font-size: 14px;
--unit-font-weight: bolder;
--unit-label-border-width: 2px;
--unit-spotlight-fill: var(--secondary-yellow);
--unit-spotlight-radius: 26px;
--unit-stroke-width: 3px;
--unit-height: 50px;
--unit-width: 50px;
/*** Unit marker settings ***/
/*** All markers **/
--unit-border-radius: var(--border-radius-xs);
--unit-font-size: 14px;
--unit-font-weight: bolder;
--unit-label-border-width: 2px;
--unit-spotlight-fill: var(--secondary-yellow);
--unit-spotlight-radius: 26px;
--unit-stroke-width: 3px;
--unit-height: 50px;
--unit-width: 50px;
--unit-health-border-width: 2px;
--unit-health-height: 6px;
--unit-health-width: 36px;
--unit-health-x: 0px;
--unit-health-y: 26px;
--unit-health-border-width: 2px;
--unit-health-height: 6px;
--unit-health-width: 36px;
--unit-health-x: 0px;
--unit-health-y: 26px;
/*** Air units ***/
--unit-ammo-gap: calc(2px + var(--unit-stroke-width));
--unit-ammo-border-radius: 50%;
--unit-ammo-border-width: 2px;
--unit-ammo-radius: 2px;
--unit-ammo-spacing: 2px;
--unit-ammo-x: 0px;
--unit-ammo-y: 30px;
--unit-fuel-border-width: 2px;
--unit-fuel-height: 6px;
--unit-fuel-width: 36px;
--unit-fuel-x: 0px;
--unit-fuel-y: 22px;
--unit-vvi-width: 4px;
/*** Air units ***/
--unit-ammo-gap: calc(2px + var(--unit-stroke-width));
--unit-ammo-border-radius: 50%;
--unit-ammo-border-width: 2px;
--unit-ammo-radius: 2px;
--unit-ammo-spacing: 2px;
--unit-ammo-x: 0px;
--unit-ammo-y: 30px;
--unit-fuel-border-width: 2px;
--unit-fuel-height: 6px;
--unit-fuel-width: 36px;
--unit-fuel-x: 0px;
--unit-fuel-y: 22px;
--unit-vvi-width: 4px;
}
* {
font-weight:600;
font-weight: 600;
}

View File

@@ -1,136 +1,149 @@
import { Map, Point } from 'leaflet';
import { Handler } from 'leaflet';
import { Util } from 'leaflet';
import { DomUtil } from 'leaflet';
import { DomEvent } from 'leaflet';
import { LatLngBounds } from 'leaflet';
import { Bounds } from 'leaflet';
import { Map, Point } from "leaflet";
import { Handler } from "leaflet";
import { Util } from "leaflet";
import { DomUtil } from "leaflet";
import { DomEvent } from "leaflet";
import { LatLngBounds } from "leaflet";
import { Bounds } from "leaflet";
export var TouchBoxSelect = Handler.extend({
initialize: function (map: Map) {
this._map = map;
this._container = map.getContainer();
this._pane = map.getPanes().overlayPane;
this._resetStateTimeout = 0;
this._doubleClicked = false;
map.on('unload', this._destroy, this);
},
initialize: function (map: Map) {
this._map = map;
this._container = map.getContainer();
this._pane = map.getPanes().overlayPane;
this._resetStateTimeout = 0;
this._doubleClicked = false;
map.on("unload", this._destroy, this);
},
addHooks: function () {
DomEvent.on(this._container, 'touchstart', this._onMouseDown, this);
},
addHooks: function () {
DomEvent.on(this._container, "touchstart", this._onMouseDown, this);
},
removeHooks: function () {
DomEvent.off(this._container, 'touchstart', this._onMouseDown, this);
},
removeHooks: function () {
DomEvent.off(this._container, "touchstart", this._onMouseDown, this);
},
moved: function () {
return this._moved;
},
moved: function () {
return this._moved;
},
_destroy: function () {
DomUtil.remove(this._pane);
delete this._pane;
},
_destroy: function () {
DomUtil.remove(this._pane);
delete this._pane;
},
_resetState: function () {
this._resetStateTimeout = 0;
this._moved = false;
},
_resetState: function () {
this._resetStateTimeout = 0;
this._moved = false;
},
_clearDeferredResetState: function () {
if (this._resetStateTimeout !== 0) {
clearTimeout(this._resetStateTimeout);
this._resetStateTimeout = 0;
}
},
_clearDeferredResetState: function () {
if (this._resetStateTimeout !== 0) {
clearTimeout(this._resetStateTimeout);
this._resetStateTimeout = 0;
}
},
_onMouseDown: function (e: any) {
if ((e.which == 0)) {
this._map.fire('selectionstart');
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();
this._resetState();
_onMouseDown: function (e: any) {
if (e.which == 0) {
this._map.fire("selectionstart");
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();
this._resetState();
DomUtil.disableTextSelection();
DomUtil.disableImageDrag();
DomUtil.disableTextSelection();
DomUtil.disableImageDrag();
this._startPoint = this._getMousePosition(e);
this._startPoint = this._getMousePosition(e);
//@ts-ignore
DomEvent.on(document, {
contextmenu: DomEvent.stop,
touchmove: this._onMouseMove,
touchend: this._onMouseUp
}, this);
} else {
return false;
}
},
//@ts-ignore
DomEvent.on(
document,
{
contextmenu: DomEvent.stop,
touchmove: this._onMouseMove,
touchend: this._onMouseUp,
},
this
);
} else {
return false;
}
},
_onMouseMove: function (e: any) {
if (!this._moved) {
this._moved = true;
_onMouseMove: function (e: any) {
if (!this._moved) {
this._moved = true;
this._box = DomUtil.create('div', 'leaflet-zoom-box', this._container);
DomUtil.addClass(this._container, 'leaflet-crosshair');
}
this._box = DomUtil.create("div", "leaflet-zoom-box", this._container);
DomUtil.addClass(this._container, "leaflet-crosshair");
}
this._point = this._getMousePosition(e);
this._point = this._getMousePosition(e);
var bounds = new Bounds(this._point, this._startPoint),
size = bounds.getSize();
var bounds = new Bounds(this._point, this._startPoint),
size = bounds.getSize();
if (bounds.min != undefined)
DomUtil.setPosition(this._box, bounds.min);
if (bounds.min != undefined) DomUtil.setPosition(this._box, bounds.min);
this._box.style.width = size.x + 'px';
this._box.style.height = size.y + 'px';
},
this._box.style.width = size.x + "px";
this._box.style.height = size.y + "px";
},
_finish: function () {
if (this._moved) {
DomUtil.remove(this._box);
DomUtil.removeClass(this._container, 'leaflet-crosshair');
}
_finish: function () {
if (this._moved) {
DomUtil.remove(this._box);
DomUtil.removeClass(this._container, "leaflet-crosshair");
}
DomUtil.enableTextSelection();
DomUtil.enableImageDrag();
DomUtil.enableTextSelection();
DomUtil.enableImageDrag();
//@ts-ignore
DomEvent.off(document, {
contextmenu: DomEvent.stop,
touchmove: this._onMouseMove,
touchend: this._onMouseUp
}, this);
},
//@ts-ignore
DomEvent.off(
document,
{
contextmenu: DomEvent.stop,
touchmove: this._onMouseMove,
touchend: this._onMouseUp,
},
this
);
},
_onMouseUp: function (e: any) {
if ((e.which !== 0)) { return; }
_onMouseUp: function (e: any) {
if (e.which !== 0) {
return;
}
this._finish();
this._finish();
if (!this._moved) { return; }
// Postpone to next JS tick so internal click event handling
// still see it as "moved".
window.setTimeout(Util.bind(this._resetState, this), 0);
var bounds = new LatLngBounds(
this._map.containerPointToLatLng(this._startPoint),
this._map.containerPointToLatLng(this._point));
if (!this._moved) {
return;
}
// Postpone to next JS tick so internal click event handling
// still see it as "moved".
window.setTimeout(Util.bind(this._resetState, this), 0);
var bounds = new LatLngBounds(
this._map.containerPointToLatLng(this._startPoint),
this._map.containerPointToLatLng(this._point)
);
this._map.fire('selectionend', { selectionBounds: bounds });
},
this._map.fire("selectionend", { selectionBounds: bounds });
},
_getMousePosition(e: any) {
var scale = DomUtil.getScale(this._container), offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
return new Point(
// offset.left/top values are in page scale (like clientX/Y),
// whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
(e.touches[0].clientX - offset.left) / scale.x - this._container.clientLeft,
(e.touches[0].clientY - offset.top) / scale.y - this._container.clientTop
);
}
_getMousePosition(e: any) {
var scale = DomUtil.getScale(this._container),
offset = scale.boundingClientRect; // left and top values are in page scale (like the event clientX/Y)
return new Point(
// offset.left/top values are in page scale (like clientX/Y),
// whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
(e.touches[0].clientX - offset.left) / scale.x -
this._container.clientLeft,
(e.touches[0].clientY - offset.top) / scale.y - this._container.clientTop
);
},
});

View File

@@ -1,96 +1,101 @@
import { DivIcon } from 'leaflet';
import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { AirbaseChartData, AirbaseOptions } from '../interfaces';
import { DivIcon } from "leaflet";
import { CustomMarker } from "../map/markers/custommarker";
import { SVGInjector } from "@tanem/svg-injector";
import { AirbaseChartData, AirbaseOptions } from "../interfaces";
export class Airbase extends CustomMarker {
#name: string = "";
#chartData: AirbaseChartData = {
elevation: "",
ICAO: "",
TACAN: "",
runways: []
};
#coalition: string = "";
#hasChartDataBeenSet: boolean = false;
#properties: string[] = [];
#parkings: string[] = [];
#name: string = "";
#chartData: AirbaseChartData = {
elevation: "",
ICAO: "",
TACAN: "",
runways: [],
};
#coalition: string = "";
#hasChartDataBeenSet: boolean = false;
#properties: string[] = [];
#parkings: string[] = [];
constructor(options: AirbaseOptions) {
super(options.position, { riseOnHover: true });
constructor(options: AirbaseOptions) {
super(options.position, { riseOnHover: true });
this.#name = options.name;
}
this.#name = options.name;
}
chartDataHasBeenSet() {
return this.#hasChartDataBeenSet;
}
chartDataHasBeenSet() {
return this.#hasChartDataBeenSet;
}
createIcon() {
var icon = new DivIcon({
className: 'leaflet-airbase-marker',
iconSize: [40, 40],
iconAnchor: [20, 20]
}); // Set the marker, className must be set to avoid white square
this.setIcon(icon);
createIcon() {
var icon = new DivIcon({
className: "leaflet-airbase-marker",
iconSize: [40, 40],
iconAnchor: [20, 20],
}); // Set the marker, className must be set to avoid white square
this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("airbase-icon");
el.setAttribute("data-object", "airbase");
var img = document.createElement("img");
img.src = "/resources/theme/images/markers/airbase.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
el.addEventListener( "mouseover", ( ev ) => {
document.dispatchEvent( new CustomEvent( "airbaseMouseover", { detail: this }));
});
el.addEventListener( "mouseout", ( ev ) => {
document.dispatchEvent( new CustomEvent( "airbaseMouseout", { detail: this }));
});
el.dataset.coalition = this.#coalition;
}
var el = document.createElement("div");
el.classList.add("airbase-icon");
el.setAttribute("data-object", "airbase");
var img = document.createElement("img");
img.src = "/resources/theme/images/markers/airbase.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
el.addEventListener("mouseover", (ev) => {
document.dispatchEvent(
new CustomEvent("airbaseMouseover", { detail: this })
);
});
el.addEventListener("mouseout", (ev) => {
document.dispatchEvent(
new CustomEvent("airbaseMouseout", { detail: this })
);
});
el.dataset.coalition = this.#coalition;
}
setCoalition(coalition: string) {
this.#coalition = coalition;
(<HTMLElement>this.getElement()?.querySelector(".airbase-icon")).dataset.coalition = this.#coalition;
}
setCoalition(coalition: string) {
this.#coalition = coalition;
(<HTMLElement>(
this.getElement()?.querySelector(".airbase-icon")
)).dataset.coalition = this.#coalition;
}
getChartData() {
return this.#chartData;
}
getChartData() {
return this.#chartData;
}
getCoalition() {
return this.#coalition;
}
getCoalition() {
return this.#coalition;
}
setName(name: string) {
this.#name = name;
}
setName(name: string) {
this.#name = name;
}
getName() {
return this.#name;
}
getName() {
return this.#name;
}
setChartData(chartData: AirbaseChartData) {
this.#hasChartDataBeenSet = true;
this.#chartData = chartData;
}
setChartData(chartData: AirbaseChartData) {
this.#hasChartDataBeenSet = true;
this.#chartData = chartData;
}
setProperties(properties: string[]) {
this.#properties = properties;
}
setProperties(properties: string[]) {
this.#properties = properties;
}
getProperties() {
return this.#properties;
}
getProperties() {
return this.#properties;
}
setParkings(parkings: string[]) {
this.#parkings = parkings;
}
setParkings(parkings: string[]) {
this.#parkings = parkings;
}
getParkings() {
return this.#parkings;
}
}
getParkings() {
return this.#parkings;
}
}

View File

@@ -3,34 +3,34 @@ import { CustomMarker } from "../map/markers/custommarker";
import { SVGInjector } from "@tanem/svg-injector";
export class Bullseye extends CustomMarker {
#coalition: string = "";
#coalition: string = "";
createIcon() {
var icon = new DivIcon({
className: 'leaflet-bullseye-marker',
iconSize: [40, 40],
iconAnchor: [20, 20]
}); // Set the marker, className must be set to avoid white square
this.setIcon(icon);
createIcon() {
var icon = new DivIcon({
className: "leaflet-bullseye-marker",
iconSize: [40, 40],
iconAnchor: [20, 20],
}); // Set the marker, className must be set to avoid white square
this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("bullseye-icon");
el.setAttribute("data-object", "bullseye");
var img = document.createElement("img");
img.src = "/resources/theme/images/markers/bullseye.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
}
var el = document.createElement("div");
el.classList.add("bullseye-icon");
el.setAttribute("data-object", "bullseye");
var img = document.createElement("img");
img.src = "/resources/theme/images/markers/bullseye.svg";
img.onload = () => SVGInjector(img);
el.appendChild(img);
this.getElement()?.appendChild(el);
}
setCoalition(coalition: string)
{
this.#coalition = coalition;
(<HTMLElement> this.getElement()?.querySelector(".bullseye-icon")).dataset.coalition = this.#coalition;
}
setCoalition(coalition: string) {
this.#coalition = coalition;
(<HTMLElement>(
this.getElement()?.querySelector(".bullseye-icon")
)).dataset.coalition = this.#coalition;
}
getCoalition()
{
return this.#coalition;
}
}
getCoalition() {
return this.#coalition;
}
}

View File

@@ -2,7 +2,13 @@ import { LatLng } from "leaflet";
import { getApp } from "../olympusapp";
import { Airbase } from "./airbase";
import { Bullseye } from "./bullseye";
import { BLUE_COMMANDER, ERAS, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/constants";
import {
BLUE_COMMANDER,
ERAS,
GAME_MASTER,
NONE,
RED_COMMANDER,
} from "../constants/constants";
//import { Dropdown } from "../controls/dropdown";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
//import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
@@ -10,335 +16,422 @@ import { aircraftDatabase } from "../unit/databases/aircraftdatabase";
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
//import { Popup } from "../popups/popup";
import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData } from "../interfaces";
import {
AirbasesData,
BullseyesData,
CommandModeOptions,
DateAndTime,
MissionData,
} from "../interfaces";
/** The MissionManager */
export class MissionManager {
#bullseyes: { [name: string]: Bullseye } = {};
#airbases: { [name: string]: Airbase } = {};
#theatre: string = "";
#dateAndTime: DateAndTime = {date: {Year: 0, Month: 0, Day: 0}, time: {h: 0, m: 0, s: 0}, startTime: 0, elapsedTime: 0};
#load: number = 0;
#frameRate: number = 0;
#commandModeOptions: CommandModeOptions = {commandMode: NONE, restrictSpawns: false, restrictToCoalition: false, setupTime: Infinity, spawnPoints: {red: Infinity, blue: Infinity}, eras: []};
#remainingSetupTime: number = 0;
#spentSpawnPoint: number = 0;
//#commandModeDialog: HTMLElement;
//#commandModeErasDropdown: Dropdown;
#coalitions: {red: string[], blue: string[]} = {red: [], blue: []};
#bullseyes: { [name: string]: Bullseye } = {};
#airbases: { [name: string]: Airbase } = {};
#theatre: string = "";
#dateAndTime: DateAndTime = {
date: { Year: 0, Month: 0, Day: 0 },
time: { h: 0, m: 0, s: 0 },
startTime: 0,
elapsedTime: 0,
};
#load: number = 0;
#frameRate: number = 0;
#commandModeOptions: CommandModeOptions = {
commandMode: NONE,
restrictSpawns: false,
restrictToCoalition: false,
setupTime: Infinity,
spawnPoints: { red: Infinity, blue: Infinity },
eras: [],
};
#remainingSetupTime: number = 0;
#spentSpawnPoint: number = 0;
//#commandModeDialog: HTMLElement;
//#commandModeErasDropdown: Dropdown;
#coalitions: { red: string[]; blue: string[] } = { red: [], blue: [] };
constructor() {
document.addEventListener("applycommandModeOptions", () => this.#applycommandModeOptions());
document.addEventListener("showCommandModeDialog", () => this.showCommandModeDialog());
document.addEventListener("toggleSpawnRestrictions", (ev:CustomEventInit) => {
this.#toggleSpawnRestrictions(ev.detail._element.checked)
});
constructor() {
document.addEventListener("applycommandModeOptions", () =>
this.#applycommandModeOptions()
);
document.addEventListener("showCommandModeDialog", () =>
this.showCommandModeDialog()
);
document.addEventListener(
"toggleSpawnRestrictions",
(ev: CustomEventInit) => {
this.#toggleSpawnRestrictions(ev.detail._element.checked);
}
);
/* command-mode settings dialog */
//this.#commandModeDialog = document.querySelector("#command-mode-settings-dialog") as HTMLElement;
//this.#commandModeErasDropdown = new Dropdown("command-mode-era-options", () => {});
/* command-mode settings dialog */
//this.#commandModeDialog = document.querySelector("#command-mode-settings-dialog") as HTMLElement;
//this.#commandModeErasDropdown = new Dropdown("command-mode-era-options", () => {});
}
/** Update location of bullseyes
*
* @param object <BulleyesData>
*/
updateBullseyes(data: BullseyesData) {
const commandMode = getApp()
.getMissionManager()
.getCommandModeOptions().commandMode;
for (let idx in data.bullseyes) {
const bullseye = data.bullseyes[idx];
// Prevent Red and Blue coalitions seeing each other's bulleye(s)
if (
(bullseye.coalition === "red" && commandMode === BLUE_COMMANDER) ||
(bullseye.coalition === "blue" && commandMode === RED_COMMANDER)
) {
continue;
}
if (!(idx in this.#bullseyes))
this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getApp().getMap());
if (bullseye.latitude && bullseye.longitude && bullseye.coalition) {
this.#bullseyes[idx].setLatLng(
new LatLng(bullseye.latitude, bullseye.longitude)
);
this.#bullseyes[idx].setCoalition(bullseye.coalition);
}
}
}
/** Update location of bullseyes
*
* @param object <BulleyesData>
*/
updateBullseyes(data: BullseyesData) {
const commandMode = getApp().getMissionManager().getCommandModeOptions().commandMode;
for (let idx in data.bullseyes) {
const bullseye = data.bullseyes[idx];
/** Update airbase information
*
* @param object <AirbasesData>
*/
updateAirbases(data: AirbasesData) {
for (let idx in data.airbases) {
var airbase = data.airbases[idx];
if (
this.#airbases[airbase.callsign] === undefined &&
airbase.callsign != ""
) {
this.#airbases[airbase.callsign] = new Airbase({
position: new LatLng(airbase.latitude, airbase.longitude),
name: airbase.callsign,
}).addTo(getApp().getMap());
this.#airbases[airbase.callsign].on("contextmenu", (e) =>
this.#onAirbaseClick(e)
);
this.#loadAirbaseChartData(airbase.callsign);
}
// Prevent Red and Blue coalitions seeing each other's bulleye(s)
if ((bullseye.coalition === "red" && commandMode === BLUE_COMMANDER)
|| (bullseye.coalition === "blue" && commandMode === RED_COMMANDER)) {
continue;
}
if (!(idx in this.#bullseyes))
this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getApp().getMap());
if (bullseye.latitude && bullseye.longitude && bullseye.coalition) {
this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude));
this.#bullseyes[idx].setCoalition(bullseye.coalition);
}
}
if (
this.#airbases[airbase.callsign] != undefined &&
airbase.latitude &&
airbase.longitude &&
airbase.coalition
) {
this.#airbases[airbase.callsign].setLatLng(
new LatLng(airbase.latitude, airbase.longitude)
);
this.#airbases[airbase.callsign].setCoalition(airbase.coalition);
}
}
}
/** Update airbase information
*
* @param object <AirbasesData>
*/
updateAirbases(data: AirbasesData) {
for (let idx in data.airbases) {
var airbase = data.airbases[idx]
if (this.#airbases[airbase.callsign] === undefined && airbase.callsign != '') {
this.#airbases[airbase.callsign] = new Airbase({
position: new LatLng(airbase.latitude, airbase.longitude),
name: airbase.callsign
}).addTo(getApp().getMap());
this.#airbases[airbase.callsign].on('contextmenu', (e) => this.#onAirbaseClick(e));
this.#loadAirbaseChartData(airbase.callsign);
}
/** Update mission information
*
* @param object <MissionData>
*/
updateMission(data: MissionData) {
if (data.mission) {
/* Set the mission theatre */
if (data.mission.theatre != this.#theatre) {
this.#theatre = data.mission.theatre;
getApp().getMap().setTheatre(this.#theatre);
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Map set to " + this.#theatre);
}
if (this.#airbases[airbase.callsign] != undefined && airbase.latitude && airbase.longitude && airbase.coalition) {
this.#airbases[airbase.callsign].setLatLng(new LatLng(airbase.latitude, airbase.longitude));
this.#airbases[airbase.callsign].setCoalition(airbase.coalition);
}
}
}
/* Set the date and time data */
this.#dateAndTime = data.mission.dateAndTime;
data.mission.dateAndTime.time.s -= 1; // ED has seconds 1-60 and not 0-59?!
/** Update mission information
*
* @param object <MissionData>
*/
updateMission(data: MissionData) {
if (data.mission) {
/* Set the coalition countries */
this.#coalitions = data.mission.coalitions;
/* Set the mission theatre */
if (data.mission.theatre != this.#theatre) {
this.#theatre = data.mission.theatre;
getApp().getMap().setTheatre(this.#theatre);
//(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Map set to " + this.#theatre);
}
/* Set the date and time data */
this.#dateAndTime = data.mission.dateAndTime;
data.mission.dateAndTime.time.s -= 1; // ED has seconds 1-60 and not 0-59?!
/* Set the coalition countries */
this.#coalitions = data.mission.coalitions;
/* Set the command mode options */
this.#setcommandModeOptions(data.mission.commandModeOptions);
this.#remainingSetupTime = this.getCommandModeOptions().setupTime - this.getDateAndTime().elapsedTime;
var commandModePhaseEl = document.querySelector("#command-mode-phase") as HTMLElement;
if (commandModePhaseEl) {
if (this.#remainingSetupTime > 0) {
var remainingTime = `-${new Date(this.#remainingSetupTime * 1000).toISOString().substring(14, 19)}`;
commandModePhaseEl.dataset.remainingTime = remainingTime;
}
commandModePhaseEl.classList.toggle("setup-phase", this.#remainingSetupTime > 0 && this.getCommandModeOptions().restrictSpawns);
//commandModePhaseEl.classList.toggle("game-commenced", this.#remainingSetupTime <= 0 || !this.getCommandModeOptions().restrictSpawns);
//commandModePhaseEl.classList.toggle("no-restrictions", !this.getCommandModeOptions().restrictSpawns);
}
}
}
/** Get the bullseyes set in this theatre
*
* @returns object
*/
getBullseyes() {
return this.#bullseyes;
}
/** Get the airbases in this theatre
*
* @returns object
*/
getAirbases() {
return this.#airbases;
}
/** Get the options/settings as set in the command mode
*
* @returns object
*/
getCommandModeOptions() {
return this.#commandModeOptions;
}
/** Get the current date and time of the mission (based on local time)
*
* @returns object
*/
getDateAndTime() {
return this.#dateAndTime;
}
/**
* Get the number of seconds left of setup time
* @returns number
*/
getRemainingSetupTime() {
return this.#remainingSetupTime;
}
/** Get an object with the coalitions in it
*
* @returns object
*/
getCoalitions() {
return this.#coalitions;
}
/** Get the current theatre (map) name
*
* @returns string
*/
getTheatre() {
return this.#theatre;
}
getAvailableSpawnPoints() {
if (this.getCommandModeOptions().commandMode === GAME_MASTER)
return Infinity;
else if (this.getCommandModeOptions().commandMode === BLUE_COMMANDER)
return this.getCommandModeOptions().spawnPoints.blue - this.#spentSpawnPoint;
else if (this.getCommandModeOptions().commandMode === RED_COMMANDER)
return this.getCommandModeOptions().spawnPoints.red - this.#spentSpawnPoint;
else
return 0;
}
getCommandedCoalition() {
if (this.getCommandModeOptions().commandMode === BLUE_COMMANDER)
return "blue";
else if (this.getCommandModeOptions().commandMode === RED_COMMANDER)
return "red";
else
return "all";
}
refreshSpawnPoints() {
var spawnPointsEl = document.querySelector("#spawn-points");
if (spawnPointsEl) {
spawnPointsEl.textContent = `${this.getAvailableSpawnPoints()}`;
}
}
setSpentSpawnPoints(spawnPoints: number) {
this.#spentSpawnPoint = spawnPoints;
this.refreshSpawnPoints();
}
setLoad(load: number) {
this.#load = load;
}
getLoad() {
return this.#load;
}
setFrameRate(frameRate: number) {
this.#frameRate = frameRate;
}
getFrameRate() {
return this.#frameRate;
}
showCommandModeDialog() {
//const options = this.getCommandModeOptions()
//const { restrictSpawns, restrictToCoalition, setupTime } = options;
//this.#toggleSpawnRestrictions(restrictSpawns);
//
///* Create the checkboxes to select the unit eras */
//this.#commandModeErasDropdown.setOptionsElements(
// ERAS.sort((eraA, eraB) => {
// return ( eraA.chronologicalOrder > eraB.chronologicalOrder ) ? 1 : -1;
// }).map((era) => {
// return createCheckboxOption(era.name, `Enable ${era} units spawns`, this.getCommandModeOptions().eras.includes(era.name));
// })
//);
//
//this.#commandModeDialog.classList.remove("hide");
//
//const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement;
//const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement;
//const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement;
//const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement;
//const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement;
//
//restrictSpawnsCheckbox.checked = restrictSpawns;
//restrictToCoalitionCheckbox.checked = restrictToCoalition;
//blueSpawnPointsInput.value = String(options.spawnPoints.blue);
//redSpawnPointsInput.value = String(options.spawnPoints.red);
//setupTimeInput.value = String(Math.floor(setupTime / 60.0));
}
#applycommandModeOptions() {
//this.#commandModeDialog.classList.add("hide");
//
//const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement;
//const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement;
//const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement;
//const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement;
//const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement;
//
//var eras: string[] = [];
//const enabledEras = getCheckboxOptions(this.#commandModeErasDropdown);
//Object.keys(enabledEras).forEach((key: string) => {if (enabledEras[key]) eras.push(key)});
//getApp().getServerManager().setCommandModeOptions(restrictSpawnsCheckbox.checked, restrictToCoalitionCheckbox.checked, {blue: parseInt(blueSpawnPointsInput.value), red: parseInt(redSpawnPointsInput.value)}, eras, parseInt(setupTimeInput.value) * 60);
}
#setcommandModeOptions(commandModeOptions: CommandModeOptions) {
/* Refresh all the data if we have exited the NONE state */
var requestRefresh = false;
if (this.#commandModeOptions.commandMode === NONE && commandModeOptions.commandMode !== NONE)
requestRefresh = true;
/* Refresh the page if we have lost Game Master priviledges */
if (this.#commandModeOptions.commandMode === GAME_MASTER && commandModeOptions.commandMode !== GAME_MASTER)
location.reload();
/* Check if any option has changed */
var commandModeOptionsChanged = (!commandModeOptions.eras.every((value: string, idx: number) => {return value === this.getCommandModeOptions().eras[idx]}) ||
commandModeOptions.spawnPoints.red !== this.getCommandModeOptions().spawnPoints.red ||
commandModeOptions.spawnPoints.blue !== this.getCommandModeOptions().spawnPoints.blue ||
commandModeOptions.restrictSpawns !== this.getCommandModeOptions().restrictSpawns ||
commandModeOptions.restrictToCoalition !== this.getCommandModeOptions().restrictToCoalition);
this.#commandModeOptions = commandModeOptions;
this.setSpentSpawnPoints(0);
this.refreshSpawnPoints();
if (commandModeOptionsChanged) {
document.dispatchEvent(new CustomEvent("commandModeOptionsChanged", { detail: this }));
document.getElementById("command-mode-toolbar")?.classList.remove("hide");
const el = document.getElementById("command-mode");
if (el) {
el.dataset.mode = commandModeOptions.commandMode;
el.textContent = commandModeOptions.commandMode.toUpperCase();
}
/* Set the command mode options */
this.#setcommandModeOptions(data.mission.commandModeOptions);
this.#remainingSetupTime =
this.getCommandModeOptions().setupTime -
this.getDateAndTime().elapsedTime;
var commandModePhaseEl = document.querySelector(
"#command-mode-phase"
) as HTMLElement;
if (commandModePhaseEl) {
if (this.#remainingSetupTime > 0) {
var remainingTime = `-${new Date(this.#remainingSetupTime * 1000).toISOString().substring(14, 19)}`;
commandModePhaseEl.dataset.remainingTime = remainingTime;
}
document.querySelector("#spawn-points-container")?.classList.toggle("hide", this.getCommandModeOptions().commandMode === GAME_MASTER || !this.getCommandModeOptions().restrictSpawns);
document.querySelector("#command-mode-settings-button")?.classList.toggle("hide", this.getCommandModeOptions().commandMode !== GAME_MASTER);
commandModePhaseEl.classList.toggle(
"setup-phase",
this.#remainingSetupTime > 0 &&
this.getCommandModeOptions().restrictSpawns
);
//commandModePhaseEl.classList.toggle("game-commenced", this.#remainingSetupTime <= 0 || !this.getCommandModeOptions().restrictSpawns);
//commandModePhaseEl.classList.toggle("no-restrictions", !this.getCommandModeOptions().restrictSpawns);
}
}
}
if (requestRefresh)
getApp().getServerManager().refreshAll();
/** Get the bullseyes set in this theatre
*
* @returns object
*/
getBullseyes() {
return this.#bullseyes;
}
/** Get the airbases in this theatre
*
* @returns object
*/
getAirbases() {
return this.#airbases;
}
/** Get the options/settings as set in the command mode
*
* @returns object
*/
getCommandModeOptions() {
return this.#commandModeOptions;
}
/** Get the current date and time of the mission (based on local time)
*
* @returns object
*/
getDateAndTime() {
return this.#dateAndTime;
}
/**
* Get the number of seconds left of setup time
* @returns number
*/
getRemainingSetupTime() {
return this.#remainingSetupTime;
}
/** Get an object with the coalitions in it
*
* @returns object
*/
getCoalitions() {
return this.#coalitions;
}
/** Get the current theatre (map) name
*
* @returns string
*/
getTheatre() {
return this.#theatre;
}
getAvailableSpawnPoints() {
if (this.getCommandModeOptions().commandMode === GAME_MASTER)
return Infinity;
else if (this.getCommandModeOptions().commandMode === BLUE_COMMANDER)
return (
this.getCommandModeOptions().spawnPoints.blue - this.#spentSpawnPoint
);
else if (this.getCommandModeOptions().commandMode === RED_COMMANDER)
return (
this.getCommandModeOptions().spawnPoints.red - this.#spentSpawnPoint
);
else return 0;
}
getCommandedCoalition() {
if (this.getCommandModeOptions().commandMode === BLUE_COMMANDER)
return "blue";
else if (this.getCommandModeOptions().commandMode === RED_COMMANDER)
return "red";
else return "all";
}
refreshSpawnPoints() {
var spawnPointsEl = document.querySelector("#spawn-points");
if (spawnPointsEl) {
spawnPointsEl.textContent = `${this.getAvailableSpawnPoints()}`;
}
}
setSpentSpawnPoints(spawnPoints: number) {
this.#spentSpawnPoint = spawnPoints;
this.refreshSpawnPoints();
}
setLoad(load: number) {
this.#load = load;
}
getLoad() {
return this.#load;
}
setFrameRate(frameRate: number) {
this.#frameRate = frameRate;
}
getFrameRate() {
return this.#frameRate;
}
showCommandModeDialog() {
//const options = this.getCommandModeOptions()
//const { restrictSpawns, restrictToCoalition, setupTime } = options;
//this.#toggleSpawnRestrictions(restrictSpawns);
//
///* Create the checkboxes to select the unit eras */
//this.#commandModeErasDropdown.setOptionsElements(
// ERAS.sort((eraA, eraB) => {
// return ( eraA.chronologicalOrder > eraB.chronologicalOrder ) ? 1 : -1;
// }).map((era) => {
// return createCheckboxOption(era.name, `Enable ${era} units spawns`, this.getCommandModeOptions().eras.includes(era.name));
// })
//);
//
//this.#commandModeDialog.classList.remove("hide");
//
//const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement;
//const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement;
//const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement;
//const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement;
//const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement;
//
//restrictSpawnsCheckbox.checked = restrictSpawns;
//restrictToCoalitionCheckbox.checked = restrictToCoalition;
//blueSpawnPointsInput.value = String(options.spawnPoints.blue);
//redSpawnPointsInput.value = String(options.spawnPoints.red);
//setupTimeInput.value = String(Math.floor(setupTime / 60.0));
}
#applycommandModeOptions() {
//this.#commandModeDialog.classList.add("hide");
//
//const restrictSpawnsCheckbox = this.#commandModeDialog.querySelector("#restrict-spawns")?.querySelector("input") as HTMLInputElement;
//const restrictToCoalitionCheckbox = this.#commandModeDialog.querySelector("#restrict-to-coalition")?.querySelector("input") as HTMLInputElement;
//const blueSpawnPointsInput = this.#commandModeDialog.querySelector("#blue-spawn-points")?.querySelector("input") as HTMLInputElement;
//const redSpawnPointsInput = this.#commandModeDialog.querySelector("#red-spawn-points")?.querySelector("input") as HTMLInputElement;
//const setupTimeInput = this.#commandModeDialog.querySelector("#setup-time")?.querySelector("input") as HTMLInputElement;
//
//var eras: string[] = [];
//const enabledEras = getCheckboxOptions(this.#commandModeErasDropdown);
//Object.keys(enabledEras).forEach((key: string) => {if (enabledEras[key]) eras.push(key)});
//getApp().getServerManager().setCommandModeOptions(restrictSpawnsCheckbox.checked, restrictToCoalitionCheckbox.checked, {blue: parseInt(blueSpawnPointsInput.value), red: parseInt(redSpawnPointsInput.value)}, eras, parseInt(setupTimeInput.value) * 60);
}
#setcommandModeOptions(commandModeOptions: CommandModeOptions) {
/* Refresh all the data if we have exited the NONE state */
var requestRefresh = false;
if (
this.#commandModeOptions.commandMode === NONE &&
commandModeOptions.commandMode !== NONE
)
requestRefresh = true;
/* Refresh the page if we have lost Game Master priviledges */
if (
this.#commandModeOptions.commandMode === GAME_MASTER &&
commandModeOptions.commandMode !== GAME_MASTER
)
location.reload();
/* Check if any option has changed */
var commandModeOptionsChanged =
!commandModeOptions.eras.every((value: string, idx: number) => {
return value === this.getCommandModeOptions().eras[idx];
}) ||
commandModeOptions.spawnPoints.red !==
this.getCommandModeOptions().spawnPoints.red ||
commandModeOptions.spawnPoints.blue !==
this.getCommandModeOptions().spawnPoints.blue ||
commandModeOptions.restrictSpawns !==
this.getCommandModeOptions().restrictSpawns ||
commandModeOptions.restrictToCoalition !==
this.getCommandModeOptions().restrictToCoalition;
this.#commandModeOptions = commandModeOptions;
this.setSpentSpawnPoints(0);
this.refreshSpawnPoints();
if (commandModeOptionsChanged) {
document.dispatchEvent(
new CustomEvent("commandModeOptionsChanged", { detail: this })
);
document.getElementById("command-mode-toolbar")?.classList.remove("hide");
const el = document.getElementById("command-mode");
if (el) {
el.dataset.mode = commandModeOptions.commandMode;
el.textContent = commandModeOptions.commandMode.toUpperCase();
}
}
#onAirbaseClick(e: any) {
getApp().getMap().showAirbaseContextMenu(e.sourceTarget, e.originalEvent.x, e.originalEvent.y, e.latlng);
document
.querySelector("#spawn-points-container")
?.classList.toggle(
"hide",
this.getCommandModeOptions().commandMode === GAME_MASTER ||
!this.getCommandModeOptions().restrictSpawns
);
document
.querySelector("#command-mode-settings-button")
?.classList.toggle(
"hide",
this.getCommandModeOptions().commandMode !== GAME_MASTER
);
if (requestRefresh) getApp().getServerManager().refreshAll();
}
#onAirbaseClick(e: any) {
getApp()
.getMap()
.showAirbaseContextMenu(
e.sourceTarget,
e.originalEvent.x,
e.originalEvent.y,
e.latlng
);
}
#loadAirbaseChartData(callsign: string) {
if (!this.#theatre) {
return;
}
#loadAirbaseChartData(callsign: string) {
if ( !this.#theatre ) {
return;
}
var xhr = new XMLHttpRequest();
xhr.open(
"GET",
`api/airbases/${this.#theatre.toLowerCase()}/${callsign}`,
true
);
xhr.responseType = "json";
xhr.onload = () => {
var status = xhr.status;
if (status === 200) {
const data = xhr.response;
this.getAirbases()[callsign].setChartData(data);
} else {
console.error(`Error retrieving data for ${callsign} airbase`);
}
};
xhr.send();
}
var xhr = new XMLHttpRequest();
xhr.open('GET', `api/airbases/${this.#theatre.toLowerCase()}/${callsign}`, true);
xhr.responseType = 'json';
xhr.onload = () => {
var status = xhr.status;
if (status === 200) {
const data = xhr.response;
this.getAirbases()[callsign].setChartData(data);
} else {
console.error(`Error retrieving data for ${callsign} airbase`)
}
};
xhr.send();
}
#toggleSpawnRestrictions(restrictionsEnabled:boolean) {
//this.#commandModeDialog.querySelectorAll("input, label, .ol-select").forEach( el => {
// if (!el.closest("#restrict-spawns")) el.toggleAttribute("disabled", !restrictionsEnabled);
//});
}
}
#toggleSpawnRestrictions(restrictionsEnabled: boolean) {
//this.#commandModeDialog.querySelectorAll("input, label, .ol-select").forEach( el => {
// if (!el.closest("#restrict-spawns")) el.toggleAttribute("disabled", !restrictionsEnabled);
//});
}
}

View File

@@ -2,14 +2,14 @@
var app: OlympusApp;
export function setupApp() {
if (app === undefined) {
app = new OlympusApp();
app.start();
}
if (app === undefined) {
app = new OlympusApp();
app.start();
}
}
export function getApp() {
return app;
return app;
}
import { Map } from "./map/map";
@@ -19,7 +19,12 @@ import { UnitsManager } from "./unit/unitsmanager";
import { WeaponsManager } from "./weapon/weaponsmanager";
import { ServerManager } from "./server/servermanager";
import { BLUE_COMMANDER, DEFAULT_CONTEXT, GAME_MASTER, RED_COMMANDER } from "./constants/constants";
import {
BLUE_COMMANDER,
DEFAULT_CONTEXT,
GAME_MASTER,
RED_COMMANDER,
} from "./constants/constants";
import { aircraftDatabase } from "./unit/databases/aircraftdatabase";
import { helicopterDatabase } from "./unit/databases/helicopterdatabase";
import { groundUnitDatabase } from "./unit/databases/groundunitdatabase";
@@ -28,180 +33,208 @@ import { Coalition, Context } from "./types/types";
export var VERSION = "{{OLYMPUS_VERSION_NUMBER}}";
export var IP = window.location.toString();
export var connectedToServer = true; // Temporary
export var connectedToServer = true; // Temporary
export class OlympusApp {
/* Global data */
#activeCoalition: Coalition = "blue";
#latestVersion: string|undefined = undefined;
#config: any = {};
/* Global data */
#activeCoalition: Coalition = "blue";
#latestVersion: string | undefined = undefined;
#config: any = {};
/* Main leaflet map, extended by custom methods */
#map: Map | null = null;
/* Main leaflet map, extended by custom methods */
#map: Map | null = null;
/* Managers */
#missionManager: MissionManager | null = null;
#serverManager: ServerManager | null = null;
#shortcutManager: ShortcutManager | null = null;
#unitsManager: UnitsManager | null = null;
#weaponsManager: WeaponsManager | null = null;
//#pluginsManager: // TODO
/* Managers */
#missionManager: MissionManager | null = null;
#serverManager: ServerManager | null = null;
#shortcutManager: ShortcutManager | null = null;
#unitsManager: UnitsManager | null = null;
#weaponsManager: WeaponsManager | null = null;
//#pluginsManager: // TODO
/* Current context */
#context: Context = DEFAULT_CONTEXT;
/* Current context */
#context: Context = DEFAULT_CONTEXT;
constructor() {
}
constructor() {}
getCurrentContext() {
return this.#context;
}
getCurrentContext() {
return this.#context;
}
getMap() {
return this.#map as Map;
}
getMap() {
return this.#map as Map;
}
getServerManager() {
return this.#serverManager as ServerManager;
}
getServerManager() {
return this.#serverManager as ServerManager;
}
getShortcutManager() {
return this.#shortcutManager as ShortcutManager;
}
getShortcutManager() {
return this.#shortcutManager as ShortcutManager;
}
getUnitsManager() {
return this.#unitsManager as UnitsManager;
}
getUnitsManager() {
return this.#unitsManager as UnitsManager;
}
getWeaponsManager() {
return this.#weaponsManager as WeaponsManager;
}
getWeaponsManager() {
return this.#weaponsManager as WeaponsManager;
}
getMissionManager() {
return this.#missionManager as MissionManager;
}
getMissionManager() {
return this.#missionManager as MissionManager;
}
/* TODO
/* TODO
getPluginsManager() {
return null // this.#pluginsManager as PluginsManager;
}
*/
/** Set the active coalition, i.e. the currently controlled coalition. A game master can change the active coalition, while a commander is bound to his/her coalition
*
* @param newActiveCoalition
*/
setActiveCoalition(newActiveCoalition: Coalition) {
if (this.getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER) {
this.#activeCoalition = newActiveCoalition;
document.dispatchEvent(new CustomEvent("activeCoalitionChanged"));
/** Set the active coalition, i.e. the currently controlled coalition. A game master can change the active coalition, while a commander is bound to his/her coalition
*
* @param newActiveCoalition
*/
setActiveCoalition(newActiveCoalition: Coalition) {
if (
this.getMissionManager().getCommandModeOptions().commandMode ==
GAME_MASTER
) {
this.#activeCoalition = newActiveCoalition;
document.dispatchEvent(new CustomEvent("activeCoalitionChanged"));
}
}
/**
*
* @returns The active coalition
*/
getActiveCoalition(): Coalition {
if (
this.getMissionManager().getCommandModeOptions().commandMode ==
GAME_MASTER
)
return this.#activeCoalition;
else {
if (
this.getMissionManager().getCommandModeOptions().commandMode ==
BLUE_COMMANDER
)
return "blue";
else if (
this.getMissionManager().getCommandModeOptions().commandMode ==
RED_COMMANDER
)
return "red";
else return "neutral";
}
}
/**
*
* @returns The aircraft database
*/
getAircraftDatabase() {
return aircraftDatabase;
}
/**
*
* @returns The helicopter database
*/
getHelicopterDatabase() {
return helicopterDatabase;
}
/**
*
* @returns The ground unit database
*/
getGroundUnitDatabase() {
return groundUnitDatabase;
}
/**
*
* @returns The navy unit database
*/
getNavyUnitDatabase() {
return navyUnitDatabase;
}
start() {
/* Initialize base functionalitites */
this.#map = new Map("map-container");
this.#missionManager = new MissionManager();
this.#serverManager = new ServerManager();
this.#shortcutManager = new ShortcutManager();
this.#unitsManager = new UnitsManager();
this.#weaponsManager = new WeaponsManager();
/* Set the address of the server */
this.getServerManager().setAddress(
window.location.href.split("?")[0].replace("vite/", "")
);
/* Setup all global events */
this.#setupEvents();
/* Check if we are running the latest version */
const request = new Request(
"https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json"
);
fetch(request)
.then((response) => {
if (response.status === 200) {
return response.json();
} else {
throw new Error(
"Error connecting to Github to retrieve latest version"
);
}
}
/**
*
* @returns The active coalition
*/
getActiveCoalition(): Coalition {
if (this.getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER)
return this.#activeCoalition;
else {
if (this.getMissionManager().getCommandModeOptions().commandMode == BLUE_COMMANDER)
return "blue";
else if (this.getMissionManager().getCommandModeOptions().commandMode == RED_COMMANDER)
return "red";
else
return "neutral";
})
.then((res) => {
this.#latestVersion = res["version"];
const latestVersionSpan = document.getElementById(
"latest-version"
) as HTMLElement;
if (latestVersionSpan) {
latestVersionSpan.innerHTML = this.#latestVersion ?? "Unknown";
latestVersionSpan.classList.toggle(
"new-version",
this.#latestVersion !== VERSION
);
}
}
});
/**
*
* @returns The aircraft database
*/
getAircraftDatabase() {
return aircraftDatabase;
}
/* Load the config file from the server */
const configRequest = new Request(
window.location.href.split("?")[0].replace("vite/", "") +
"resources/config"
);
fetch(configRequest)
.then((response) => {
if (response.status === 200) {
return response.json();
} else {
throw new Error("Error retrieving config file");
}
})
.then((res) => {
this.#config = res;
document.dispatchEvent(new CustomEvent("configLoaded"));
});
}
/**
*
* @returns The helicopter database
*/
getHelicopterDatabase() {
return helicopterDatabase;
}
getConfig() {
return this.#config;
}
/**
*
* @returns The ground unit database
*/
getGroundUnitDatabase() {
return groundUnitDatabase;
}
/**
*
* @returns The navy unit database
*/
getNavyUnitDatabase() {
return navyUnitDatabase;
}
start() {
/* Initialize base functionalitites */
this.#map = new Map('map-container');
this.#missionManager = new MissionManager();
this.#serverManager = new ServerManager();
this.#shortcutManager = new ShortcutManager();
this.#unitsManager = new UnitsManager();
this.#weaponsManager = new WeaponsManager();
/* Set the address of the server */
this.getServerManager().setAddress(window.location.href.split('?')[0].replace("vite/", ""));
/* Setup all global events */
this.#setupEvents();
/* Check if we are running the latest version */
const request = new Request("https://raw.githubusercontent.com/Pax1601/DCSOlympus/main/version.json");
fetch(request).then((response) => {
if (response.status === 200) {
return response.json();
} else {
throw new Error("Error connecting to Github to retrieve latest version");
}
}).then((res) => {
this.#latestVersion = res["version"];
const latestVersionSpan = document.getElementById("latest-version") as HTMLElement;
if (latestVersionSpan) {
latestVersionSpan.innerHTML = this.#latestVersion ?? "Unknown";
latestVersionSpan.classList.toggle("new-version", this.#latestVersion !== VERSION);
}
})
/* Load the config file from the server */
const configRequest = new Request(window.location.href.split('?')[0].replace("vite/", "") + "resources/config");
fetch(configRequest).then((response) => {
if (response.status === 200) {
return response.json();
} else {
throw new Error("Error retrieving config file");
}
}).then((res) => {
this.#config = res;
document.dispatchEvent(new CustomEvent("configLoaded"));
})
}
getConfig() {
return this.#config;
}
#setupEvents() {
/* Reload the page, used to mimic a restart of the app */
document.addEventListener("reloadPage", () => {
location.reload();
})
}
}
#setupEvents() {
/* Reload the page, used to mimic a restart of the app */
document.addEventListener("reloadPage", () => {
location.reload();
});
}
}

View File

@@ -1,107 +1,166 @@
import { LatLng, Point, Polygon } from "leaflet";
import * as turf from "@turf/turf";
import { UnitDatabase } from "../unit/databases/unitdatabase";
import { AircraftDatabase, aircraftDatabase } from "../unit/databases/aircraftdatabase";
import {
AircraftDatabase,
aircraftDatabase,
} from "../unit/databases/aircraftdatabase";
import { helicopterDatabase } from "../unit/databases/helicopterdatabase";
import { groundUnitDatabase } from "../unit/databases/groundunitdatabase";
//import { Buffer } from "buffer";
import { GROUND_UNIT_AIR_DEFENCE_REGEX, ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
import {
GROUND_UNIT_AIR_DEFENCE_REGEX,
ROEs,
emissionsCountermeasures,
reactionsToThreat,
states,
} from "../constants/constants";
import { navyUnitDatabase } from "../unit/databases/navyunitdatabase";
import { DateAndTime, UnitBlueprint } from "../interfaces";
import { Converter } from "usng";
import { MGRS } from "../types/types";
import { getApp } from "../olympusapp";
export function bearing(
lat1: number,
lon1: number,
lat2: number,
lon2: number
) {
const φ1 = deg2rad(lat1); // φ, λ in radians
const φ2 = deg2rad(lat2);
const λ1 = deg2rad(lon1); // φ, λ in radians
const λ2 = deg2rad(lon2);
const y = Math.sin(λ2 - λ1) * Math.cos(φ2);
const x =
Math.cos(φ1) * Math.sin(φ2) -
Math.sin(φ1) * Math.cos(φ2) * Math.cos(λ2 - λ1);
const θ = Math.atan2(y, x);
const brng = (rad2deg(θ) + 360) % 360; // in degrees
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
const φ1 = deg2rad(lat1); // φ, λ in radians
const φ2 = deg2rad(lat2);
const λ1 = deg2rad(lon1); // φ, λ in radians
const λ2 = deg2rad(lon2);
const y = Math.sin(λ2 - λ1) * Math.cos(φ2);
const x = Math.cos(φ1) * Math.sin(φ2) - Math.sin(φ1) * Math.cos(φ2) * Math.cos(λ2 - λ1);
const θ = Math.atan2(y, x);
const brng = (rad2deg(θ) + 360) % 360; // in degrees
return brng;
return brng;
}
export function distance(lat1: number, lon1: number, lat2: number, lon2: number) {
const R = 6371e3; // metres
const φ1 = deg2rad(lat1); // φ, λ in radians
const φ2 = deg2rad(lat2);
const Δφ = deg2rad(lat2 - lat1);
const Δλ = deg2rad(lon2 - lon1);
export function distance(
lat1: number,
lon1: number,
lat2: number,
lon2: number
) {
const R = 6371e3; // metres
const φ1 = deg2rad(lat1); // φ, λ in radians
const φ2 = deg2rad(lat2);
const Δφ = deg2rad(lat2 - lat1);
const Δλ = deg2rad(lon2 - lon1);
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const d = R * c; // in metres
const d = R * c; // in metres
return d;
return d;
}
export function bearingAndDistanceToLatLng(lat: number, lon: number, brng: number, dist: number) {
const R = 6371e3; // metres
const φ1 = deg2rad(lat); // φ, λ in radians
const λ1 = deg2rad(lon);
const φ2 = Math.asin(Math.sin(φ1) * Math.cos(dist / R) + Math.cos(φ1) * Math.sin(dist / R) * Math.cos(brng));
const λ2 = λ1 + Math.atan2(Math.sin(brng) * Math.sin(dist / R) * Math.cos(φ1), Math.cos(dist / R) - Math.sin(φ1) * Math.sin(φ2));
export function bearingAndDistanceToLatLng(
lat: number,
lon: number,
brng: number,
dist: number
) {
const R = 6371e3; // metres
const φ1 = deg2rad(lat); // φ, λ in radians
const λ1 = deg2rad(lon);
const φ2 = Math.asin(
Math.sin(φ1) * Math.cos(dist / R) +
Math.cos(φ1) * Math.sin(dist / R) * Math.cos(brng)
);
const λ2 =
λ1 +
Math.atan2(
Math.sin(brng) * Math.sin(dist / R) * Math.cos(φ1),
Math.cos(dist / R) - Math.sin(φ1) * Math.sin(φ2)
);
return new LatLng(rad2deg(φ2), rad2deg(λ2));
return new LatLng(rad2deg(φ2), rad2deg(λ2));
}
export function ConvertDDToDMS(D: number, lng: boolean) {
var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N";
var deg = 0 | (D < 0 ? (D = -D) : D);
var min = 0 | (((D += 1e-9) % 1) * 60);
var sec = (0 | (((D * 60) % 1) * 6000)) / 100;
var dec = Math.round((sec - Math.floor(sec)) * 100);
var sec = Math.floor(sec);
if (lng)
return dir + zeroPad(deg, 3) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\"";
else
return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\"";
var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N";
var deg = 0 | (D < 0 ? (D = -D) : D);
var min = 0 | (((D += 1e-9) % 1) * 60);
var sec = (0 | (((D * 60) % 1) * 6000)) / 100;
var dec = Math.round((sec - Math.floor(sec)) * 100);
var sec = Math.floor(sec);
if (lng)
return (
dir +
zeroPad(deg, 3) +
"°" +
zeroPad(min, 2) +
"'" +
zeroPad(sec, 2) +
"." +
zeroPad(dec, 2) +
'"'
);
else
return (
dir +
zeroPad(deg, 2) +
"°" +
zeroPad(min, 2) +
"'" +
zeroPad(sec, 2) +
"." +
zeroPad(dec, 2) +
'"'
);
}
export function dataPointMap(container: HTMLElement, data: any) {
Object.keys(data).forEach((key) => {
const val = "" + data[key]; // Ensure a string
container.querySelectorAll(`[data-point="${key}"]`).forEach(el => {
// We could probably have options here
if (el instanceof HTMLInputElement) {
el.value = val;
} else if (el instanceof HTMLElement) {
el.innerText = val;
}
});
Object.keys(data).forEach((key) => {
const val = "" + data[key]; // Ensure a string
container.querySelectorAll(`[data-point="${key}"]`).forEach((el) => {
// We could probably have options here
if (el instanceof HTMLInputElement) {
el.value = val;
} else if (el instanceof HTMLElement) {
el.innerText = val;
}
});
});
}
export function deg2rad(deg: number) {
var pi = Math.PI;
return deg * (pi / 180);
var pi = Math.PI;
return deg * (pi / 180);
}
export function rad2deg(rad: number) {
var pi = Math.PI;
return rad / (pi / 180);
var pi = Math.PI;
return rad / (pi / 180);
}
export function generateUUIDv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
var r = (Math.random() * 16) | 0,
v = c == "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
export function keyEventWasInInput(event: KeyboardEvent) {
const target = event.target;
return (target instanceof HTMLElement && (["INPUT", "TEXTAREA"].includes(target.nodeName)));
const target = event.target;
return (
target instanceof HTMLElement &&
["INPUT", "TEXTAREA"].includes(target.nodeName)
);
}
export function reciprocalHeading(heading: number): number {
return heading > 180 ? heading - 180 : heading + 180;
return heading > 180 ? heading - 180 : heading + 180;
}
/**
@@ -110,367 +169,386 @@ export function reciprocalHeading(heading: number): number {
* @param num <number> subject number
* @param places <number> places to pad
* @param decimal <boolean> whether this is a decimal number or not
*
*
* */
export const zeroAppend = function (num: number, places: number, decimal: boolean = false) {
var string = decimal ? num.toFixed(2) : String(num);
while (string.length < places) {
string = "0" + string;
}
return string;
}
export const zeroAppend = function (
num: number,
places: number,
decimal: boolean = false
) {
var string = decimal ? num.toFixed(2) : String(num);
while (string.length < places) {
string = "0" + string;
}
return string;
};
export const zeroPad = function (num: number, places: number) {
var string = String(num);
while (string.length < places) {
string += "0";
}
return string;
}
var string = String(num);
while (string.length < places) {
string += "0";
}
return string;
};
export function similarity(s1: string, s2: string) {
var longer = s1;
var shorter = s2;
if (s1.length < s2.length) {
longer = s2;
shorter = s1;
}
var longerLength = longer.length;
if (longerLength == 0) {
return 1.0;
}
return (longerLength - editDistance(longer, shorter)) / longerLength;
var longer = s1;
var shorter = s2;
if (s1.length < s2.length) {
longer = s2;
shorter = s1;
}
var longerLength = longer.length;
if (longerLength == 0) {
return 1.0;
}
return (longerLength - editDistance(longer, shorter)) / longerLength;
}
export function editDistance(s1: string, s2: string) {
s1 = s1.toLowerCase();
s2 = s2.toLowerCase();
s1 = s1.toLowerCase();
s2 = s2.toLowerCase();
var costs = new Array();
for (var i = 0; i <= s1.length; i++) {
var lastValue = i;
for (var j = 0; j <= s2.length; j++) {
if (i == 0)
costs[j] = j;
else {
if (j > 0) {
var newValue = costs[j - 1];
if (s1.charAt(i - 1) != s2.charAt(j - 1))
newValue = Math.min(Math.min(newValue, lastValue),
costs[j]) + 1;
costs[j - 1] = lastValue;
lastValue = newValue;
}
}
var costs = new Array();
for (var i = 0; i <= s1.length; i++) {
var lastValue = i;
for (var j = 0; j <= s2.length; j++) {
if (i == 0) costs[j] = j;
else {
if (j > 0) {
var newValue = costs[j - 1];
if (s1.charAt(i - 1) != s2.charAt(j - 1))
newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1;
costs[j - 1] = lastValue;
lastValue = newValue;
}
if (i > 0)
costs[s2.length] = lastValue;
}
}
return costs[s2.length];
if (i > 0) costs[s2.length] = lastValue;
}
return costs[s2.length];
}
export function latLngToMGRS(lat: number, lng: number, precision: number = 4): MGRS | false {
export function latLngToMGRS(
lat: number,
lng: number,
precision: number = 4
): MGRS | false {
if (precision < 0 || precision > 6) {
console.error(
"latLngToMGRS: precision must be a number >= 0 and <= 6. Given precision: " +
precision
);
return false;
}
const mgrs = new Converter({}).LLtoMGRS(lat, lng, precision);
const match = mgrs.match(new RegExp(`^(\\d{2})([A-Z])([A-Z])([A-Z])(\\d+)$`));
const easting = match[5].substr(0, match[5].length / 2);
const northing = match[5].substr(match[5].length / 2);
if (precision < 0 || precision > 6) {
console.error("latLngToMGRS: precision must be a number >= 0 and <= 6. Given precision: " + precision);
return false;
}
const mgrs = new Converter({}).LLtoMGRS(lat, lng, precision);
const match = mgrs.match(new RegExp(`^(\\d{2})([A-Z])([A-Z])([A-Z])(\\d+)$`));
const easting = match[5].substr(0, match[5].length / 2);
const northing = match[5].substr(match[5].length / 2);
let output: MGRS = {
bandLetter: match[2],
columnLetter: match[3],
groups: [match[1] + match[2], match[3] + match[4], easting, northing],
easting: easting,
northing: northing,
precision: precision,
rowLetter: match[4],
string: match[0],
zoneNumber: match[1],
};
let output: MGRS = {
bandLetter: match[2],
columnLetter: match[3],
groups: [match[1] + match[2], match[3] + match[4], easting, northing],
easting: easting,
northing: northing,
precision: precision,
rowLetter: match[4],
string: match[0],
zoneNumber: match[1]
}
return output;
return output;
}
export function latLngToUTM(lat: number, lng: number) {
return new Converter({}).LLtoUTM(lat, lng);
return new Converter({}).LLtoUTM(lat, lng);
}
export function latLngToMercator(lat: number, lng: number): { x: number, y: number } {
var rMajor = 6378137; //Equatorial Radius, WGS84
var shift = Math.PI * rMajor;
var x = lng * shift / 180;
var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
y = y * shift / 180;
export function latLngToMercator(
lat: number,
lng: number
): { x: number; y: number } {
var rMajor = 6378137; //Equatorial Radius, WGS84
var shift = Math.PI * rMajor;
var x = (lng * shift) / 180;
var y = Math.log(Math.tan(((90 + lat) * Math.PI) / 360)) / (Math.PI / 180);
y = (y * shift) / 180;
return { x: x, y: y };
return { x: x, y: y };
}
export function mercatorToLatLng(x: number, y: number) {
var rMajor = 6378137; //Equatorial Radius, WGS84
var shift = Math.PI * rMajor;
var lng = x / shift * 180.0;
var lat = y / shift * 180.0;
lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0);
var rMajor = 6378137; //Equatorial Radius, WGS84
var shift = Math.PI * rMajor;
var lng = (x / shift) * 180.0;
var lat = (y / shift) * 180.0;
lat =
(180 / Math.PI) *
(2 * Math.atan(Math.exp((lat * Math.PI) / 180.0)) - Math.PI / 2.0);
return { lng: lng, lat: lat };
return { lng: lng, lat: lat };
}
export function createDivWithClass(className: string) {
var el = document.createElement("div");
el.classList.add(className);
return el;
var el = document.createElement("div");
el.classList.add(className);
return el;
}
export function knotsToMs(knots: number) {
return knots / 1.94384;
return knots / 1.94384;
}
export function msToKnots(ms: number) {
return ms * 1.94384;
return ms * 1.94384;
}
export function ftToM(ft: number) {
return ft * 0.3048;
return ft * 0.3048;
}
export function mToFt(m: number) {
return m / 0.3048;
return m / 0.3048;
}
export function mToNm(m: number) {
return m * 0.000539957;
return m * 0.000539957;
}
export function nmToM(nm: number) {
return nm / 0.000539957;
return nm / 0.000539957;
}
export function nmToFt(nm: number) {
return nm * 6076.12;
return nm * 6076.12;
}
export function polyContains(latlng: LatLng, polygon: Polygon) {
var poly = polygon.toGeoJSON();
return turf.inside(turf.point([latlng.lng, latlng.lat]), poly);
var poly = polygon.toGeoJSON();
return turf.inside(turf.point([latlng.lng, latlng.lat]), poly);
}
export function randomPointInPoly(polygon: Polygon): LatLng {
var bounds = polygon.getBounds();
var x_min = bounds.getEast();
var x_max = bounds.getWest();
var y_min = bounds.getSouth();
var y_max = bounds.getNorth();
var bounds = polygon.getBounds();
var x_min = bounds.getEast();
var x_max = bounds.getWest();
var y_min = bounds.getSouth();
var y_max = bounds.getNorth();
var lat = y_min + (Math.random() * (y_max - y_min));
var lng = x_min + (Math.random() * (x_max - x_min));
var lat = y_min + Math.random() * (y_max - y_min);
var lng = x_min + Math.random() * (x_max - x_min);
var poly = polygon.toGeoJSON();
var inside = turf.inside(turf.point([lng, lat]), poly);
var poly = polygon.toGeoJSON();
var inside = turf.inside(turf.point([lng, lat]), poly);
if (inside) {
return new LatLng(lat, lng);
} else {
return randomPointInPoly(polygon);
}
if (inside) {
return new LatLng(lat, lng);
} else {
return randomPointInPoly(polygon);
}
}
export function polygonArea(polygon: Polygon) {
var poly = polygon.toGeoJSON();
return turf.area(poly);
var poly = polygon.toGeoJSON();
return turf.area(poly);
}
export function randomUnitBlueprint(unitDatabase: UnitDatabase, options: { type?: string, role?: string, ranges?: string[], eras?: string[], coalition?: string }) {
/* Start from all the unit blueprints in the database */
var unitBlueprints = Object.values(unitDatabase.getBlueprints());
export function randomUnitBlueprint(
unitDatabase: UnitDatabase,
options: {
type?: string;
role?: string;
ranges?: string[];
eras?: string[];
coalition?: string;
}
) {
/* Start from all the unit blueprints in the database */
var unitBlueprints = Object.values(unitDatabase.getBlueprints());
/* If a specific type or role is provided, use only the blueprints of that type or role */
if (options.type && options.role) {
console.error("Can't create random unit if both type and role are provided. Either create by type or by role.")
return null;
}
/* If a specific type or role is provided, use only the blueprints of that type or role */
if (options.type && options.role) {
console.error(
"Can't create random unit if both type and role are provided. Either create by type or by role."
);
return null;
}
if (options.type) {
unitBlueprints = unitDatabase.getByType(options.type);
}
else if (options.role) {
unitBlueprints = unitDatabase.getByType(options.role);
}
if (options.type) {
unitBlueprints = unitDatabase.getByType(options.type);
} else if (options.role) {
unitBlueprints = unitDatabase.getByType(options.role);
}
/* Keep only the units that have a range included in the requested values */
if (options.ranges) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
var rangeType = "";
var range = unitBlueprint.acquisitionRange;
if (range !== undefined) {
if (range >= 0 && range < 10000)
rangeType = "Short range";
else if (range >= 10000 && range < 100000)
rangeType = "Medium range";
else if (range >= 100000 && range < 999999)
rangeType = "Long range";
}
return options.ranges?.includes(rangeType);
});
}
/* Keep only the units that have a range included in the requested values */
if (options.ranges) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
var rangeType = "";
var range = unitBlueprint.acquisitionRange;
if (range !== undefined) {
if (range >= 0 && range < 10000) rangeType = "Short range";
else if (range >= 10000 && range < 100000) rangeType = "Medium range";
else if (range >= 100000 && range < 999999) rangeType = "Long range";
}
return options.ranges?.includes(rangeType);
});
}
/* Keep only the units that have an era included in the requested values */
if (options.eras) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
return unitBlueprint.era ? options.eras?.includes(unitBlueprint.era) : true;
});
}
/* Keep only the units that have an era included in the requested values */
if (options.eras) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
return unitBlueprint.era
? options.eras?.includes(unitBlueprint.era)
: true;
});
}
/* Keep only the units that have the correct coalition, if selected */
if (options.coalition) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
return (unitBlueprint.coalition && unitBlueprint.coalition !== "") ? options.coalition === unitBlueprint.coalition : true;
});
}
/* Keep only the units that have the correct coalition, if selected */
if (options.coalition) {
unitBlueprints = unitBlueprints.filter((unitBlueprint: UnitBlueprint) => {
return unitBlueprint.coalition && unitBlueprint.coalition !== ""
? options.coalition === unitBlueprint.coalition
: true;
});
}
var index = Math.floor(Math.random() * unitBlueprints.length);
return unitBlueprints[index];
var index = Math.floor(Math.random() * unitBlueprints.length);
return unitBlueprints[index];
}
export function getMarkerCategoryByName(name: string) {
if (aircraftDatabase.getByName(name) != null)
return "aircraft";
else if (helicopterDatabase.getByName(name) != null)
return "helicopter";
else if (groundUnitDatabase.getByName(name) != null) {
var type = groundUnitDatabase.getByName(name)?.type ?? "";
if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type))
return "groundunit-sam";
else
return "groundunit-other";
}
else if (navyUnitDatabase.getByName(name) != null)
return "navyunit";
else
return "aircraft"; // TODO add other unit types
if (aircraftDatabase.getByName(name) != null) return "aircraft";
else if (helicopterDatabase.getByName(name) != null) return "helicopter";
else if (groundUnitDatabase.getByName(name) != null) {
var type = groundUnitDatabase.getByName(name)?.type ?? "";
if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type))
return "groundunit-sam";
else return "groundunit-other";
} else if (navyUnitDatabase.getByName(name) != null) return "navyunit";
else return "aircraft"; // TODO add other unit types
}
export function getUnitDatabaseByCategory(category: string) {
if (category.toLowerCase() == "aircraft")
return aircraftDatabase;
else if (category.toLowerCase() == "helicopter")
return helicopterDatabase;
else if (category.toLowerCase().includes("groundunit"))
return groundUnitDatabase;
else if (category.toLowerCase().includes("navyunit"))
return navyUnitDatabase;
else
return null;
if (category.toLowerCase() == "aircraft") return aircraftDatabase;
else if (category.toLowerCase() == "helicopter") return helicopterDatabase;
else if (category.toLowerCase().includes("groundunit"))
return groundUnitDatabase;
else if (category.toLowerCase().includes("navyunit")) return navyUnitDatabase;
else return null;
}
export function getUnitCategoryByBlueprint(blueprint: UnitBlueprint) {
for (let database of [getApp()?.getAircraftDatabase(), getApp()?.getHelicopterDatabase(), getApp()?.getGroundUnitDatabase(), getApp()?.getNavyUnitDatabase()]) {
if (blueprint.name in database.blueprints)
return database.getCategory();
}
return "unknown";
for (let database of [
getApp()?.getAircraftDatabase(),
getApp()?.getHelicopterDatabase(),
getApp()?.getGroundUnitDatabase(),
getApp()?.getNavyUnitDatabase(),
]) {
if (blueprint.name in database.blueprints) return database.getCategory();
}
return "unknown";
}
export function base64ToBytes(base64: string) {
//return Buffer.from(base64, 'base64').buffer;
//return Buffer.from(base64, 'base64').buffer;
}
export function enumToState(state: number) {
if (state < states.length)
return states[state];
else
return states[0];
if (state < states.length) return states[state];
else return states[0];
}
export function enumToROE(ROE: number) {
if (ROE < ROEs.length)
return ROEs[ROE];
else
return ROEs[0];
if (ROE < ROEs.length) return ROEs[ROE];
else return ROEs[0];
}
export function enumToReactionToThreat(reactionToThreat: number) {
if (reactionToThreat < reactionsToThreat.length)
return reactionsToThreat[reactionToThreat];
else
return reactionsToThreat[0];
if (reactionToThreat < reactionsToThreat.length)
return reactionsToThreat[reactionToThreat];
else return reactionsToThreat[0];
}
export function enumToEmissioNCountermeasure(emissionCountermeasure: number) {
if (emissionCountermeasure < emissionsCountermeasures.length)
return emissionsCountermeasures[emissionCountermeasure];
else
return emissionsCountermeasures[0];
if (emissionCountermeasure < emissionsCountermeasures.length)
return emissionsCountermeasures[emissionCountermeasure];
else return emissionsCountermeasures[0];
}
export function enumToCoalition(coalitionID: number) {
switch (coalitionID) {
case 0: return "neutral";
case 1: return "red";
case 2: return "blue";
}
return "";
switch (coalitionID) {
case 0:
return "neutral";
case 1:
return "red";
case 2:
return "blue";
}
return "";
}
export function coalitionToEnum(coalition: string) {
switch (coalition) {
case "neutral": return 0;
case "red": return 1;
case "blue": return 2;
}
return 0;
switch (coalition) {
case "neutral":
return 0;
case "red":
return 1;
case "blue":
return 2;
}
return 0;
}
export function convertDateAndTimeToDate(dateAndTime: DateAndTime) {
const date = dateAndTime.date;
const time = dateAndTime.time;
const date = dateAndTime.date;
const time = dateAndTime.time;
if (!date) {
return new Date();
}
if (!date) {
return new Date();
}
let year = date.Year;
let month = date.Month - 1;
let year = date.Year;
let month = date.Month - 1;
if (month < 0) {
month = 11;
year--;
}
if (month < 0) {
month = 11;
year--;
}
return new Date(year, month, date.Day, time.h, time.m, time.s);
return new Date(year, month, date.Day, time.h, time.m, time.s);
}
export function getGroundElevation(latlng: LatLng, callback: CallableFunction) {
/* Get the ground elevation from the server endpoint */
const xhr = new XMLHttpRequest();
xhr.open('GET', `api/elevation/${latlng.lat}/${latlng.lng}`, true);
xhr.timeout = 500; // ms
xhr.responseType = 'json';
xhr.onload = () => {
var status = xhr?.status;
if (status === 200) {
callback(xhr.response)
}
};
xhr.send();
/* Get the ground elevation from the server endpoint */
const xhr = new XMLHttpRequest();
xhr.open("GET", `api/elevation/${latlng.lat}/${latlng.lng}`, true);
xhr.timeout = 500; // ms
xhr.responseType = "json";
xhr.onload = () => {
var status = xhr?.status;
if (status === 200) {
callback(xhr.response);
}
};
xhr.send();
}
export function getWikipediaEntry(search: string, callback: CallableFunction) {
/* Get the ground elevation from the server endpoint */
const xhr = new XMLHttpRequest();
xhr.open('GET', `https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts|pageimages&exintro&explaintext&generator=search&gsrsearch=intitle:${search}&gsrlimit=1&redirects=1&origin=*`, true);
xhr.timeout = 500; // ms
xhr.responseType = 'json';
xhr.onload = () => {
var status = xhr?.status;
if (status === 200) {
callback(xhr.response)
}
};
xhr.send();
}
/* Get the ground elevation from the server endpoint */
const xhr = new XMLHttpRequest();
xhr.open(
"GET",
`https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts|pageimages&exintro&explaintext&generator=search&gsrsearch=intitle:${search}&gsrlimit=1&redirects=1&origin=*`,
true
);
xhr.timeout = 500; // ms
xhr.responseType = "json";
xhr.onload = () => {
var status = xhr?.status;
if (status === 200) {
callback(xhr.response);
}
};
xhr.send();
}

View File

@@ -1,164 +1,176 @@
import { LatLng } from "leaflet";
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../interfaces";
import {
Ammo,
Contact,
GeneralSettings,
Offset,
Radio,
TACAN,
} from "../interfaces";
export class DataExtractor {
#seekPosition = 0;
#dataview: DataView;
#decoder: TextDecoder;
#buffer: ArrayBuffer;
#seekPosition = 0;
#dataview: DataView;
#decoder: TextDecoder;
#buffer: ArrayBuffer;
constructor(buffer: ArrayBuffer) {
this.#buffer = buffer;
this.#dataview = new DataView(this.#buffer);
this.#decoder = new TextDecoder("utf-8");
}
constructor(buffer: ArrayBuffer) {
this.#buffer = buffer;
this.#dataview = new DataView(this.#buffer);
this.#decoder = new TextDecoder("utf-8");
}
setSeekPosition(seekPosition: number) {
this.#seekPosition = seekPosition;
}
setSeekPosition(seekPosition: number) {
this.#seekPosition = seekPosition;
}
getSeekPosition() {
return this.#seekPosition;
}
getSeekPosition() {
return this.#seekPosition;
}
extractBool() {
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value > 0;
}
extractBool() {
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value > 0;
}
extractUInt8() {
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value;
}
extractUInt8() {
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value;
}
extractUInt16() {
const value = this.#dataview.getUint16(this.#seekPosition, true);
this.#seekPosition += 2;
return value;
}
extractUInt16() {
const value = this.#dataview.getUint16(this.#seekPosition, true);
this.#seekPosition += 2;
return value;
}
extractUInt32() {
const value = this.#dataview.getUint32(this.#seekPosition, true);
this.#seekPosition += 4;
return value;
}
extractUInt32() {
const value = this.#dataview.getUint32(this.#seekPosition, true);
this.#seekPosition += 4;
return value;
}
extractUInt64() {
const value = this.#dataview.getBigUint64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractUInt64() {
const value = this.#dataview.getBigUint64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractFloat64() {
const value = this.#dataview.getFloat64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractFloat64() {
const value = this.#dataview.getFloat64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractLatLng() {
return new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64())
}
extractLatLng() {
return new LatLng(
this.extractFloat64(),
this.extractFloat64(),
this.extractFloat64()
);
}
extractFromBitmask(bitmask: number, position: number) {
return ((bitmask >> position) & 1) > 0;
}
extractFromBitmask(bitmask: number, position: number) {
return ((bitmask >> position) & 1) > 0;
}
extractString(length?: number) {
if (length === undefined)
length = this.extractUInt16()
var stringBuffer = this.#buffer.slice(this.#seekPosition, this.#seekPosition + length);
var view = new Int8Array(stringBuffer);
var stringLength = length;
view.every((value: number, idx: number) => {
if (value === 0) {
stringLength = idx;
return false;
} else
return true;
});
const value = this.#decoder.decode(stringBuffer);
this.#seekPosition += length;
return value.substring(0, stringLength).trim();
}
extractString(length?: number) {
if (length === undefined) length = this.extractUInt16();
var stringBuffer = this.#buffer.slice(
this.#seekPosition,
this.#seekPosition + length
);
var view = new Int8Array(stringBuffer);
var stringLength = length;
view.every((value: number, idx: number) => {
if (value === 0) {
stringLength = idx;
return false;
} else return true;
});
const value = this.#decoder.decode(stringBuffer);
this.#seekPosition += length;
return value.substring(0, stringLength).trim();
}
extractChar() {
return this.extractString(1);
}
extractChar() {
return this.extractString(1);
}
extractTACAN() {
const value: TACAN = {
isOn: this.extractBool(),
channel: this.extractUInt8(),
XY: this.extractChar(),
callsign: this.extractString(4)
}
return value;
}
extractTACAN() {
const value: TACAN = {
isOn: this.extractBool(),
channel: this.extractUInt8(),
XY: this.extractChar(),
callsign: this.extractString(4),
};
return value;
}
extractRadio() {
const value: Radio = {
frequency: this.extractUInt32(),
callsign: this.extractUInt8(),
callsignNumber: this.extractUInt8()
}
return value;
}
extractRadio() {
const value: Radio = {
frequency: this.extractUInt32(),
callsign: this.extractUInt8(),
callsignNumber: this.extractUInt8(),
};
return value;
}
extractGeneralSettings() {
const value: GeneralSettings = {
prohibitJettison: this.extractBool(),
prohibitAA: this.extractBool(),
prohibitAG: this.extractBool(),
prohibitAfterburner: this.extractBool(),
prohibitAirWpn: this.extractBool(),
}
return value;
}
extractGeneralSettings() {
const value: GeneralSettings = {
prohibitJettison: this.extractBool(),
prohibitAA: this.extractBool(),
prohibitAG: this.extractBool(),
prohibitAfterburner: this.extractBool(),
prohibitAirWpn: this.extractBool(),
};
return value;
}
extractAmmo() {
const value: Ammo[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
quantity: this.extractUInt16(),
name: this.extractString(33),
guidance: this.extractUInt8(),
category: this.extractUInt8(),
missileCategory: this.extractUInt8()
});
}
return value;
extractAmmo() {
const value: Ammo[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
quantity: this.extractUInt16(),
name: this.extractString(33),
guidance: this.extractUInt8(),
category: this.extractUInt8(),
missileCategory: this.extractUInt8(),
});
}
return value;
}
extractContacts(){
const value: Contact[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
ID: this.extractUInt32(),
detectionMethod: this.extractUInt8()
});
}
return value;
extractContacts() {
const value: Contact[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
ID: this.extractUInt32(),
detectionMethod: this.extractUInt8(),
});
}
return value;
}
extractActivePath() {
const value: LatLng[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push(this.extractLatLng());
}
return value;
extractActivePath() {
const value: LatLng[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push(this.extractLatLng());
}
return value;
}
extractOffset() {
const value: Offset = {
x: this.extractFloat64(),
y: this.extractFloat64(),
z: this.extractFloat64(),
}
return value;
}
}
extractOffset() {
const value: Offset = {
x: this.extractFloat64(),
y: this.extractFloat64(),
z: this.extractFloat64(),
};
return value;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,48 +1,63 @@
import { getApp } from "../olympusapp";
import { ShortcutKeyboardOptions, ShortcutMouseOptions, ShortcutOptions } from "../interfaces";
import {
ShortcutKeyboardOptions,
ShortcutMouseOptions,
ShortcutOptions,
} from "../interfaces";
import { keyEventWasInInput } from "../other/utils";
export abstract class Shortcut {
#config: ShortcutOptions
#config: ShortcutOptions;
constructor(config: ShortcutOptions) {
this.#config = config;
}
constructor(config: ShortcutOptions) {
this.#config = config;
}
getConfig() {
return this.#config;
}
getConfig() {
return this.#config;
}
}
export class ShortcutKeyboard extends Shortcut {
constructor(config: ShortcutKeyboardOptions) {
config.event = config.event || "keyup";
super(config);
constructor(config: ShortcutKeyboardOptions) {
config.event = config.event || "keyup";
super(config);
document.addEventListener(config.event, (ev: any) => {
if ( typeof config.context === "string" && getApp().getCurrentContext() !== config.context ) {
return;
}
document.addEventListener(config.event, (ev: any) => {
if (
typeof config.context === "string" &&
getApp().getCurrentContext() !== config.context
) {
return;
}
if (ev instanceof KeyboardEvent === false || keyEventWasInInput(ev)) {
return;
}
if (ev instanceof KeyboardEvent === false || keyEventWasInInput(ev)) {
return;
}
if (config.code !== ev.code) {
return;
}
if (config.code !== ev.code) {
return;
}
if (((typeof config.altKey !== "boolean") || (typeof config.altKey === "boolean" && ev.altKey === config.altKey))
&& ((typeof config.ctrlKey !== "boolean") || (typeof config.ctrlKey === "boolean" && ev.ctrlKey === config.ctrlKey))
&& ((typeof config.shiftKey !== "boolean") || (typeof config.shiftKey === "boolean" && ev.shiftKey === config.shiftKey))) {
config.callback(ev);
}
});
}
if (
(typeof config.altKey !== "boolean" ||
(typeof config.altKey === "boolean" &&
ev.altKey === config.altKey)) &&
(typeof config.ctrlKey !== "boolean" ||
(typeof config.ctrlKey === "boolean" &&
ev.ctrlKey === config.ctrlKey)) &&
(typeof config.shiftKey !== "boolean" ||
(typeof config.shiftKey === "boolean" &&
ev.shiftKey === config.shiftKey))
) {
config.callback(ev);
}
});
}
}
export class ShortcutMouse extends Shortcut {
constructor(config: ShortcutMouseOptions) {
super(config);
}
}
constructor(config: ShortcutMouseOptions) {
super(config);
}
}

View File

@@ -4,177 +4,266 @@ import { getApp } from "../olympusapp";
import { ShortcutKeyboard, ShortcutMouse } from "./shortcut";
export class ShortcutManager {
#items: { [key: string]: any } = {};
#keysBeingHeld: string[] = [];
#keyDownCallbacks: CallableFunction[] = [];
#keyUpCallbacks: CallableFunction[] = [];
#items: { [key: string]: any } = {};
#keysBeingHeld: string[] = [];
#keyDownCallbacks: CallableFunction[] = [];
#keyUpCallbacks: CallableFunction[] = [];
constructor() {
document.addEventListener("keydown", (ev: KeyboardEvent) => {
if (this.#keysBeingHeld.indexOf(ev.code) < 0) {
this.#keysBeingHeld.push(ev.code);
}
this.#keyDownCallbacks.forEach((callback) => callback(ev));
});
constructor() {
document.addEventListener("keydown", (ev: KeyboardEvent) => {
if (this.#keysBeingHeld.indexOf(ev.code) < 0) {
this.#keysBeingHeld.push(ev.code)
}
this.#keyDownCallbacks.forEach(callback => callback(ev));
});
document.addEventListener("keyup", (ev: KeyboardEvent) => {
this.#keysBeingHeld = this.#keysBeingHeld.filter(
(held) => held !== ev.code
);
this.#keyUpCallbacks.forEach((callback) => callback(ev));
});
document.addEventListener("keyup", (ev: KeyboardEvent) => {
this.#keysBeingHeld = this.#keysBeingHeld.filter(held => held !== ev.code);
this.#keyUpCallbacks.forEach(callback => callback(ev));
});
this.addKeyboardShortcut("togglePause", {
altKey: false,
callback: () => {
getApp()
.getServerManager()
.setPaused(!getApp().getServerManager().getPaused());
},
code: "Space",
context: DEFAULT_CONTEXT,
ctrlKey: false,
})
.addKeyboardShortcut("deselectAll", {
callback: (ev: KeyboardEvent) => {
getApp().getUnitsManager().deselectAllUnits();
},
code: "Escape",
context: DEFAULT_CONTEXT,
})
.addKeyboardShortcut("toggleUnitLabels", {
altKey: false,
callback: () => {
getApp()
.getMap()
.setOption(
"showUnitLabels",
!getApp().getMap().getOptions().showUnitLabels
);
},
code: "KeyL",
context: DEFAULT_CONTEXT,
ctrlKey: false,
shiftKey: false,
})
.addKeyboardShortcut("toggleAcquisitionRings", {
altKey: false,
callback: () => {
getApp()
.getMap()
.setOption(
"showUnitsAcquisitionRings",
!getApp().getMap().getOptions().showUnitsAcquisitionRings
);
},
code: "KeyE",
context: DEFAULT_CONTEXT,
ctrlKey: false,
shiftKey: false,
})
.addKeyboardShortcut("toggleEngagementRings", {
altKey: false,
callback: () => {
getApp()
.getMap()
.setOption(
"showUnitsEngagementRings",
!getApp().getMap().getOptions().showUnitsEngagementRings
);
},
code: "KeyQ",
context: DEFAULT_CONTEXT,
ctrlKey: false,
shiftKey: false,
})
.addKeyboardShortcut("toggleHideShortEngagementRings", {
altKey: false,
callback: () => {
getApp()
.getMap()
.setOption(
"hideUnitsShortRangeRings",
!getApp().getMap().getOptions().hideUnitsShortRangeRings
);
},
code: "KeyR",
context: DEFAULT_CONTEXT,
ctrlKey: false,
shiftKey: false,
})
.addKeyboardShortcut("toggleDetectionLines", {
altKey: false,
callback: () => {
getApp()
.getMap()
.setOption(
"showUnitTargets",
!getApp().getMap().getOptions().showUnitTargets
);
},
code: "KeyF",
context: DEFAULT_CONTEXT,
ctrlKey: false,
shiftKey: false,
})
.addKeyboardShortcut("toggleGroupMembers", {
altKey: false,
callback: () => {
getApp()
.getMap()
.setOption(
"hideGroupMembers",
!getApp().getMap().getOptions().hideGroupMembers
);
},
code: "KeyG",
context: DEFAULT_CONTEXT,
ctrlKey: false,
shiftKey: false,
})
.addKeyboardShortcut("increaseCameraZoom", {
altKey: true,
callback: () => {
//getApp().getMap().increaseCameraZoom();
},
code: "Equal",
context: DEFAULT_CONTEXT,
ctrlKey: false,
shiftKey: false,
})
.addKeyboardShortcut("decreaseCameraZoom", {
altKey: true,
callback: () => {
//getApp().getMap().decreaseCameraZoom();
},
code: "Minus",
context: DEFAULT_CONTEXT,
ctrlKey: false,
shiftKey: false,
});
this.addKeyboardShortcut("togglePause", {
"altKey": false,
"callback": () => {
getApp().getServerManager().setPaused(!getApp().getServerManager().getPaused());
},
"code": "Space",
"context": DEFAULT_CONTEXT,
"ctrlKey": false
}).addKeyboardShortcut("deselectAll", {
"callback": (ev: KeyboardEvent) => {
getApp().getUnitsManager().deselectAllUnits();
},
"code": "Escape",
"context": DEFAULT_CONTEXT
}).addKeyboardShortcut("toggleUnitLabels", {
"altKey": false,
"callback": () => { getApp().getMap().setOption("showUnitLabels", !getApp().getMap().getOptions().showUnitLabels) },
"code": "KeyL",
"context": DEFAULT_CONTEXT,
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("toggleAcquisitionRings", {
"altKey": false,
"callback": () => { getApp().getMap().setOption("showUnitsAcquisitionRings", !getApp().getMap().getOptions().showUnitsAcquisitionRings) },
"code": "KeyE",
"context": DEFAULT_CONTEXT,
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("toggleEngagementRings", {
"altKey": false,
"callback": () => { getApp().getMap().setOption("showUnitsEngagementRings", !getApp().getMap().getOptions().showUnitsEngagementRings) },
"code": "KeyQ",
"context": DEFAULT_CONTEXT,
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("toggleHideShortEngagementRings", {
"altKey": false,
"callback": () => { getApp().getMap().setOption("hideUnitsShortRangeRings", !getApp().getMap().getOptions().hideUnitsShortRangeRings) },
"code": "KeyR",
"context": DEFAULT_CONTEXT,
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("toggleDetectionLines", {
"altKey": false,
"callback": () => { getApp().getMap().setOption("showUnitTargets", !getApp().getMap().getOptions().showUnitTargets) },
"code": "KeyF",
"context": DEFAULT_CONTEXT,
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("toggleGroupMembers", {
"altKey": false,
"callback": () => { getApp().getMap().setOption("hideGroupMembers", !getApp().getMap().getOptions().hideGroupMembers) },
"code": "KeyG",
"context": DEFAULT_CONTEXT,
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("increaseCameraZoom", {
"altKey": true,
"callback": () => {
//getApp().getMap().increaseCameraZoom();
},
"code": "Equal",
"context": DEFAULT_CONTEXT,
"ctrlKey": false,
"shiftKey": false
}).addKeyboardShortcut("decreaseCameraZoom", {
"altKey": true,
"callback": () => {
//getApp().getMap().decreaseCameraZoom();
},
"code": "Minus",
"context": DEFAULT_CONTEXT,
"ctrlKey": false,
"shiftKey": false
});
[
"KeyW",
"KeyA",
"KeyS",
"KeyD",
"ArrowLeft",
"ArrowRight",
"ArrowUp",
"ArrowDown",
].forEach((code) => {
this.addKeyboardShortcut(`pan${code}keydown`, {
altKey: false,
callback: (ev: KeyboardEvent) => {
//getApp().getMap().handleMapPanning(ev);
},
code: code,
context: DEFAULT_CONTEXT,
ctrlKey: false,
event: "keydown",
});
["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => {
this.addKeyboardShortcut(`pan${code}keydown`, {
"altKey": false,
"callback": (ev: KeyboardEvent) => {
//getApp().getMap().handleMapPanning(ev);
},
"code": code,
"context": DEFAULT_CONTEXT,
"ctrlKey": false,
"event": "keydown"
});
this.addKeyboardShortcut(`pan${code}keyup`, {
callback: (ev: KeyboardEvent) => {
//getApp().getMap().handleMapPanning(ev);
},
code: code,
context: DEFAULT_CONTEXT,
});
});
this.addKeyboardShortcut(`pan${code}keyup`, {
"callback": (ev: KeyboardEvent) => {
//getApp().getMap().handleMapPanning(ev);
},
"code": code,
"context": DEFAULT_CONTEXT
});
});
const digits = [
"Digit1",
"Digit2",
"Digit3",
"Digit4",
"Digit5",
"Digit6",
"Digit7",
"Digit8",
"Digit9",
];
const digits = ["Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9"];
digits.forEach((code) => {
this.addKeyboardShortcut(`hotgroup${code}`, {
altKey: false,
callback: (ev: KeyboardEvent) => {
if (ev.ctrlKey && ev.shiftKey)
getApp()
.getUnitsManager()
.selectUnitsByHotgroup(parseInt(ev.code.substring(5)), false); // "Select hotgroup X in addition to any units already selected"
else if (ev.ctrlKey && !ev.shiftKey)
getApp()
.getUnitsManager()
.setHotgroup(parseInt(ev.code.substring(5))); // "These selected units are hotgroup X (forget any previous membership)"
else if (!ev.ctrlKey && ev.shiftKey)
getApp()
.getUnitsManager()
.addToHotgroup(parseInt(ev.code.substring(5))); // "Add (append) these units to hotgroup X (in addition to any existing members)"
else
getApp()
.getUnitsManager()
.selectUnitsByHotgroup(parseInt(ev.code.substring(5))); // "Select hotgroup X, deselect any units not in it."
},
code: code,
});
digits.forEach(code => {
this.addKeyboardShortcut(`hotgroup${code}`, {
"altKey": false,
"callback": (ev: KeyboardEvent) => {
if (ev.ctrlKey && ev.shiftKey)
getApp().getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5)), false); // "Select hotgroup X in addition to any units already selected"
else if (ev.ctrlKey && !ev.shiftKey)
getApp().getUnitsManager().setHotgroup(parseInt(ev.code.substring(5))); // "These selected units are hotgroup X (forget any previous membership)"
else if (!ev.ctrlKey && ev.shiftKey)
getApp().getUnitsManager().addToHotgroup(parseInt(ev.code.substring(5))); // "Add (append) these units to hotgroup X (in addition to any existing members)"
else
getApp().getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5))); // "Select hotgroup X, deselect any units not in it."
},
"code": code
});
// Stop hotgroup controls sending the browser to another tab
document.addEventListener("keydown", (ev: KeyboardEvent) => {
if (ev.code === code && ev.ctrlKey === true && ev.altKey === false && ev.shiftKey === false) {
ev.preventDefault();
}
});
});
}
addKeyboardShortcut(name: string, shortcutKeyboardOptions: ShortcutKeyboardOptions) {
this.#items[name] = new ShortcutKeyboard(shortcutKeyboardOptions);
return this;
}
addMouseShortcut(name: string, shortcutMouseOptions: ShortcutMouseOptions) {
this.#items[name] = new ShortcutMouse(shortcutMouseOptions);
return this;
}
getKeysBeingHeld() {
return this.#keysBeingHeld;
}
keyComboMatches(combo: string[]) {
const heldKeys = this.getKeysBeingHeld();
if (combo.length !== heldKeys.length) {
return false;
// Stop hotgroup controls sending the browser to another tab
document.addEventListener("keydown", (ev: KeyboardEvent) => {
if (
ev.code === code &&
ev.ctrlKey === true &&
ev.altKey === false &&
ev.shiftKey === false
) {
ev.preventDefault();
}
});
});
}
return combo.every(key => heldKeys.indexOf(key) > -1);
addKeyboardShortcut(
name: string,
shortcutKeyboardOptions: ShortcutKeyboardOptions
) {
this.#items[name] = new ShortcutKeyboard(shortcutKeyboardOptions);
return this;
}
addMouseShortcut(name: string, shortcutMouseOptions: ShortcutMouseOptions) {
this.#items[name] = new ShortcutMouse(shortcutMouseOptions);
return this;
}
getKeysBeingHeld() {
return this.#keysBeingHeld;
}
keyComboMatches(combo: string[]) {
const heldKeys = this.getKeysBeingHeld();
if (combo.length !== heldKeys.length) {
return false;
}
onKeyDown(callback: CallableFunction) {
this.#keyDownCallbacks.push(callback);
}
return combo.every((key) => heldKeys.indexOf(key) > -1);
}
onKeyUp(callback: CallableFunction) {
this.#keyUpCallbacks.push(callback);
}
}
onKeyDown(callback: CallableFunction) {
this.#keyDownCallbacks.push(callback);
}
onKeyUp(callback: CallableFunction) {
this.#keyUpCallbacks.push(callback);
}
}

View File

@@ -1,18 +1,21 @@
import { createContext } from "react";
import { MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from "./constants/constants";
import {
MAP_HIDDEN_TYPES_DEFAULTS,
MAP_OPTIONS_DEFAULTS,
} from "./constants/constants";
export const StateContext = createContext({
mainMenuVisible: false,
spawnMenuVisible: false,
unitControlMenuVisible: false,
measureMenuVisible: false,
drawingMenuVisible: false,
optionsMenuVisible: false,
mapHiddenTypes: MAP_HIDDEN_TYPES_DEFAULTS,
mapOptions: MAP_OPTIONS_DEFAULTS,
mapSources: [] as string[],
activeMapSource: ""
})
mainMenuVisible: false,
spawnMenuVisible: false,
unitControlMenuVisible: false,
measureMenuVisible: false,
drawingMenuVisible: false,
optionsMenuVisible: false,
mapHiddenTypes: MAP_HIDDEN_TYPES_DEFAULTS,
mapOptions: MAP_OPTIONS_DEFAULTS,
mapSources: [] as string[],
activeMapSource: "",
});
export const StateProvider = StateContext.Provider;
export const StateConsumer = StateContext.Consumer;

View File

@@ -1,53 +1,53 @@
/* Types definition */
export type MapMarkerVisibilityControl = {
"category"?: string;
"image": string;
"isProtected"?: boolean,
"name": string,
"protectable"?: boolean,
"toggles": string[],
"tooltip": string
}
category?: string;
image: string;
isProtected?: boolean;
name: string;
protectable?: boolean;
toggles: string[];
tooltip: string;
};
export type MapOptions = {
hideGroupMembers: boolean,
hideUnitsShortRangeRings: boolean,
showUnitContacts: boolean,
showUnitPaths: boolean,
showUnitTargets: boolean,
showUnitLabels: boolean,
showUnitsEngagementRings: boolean,
showUnitsAcquisitionRings: boolean,
fillSelectedRing: boolean
}
hideGroupMembers: boolean;
hideUnitsShortRangeRings: boolean;
showUnitContacts: boolean;
showUnitPaths: boolean;
showUnitTargets: boolean;
showUnitLabels: boolean;
showUnitsEngagementRings: boolean;
showUnitsAcquisitionRings: boolean;
fillSelectedRing: boolean;
};
export type MapHiddenTypes = {
'human': boolean,
'olympus': boolean,
'dcs': boolean,
'aircraft': boolean,
'helicopter': boolean,
'groundunit-sam': boolean,
'groundunit': boolean,
'navyunit': boolean,
'airbase': boolean,
'dead': boolean,
'blue': boolean,
'red': boolean,
'neutral': boolean
}
human: boolean;
olympus: boolean;
dcs: boolean;
aircraft: boolean;
helicopter: boolean;
"groundunit-sam": boolean;
groundunit: boolean;
navyunit: boolean;
airbase: boolean;
dead: boolean;
blue: boolean;
red: boolean;
neutral: boolean;
};
export type MGRS = {
bandLetter: string,
columnLetter: string,
easting: string,
groups: string[],
northing: string,
precision: number,
rowLetter: string,
string: string,
zoneNumber: string
}
bandLetter: string;
columnLetter: string;
easting: string;
groups: string[];
northing: string;
precision: number;
rowLetter: string;
string: string;
zoneNumber: string;
};
export type Coalition = "blue" | "neutral" | "red";

View File

@@ -1,42 +1,112 @@
import React, { useEffect, useRef, useState } from "react"
import React, { useEffect, useRef, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowCircleDown } from "@fortawesome/free-solid-svg-icons";
export function OlAccordion(props: {
title: string,
children?: JSX.Element | JSX.Element[],
showArrows?: boolean
title: string;
children?: JSX.Element | JSX.Element[];
showArrows?: boolean;
}) {
var [open, setOpen] = useState(false);
var [scrolledUp, setScrolledUp] = useState(true);
var [scrolledDown, setScrolledDown] = useState(false);
var [open, setOpen] = useState(false);
var [scrolledUp, setScrolledUp] = useState(true);
var [scrolledDown, setScrolledDown] = useState(false);
var contentRef = useRef(null);
var contentRef = useRef(null);
useEffect(() => {
contentRef.current && (contentRef.current as HTMLElement).children[0]?.addEventListener('scroll', (e: any) => {
if (e.target.clientHeight < e.target.scrollHeight) {
setScrolledDown(e.target.scrollTop === (e.target.scrollHeight - e.target.offsetHeight));
setScrolledUp(e.target.scrollTop === 0);
}
})
})
useEffect(() => {
contentRef.current &&
(contentRef.current as HTMLElement).children[0]?.addEventListener(
"scroll",
(e: any) => {
if (e.target.clientHeight < e.target.scrollHeight) {
setScrolledDown(
e.target.scrollTop ===
e.target.scrollHeight - e.target.offsetHeight
);
setScrolledUp(e.target.scrollTop === 0);
}
}
);
});
return <div className="bg-white dark:bg-transparent text-gray-900 dark:text-white">
<h3>
<button type="button" onClick={() => setOpen(!open)} className="flex items-center justify-between w-full py-2 rtl:text-right text-gray-700 border-gray-200 dark:border-gray-700 dark:text-white gap-3 hover:dark:underline hover:dark:underline-offset-4 hover:dark:underline-gray-700">
<span className="font-normal">{props.title}</span>
<svg data-open={open} className="w-3 h-3 dark:text-olympus-50 -rotate-180 data-[open='false']:-rotate-90 shrink-0 transition-transform" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 5 5 1 1 5" />
</svg>
</button>
</h3>
<div className={open? "": "hidden"}>
{props.showArrows && <div className="rotate-180"> {!scrolledUp && <FontAwesomeIcon icon={faArrowCircleDown} className="text-white animate-bounce opacity-20 absolute w-full"/>}</div>}
<div ref={contentRef} className="py-2 border-gray-200 dark:border-gray-700">
{props.children}
</div>
{props.showArrows && <div>{!scrolledDown && <FontAwesomeIcon icon={faArrowCircleDown} className="text-white animate-bounce opacity-20 absolute w-full"/>}</div>}
</div>
</div>
}
return (
<div
className={`
bg-white text-gray-900
dark:bg-transparent dark:text-white
`}
>
<h3>
<button
type="button"
onClick={() => setOpen(!open)}
className={`
flex w-full items-center justify-between gap-3 border-gray-200 py-2
text-gray-700
dark:border-gray-700 dark:text-white
hover:dark:underline hover:dark:underline-offset-4
hover:dark:underline-gray-700
rtl:text-right
`}
>
<span className="font-normal">{props.title}</span>
<svg
data-open={open}
className={`
h-3 w-3 shrink-0 -rotate-180 transition-transform
dark:text-olympus-50
data-[open='false']:-rotate-90
`}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 10 6"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5 5 1 1 5"
/>
</svg>
</button>
</h3>
<div className={open ? "" : "hidden"}>
{props.showArrows && (
<div className="rotate-180">
{" "}
{!scrolledUp && (
<FontAwesomeIcon
icon={faArrowCircleDown}
className={`
absolute w-full animate-bounce text-white opacity-20
`}
/>
)}
</div>
)}
<div
ref={contentRef}
className={`
border-gray-200 py-2
dark:border-gray-700
`}
>
{props.children}
</div>
{props.showArrows && (
<div>
{!scrolledDown && (
<FontAwesomeIcon
icon={faArrowCircleDown}
className={`
absolute w-full animate-bounce text-white opacity-20
`}
/>
)}
</div>
)}
</div>
</div>
);
}

View File

@@ -3,19 +3,37 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React from "react";
export function OlButtonGroup(props: {
children?: JSX.Element | JSX.Element[]
children?: JSX.Element | JSX.Element[];
}) {
return <div className="inline-flex rounded-md shadow-sm" >
{props.children}
</div>
return (
<div className="inline-flex rounded-md shadow-sm">{props.children}</div>
);
}
export function OlButtonGroupItem(props: {
icon: IconProp
active: boolean,
onClick: () => void
icon: IconProp;
active: boolean;
onClick: () => void;
}) {
return <button onClick={props.onClick} type="button" data-active={props.active} className="h-11 w-11 first-of-type:rounded-s-md last-of-type:rounded-e-md py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-700 dark:data-[active='true']:bg-blue-500 dark:data-[active='true']:border-none dark:border-gray-600 dark:text-white dark:hover:text-white dark:hover:bg-gray-500 dark:focus:ring-blue-500 dark:focus:text-white">
<FontAwesomeIcon icon={props.icon} />
return (
<button
onClick={props.onClick}
type="button"
data-active={props.active}
className={`
h-11 w-11 border border-gray-200 bg-white py-2 text-sm font-medium
text-gray-900
dark:border-gray-600 dark:bg-gray-700 dark:text-white
dark:hover:bg-gray-500 dark:hover:text-white dark:focus:text-white
dark:focus:ring-blue-500 dark:data-[active='true']:border-none
dark:data-[active='true']:bg-blue-500
first-of-type:rounded-s-md
focus:z-10 focus:text-blue-700 focus:ring-2 focus:ring-blue-700
hover:bg-gray-100 hover:text-blue-700
last-of-type:rounded-e-md
`}
>
<FontAwesomeIcon icon={props.icon} />
</button>
}
);
}

View File

@@ -1,54 +1,116 @@
import React from "react";
import { faSkull, faCamera, faFlag, faLink, faUnlink, faAngleDoubleRight, faExclamationCircle, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import {
faSkull,
faCamera,
faFlag,
faLink,
faUnlink,
faAngleDoubleRight,
faExclamationCircle,
faInfoCircle,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
// Error message callout, only to be used for error messages
export function ErrorCallout(props: {
title?: string,
description?: string
}) {
return <div className="flex flex-row gap-2 w-fit bg-gray-300 dark:bg-gray-800 p-4 rounded-md border-[1px] border-red-800 text-red-700 dark:text-red-500 w-full">
{props.title && (<FontAwesomeIcon className="mt-1" icon={faExclamationCircle}></FontAwesomeIcon>)}
<div className="flex flex-col font-bold text-pretty content-center items-start gap-2">
{props.title}
<div className="flex text-xs font-medium whitespace-pre-line text-red-500">
{props.description}
</div>
export function ErrorCallout(props: { title?: string; description?: string }) {
return (
<div
className={`
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-red-800
bg-gray-300 p-4 text-red-700
dark:bg-gray-800 dark:text-red-500
`}
>
{props.title && (
<FontAwesomeIcon
className="mt-1"
icon={faExclamationCircle}
></FontAwesomeIcon>
)}
<div
className={`
flex flex-col content-center items-start gap-2 text-pretty font-bold
`}
>
{props.title}
<div
className={`
flex whitespace-pre-line text-xs font-medium text-red-500
`}
>
{props.description}
</div>
</div>
</div>
);
}
// General information callout for something that is just nice to know
export function InfoCallout(props: {
title?: string,
description?: string
}) {
return <div className="flex flex-row gap-2 w-fit bg-gray-300 dark:bg-gray-800 p-4 rounded-md border-[1px] border-blue-800 text-blue-400 dark:text-blue-400 w-full">
{props.title && (<FontAwesomeIcon className="mt-1" icon={faInfoCircle}></FontAwesomeIcon>)}
<div className="flex flex-col font-medium text-pretty content-center items-start gap-2">
{props.title}
{props.description && (<div className="flex text-xs font-medium whitespace-pre-line">
{props.description}
</div>)}
</div>
export function InfoCallout(props: { title?: string; description?: string }) {
return (
<div
className={`
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-blue-800
bg-gray-300 p-4 text-blue-400
dark:bg-gray-800 dark:text-blue-400
`}
>
{props.title && (
<FontAwesomeIcon className="mt-1" icon={faInfoCircle}></FontAwesomeIcon>
)}
<div
className={`
flex flex-col content-center items-start gap-2 text-pretty font-medium
`}
>
{props.title}
{props.description && (
<div
className={`
flex whitespace-pre-line text-xs font-medium
`}
>
{props.description}
</div>
)}
</div>
</div>
);
}
// Used for the "You are playing as BLUE/RED Commander" callouts, only on the login page. Accepted values for coalition are 'blue' and 'red'.
export function CommandCallout(props: {
coalition?: string,
}) {
return <div className="flex flex-row gap-2 w-fit bg-gray-300 dark:bg-gray-800 p-4 rounded-md border-[1px] border-gray-700 text-gray-400 dark:text-gray-400 w-full">
<FontAwesomeIcon className="mt-1" icon={faAngleDoubleRight}></FontAwesomeIcon>
<div className="font-medium text-pretty content-center items-start gap-2 whitespace-break-spaces">
You are playing as
{props.coalition=='blue' ? (
<div className="inline-block font-bold text-blue-500"> BLUE COMMANDER </div>
) : (
<div className="inline-block font-bold text-red-500"> RED COMMANDER </div>
)}
</div>
export function CommandCallout(props: { coalition?: string }) {
return (
<div
className={`
flex w-fit w-full flex-row gap-2 rounded-md border-[1px] border-gray-700
bg-gray-300 p-4 text-gray-400
dark:bg-gray-800 dark:text-gray-400
`}
>
<FontAwesomeIcon
className="mt-1"
icon={faAngleDoubleRight}
></FontAwesomeIcon>
<div
className={`
content-center items-start gap-2 whitespace-break-spaces text-pretty
font-medium
`}
>
You are playing as
{props.coalition == "blue" ? (
<div className="inline-block font-bold text-blue-500">
{" "}
BLUE COMMANDER{" "}
</div>
) : (
<div className="inline-block font-bold text-red-500">
{" "}
RED COMMANDER{" "}
</div>
)}
</div>
</div>
);
}

View File

@@ -1,13 +1,22 @@
import React, { ChangeEvent } from "react";
export function OlCheckbox(props: {
checked: boolean,
onChange: (e: ChangeEvent<HTMLInputElement>) => void
checked: boolean;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
}) {
return <input onChange={props.onChange}
type="checkbox"
checked={props.checked}
value=""
className="w-4 h-4 my-auto text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 cursor-pointer"
return (
<input
onChange={props.onChange}
type="checkbox"
checked={props.checked}
value=""
className={`
my-auto h-4 w-4 cursor-pointer rounded border-gray-300 bg-gray-100
text-blue-600
dark:border-gray-600 dark:bg-gray-700 dark:ring-offset-gray-800
dark:focus:ring-blue-600
focus:ring-2 focus:ring-blue-500
`}
/>
}
);
}

View File

@@ -2,21 +2,47 @@ import React from "react";
import { Coalition } from "../../types/types";
export function OlCoalitionToggle(props: {
coalition: Coalition | undefined,
onClick: () => void
coalition: Coalition | undefined;
onClick: () => void;
}) {
return <div className="inline-flex items-center cursor-pointer" onClick={props.onClick}>
<button className="sr-only peer" />
<div data-flash={props.coalition === undefined} data-coalition={props.coalition ?? 'blue'} className={"relative w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300 " +
"dark:peer-focus:ring-blue-800 rounded-full peer " +
" after:content-[''] after:absolute after:top-0.5 after:start-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 " +
"after:w-6 after:transition-all dark:border-gray-600 " +
"data-[coalition='neutral']:after:translate-x-[50%] rtl:data-[coalition='neutral']:after:-translate-x-[50%] data-[coalition='neutral']:after:border-white " +
"data-[coalition='red']:after:translate-x-full rtl:data-[coalition='red']:after:-translate-x-full data-[coalition='red']:after:border-white " +
" data-[coalition='blue']:bg-blue-600 data-[coalition='neutral']:bg-gray-400 data-[coalition='red']:bg-red-500"}>
</div>
<span className="ms-3 text-gray-900 dark:text-white data-[flash='true']:after:animate-pulse">
{props.coalition? `Coalition (${props.coalition[0].toLocaleUpperCase() + props.coalition.substring(1)})`: "Different values"}
</span>
</div>
}
return (
<div
className="inline-flex cursor-pointer items-center"
onClick={props.onClick}
>
<button className="peer sr-only" />
<div
data-flash={props.coalition === undefined}
data-coalition={props.coalition ?? "blue"}
className={`
peer relative h-7 w-14 rounded-full bg-gray-200
after:absolute after:start-[4px] after:top-0.5 after:h-6 after:w-6
after:rounded-full after:border after:border-gray-300 after:bg-white
after:transition-all after:content-['']
dark:border-gray-600 dark:peer-focus:ring-blue-800
data-[coalition='blue']:bg-blue-600
data-[coalition='neutral']:bg-gray-400
data-[coalition='neutral']:after:translate-x-[50%]
data-[coalition='neutral']:after:border-white
data-[coalition='red']:bg-red-500
data-[coalition='red']:after:translate-x-full
data-[coalition='red']:after:border-white
peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300
rtl:data-[coalition='neutral']:after:-translate-x-[50%]
rtl:data-[coalition='red']:after:-translate-x-full
`}
></div>
<span
className={`
ms-3 text-gray-900
dark:text-white
data-[flash='true']:after:animate-pulse
`}
>
{props.coalition
? `Coalition (${props.coalition[0].toLocaleUpperCase() + props.coalition.substring(1)})`
: "Different values"}
</span>
</div>
);
}

View File

@@ -1,100 +1,176 @@
import React, { useState, useEffect, useRef } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
export function OlDropdown(props: {
className: string,
leftIcon?: IconProp,
rightIcon?: IconProp,
label: string,
children?: JSX.Element | JSX.Element[]
className: string;
leftIcon?: IconProp;
rightIcon?: IconProp;
label: string;
children?: JSX.Element | JSX.Element[];
}) {
var [open, setOpen] = useState(false);
var contentRef = useRef(null);
var buttonRef = useRef(null);
var [open, setOpen] = useState(false);
var contentRef = useRef(null);
var buttonRef = useRef(null);
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
/* Reset the position of the content */
content.style.left = "0px";
content.style.top = "0px";
content.style.height = "";
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
/* Reset the position of the content */
content.style.left = "0px";
content.style.top = "0px";
content.style.height = "";
/* Get the position and size of the button and the content elements */
var [cxl, cyt, cxr, cyb, cw, ch] = [content.getBoundingClientRect().x, content.getBoundingClientRect().y, content.getBoundingClientRect().x + content.clientWidth, content.getBoundingClientRect().y + content.clientHeight, content.clientWidth, content.clientHeight];
var [bxl, byt, bxr, byb, bbw, bh] = [button.getBoundingClientRect().x, button.getBoundingClientRect().y, button.getBoundingClientRect().x + button.clientWidth, button.getBoundingClientRect().y + button.clientHeight, button.clientWidth, button.clientHeight];
/* Get the position and size of the button and the content elements */
var [cxl, cyt, cxr, cyb, cw, ch] = [
content.getBoundingClientRect().x,
content.getBoundingClientRect().y,
content.getBoundingClientRect().x + content.clientWidth,
content.getBoundingClientRect().y + content.clientHeight,
content.clientWidth,
content.clientHeight,
];
var [bxl, byt, bxr, byb, bbw, bh] = [
button.getBoundingClientRect().x,
button.getBoundingClientRect().y,
button.getBoundingClientRect().x + button.clientWidth,
button.getBoundingClientRect().y + button.clientHeight,
button.clientWidth,
button.clientHeight,
];
/* Limit the maximum height */
if (ch > 400) {
ch = 400;
content.style.height = `${ch}px`;
}
/* Compute the horizontal position of the center of the button and the content */
var cxc = (cxl + cxr) / 2;
var bxc = (bxl + bxr) / 2;
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
var offsetX = bxc - cxc;
var offsetY = byb - cyt + 8;
/* Compute the new position of the left and right margins of the content */
cxl += offsetX;
cxr += offsetX;
cyb += offsetY;
/* Try and move the content so it is inside the screen */
if (cxl < 0)
offsetX -= cxl;
if (cxr > window.innerWidth)
offsetX -= (cxr - window.innerWidth)
if (cyb > window.innerHeight)
offsetY -= bh + ch + 16;
/* Apply the offset */
content.style.left = `${offsetX}px`
content.style.top = `${offsetY}px`
/* Limit the maximum height */
if (ch > 400) {
ch = 400;
content.style.height = `${ch}px`;
}
useEffect(() => {
if (contentRef.current && buttonRef.current) {
const content = contentRef.current as HTMLDivElement;
const button = buttonRef.current as HTMLButtonElement;
/* Compute the horizontal position of the center of the button and the content */
var cxc = (cxl + cxr) / 2;
var bxc = (bxl + bxr) / 2;
setPosition(content, button);
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
var offsetX = bxc - cxc;
var offsetY = byb - cyt + 8;
/* Register click events to automatically close the dropdown when clicked anywhere outside of it */
document.addEventListener('click', function (event) {
const target = event.target;
if (target && /*!content.contains(target as HTMLElement) &&*/ !button.contains(target as HTMLElement)) {
setOpen(false);
}
});
/* Compute the new position of the left and right margins of the content */
cxl += offsetX;
cxr += offsetX;
cyb += offsetY;
/* Try and move the content so it is inside the screen */
if (cxl < 0) offsetX -= cxl;
if (cxr > window.innerWidth) offsetX -= cxr - window.innerWidth;
if (cyb > window.innerHeight) offsetY -= bh + ch + 16;
/* Apply the offset */
content.style.left = `${offsetX}px`;
content.style.top = `${offsetY}px`;
}
useEffect(() => {
if (contentRef.current && buttonRef.current) {
const content = contentRef.current as HTMLDivElement;
const button = buttonRef.current as HTMLButtonElement;
setPosition(content, button);
/* Register click events to automatically close the dropdown when clicked anywhere outside of it */
document.addEventListener("click", function (event) {
const target = event.target;
if (
target &&
/*!content.contains(target as HTMLElement) &&*/ !button.contains(
target as HTMLElement
)
) {
setOpen(false);
}
})
});
}
});
return <div className={(props.className ?? "") + " relative"}>
<button ref={buttonRef} onClick={() => { setOpen(!open) }} className={"w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-2 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center inline-flex items-center justify-between border dark:border-gray-700 dark:text-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 dark:focus:ring-blue-800"} type="button">
{props.leftIcon && <FontAwesomeIcon icon={props.leftIcon} className="mr-3" />}
<span className="text-nowrap text-ellipsis overflow-hidden">
{props.label}
</span>
<svg className="flex-none w-2.5 h-2.5 ms-3 data-[open='true']:-scale-y-100 transition-transform ml-auto" data-open={open} xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="m1 1 4 4 4-4" />
</svg>
</button>
return (
<div className={(props.className ?? "") + "relative"}>
<button
ref={buttonRef}
onClick={() => {
setOpen(!open);
}}
className={`
inline-flex w-full items-center justify-between rounded-lg border
bg-blue-700 px-5 py-2.5 text-center text-sm font-medium text-white
dark:border-gray-700 dark:bg-gray-700 dark:text-gray-100
dark:hover:bg-gray-600 dark:focus:ring-blue-800
focus:outline-none focus:ring-2 focus:ring-blue-300
hover:bg-blue-800
`}
type="button"
>
{props.leftIcon && (
<FontAwesomeIcon
icon={props.leftIcon}
className={`mr-3`}
/>
)}
<span className="overflow-hidden text-ellipsis text-nowrap">
{props.label}
</span>
<svg
className={`
ml-auto ms-3 h-2.5 w-2.5 flex-none transition-transform
data-[open='true']:-scale-y-100
`}
data-open={open}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 10 6"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m1 1 4 4 4-4"
/>
</svg>
</button>
<div ref={contentRef} data-open={open} className="absolute z-ui-2 w-full p-2 data-[open='false']:hidden bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 overflow-y-scroll">
<div className="text-sm text-gray-700 dark:text-gray-200 w-full h-fit">
{props.children}
</div>
<div
ref={contentRef}
data-open={open}
className={`
absolute z-ui-2 w-full divide-y divide-gray-100 overflow-y-scroll
rounded-lg bg-white p-2 shadow
dark:bg-gray-700
data-[open='false']:hidden
`}
>
<div
className={`
h-fit w-full text-sm text-gray-700
dark:text-gray-200
`}
>
{props.children}
</div>
</div>
</div>
);
}
/* Conveniency Component for dropdown elements */
export function OlDropdownItem(props) {
return <button onClick={props.onClick ?? (() => { })} className={(props.className ?? "") + " px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white flex flex-row content-center rounded-md select-none cursor-pointer"}>
{props.children}
return (
<button
onClick={props.onClick ?? (() => {})}
className={`
${props.className ?? ""}
flex cursor-pointer select-none flex-row content-center rounded-md px-4
py-2
dark:hover:bg-gray-600 dark:hover:text-white
hover:bg-gray-100
`}
>
{props.children}
</button>
}
);
}

File diff suppressed because one or more lines are too long

View File

@@ -1,14 +1,54 @@
import React from "react";
export function OlLabelToggle(props: {
toggled: boolean | undefined,
leftLabel: string,
rightLabel: string,
onClick: () => void
toggled: boolean | undefined;
leftLabel: string;
rightLabel: string;
onClick: () => void;
}) {
return <button onClick={props.onClick} className="border dark:border-gray-600 relative text-sm flex flex-row flex-none contents-center justify-between w-[120px] h-10 border dark:border-transparent dark:bg-gray-700 rounded-md py-[5px] px-1 select-none cursor-pointer focus:ring-2 focus:outline-none focus:ring-blue-300 dark:hover:bg-gray-600 dark:focus:ring-blue-800">
<span data-flash={props.toggled === undefined} data-toggled={props.toggled ?? false} className="data-[flash='true']:animate-pulse absolute my-auto h-[28px] w-[54px] bg-blue-500 rounded-md data-[toggled='true']:translate-x-14 transition-transform"></span>
<span data-active={!(props.toggled ?? false)} className="my-auto dark:data-[active='true']:text-white font-normal dark:data-[active='false']:text-gray-400 dark:data-[active='false']:text-gray-400 pl-3 z-ui-2 transition-colors">{props.leftLabel}</span>
<span data-active={props.toggled ?? false} className="my-auto dark:data-[active='true']:text-white font-normal dark:data-[active='false']:text-gray-400 pr-3.5 z-ui-2 transition-colors">{props.rightLabel}</span>
return (
<button
onClick={props.onClick}
className={`
relative flex h-10 w-[120px] flex-none cursor-pointer select-none
flex-row justify-between rounded-md border border px-1 py-[5px] text-sm
contents-center
dark:border-gray-600 dark:border-transparent dark:bg-gray-700
dark:hover:bg-gray-600 dark:focus:ring-blue-800
focus:outline-none focus:ring-2 focus:ring-blue-300
`}
>
<span
data-flash={props.toggled === undefined}
data-toggled={props.toggled ?? false}
className={`
absolute my-auto h-[28px] w-[54px] rounded-md bg-blue-500
transition-transform
data-[flash='true']:animate-pulse
data-[toggled='true']:translate-x-14
`}
></span>
<span
data-active={!(props.toggled ?? false)}
className={`
my-auto pl-3 font-normal transition-colors z-ui-2
dark:data-[active='false']:text-gray-400
dark:data-[active='false']:text-gray-400
dark:data-[active='true']:text-white
`}
>
{props.leftLabel}
</span>
<span
data-active={props.toggled ?? false}
className={`
my-auto pr-3.5 font-normal transition-colors z-ui-2
dark:data-[active='false']:text-gray-400
dark:data-[active='true']:text-white
`}
>
{props.rightLabel}
</span>
</button>
}
);
}

View File

@@ -1,26 +1,90 @@
import React, {ChangeEvent, useEffect, useId} from "react";
import React, { ChangeEvent, useEffect, useId } from "react";
export function OlNumberInput(props: {
value: number,
min: number,
max: number,
onDecrease: () => void,
onIncrease: () => void,
onChange: (e: ChangeEvent<HTMLInputElement>) => void
value: number;
min: number;
max: number;
onDecrease: () => void;
onIncrease: () => void;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
}) {
return <div className="w-fit">
<div className="relative flex items-center max-w-[8rem]">
<button type="button" onClick={props.onDecrease} className="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 hover:bg-gray-200 rounded-s-lg p-3 h-10 focus:ring-gray-100 dark:focus:ring-blue-700 focus:ring-2 focus:outline-none">
<svg className="w-3 h-3 text-gray-900 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 2">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M1 1h16"/>
</svg>
</button>
<input type="text" onChange={props.onChange} min={props.min} max={props.max} className="bg-gray-50 h-10 text-center text-gray-900 text-sm focus:ring-blue-500 focus:border-blue-700 block w-full py-2.5 border-[2px] dark:border-gray-700 dark:bg-olympus-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-700 dark:focus:border-blue-700" value={props.value} />
<button type="button" onClick={props.onIncrease} className="bg-gray-100 dark:bg-gray-700 dark:hover:bg-gray-600 hover:bg-gray-200 rounded-e-lg p-3 h-10 focus:ring-gray-100 dark:focus:ring-blue-500 focus:ring-2 focus:outline-none">
<svg className="w-3 h-3 text-gray-900 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 18 18">
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M9 1v16M1 9h16"/>
</svg>
</button>
</div>
return (
<div className="w-fit">
<div className="relative flex max-w-[8rem] items-center">
<button
type="button"
onClick={props.onDecrease}
className={`
h-10 rounded-s-lg bg-gray-100 p-3
dark:bg-gray-700 dark:hover:bg-gray-600 dark:focus:ring-blue-700
focus:outline-none focus:ring-2 focus:ring-gray-100
hover:bg-gray-200
`}
>
<svg
className={`
h-3 w-3 text-gray-900
dark:text-white
`}
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 18 2"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M1 1h16"
/>
</svg>
</button>
<input
type="text"
onChange={props.onChange}
min={props.min}
max={props.max}
className={`
block h-10 w-full border-[2px] bg-gray-50 py-2.5 text-center text-sm
text-gray-900
dark:border-gray-700 dark:bg-olympus-600 dark:text-white
dark:placeholder-gray-400 dark:focus:border-blue-700
dark:focus:ring-blue-700
focus:border-blue-700 focus:ring-blue-500
`}
value={props.value}
/>
<button
type="button"
onClick={props.onIncrease}
className={`
h-10 rounded-e-lg bg-gray-100 p-3
dark:bg-gray-700 dark:hover:bg-gray-600 dark:focus:ring-blue-500
focus:outline-none focus:ring-2 focus:ring-gray-100
hover:bg-gray-200
`}
>
<svg
className={`
h-3 w-3 text-gray-900
dark:text-white
`}
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 18 18"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 1v16M1 9h16"
/>
</svg>
</button>
</div>
</div>
}
);
}

View File

@@ -1,29 +1,36 @@
import React, { ChangeEvent, useEffect, useRef } from "react";
export function OlRangeSlider(props: {
value: number | undefined,
min?: number,
max?: number,
step?: number,
onChange: (e: ChangeEvent<HTMLInputElement>) => void
value: number | undefined;
min?: number;
max?: number;
step?: number;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
}) {
var elementRef = useRef(null);
var elementRef = useRef(null);
useEffect(() => {
if (elementRef.current) {
const sliderEl = elementRef.current as HTMLInputElement;
const tempSliderValue = Number(sliderEl.value);
const progress = (tempSliderValue / Number(sliderEl.max)) * 100;
sliderEl.style.background = `linear-gradient(to right, #3F83F8 ${progress}%, #4B5563 ${progress}%)`;
}
})
useEffect(() => {
if (elementRef.current) {
const sliderEl = elementRef.current as HTMLInputElement;
const tempSliderValue = Number(sliderEl.value);
const progress = (tempSliderValue / Number(sliderEl.max)) * 100;
sliderEl.style.background = `linear-gradient(to right, #3F83F8 ${progress}%, #4B5563 ${progress}%)`;
}
});
return <input type="range"
ref={elementRef}
onChange={props.onChange}
value={props.value ?? 0}
min={props.min ?? 0}
max={props.max ?? 100}
step={props.step ?? 1}
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700" />
}
return (
<input
type="range"
ref={elementRef}
onChange={props.onChange}
value={props.value ?? 0}
min={props.min ?? 0}
max={props.max ?? 100}
step={props.step ?? 1}
className={`
h-2 w-full cursor-pointer appearance-none rounded-lg bg-gray-200
dark:bg-gray-700
`}
/>
);
}

View File

@@ -1,25 +1,62 @@
import { faMultiply, faSearch } from "@fortawesome/free-solid-svg-icons"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import React, {ChangeEvent, useId, useRef} from "react"
import { faMultiply, faSearch } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { ChangeEvent, useId, useRef } from "react";
export function OlSearchBar(props: {
onChange: (e: ChangeEvent<HTMLInputElement>) => void
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
}) {
const searchId = useId();
const inputRef = useRef(null);
const searchId = useId();
const inputRef = useRef(null);
function resetSearch() {
inputRef.current && ((inputRef.current as HTMLInputElement).value = '');
}
function resetSearch() {
inputRef.current && ((inputRef.current as HTMLInputElement).value = "");
}
return <div>
<label htmlFor={searchId} className="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Search</label>
<div className="relative">
<div className="absolute inset-y-0 start-0 flex items-center ps-4 pointer-events-none">
<FontAwesomeIcon icon={faSearch} className="dark:text-gray-400" />
</div>
<input type="search" ref={inputRef} id={searchId} onChange= {props.onChange} className="block w-full p-3 mb-2 ps-10 text-sm text-gray-900 border border-gray-300 rounded-full bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Search" required />
<FontAwesomeIcon icon={faMultiply} className="absolute cursor-pointer end-4 bottom-4 my-auto dark:text-gray-400 text-sm" onClick={resetSearch}/>
return (
<div>
<label
htmlFor={searchId}
className={`
sr-only mb-2 text-sm font-medium text-gray-900
dark:text-white
`}
>
Search
</label>
<div className="relative">
<div
className={`
pointer-events-none absolute inset-y-0 start-0 flex items-center
ps-4
`}
>
<FontAwesomeIcon icon={faSearch} className="dark:text-gray-400" />
</div>
<input
type="search"
ref={inputRef}
id={searchId}
onChange={props.onChange}
className={`
mb-2 block w-full rounded-full border border-gray-300 bg-gray-50 p-3
ps-10 text-sm text-gray-900
dark:border-gray-600 dark:bg-gray-700 dark:text-white
dark:placeholder-gray-400 dark:focus:border-blue-500
dark:focus:ring-blue-500
focus:border-blue-500 focus:ring-blue-500
`}
placeholder="Search"
required
/>
<FontAwesomeIcon
icon={faMultiply}
className={`
absolute bottom-4 end-4 my-auto cursor-pointer text-sm
dark:text-gray-400
`}
onClick={resetSearch}
/>
</div>
</div>
}
);
}

View File

@@ -1,61 +1,150 @@
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { faExternalLink, faLock, faLockOpen, faUnlock, faUnlockAlt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import React, { useRef, useState } from "react"
import {
faExternalLink,
faLock,
faLockOpen,
faUnlock,
faUnlockAlt,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { useRef, useState } from "react";
import { OlTooltip } from "./oltooltip";
export function OlStateButton(props: {
className?: string,
checked: boolean,
icon: IconProp,
tooltip: string,
onClick: () => void
className?: string;
checked: boolean;
icon: IconProp;
tooltip: string;
onClick: () => void;
}) {
var [hover, setHover] = useState(false);
var buttonRef = useRef(null);
var [hover, setHover] = useState(false);
var buttonRef = useRef(null);
const className = (props.className ?? '') + ` h-[40px] w-[40px] flex-none font-medium rounded-md text-lg dark:bg-olympus-600 dark:hover:bg-olympus-300 dark:data-[checked='true']:bg-blue-500 dark:data-[checked='true']:text-white dark:text-gray-300 dark:border-gray-600 `;
const className =
(props.className ?? "") +
`
h-[40px] w-[40px] flex-none rounded-md text-lg font-medium
dark:border-gray-600 dark:bg-olympus-600 dark:text-gray-300
dark:hover:bg-olympus-300 dark:data-[checked='true']:bg-blue-500
dark:data-[checked='true']:text-white
`;
return <><button ref={buttonRef} onClick={() => { props.onClick(); setHover(false); }} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
return (
<>
<button
ref={buttonRef}
onClick={() => {
props.onClick();
setHover(false);
}}
data-checked={props.checked}
type="button"
className={className}
onMouseEnter={() => {
setHover(true);
}}
onMouseLeave={() => {
setHover(false);
}}
>
<FontAwesomeIcon icon={props.icon} />
</button>
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
</button>
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
</>
);
}
export function OlRoundStateButton(props: {
className?: string,
checked: boolean,
icon: IconProp,
tooltip: string,
onClick: () => void
className?: string;
checked: boolean;
icon: IconProp;
tooltip: string;
onClick: () => void;
}) {
var [hover, setHover] = useState(false);
var buttonRef = useRef(null);
var [hover, setHover] = useState(false);
var buttonRef = useRef(null);
const className = (props.className ?? '') + ` h-8 w-8 flex-none m-auto border-2 border-gray-900 font-medium rounded-full text-sm dark:bg-[transparent] dark:data-[checked='true']:bg-white dark:text-gray-400 dark:data-[checked='true']:text-gray-900 dark:data-[checked='true']:border-white dark:border-gray-400 dark:data-[checked='true']:hover:bg-gray-200 dark:data-[checked='true']:hover:border-gray-200 dark:hover:bg-gray-800`;
const className =
(props.className ?? "") +
`
m-auto h-8 w-8 flex-none rounded-full border-2 border-gray-900 text-sm
font-medium
dark:border-gray-400 dark:bg-[transparent] dark:text-gray-400
dark:hover:bg-gray-800 dark:data-[checked='true']:border-white
dark:data-[checked='true']:bg-white
dark:data-[checked='true']:text-gray-900
dark:data-[checked='true']:hover:border-gray-200
dark:data-[checked='true']:hover:bg-gray-200
`;
return <><button ref={buttonRef} onClick={() => { props.onClick(); setHover(false); }} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
return (
<>
<button
ref={buttonRef}
onClick={() => {
props.onClick();
setHover(false);
}}
data-checked={props.checked}
type="button"
className={className}
onMouseEnter={() => {
setHover(true);
}}
onMouseLeave={() => {
setHover(false);
}}
>
<FontAwesomeIcon className="pt-[3px]" icon={props.icon} />
</button>
{ hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} /> }
</button>
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
</>
);
}
export function OlLockStateButton(props: {
className?: string,
checked: boolean,
tooltip: string,
onClick: () => void
className?: string;
checked: boolean;
tooltip: string;
onClick: () => void;
}) {
var [hover, setHover] = useState(false);
var buttonRef = useRef(null);
var [hover, setHover] = useState(false);
var buttonRef = useRef(null);
const className = (props.className ?? '') + ` h-8 w-8 flex-none m-auto border-gray-900 font-medium rounded-full text-sm dark:bg-red-500 dark:data-[checked='true']:bg-green-500 dark:text-olympus-900 dark:data-[checked='true']:text-green-900 dark:data-[checked='true']:hover:bg-green-400 dark:hover:bg-red-400`;
const className =
(props.className ?? "") +
`
m-auto h-8 w-8 flex-none rounded-full border-gray-900 text-sm font-medium
dark:bg-red-500 dark:text-olympus-900 dark:hover:bg-red-400
dark:data-[checked='true']:bg-green-500
dark:data-[checked='true']:text-green-900
dark:data-[checked='true']:hover:bg-green-400
`;
return <><button ref={buttonRef} onClick={() => { props.onClick(); setHover(false); }} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
<FontAwesomeIcon className="pt-[3px]" icon={props.checked == true ? faUnlockAlt : faLock} />
</button>
{ hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} /> }
return (
<>
<button
ref={buttonRef}
onClick={() => {
props.onClick();
setHover(false);
}}
data-checked={props.checked}
type="button"
className={className}
onMouseEnter={() => {
setHover(true);
}}
onMouseLeave={() => {
setHover(false);
}}
>
<FontAwesomeIcon
className="pt-[3px]"
icon={props.checked == true ? faUnlockAlt : faLock}
/>
</button>
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
</>
}
);
}

View File

@@ -1,16 +1,33 @@
import React from "react";
export function OlToggle(props: {
toggled: boolean | undefined,
onClick: () => void
toggled: boolean | undefined;
onClick: () => void;
}) {
return <div className="inline-flex items-center cursor-pointer" onClick={props.onClick}>
<button className="sr-only peer" />
<div data-flash={props.toggled === undefined} data-toggled={props.toggled ?? false} className={"relative w-14 h-7 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300 " +
"dark:peer-focus:ring-blue-800 rounded-full peer " +
" after:content-[''] after:absolute after:top-0.5 after:start-[4px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-6 " +
"after:w-6 after:transition-all dark:border-gray-600 " +
"data-[toggled='true']:after:translate-x-full rtl:data-[toggled='true']:after:-translate-x-full data-[toggled='true']:after:border-white " +
"data-[toggled='false']:bg-gray-500 dark:data-[toggled='true']:bg-blue-500 data-[flash='true']:after:animate-pulse"}></div>
</div>
}
return (
<div
className="inline-flex cursor-pointer items-center"
onClick={props.onClick}
>
<button className="peer sr-only" />
<div
data-flash={props.toggled === undefined}
data-toggled={props.toggled ?? false}
className={`
peer relative h-7 w-14 rounded-full bg-gray-200
after:absolute after:start-[4px] after:top-0.5 after:h-6 after:w-6
after:rounded-full after:border after:border-gray-300 after:bg-white
after:transition-all after:content-['']
dark:border-gray-600 dark:peer-focus:ring-blue-800
dark:data-[toggled='true']:bg-blue-500
data-[flash='true']:after:animate-pulse
data-[toggled='false']:bg-gray-500
data-[toggled='true']:after:translate-x-full
data-[toggled='true']:after:border-white
peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-300
rtl:data-[toggled='true']:after:-translate-x-full
`}
></div>
</div>
);
}

View File

@@ -1,63 +1,85 @@
import React, { useEffect, useRef, useState } from "react";
export function OlTooltip(props: {
content: string,
buttonRef: React.MutableRefObject<null>
content: string;
buttonRef: React.MutableRefObject<null>;
}) {
var contentRef = useRef(null);
var contentRef = useRef(null);
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
/* Reset the position of the content */
content.style.left = "0px";
content.style.top = "0px";
content.style.height = "";
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
/* Reset the position of the content */
content.style.left = "0px";
content.style.top = "0px";
content.style.height = "";
/* Get the position and size of the button and the content elements */
var [cxl, cyt, cxr, cyb, cw, ch] = [content.getBoundingClientRect().x, content.getBoundingClientRect().y, content.getBoundingClientRect().x + content.clientWidth, content.getBoundingClientRect().y + content.clientHeight, content.clientWidth, content.clientHeight];
var [bxl, byt, bxr, byb, bbw, bh] = [button.getBoundingClientRect().x, button.getBoundingClientRect().y, button.getBoundingClientRect().x + button.clientWidth, button.getBoundingClientRect().y + button.clientHeight, button.clientWidth, button.clientHeight];
/* Get the position and size of the button and the content elements */
var [cxl, cyt, cxr, cyb, cw, ch] = [
content.getBoundingClientRect().x,
content.getBoundingClientRect().y,
content.getBoundingClientRect().x + content.clientWidth,
content.getBoundingClientRect().y + content.clientHeight,
content.clientWidth,
content.clientHeight,
];
var [bxl, byt, bxr, byb, bbw, bh] = [
button.getBoundingClientRect().x,
button.getBoundingClientRect().y,
button.getBoundingClientRect().x + button.clientWidth,
button.getBoundingClientRect().y + button.clientHeight,
button.clientWidth,
button.clientHeight,
];
/* Limit the maximum height */
if (ch > 400) {
ch = 400;
content.style.height = `${ch}px`;
}
/* Compute the horizontal position of the center of the button and the content */
var cxc = (cxl + cxr) / 2;
var bxc = (bxl + bxr) / 2;
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
var offsetX = bxc - cxc;
var offsetY = byb - cyt + 8;
/* Compute the new position of the left and right margins of the content */
cxl += offsetX;
cxr += offsetX;
cyb += offsetY;
/* Try and move the content so it is inside the screen */
if (cxl < 0)
offsetX -= cxl;
if (cxr > window.innerWidth)
offsetX -= (cxr - window.innerWidth)
if (cyb > window.innerHeight)
offsetY -= bh + ch + 16;
/* Apply the offset */
content.style.left = `${offsetX}px`
content.style.top = `${offsetY}px`
/* Limit the maximum height */
if (ch > 400) {
ch = 400;
content.style.height = `${ch}px`;
}
useEffect(() => {
if (contentRef.current && props.buttonRef.current) {
const content = contentRef.current as HTMLDivElement;
const button = props.buttonRef.current as HTMLButtonElement;
/* Compute the horizontal position of the center of the button and the content */
var cxc = (cxl + cxr) / 2;
var bxc = (bxl + bxr) / 2;
setPosition(content, button);
}
})
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
var offsetX = bxc - cxc;
var offsetY = byb - cyt + 8;
return props.content !== "" && <div ref={contentRef} className={`absolute whitespace-nowrap z-ui-4 px-3 py-2 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm dark:bg-gray-700`}>
{ props.content }
</div>
}
/* Compute the new position of the left and right margins of the content */
cxl += offsetX;
cxr += offsetX;
cyb += offsetY;
/* Try and move the content so it is inside the screen */
if (cxl < 0) offsetX -= cxl;
if (cxr > window.innerWidth) offsetX -= cxr - window.innerWidth;
if (cyb > window.innerHeight) offsetY -= bh + ch + 16;
/* Apply the offset */
content.style.left = `${offsetX}px`;
content.style.top = `${offsetY}px`;
}
useEffect(() => {
if (contentRef.current && props.buttonRef.current) {
const content = contentRef.current as HTMLDivElement;
const button = props.buttonRef.current as HTMLButtonElement;
setPosition(content, button);
}
});
return (
props.content !== "" && (
<div
ref={contentRef}
className={`
absolute whitespace-nowrap z-ui-4 rounded-lg bg-gray-900 px-3 py-2
text-sm font-medium text-white shadow-sm
dark:bg-gray-700
`}
>
{props.content}
</div>
)
);
}

View File

@@ -1,21 +1,53 @@
import React from "react";
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { UnitBlueprint } from "../../interfaces";
import { faArrowRightLong, faCaretRight, faCircleArrowRight, faLongArrowAltRight } from "@fortawesome/free-solid-svg-icons";
import {
faArrowRightLong,
faCaretRight,
faCircleArrowRight,
faLongArrowAltRight,
} from "@fortawesome/free-solid-svg-icons";
import { faArrowRight } from "@fortawesome/free-solid-svg-icons/faArrowRight";
export function OlUnitEntryList(props: {
icon: IconProp,
blueprint: UnitBlueprint,
onClick: () => void
icon: IconProp;
blueprint: UnitBlueprint;
onClick: () => void;
}) {
return <div onClick={props.onClick} className="group relative text-sm cursor-pointer select-none flex justify-between items-center dark:text-gray-300 dark:hover:bg-white dark:hover:bg-olympus-500 px-2 py-2 rounded-sm mr-2">
<FontAwesomeIcon icon={props.icon} className="text-sm"></FontAwesomeIcon>
<div className="font-normal text-left flex-1 px-2">{props.blueprint.label}</div>
<div className="font-bold bg-olympus-800 dark:text-olympus-50 rounded-full px-2 py-0.5 text-xs">{props.blueprint.era === "WW2" ? "WW2" : props.blueprint.era.split(" ").map((word) => {
return word.charAt(0).toLocaleUpperCase();
})}</div>
<FontAwesomeIcon icon={faArrowRight} className="px-1 dark:text-olympus-50 group-hover:translate-x-1 transition-transform"></FontAwesomeIcon>
return (
<div
onClick={props.onClick}
className={`
group relative mr-2 flex cursor-pointer select-none items-center
justify-between rounded-sm px-2 py-2 text-sm
dark:text-gray-300 dark:hover:bg-olympus-500 dark:hover:bg-white
`}
>
<FontAwesomeIcon icon={props.icon} className="text-sm"></FontAwesomeIcon>
<div className="flex-1 px-2 text-left font-normal">
{props.blueprint.label}
</div>
<div
className={`
rounded-full bg-olympus-800 px-2 py-0.5 text-xs font-bold
dark:text-olympus-50
`}
>
{props.blueprint.era === "WW2"
? "WW2"
: props.blueprint.era.split(" ").map((word) => {
return word.charAt(0).toLocaleUpperCase();
})}
</div>
<FontAwesomeIcon
icon={faArrowRight}
className={`
px-1 transition-transform
dark:text-olympus-50
group-hover:translate-x-1
`}
></FontAwesomeIcon>
</div>
);
}

View File

@@ -3,28 +3,80 @@ import { UnitBlueprint } from "../../interfaces";
import { Coalition } from "../../types/types";
export function OlUnitSummary(props: {
blueprint: UnitBlueprint,
coalition: Coalition
blueprint: UnitBlueprint;
coalition: Coalition;
}) {
return <div data-coalition={props.coalition} className="relative border-l-4 flex flex-col gap-2 p-2 pt-4 pb-4 items-start shadow-lg bg-white dark:bg-olympus-200/30 data-[coalition='blue']:border-blue-600 data-[coalition='neutral']:border-gray-400 data-[coalition='red']:border-red-500">
<div className="flex flex-row gap-2 content-center">
<img className="absolute object-cover h-full invert opacity-10 right-5 top-0" src={"images/units/"+props.blueprint.filename} alt="" />
<div className="ml-2 my-auto w-full font-semibold tracking-tight text-gray-900 dark:text-white">{props.blueprint.label}</div>
</div>
<div className="flex flex-col justify-between px-2 leading-normal h-fit">
<p className="text-sm text-gray-700 dark:text-gray-400 mb-1">{props.blueprint.description}</p>
</div>
<div className="flex flex-row gap-1 px-2">
{props.blueprint.abilities?.split(" ").map((tag) => {
return <div key={tag} className="font-bold bg-olympus-800 dark:text-olympus-50 rounded-full px-2 py-0.5 text-xs">
{tag}
</div>
})}
<div className="font-bold bg-olympus-800 dark:text-olympus-50 rounded-full px-2 py-0.5 text-xs">{props.blueprint.era === "WW2" ? "WW2" : props.blueprint.era.split(" ").map((word) => {
return word.charAt(0).toLocaleUpperCase();
})}</div>
</div>
return (
<div
data-coalition={props.coalition}
className={`
relative flex flex-col items-start gap-2 border-l-4 bg-white p-2 pb-4
pt-4 shadow-lg
dark:bg-olympus-200/30
data-[coalition='blue']:border-blue-600
data-[coalition='neutral']:border-gray-400
data-[coalition='red']:border-red-500
`}
>
<div className="flex flex-row content-center gap-2">
<img
className={`
absolute right-5 top-0 h-full object-cover opacity-10 invert
`}
src={"images/units/" + props.blueprint.filename}
alt=""
/>
<div
className={`
my-auto ml-2 w-full font-semibold tracking-tight text-gray-900
dark:text-white
`}
>
{props.blueprint.label}
</div>
}
</div>
<div
className={`
flex h-fit flex-col justify-between px-2 leading-normal
`}
>
<p
className={`
mb-1 text-sm text-gray-700
dark:text-gray-400
`}
>
{props.blueprint.description}
</p>
</div>
<div className="flex flex-row gap-1 px-2">
{props.blueprint.abilities?.split(" ").map((tag) => {
return (
<div
key={tag}
className={`
rounded-full bg-olympus-800 px-2 py-0.5 text-xs font-bold
dark:text-olympus-50
`}
>
{tag}
</div>
);
})}
<div
className={`
rounded-full bg-olympus-800 px-2 py-0.5 text-xs font-bold
dark:text-olympus-50
`}
>
{props.blueprint.era === "WW2"
? "WW2"
: props.blueprint.era.split(" ").map((word) => {
return word.charAt(0).toLocaleUpperCase();
})}
</div>
</div>
</div>
);
}

View File

@@ -1,13 +1,43 @@
import React from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowRight, faCheckCircle, faExternalLink, faLink, faUnlink } from '@fortawesome/free-solid-svg-icons'
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowRight,
faCheckCircle,
faExternalLink,
faLink,
faUnlink,
} from "@fortawesome/free-solid-svg-icons";
export function Card(props: {
children?: JSX.Element | JSX.Element[],
className?: string
children?: JSX.Element | JSX.Element[];
className?: string;
}) {
return <div className={props.className + " group flex flex-col gap-3 border-[1px] border-black/10 max-h-80 w-full max-w-64 max-lg:max-w-[320px] dark:hover:bg-olympus-300 content-start rounded-md p-4 drop-shadow-md dark:bg-olympus-400 text-black dark:text-white text-pretty cursor-pointer"}>
{props.children}
<div className='flex flex-grow justify-end items-end text-black dark:text-gray-500 pr-2'><FontAwesomeIcon className="group-hover:translate-x-2 transition-transform" icon={faArrowRight} /></div>
return (
<div
className={`
${props.className}
group flex max-h-80 w-full max-w-64 cursor-pointer flex-col
content-start gap-3 text-pretty rounded-md border-[1px] border-black/10
p-4 text-black drop-shadow-md
dark:bg-olympus-400 dark:text-white dark:hover:bg-olympus-300
max-lg:max-w-[320px]
`}
>
{props.children}
<div
className={`
flex flex-grow items-end justify-end pr-2 text-black
dark:text-gray-500
`}
>
<FontAwesomeIcon
className={`
transition-transform
group-hover:translate-x-2
`}
icon={faArrowRight}
/>
</div>
</div>
}
);
}

View File

@@ -1,11 +1,19 @@
import React from 'react'
import React from "react";
export function Modal(props: {
grayout?: boolean,
children?: JSX.Element | JSX.Element[],
className?: string
grayout?: boolean;
children?: JSX.Element | JSX.Element[];
className?: string;
}) {
return <div className={props.className + "fixed top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%] z-ui-4 rounded-xl border-solid border-[1px] border-gray-700 drop-shadow-md "}>
{props.children}
return (
<div
className={`
${props.className}
fixed left-[50%] top-[50%] translate-x-[-50%] translate-y-[-50%] z-ui-4
rounded-xl border-[1px] border-solid border-gray-700 drop-shadow-md
`}
>
{props.children}
</div>
}
);
}

View File

@@ -1,120 +1,418 @@
import React, { useState, version } from 'react'
import { Modal } from './components/modal'
import { Card } from './components/card'
import { ErrorCallout, InfoCallout, CommandCallout } from '../../ui/components/olcallout'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faArrowRight, faCheckCircle, faDatabase, faExclamation, faExclamationCircle, faExternalLink, faLink, faServer, faSitemap, faUnlink, faWindowMaximize } from '@fortawesome/free-solid-svg-icons'
import { VERSION, connectedToServer } from '../../olympusapp'
import { faFirefoxBrowser } from '@fortawesome/free-brands-svg-icons'
import React, { useState, version } from "react";
import { Modal } from "./components/modal";
import { Card } from "./components/card";
import {
ErrorCallout,
InfoCallout,
CommandCallout,
} from "../../ui/components/olcallout";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faArrowRight,
faCheckCircle,
faDatabase,
faExclamation,
faExclamationCircle,
faExternalLink,
faLink,
faServer,
faSitemap,
faUnlink,
faWindowMaximize,
} from "@fortawesome/free-solid-svg-icons";
import { VERSION, connectedToServer } from "../../olympusapp";
import { faFirefoxBrowser } from "@fortawesome/free-brands-svg-icons";
export function LoginModal(props: {
checkingPassword: boolean,
loginError: boolean,
commandMode: string | null,
onLogin: (password: string) => void,
onContinue: (username: string) => void,
onBack: () => void
checkingPassword: boolean;
loginError: boolean;
commandMode: string | null;
onLogin: (password: string) => void;
onContinue: (username: string) => void;
onBack: () => void;
}) {
const [password, setPassword] = useState("");
const [displayName, setDisplayName] = useState("");
const [password, setPassword] = useState("");
const [displayName, setDisplayName] = useState("");
return <Modal className="inline-flex max-h-[530px] overflow-y-auto h-[75%] scroll-smooth w-[80%] max-w-[1100px] bg-white dark:bg-olympus-800 max-md:w-full max-md:h-full max-md:max-h-full max-md:rounded-none max-md:border-none ">
<img src="/resources/theme/images/splash/1.jpg" className='contents-center w-full object-cover opacity-[7%]'></img>
<div className='absolute w-full h-full bg-gradient-to-r from-blue-200/25 to-transparent'></div>
<div className='absolute w-full h-full bg-gradient-to-t from-olympus-800 to-transparent'></div>
<div className='absolute gap-8 flex flex-col p-16 max-lg:p-8 w-full '>
<div className="flex flex-row max-lg:flex-col w-full gap-6">
<div className="flex flex-grow flex-col gap-5 w-[40%] max-lg:w-[100%] content-center justify-start">
{
!props.checkingPassword ?
<>
<div className="flex flex-col items-start">
<div className="pt-1 text-gray-800 dark:text-gray-400 text-xs">Connect to</div>
<div className="flex text-gray-800 dark:text-gray-200 text-md font-bold items-center justify-center gap-2">{window.location.toString()} </div>
</div>
<div className='flex flex-row gap-2 content-center items-center w-[100%]'>
<span className='size-[80px] min-w-14'><img src="..\images\olympus-500x500.png" className='flex w-full'></img></span>
<div className="flex flex-col items-start gap-1">
<h1 className="flex text-4xl text-gray-800 dark:text-white font-bold text-wrap">DCS Olympus</h1>
<div className="flex gap-2 select-none rounded-sm content-center text-green-700 dark:text-green-400 text-sm font-semibold"><FontAwesomeIcon icon={faCheckCircle} className="my-auto" />Version {VERSION}</div>
</div>
</div>
{
!props.loginError ?
<>
{props.commandMode === null ?
<>
<div className="flex flex-col items-start gap-2">
<label className=" text-gray-800 dark:text-white text-md">Password </label>
<input type="text" onChange={(ev) => setPassword(ev.currentTarget.value)} className="w-full max-w-80 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Enter password" required />
</div>
<div className='flex'>
<button type="button" onClick={() => props.onLogin(password)} className="flex content-center items-center gap-2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800 rounded-sm">
Login <FontAwesomeIcon className="my-auto" icon={faArrowRight} />
</button>
{/*
return (
<Modal
className={`
inline-flex h-[75%] max-h-[530px] w-[80%] max-w-[1100px] overflow-y-auto
scroll-smooth bg-white
dark:bg-olympus-800
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
max-md:border-none
`}
>
<img
src="/resources/theme/images/splash/1.jpg"
className={`contents-center w-full object-cover opacity-[7%]`}
></img>
<div
className={`
absolute h-full w-full bg-gradient-to-r from-blue-200/25
to-transparent
`}
></div>
<div
className={`
absolute h-full w-full bg-gradient-to-t from-olympus-800
to-transparent
`}
></div>
<div
className={`
absolute flex w-full flex-col gap-8 p-16
max-lg:p-8
`}
>
<div
className={`
flex w-full flex-row gap-6
max-lg:flex-col
`}
>
<div
className={`
flex w-[40%] flex-grow flex-col content-center justify-start gap-5
max-lg:w-[100%]
`}
>
{!props.checkingPassword ? (
<>
<div className="flex flex-col items-start">
<div
className={`
pt-1 text-xs text-gray-800
dark:text-gray-400
`}
>
Connect to
</div>
<div
className={`
flex items-center justify-center gap-2 text-gray-800
text-md font-bold
dark:text-gray-200
`}
>
{window.location.toString()}{" "}
</div>
</div>
<div
className={`
flex w-[100%] flex-row content-center items-center gap-2
`}
>
<span className="size-[80px] min-w-14">
<img
src="..\images\olympus-500x500.png"
className={`flex w-full`}
></img>
</span>
<div
className={`
flex flex-col items-start gap-1
`}
>
<h1
className={`
flex text-wrap text-4xl font-bold text-gray-800
dark:text-white
`}
>
DCS Olympus
</h1>
<div
className={`
flex select-none content-center gap-2 rounded-sm text-sm
font-semibold text-green-700
dark:text-green-400
`}
>
<FontAwesomeIcon
icon={faCheckCircle}
className={`my-auto`}
/>
Version {VERSION}
</div>
</div>
</div>
{!props.loginError ? (
<>
{props.commandMode === null ? (
<>
<div
className={`
flex flex-col items-start
gap-2
`}
>
<label
className={`
text-gray-800 text-md
dark:text-white
`}
>
Password{" "}
</label>
<input
type="text"
onChange={(ev) =>
setPassword(ev.currentTarget.value)
}
className={`
block w-full max-w-80 rounded-lg border
border-gray-300 bg-gray-50 p-2.5 text-sm
text-gray-900
dark:border-gray-600 dark:bg-gray-700
dark:text-white dark:placeholder-gray-400
dark:focus:border-blue-500
dark:focus:ring-blue-500
focus:border-blue-500 focus:ring-blue-500
`}
placeholder="Enter password"
required
/>
</div>
<div className="flex">
<button
type="button"
onClick={() => props.onLogin(password)}
className={`
mb-2 me-2 flex content-center items-center gap-2
rounded-sm bg-blue-700 px-5 py-2.5 text-sm
font-medium text-white
dark:bg-blue-600 dark:hover:bg-blue-700
dark:focus:ring-blue-800
focus:outline-none focus:ring-4
focus:ring-blue-300
hover:bg-blue-800
`}
>
Login{" "}
<FontAwesomeIcon
className={`my-auto`}
icon={faArrowRight}
/>
</button>
{/*
<button type="button" className="flex content-center items-center gap-2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:hover:bg-gray-700 focus:outline-none dark:focus:ring-blue-800 rounded-sm dark:border-gray-600 border-[1px] dark:text-gray-400">
View Guide <FontAwesomeIcon className="my-auto text-xs" icon={faExternalLink} />
</button>
*/}
</div>
</>
:
<>
<div className="flex flex-col items-start gap-2">
<label className=" text-gray-800 dark:text-white text-md">Set display name</label>
<input type="text" onChange={(ev) => setDisplayName(ev.currentTarget.value)} className="w-full max-w-80 bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Enter display name" required />
</div>
<div className='flex'>
<button type="button" onClick={() => props.onContinue(displayName)} className="flex content-center items-center gap-2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium text-sm px-5 py-2.5 me-2 mb-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800 rounded-sm">
Continue <FontAwesomeIcon className="my-auto" icon={faArrowRight} />
</button>
<button type="button" className="flex content-center items-center gap-2 text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium text-sm px-5 py-2.5 me-2 mb-2 dark:bg-gray-800 dark:hover:bg-gray-700 focus:outline-none dark:focus:ring-blue-800 rounded-sm dark:border-gray-600 border-[1px] dark:text-gray-400">
Back
</button>
</div>
</>
}
</>
:
<><ErrorCallout title='Server could not be reached' description='The Olympus Server at this address could not be reached. Check the address is correct, restart the Olympus server or reinstall Olympus. Ensure the ports set are not already used.'></ErrorCallout><div className='text-gray-200 text-sm font-medium'>Still having issues? See our <a href='' className='underline text-blue-300 hover:no-underline'>troubleshooting guide here</a>.</div></>
}
</>
:
<div>
<svg aria-hidden="true" className="mx-auto my-auto w-40 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor" />
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill" />
</svg>
</div>
}
</div>
<div className='flex flex-grow flex-row overflow-hidden gap-3 content-end justify-center max-lg:justify-start max-md:flex-col'>
<Card className='flex'>
<img src="/resources/theme/images/splash/1.jpg" className='h-[40%] max-h-[120px] contents-center w-full object-cover rounded-md'></img>
<div className='flex font-bold mt-2 content-center items-center gap-2 '>
YouTube Video Guide <FontAwesomeIcon className="my-auto text-xs text-gray-400" icon={faExternalLink} />
</div>
<div className='text-xs text-black dark:text-gray-400 overflow-hidden text-ellipsis'>
Check out our official video tutorial on how to get started with Olympus - so you can immediately start controlling the battlefield.
</>
) : (
<>
<div
className={`
flex flex-col items-start
gap-2
`}
>
<label
className={`
text-gray-800 text-md
dark:text-white
`}
>
Set display name
</label>
<input
type="text"
onChange={(ev) =>
setDisplayName(ev.currentTarget.value)
}
className={`
block w-full max-w-80 rounded-lg border
border-gray-300 bg-gray-50 p-2.5 text-sm
text-gray-900
dark:border-gray-600 dark:bg-gray-700
dark:text-white dark:placeholder-gray-400
dark:focus:border-blue-500
dark:focus:ring-blue-500
focus:border-blue-500 focus:ring-blue-500
`}
placeholder="Enter display name"
required
/>
</div>
</Card>
<Card className='flex'>
<img src="/resources/theme/images/splash/1.jpg" className='h-[40%] max-h-[120px] contents-center w-full object-cover rounded-md'></img>
<div className='flex font-bold mt-2 content-center items-center gap-2'>
Wiki Guide <FontAwesomeIcon className="my-auto text-xs text-gray-400" icon={faExternalLink} />
<div className="flex">
<button
type="button"
onClick={() => props.onContinue(displayName)}
className={`
mb-2 me-2 flex content-center items-center gap-2
rounded-sm bg-blue-700 px-5 py-2.5 text-sm
font-medium text-white
dark:bg-blue-600 dark:hover:bg-blue-700
dark:focus:ring-blue-800
focus:outline-none focus:ring-4
focus:ring-blue-300
hover:bg-blue-800
`}
>
Continue{" "}
<FontAwesomeIcon
className={`my-auto`}
icon={faArrowRight}
/>
</button>
<button
type="button"
className={`
mb-2 me-2 flex content-center items-center gap-2
rounded-sm border-[1px] bg-blue-700 px-5 py-2.5
text-sm font-medium text-white
dark:border-gray-600 dark:bg-gray-800
dark:text-gray-400 dark:hover:bg-gray-700
dark:focus:ring-blue-800
focus:outline-none focus:ring-4
focus:ring-blue-300
hover:bg-blue-800
`}
>
Back
</button>
</div>
<div className='text-xs text-black dark:text-gray-400 overflow-hidden text-ellipsis'>
Find out more about Olympus through our online wiki guide.
</div>
</Card>
</div>
</div>
<div className="flex max-lg:flex-col w-full h-full text-gray-600 text-xs font-light">
DCS Olympus (the "MATERIAL" or "Software") is provided completely free to users subject to the terms of the CC BY-NC-SA 4.0 Licence except where such terms conflict with this disclaimer, in which case, the terms of this disclaimer shall prevail. Any party making use of the Software in any manner agrees to be bound by the terms set out in the disclaimer. THIS MATERIAL IS NOT MADE OR SUPPORTED BY EAGLE DYNAMICS SA.
</div>
</>
)}
</>
) : (
<>
<ErrorCallout
title="Server could not be reached"
description="The Olympus Server at this address could not be reached. Check the address is correct, restart the Olympus server or reinstall Olympus. Ensure the ports set are not already used."
></ErrorCallout>
<div
className={`
text-sm font-medium text-gray-200
`}
>
Still having issues? See our{" "}
<a
href=""
className={`
text-blue-300 underline
hover:no-underline
`}
>
troubleshooting guide here
</a>
.
</div>
</>
)}
</>
) : (
<div>
<svg
aria-hidden="true"
className={`
mx-auto my-auto w-40 animate-spin fill-blue-600
text-gray-200
dark:text-gray-600
`}
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
</div>
)}
</div>
<div
className={`
flex flex-grow flex-row content-end justify-center gap-3
overflow-hidden
max-lg:justify-start
max-md:flex-col
`}
>
<Card className="flex">
<img
src="/resources/theme/images/splash/1.jpg"
className={`
h-[40%] max-h-[120px] contents-center w-full rounded-md
object-cover
`}
></img>
<div
className={`
mt-2 flex content-center items-center gap-2 font-bold
`}
>
YouTube Video Guide{" "}
<FontAwesomeIcon
className={`my-auto text-xs text-gray-400`}
icon={faExternalLink}
/>
</div>
<div
className={`
overflow-hidden text-ellipsis text-xs text-black
dark:text-gray-400
`}
>
Check out our official video tutorial on how to get started with
Olympus - so you can immediately start controlling the
battlefield.
</div>
</Card>
<Card className="flex">
<img
src="/resources/theme/images/splash/1.jpg"
className={`
h-[40%] max-h-[120px] contents-center w-full rounded-md
object-cover
`}
></img>
<div
className={`
mt-2 flex content-center items-center gap-2 font-bold
`}
>
Wiki Guide{" "}
<FontAwesomeIcon
className={`my-auto text-xs text-gray-400`}
icon={faExternalLink}
/>
</div>
<div
className={`
overflow-hidden text-ellipsis text-xs text-black
dark:text-gray-400
`}
>
Find out more about Olympus through our online wiki guide.
</div>
</Card>
</div>
</div>
</Modal >
}
<div
className={`
flex h-full w-full text-xs font-light text-gray-600
max-lg:flex-col
`}
>
DCS Olympus (the "MATERIAL" or "Software") is provided completely free
to users subject to the terms of the CC BY-NC-SA 4.0 Licence except
where such terms conflict with this disclaimer, in which case, the
terms of this disclaimer shall prevail. Any party making use of the
Software in any manner agrees to be bound by the terms set out in the
disclaimer. THIS MATERIAL IS NOT MADE OR SUPPORTED BY EAGLE DYNAMICS
SA.
</div>
</div>
</Modal>
);
}

View File

@@ -3,18 +3,54 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React from "react";
export function Menu(props: {
title: string,
open: boolean,
onClose: () => void,
onBack?: () => void,
showBackButton?: boolean,
children?: JSX.Element | JSX.Element[],
title: string;
open: boolean;
onClose: () => void;
onBack?: () => void;
showBackButton?: boolean;
children?: JSX.Element | JSX.Element[];
}) {
return <div data-open={props.open} className="w-[430px] absolute top-[62px] left-16 z-ui-0 h-screen overflow-y-auto transition-transform data-[open='false']:-translate-x-full bg-gray-200 dark:bg-olympus-800/90 backdrop-blur-lg backdrop-grayscale" tabIndex={-1}>
<h5 className="w-full inline-flex items-center py-3 pb-2 px-5 shadow-lg font-semibold text-gray-800 dark:text-gray-400">
{props.showBackButton && <FontAwesomeIcon onClick={props.onBack ?? (() => { })} icon={faArrowLeft} className="mr-1 cursor-pointer p-2 rounded-md dark:hover:bg-gray-700 dark:text-gray-500 dark:hover:text-white"/>} {props.title}
<FontAwesomeIcon onClick={props.onClose} icon={faClose} className="flex text-lg items-center cursor-pointer justify-center ml-auto p-2 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 dark:hover:text-white dark:text-gray-500 "/>
</h5>
{props.children}
</div>
}
return (
<div
data-open={props.open}
className={`
absolute left-16 top-[62px] w-[430px] z-ui-0 h-screen overflow-y-auto
bg-gray-200 backdrop-blur-lg backdrop-grayscale transition-transform
dark:bg-olympus-800/90
data-[open='false']:-translate-x-full
`}
tabIndex={-1}
>
<h5
className={`
inline-flex w-full items-center px-5 py-3 pb-2 font-semibold
text-gray-800 shadow-lg
dark:text-gray-400
`}
>
{props.showBackButton && (
<FontAwesomeIcon
onClick={props.onBack ?? (() => {})}
icon={faArrowLeft}
className={`
mr-1 cursor-pointer rounded-md p-2
dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white
`}
/>
)}{" "}
{props.title}
<FontAwesomeIcon
onClick={props.onClose}
icon={faClose}
className={`
ml-auto flex cursor-pointer items-center justify-center rounded-md
p-2 text-lg
dark:text-gray-500 dark:hover:bg-gray-700 dark:hover:text-white
hover:bg-gray-200
`}
/>
</h5>
{props.children}
</div>
);
}

View File

@@ -1,96 +1,253 @@
import React, { useState } from 'react'
import { OlRoundStateButton, OlStateButton, OlLockStateButton } from '../components/olstatebutton';
import { faSkull, faCamera, faFlag, faLink, faUnlink, faBars } from '@fortawesome/free-solid-svg-icons';
import React, { useState } from "react";
import {
OlRoundStateButton,
OlStateButton,
OlLockStateButton,
} from "../components/olstatebutton";
import {
faSkull,
faCamera,
faFlag,
faLink,
faUnlink,
faBars,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { EventsConsumer } from '../../eventscontext';
import { StateConsumer } from '../../statecontext';
import { OlDropdownItem, OlDropdown } from '../components/oldropdown';
import { OlLabelToggle } from '../components/ollabeltoggle';
import { getApp, IP, connectedToServer } from '../../olympusapp';
import { olButtonsVisibilityAirbase, olButtonsVisibilityAircraft, olButtonsVisibilityDcs, olButtonsVisibilityGroundunit, olButtonsVisibilityGroundunitSam, olButtonsVisibilityHelicopter, olButtonsVisibilityHuman, olButtonsVisibilityNavyunit, olButtonsVisibilityOlympus } from '../components/olicons';
import { EventsConsumer } from "../../eventscontext";
import { StateConsumer } from "../../statecontext";
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
import { OlLabelToggle } from "../components/ollabeltoggle";
import { getApp, IP, connectedToServer } from "../../olympusapp";
import {
olButtonsVisibilityAirbase,
olButtonsVisibilityAircraft,
olButtonsVisibilityDcs,
olButtonsVisibilityGroundunit,
olButtonsVisibilityGroundunitSam,
olButtonsVisibilityHelicopter,
olButtonsVisibilityHuman,
olButtonsVisibilityNavyunit,
olButtonsVisibilityOlympus,
} from "../components/olicons";
export function Header() {
const [collapsed, setCollapsed] = useState(true);
return <StateConsumer>
{(appState) =>
<EventsConsumer>
{(events) =>
<nav className={`${collapsed? 'h-[60px]': 'h-fit'} flex w-screen bg-gray-300 border-gray-200 dark:bg-olympus-900 dark:border-gray-800 px-3 z-ui-2 drop-shadow-md`}>
<div className="w-full max-w-full flex flex-wrap overflow-hidden items-center justify-end gap-3 my-2">
<div className="flex flex-row items-center justify-start gap-6 flex-none mr-auto basis-5/6 sm:basis-0">
<img src="images/icon.png" className='h-10 w-10 p-0 rounded-md'></img>
<div className="flex flex-col items-start">
<div className="pt-1 text-gray-800 dark:text-gray-400 text-xs">Connected to</div>
<div className="flex text-gray-800 dark:text-gray-200 text-sm font-extrabold items-center justify-center gap-2">{IP} <FontAwesomeIcon icon={connectedToServer ? faLink : faUnlink} data-connected={connectedToServer} className="py-auto text-green-400 data-[connected='true']:dark:text-green-400 dark:text-red-500" /></div>
</div>
</div>
<OlStateButton onClick={() => setCollapsed(!collapsed) } checked={!collapsed} icon={faBars} tooltip={"Show more options"}></OlStateButton>
<div>
<OlLockStateButton checked={false} onClick={() => {}} tooltip="Lock/unlock protected units (from scripted mission)"/>
</div>
<div className="flex flex-row h-fit items-center justify-start gap-1">
{
Object.entries({
'human': olButtonsVisibilityHuman,'olympus': olButtonsVisibilityOlympus, 'dcs': olButtonsVisibilityDcs
}).map((entry) => {
return <OlRoundStateButton
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units" } />
})
}
</div>
<div className='h-8 w-0 border-l-[2px] border-gray-700'></div>
<div className="flex flex-row h-fit items-center justify-start gap-1">
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType( 'blue', !appState.mapHiddenTypes['blue'] )}
checked={!appState.mapHiddenTypes['blue']}
icon={faFlag} className={"!text-blue-500"}
tooltip={"Hide/show blue units" } />
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType('red', !appState.mapHiddenTypes['red'] )}
checked={!appState.mapHiddenTypes['red']}
icon={faFlag} className={"!text-red-500"}
tooltip={"Hide/show red units" } />
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType('neutral', !appState.mapHiddenTypes['neutral'] )}
checked={!appState.mapHiddenTypes['neutral']}
icon={faFlag} className={"!text-gray-500"}
tooltip={"Hide/show neutral units" } />
</div>
<div className='h-8 w-0 border-l-[2px] border-gray-700'></div>
<div className="flex flex-row h-fit items-center justify-start gap-1">
{
Object.entries({
'aircraft': olButtonsVisibilityAircraft,'helicopter': olButtonsVisibilityHelicopter, 'groundunit-sam': olButtonsVisibilityGroundunitSam,
'groundunit': olButtonsVisibilityGroundunit, 'navyunit': olButtonsVisibilityNavyunit, 'airbase': olButtonsVisibilityAirbase, 'dead': faSkull
}).map((entry) => {
return <OlRoundStateButton
onClick={() => {
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units" } />
})
}
</div>
<OlLabelToggle toggled={false} leftLabel={"Live"} rightLabel={"Map"} onClick={() => {}}></OlLabelToggle>
<OlStateButton checked={false} icon={faCamera} onClick={() => {}} tooltip="Activate/deactivate camera plugin" />
<OlDropdown label={appState.activeMapSource} className="w-80">
{appState.mapSources.map((source) => {
return <OlDropdownItem className="w-full" onClick={() => getApp().getMap().setLayerName(source)}>{ source }</OlDropdownItem>
})}
</OlDropdown>
</div>
</nav>
}
</EventsConsumer>
}
</StateConsumer>
const [collapsed, setCollapsed] = useState(true);
return (
<StateConsumer>
{(appState) => (
<EventsConsumer>
{() => (
<nav
className={`
${collapsed ? "h-[60px]" : "h-fit"}
flex w-screen border-gray-200 bg-gray-300 px-3 drop-shadow-md
z-ui-2
dark:border-gray-800 dark:bg-olympus-900
`}
>
<div
className={`
my-2 flex w-full max-w-full flex-wrap items-center justify-end
gap-3 overflow-hidden
`}
>
<div
className={`
mr-auto flex flex-none basis-5/6 flex-row items-center
justify-start gap-6
sm:basis-0
`}
>
<img
src="images/icon.png"
className="h-10 w-10 rounded-md p-0"
></img>
<div className="flex flex-col items-start">
<div
className={`
pt-1 text-xs text-gray-800
dark:text-gray-400
`}
>
Connected to
</div>
<div
className={`
flex items-center justify-center gap-2 text-sm
font-extrabold text-gray-800
dark:text-gray-200
`}
>
{IP}{" "}
<FontAwesomeIcon
icon={connectedToServer ? faLink : faUnlink}
data-connected={connectedToServer}
className={`
py-auto text-green-400
dark:text-red-500
data-[connected='true']:dark:text-green-400
`}
/>
</div>
</div>
</div>
<OlStateButton
onClick={() => setCollapsed(!collapsed)}
checked={!collapsed}
icon={faBars}
tooltip={"Show more options"}
></OlStateButton>
<div>
<OlLockStateButton
checked={false}
onClick={() => {}}
tooltip="Lock/unlock protected units (from scripted mission)"
/>
</div>
<div
className={`
flex h-fit flex-row items-center justify-start gap-1
`}
>
{Object.entries({
human: olButtonsVisibilityHuman,
olympus: olButtonsVisibilityOlympus,
dcs: olButtonsVisibilityDcs,
}).map((entry) => {
return (
<OlRoundStateButton
key={entry[0]}
onClick={() => {
getApp()
.getMap()
.setHiddenType(
entry[0],
!appState.mapHiddenTypes[entry[0]]
);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units"}
/>
);
})}
</div>
<div
className={`
h-8 w-0 border-l-[2px] border-gray-700
`}
></div>
<div
className={`
flex h-fit flex-row items-center justify-start gap-1
`}
>
<OlRoundStateButton
onClick={() =>
getApp()
.getMap()
.setHiddenType("blue", !appState.mapHiddenTypes["blue"])
}
checked={!appState.mapHiddenTypes["blue"]}
icon={faFlag}
className={"!text-blue-500"}
tooltip={"Hide/show blue units"}
/>
<OlRoundStateButton
onClick={() =>
getApp()
.getMap()
.setHiddenType("red", !appState.mapHiddenTypes["red"])
}
checked={!appState.mapHiddenTypes["red"]}
icon={faFlag}
className={"!text-red-500"}
tooltip={"Hide/show red units"}
/>
<OlRoundStateButton
onClick={() =>
getApp()
.getMap()
.setHiddenType(
"neutral",
!appState.mapHiddenTypes["neutral"]
)
}
checked={!appState.mapHiddenTypes["neutral"]}
icon={faFlag}
className={"!text-gray-500"}
tooltip={"Hide/show neutral units"}
/>
</div>
<div
className={`
h-8 w-0 border-l-[2px] border-gray-700
`}
></div>
<div
className={`
flex h-fit flex-row items-center justify-start gap-1
`}
>
{Object.entries({
aircraft: olButtonsVisibilityAircraft,
helicopter: olButtonsVisibilityHelicopter,
"groundunit-sam": olButtonsVisibilityGroundunitSam,
groundunit: olButtonsVisibilityGroundunit,
navyunit: olButtonsVisibilityNavyunit,
airbase: olButtonsVisibilityAirbase,
dead: faSkull,
}).map((entry) => {
return (
<OlRoundStateButton
key={entry[0]}
onClick={() => {
getApp()
.getMap()
.setHiddenType(
entry[0],
!appState.mapHiddenTypes[entry[0]]
);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units"}
/>
);
})}
</div>
<OlLabelToggle
toggled={false}
leftLabel={"Live"}
rightLabel={"Map"}
onClick={() => {}}
></OlLabelToggle>
<OlStateButton
checked={false}
icon={faCamera}
onClick={() => {}}
tooltip="Activate/deactivate camera plugin"
/>
<OlDropdown label={appState.activeMapSource} className="w-80">
{appState.mapSources.map((source) => {
return (
<OlDropdownItem
key={source}
className="w-full"
onClick={() => getApp().getMap().setLayerName(source)}
>
{source}
</OlDropdownItem>
);
})}
</OlDropdown>
</div>
</nav>
)}
</EventsConsumer>
)}
</StateConsumer>
);
}

View File

@@ -1,36 +1,243 @@
import React from "react";
import { Menu } from "./components/menu";
import { faArrowRightLong, faCheckCircle, faDatabase, faExternalLink, faExternalLinkAlt, faFile, faFileAlt, faFileExport, faFileImport, faTimesCircle } from '@fortawesome/free-solid-svg-icons';
import {
faArrowRightLong,
faCheckCircle,
faDatabase,
faExternalLink,
faExternalLinkAlt,
faFile,
faFileAlt,
faFileExport,
faFileImport,
faTimesCircle,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { VERSION } from "../../olympusapp";
import { faGithub } from "@fortawesome/free-brands-svg-icons";
export function MainMenu(props: {
open: boolean,
onClose: () => void,
children?: JSX.Element | JSX.Element[],
open: boolean;
onClose: () => void;
children?: JSX.Element | JSX.Element[];
}) {
return <Menu
title="Main Menu"
open={props.open}
showBackButton={false}
onClose={props.onClose}
return (
<Menu
title="Main Menu"
open={props.open}
showBackButton={false}
onClose={props.onClose}
>
<div className="flex flex-col p-5 gap-1 font-normal text-gray-900 dark:text-white">
<div className="mb-1">
<div className="flex gap-2 mb-2 select-none rounded-sm content-center text-green-700 dark:text-green-400 font-bold text-lg"><FontAwesomeIcon icon={faCheckCircle} className="my-auto" />Olympus Version {VERSION}</div>
<div className="text-gray-400 text-sm">You can use the Olympus Manager to update port, passwords or other settings.</div>
</div>
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faGithub} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}View GitHub Repo <FontAwesomeIcon icon={faExternalLinkAlt} className="my-auto w-4 text-gray-800 dark:text-gray-500 text-sm" /><div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faFile} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}View User Guide <FontAwesomeIcon icon={faExternalLinkAlt} className="my-auto w-4 text-gray-800 dark:text-gray-500 text-sm" /><div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
<hr className="w-auto m-2 my-1 bg-gray-700 border-[1px] dark:border-olympus-500"></hr>
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faDatabase} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}Open Olympus Manager<div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faDatabase} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}Database Manager<div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faFileExport} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}Export to file<div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faFileImport} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}Import from file<div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
<hr className="w-auto m-2 my-1 bg-gray-700 border-[1px] dark:border-olympus-500"></hr>
<div className="group flex gap-3 p-2 cursor-pointer select-none hover:bg-gray-900/10 dark:hover:bg-olympus-500 rounded-md content-center">{/*<FontAwesomeIcon icon={faTimesCircle} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}Close Olympus<div className="flex items-center ml-auto"><FontAwesomeIcon icon={faArrowRightLong} className="my-auto px-2 text-gray-800 dark:text-olympus-50 text-right group-hover:translate-x-2 transition-transform" /></div></div>
<div
className={`
flex flex-col gap-1 p-5 font-normal text-gray-900
dark:text-white
`}
>
<div className="mb-1">
<div
className={`
mb-2 flex select-none content-center gap-2 rounded-sm text-lg
font-bold text-green-700
dark:text-green-400
`}
>
<FontAwesomeIcon
icon={faCheckCircle}
className={`my-auto`}
/>
Olympus Version {VERSION}
</div>
<div className="text-sm text-gray-400">
You can use the Olympus Manager to update port, passwords or other
settings.
</div>
</div>
<div
className={`
group flex cursor-pointer select-none content-center gap-3
rounded-md p-2
dark:hover:bg-olympus-500
hover:bg-gray-900/10
`}
>
{/*<FontAwesomeIcon icon={faGithub} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
View GitHub Repo{" "}
<FontAwesomeIcon
icon={faExternalLinkAlt}
className={`
my-auto w-4 text-sm text-gray-800
dark:text-gray-500
`}
/>
<div className={`ml-auto flex items-center`}>
<FontAwesomeIcon
icon={faArrowRightLong}
className={`
my-auto px-2 text-right text-gray-800 transition-transform
dark:text-olympus-50
group-hover:translate-x-2
`}
/>
</div>
</div>
<div
className={`
group flex cursor-pointer select-none content-center gap-3
rounded-md p-2
dark:hover:bg-olympus-500
hover:bg-gray-900/10
`}
>
{/*<FontAwesomeIcon icon={faFile} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
View User Guide{" "}
<FontAwesomeIcon
icon={faExternalLinkAlt}
className={`
my-auto w-4 text-sm text-gray-800
dark:text-gray-500
`}
/>
<div className={`ml-auto flex items-center`}>
<FontAwesomeIcon
icon={faArrowRightLong}
className={`
my-auto px-2 text-right text-gray-800 transition-transform
dark:text-olympus-50
group-hover:translate-x-2
`}
/>
</div>
</div>
<hr
className={`
m-2 my-1 w-auto border-[1px] bg-gray-700
dark:border-olympus-500
`}
></hr>
<div
className={`
group flex cursor-pointer select-none content-center gap-3
rounded-md p-2
dark:hover:bg-olympus-500
hover:bg-gray-900/10
`}
>
{/*<FontAwesomeIcon icon={faDatabase} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
Open Olympus Manager
<div
className={`ml-auto flex items-center`}
>
<FontAwesomeIcon
icon={faArrowRightLong}
className={`
my-auto px-2 text-right text-gray-800 transition-transform
dark:text-olympus-50
group-hover:translate-x-2
`}
/>
</div>
</div>
<div
className={`
group flex cursor-pointer select-none content-center gap-3
rounded-md p-2
dark:hover:bg-olympus-500
hover:bg-gray-900/10
`}
>
{/*<FontAwesomeIcon icon={faDatabase} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
Database Manager
<div
className={`ml-auto flex items-center`}
>
<FontAwesomeIcon
icon={faArrowRightLong}
className={`
my-auto px-2 text-right text-gray-800 transition-transform
dark:text-olympus-50
group-hover:translate-x-2
`}
/>
</div>
</div>
<div
className={`
group flex cursor-pointer select-none content-center gap-3
rounded-md p-2
dark:hover:bg-olympus-500
hover:bg-gray-900/10
`}
>
{/*<FontAwesomeIcon icon={faFileExport} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
Export to file
<div
className={`ml-auto flex items-center`}
>
<FontAwesomeIcon
icon={faArrowRightLong}
className={`
my-auto px-2 text-right text-gray-800 transition-transform
dark:text-olympus-50
group-hover:translate-x-2
`}
/>
</div>
</div>
<div
className={`
group flex cursor-pointer select-none content-center gap-3
rounded-md p-2
dark:hover:bg-olympus-500
hover:bg-gray-900/10
`}
>
{/*<FontAwesomeIcon icon={faFileImport} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
Import from file
<div
className={`ml-auto flex items-center`}
>
<FontAwesomeIcon
icon={faArrowRightLong}
className={`
my-auto px-2 text-right text-gray-800 transition-transform
dark:text-olympus-50
group-hover:translate-x-2
`}
/>
</div>
</div>
<hr
className={`
m-2 my-1 w-auto border-[1px] bg-gray-700
dark:border-olympus-500
`}
></hr>
<div
className={`
group flex cursor-pointer select-none content-center gap-3
rounded-md p-2
dark:hover:bg-olympus-500
hover:bg-gray-900/10
`}
>
{/*<FontAwesomeIcon icon={faTimesCircle} className="my-auto w-4 text-gray-800 dark:text-gray-500" />*/}
Close Olympus
<div
className={`ml-auto flex items-center`}
>
<FontAwesomeIcon
icon={faArrowRightLong}
className={`
my-auto px-2 text-right text-gray-800 transition-transform
dark:text-olympus-50
group-hover:translate-x-2
`}
/>
</div>
</div>
</div>
</Menu>
}
);
}

View File

@@ -2,78 +2,123 @@ import React, { useState, useEffect } from "react";
import { zeroAppend } from "../../other/utils";
import { DateAndTime } from "../../interfaces";
export function MiniMapPanel(props: {
export function MiniMapPanel(props: {}) {
var [frameRate, setFrameRate] = useState(0);
var [load, setLoad] = useState(0);
var [elapsedTime, setElapsedTime] = useState(0);
var [missionTime, setMissionTime] = useState({
h: 0,
m: 0,
s: 0,
} as DateAndTime["time"]);
var [connected, setConnected] = useState(false);
var [paused, setPaused] = useState(false);
var [showMissionTime, setShowMissionTime] = useState(false);
}) {
var [frameRate, setFrameRate] = useState(0);
var [load, setLoad] = useState(0);
var [elapsedTime, setElapsedTime] = useState(0);
var [missionTime, setMissionTime] = useState({ h: 0, m: 0, s: 0 } as DateAndTime["time"]);
var [connected, setConnected] = useState(false);
var [paused, setPaused] = useState(false);
var [showMissionTime, setShowMissionTime] = useState(false);
document.addEventListener("serverStatusUpdated", (ev) => {
setFrameRate(ev.detail.frameRate);
setLoad(ev.detail.load);
setElapsedTime(ev.detail.elapsedTime);
setMissionTime(ev.detail.missionTime);
setConnected(ev.detail.connected);
setPaused(ev.detail.paused);
});
document.addEventListener("serverStatusUpdated", (ev) => {
setFrameRate(ev.detail.frameRate);
setLoad(ev.detail.load);
setElapsedTime(ev.detail.elapsedTime);
setMissionTime(ev.detail.missionTime);
setConnected(ev.detail.connected);
setPaused(ev.detail.paused);
})
// A bit of a hack to set the rounded borders to the minimap
useEffect(() => {
let miniMap = document.querySelector(".leaflet-control-minimap");
if (miniMap) {
miniMap.classList.add("rounded-t-lg")
}
})
// Compute the time string depending on mission or elapsed time
let hours = 0;
let minutes = 0;
let seconds = 0;
if (showMissionTime) {
hours = missionTime.h;
minutes = missionTime.m;
seconds = missionTime.s;
} else {
hours = Math.floor(elapsedTime / 3600);
minutes = Math.floor((elapsedTime / 60)) % 60;
seconds = Math.round(elapsedTime) % 60;
// A bit of a hack to set the rounded borders to the minimap
useEffect(() => {
let miniMap = document.querySelector(".leaflet-control-minimap");
if (miniMap) {
miniMap.classList.add("rounded-t-lg");
}
});
let timeString = `${zeroAppend(hours, 2)}:${zeroAppend(minutes, 2)}:${zeroAppend(seconds, 2)}`
// Compute the time string depending on mission or elapsed time
let hours = 0;
let minutes = 0;
let seconds = 0;
// Choose frame rate string color
let frameRateColor = "#8BFF63";
if (frameRate < 30)
frameRateColor = "#F05252";
else if (frameRate >= 30 && frameRate < 60)
frameRateColor = "#FF9900"
if (showMissionTime) {
hours = missionTime.h;
minutes = missionTime.m;
seconds = missionTime.s;
} else {
hours = Math.floor(elapsedTime / 3600);
minutes = Math.floor(elapsedTime / 60) % 60;
seconds = Math.round(elapsedTime) % 60;
}
// Choose load string color
let loadColor = "#8BFF63";
if (load > 1000)
loadColor = "#F05252";
else if (load >= 100 && load < 1000)
loadColor = "#FF9900"
let timeString = `${zeroAppend(hours, 2)}:${zeroAppend(minutes, 2)}:${zeroAppend(seconds, 2)}`;
return <div onClick={() => setShowMissionTime(!showMissionTime)} className="absolute w-[288px] top-[233px] right-[10px] z-ui-0 text-sm dark:text-gray-200 bg-gray-200 dark:bg-olympus-800/90 backdrop-blur-lg backdrop-grayscale flex items-center justify-between p-3 rounded-b-lg">
{
!connected ?
<div className="font-semibold animate-pulse flex items-center gap-2"><div className="bg-[#F05252] relative w-4 h-4 rounded-full"></div>Server disconnected</div> :
paused ?
<div className="font-semibold animate-pulse flex items-center gap-2"><div className="bg-[#FF9900] relative w-4 h-4 rounded-full"></div>Server paused</div> :
<>
<div className="font-semibold">FPS: <span style={{ 'color': frameRateColor }} className="font-semibold">{frameRate}</span> </div>
<div className="font-semibold">Load: <span style={{ 'color': loadColor }} className="font-semibold">{load}</span> </div>
<div className="font-semibold">{showMissionTime ? "MT" : "ET"}: {timeString} </div>
<div className="bg-[#8BFF63] relative w-4 h-4 rounded-full"></div>
</>
}
// Choose frame rate string color
let frameRateColor = "#8BFF63";
if (frameRate < 30) frameRateColor = "#F05252";
else if (frameRate >= 30 && frameRate < 60) frameRateColor = "#FF9900";
// Choose load string color
let loadColor = "#8BFF63";
if (load > 1000) loadColor = "#F05252";
else if (load >= 100 && load < 1000) loadColor = "#FF9900";
return (
<div
onClick={() => setShowMissionTime(!showMissionTime)}
className={`
absolute right-[10px] top-[233px] w-[288px] z-ui-0 flex items-center
justify-between rounded-b-lg bg-gray-200 p-3 text-sm backdrop-blur-lg
backdrop-grayscale
dark:bg-olympus-800/90 dark:text-gray-200
`}
>
{!connected ? (
<div
className={`
flex animate-pulse items-center gap-2 font-semibold
`}
>
<div
className={`relative h-4 w-4 rounded-full bg-[#F05252]`}
></div>
Server disconnected
</div>
) : paused ? (
<div
className={`
flex animate-pulse items-center gap-2 font-semibold
`}
>
<div
className={`relative h-4 w-4 rounded-full bg-[#FF9900]`}
></div>
Server paused
</div>
) : (
<>
<div className="font-semibold">
FPS:{" "}
<span
style={{ color: frameRateColor }}
className={`font-semibold`}
>
{frameRate}
</span>{" "}
</div>
<div className="font-semibold">
Load:{" "}
<span
style={{ color: loadColor }}
className={`font-semibold`}
>
{load}
</span>{" "}
</div>
<div className="font-semibold">
{showMissionTime ? "MT" : "ET"}: {timeString}{" "}
</div>
<div
className={`relative h-4 w-4 rounded-full bg-[#8BFF63]`}
></div>
</>
)}
</div>
}
);
}

View File

@@ -7,68 +7,197 @@ import { MapOptions } from "../../types/types";
import { getApp } from "../../olympusapp";
export function Options(props: {
open: boolean,
onClose: () => void,
options: MapOptions,
children?: JSX.Element | JSX.Element[],
open: boolean;
onClose: () => void;
options: MapOptions;
children?: JSX.Element | JSX.Element[];
}) {
return <Menu
title="User preferences"
open={props.open}
showBackButton={false}
onClose={props.onClose}
return (
<Menu
title="User preferences"
open={props.open}
showBackButton={false}
onClose={props.onClose}
>
<div className="flex flex-col p-5 gap-2 font-normal text-gray-800 dark:text-white ">
<div
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
onClick={() => { getApp().getMap().setOption("showUnitLabels", !props.options.showUnitLabels) }}
>
<OlCheckbox checked={props.options.showUnitLabels} onChange={() => { }}></OlCheckbox>
<span>Show Unit Labels</span>
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">L</kbd>
</div>
<div
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
onClick={() => { getApp().getMap().setOption("showUnitsEngagementRings", !props.options.showUnitsEngagementRings) }}
>
<OlCheckbox checked={props.options.showUnitsEngagementRings} onChange={() => { }}></OlCheckbox>
<span>Show Threat Rings</span>
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">Q</kbd>
</div>
<div
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
onClick={() => { getApp().getMap().setOption("showUnitsAcquisitionRings", !props.options.showUnitsAcquisitionRings) }}
>
<OlCheckbox checked={props.options.showUnitsAcquisitionRings} onChange={() => { }}></OlCheckbox>
<span>Show Detection rings</span>
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">E</kbd>
</div>
<div
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
onClick={() => { getApp().getMap().setOption("showUnitTargets", !props.options.showUnitTargets) }}
>
<OlCheckbox checked={props.options.showUnitTargets} onChange={() => { }}></OlCheckbox>
<span>Show Detection lines</span>
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">F</kbd>
</div>
<div
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
onClick={() => { getApp().getMap().setOption("hideUnitsShortRangeRings", !props.options.hideUnitsShortRangeRings) }}
>
<OlCheckbox checked={props.options.hideUnitsShortRangeRings} onChange={() => { }}></OlCheckbox>
<span>Hide Short range Rings</span>
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">R</kbd>
</div>
<div
className="group flex flex-row rounded-md justify-content gap-4 p-2 dark:hover:bg-olympus-400 cursor-pointer"
onClick={() => { getApp().getMap().setOption("hideGroupMembers", !props.options.hideGroupMembers) }}
>
<OlCheckbox checked={props.options.hideGroupMembers} onChange={() => { }}></OlCheckbox>
<span>Hide Group members</span>
<kbd className="ml-auto px-2 py-1.5 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-600 dark:text-gray-100 dark:border-gray-500">G</kbd>
</div>
<div
className={`
flex flex-col gap-2 p-5 font-normal text-gray-800
dark:text-white
`}
>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp()
.getMap()
.setOption("showUnitLabels", !props.options.showUnitLabels);
}}
>
<OlCheckbox
checked={props.options.showUnitLabels}
onChange={() => {}}
></OlCheckbox>
<span>Show Unit Labels</span>
<kbd
className={`
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
text-xs font-semibold text-gray-800
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
`}
>
L
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp()
.getMap()
.setOption(
"showUnitsEngagementRings",
!props.options.showUnitsEngagementRings
);
}}
>
<OlCheckbox
checked={props.options.showUnitsEngagementRings}
onChange={() => {}}
></OlCheckbox>
<span>Show Threat Rings</span>
<kbd
className={`
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
text-xs font-semibold text-gray-800
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
`}
>
Q
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp()
.getMap()
.setOption(
"showUnitsAcquisitionRings",
!props.options.showUnitsAcquisitionRings
);
}}
>
<OlCheckbox
checked={props.options.showUnitsAcquisitionRings}
onChange={() => {}}
></OlCheckbox>
<span>Show Detection rings</span>
<kbd
className={`
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
text-xs font-semibold text-gray-800
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
`}
>
E
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp()
.getMap()
.setOption("showUnitTargets", !props.options.showUnitTargets);
}}
>
<OlCheckbox
checked={props.options.showUnitTargets}
onChange={() => {}}
></OlCheckbox>
<span>Show Detection lines</span>
<kbd
className={`
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
text-xs font-semibold text-gray-800
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
`}
>
F
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp()
.getMap()
.setOption(
"hideUnitsShortRangeRings",
!props.options.hideUnitsShortRangeRings
);
}}
>
<OlCheckbox
checked={props.options.hideUnitsShortRangeRings}
onChange={() => {}}
></OlCheckbox>
<span>Hide Short range Rings</span>
<kbd
className={`
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
text-xs font-semibold text-gray-800
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
`}
>
R
</kbd>
</div>
<div
className={`
group flex flex-row rounded-md justify-content cursor-pointer gap-4
p-2
dark:hover:bg-olympus-400
`}
onClick={() => {
getApp()
.getMap()
.setOption("hideGroupMembers", !props.options.hideGroupMembers);
}}
>
<OlCheckbox
checked={props.options.hideGroupMembers}
onChange={() => {}}
></OlCheckbox>
<span>Hide Group members</span>
<kbd
className={`
ml-auto rounded-lg border border-gray-200 bg-gray-100 px-2 py-1.5
text-xs font-semibold text-gray-800
dark:border-gray-500 dark:bg-gray-600 dark:text-gray-100
`}
>
G
</kbd>
</div>
{/*
{/*
<hr className="w-auto m-2 my-1 bg-gray-700 border-[1px] dark:border-olympus-500"></hr>
<div className="flex flex-col content-center items-start justify-between p-2 gap-2">
<div className="flex flex-col">
@@ -97,6 +226,7 @@ export function Options(props: {
/>
</div>
</div> */}
</div>
</div>
</Menu>
}
);
}

View File

@@ -1,33 +1,92 @@
import React from 'react'
import { OlStateButton } from '../components/olstatebutton';
import { faPlus, faGamepad, faRuler, faPencil, faEllipsisV, faCog, faQuestionCircle, faPlusSquare } from '@fortawesome/free-solid-svg-icons';
import { EventsConsumer } from '../../eventscontext';
import { StateConsumer } from '../../statecontext';
import React from "react";
import { OlStateButton } from "../components/olstatebutton";
import {
faPlus,
faGamepad,
faRuler,
faPencil,
faEllipsisV,
faCog,
faQuestionCircle,
faPlusSquare,
} from "@fortawesome/free-solid-svg-icons";
import { EventsConsumer } from "../../eventscontext";
import { StateConsumer } from "../../statecontext";
export function SideBar() {
return <StateConsumer>
{(appState) =>
<EventsConsumer>
{(events) =>
<nav className="flex flex-col z-ui-1 h-full bg-gray-300 dark:bg-olympus-900">
<div className="flex-1 flex-wrap items-center justify-center p-4 w-16">
<div className="flex flex-col items-center justify-center gap-2.5">
<OlStateButton onClick={events.toggleMainMenuVisible} checked={appState.mainMenuVisible} icon={faEllipsisV} tooltip="Hide/show main menu"></OlStateButton>
<OlStateButton onClick={events.toggleSpawnMenuVisible} checked={appState.spawnMenuVisible} icon={faPlusSquare} tooltip="Hide/show unit spawn menu"></OlStateButton>
<OlStateButton onClick={events.toggleUnitControlMenuVisible} checked={appState.unitControlMenuVisible} icon={faGamepad} tooltip=""></OlStateButton>
<OlStateButton onClick={events.toggleMeasureMenuVisible} checked={appState.measureMenuVisible} icon={faRuler} tooltip=""></OlStateButton>
<OlStateButton onClick={events.toggleDrawingMenuVisible} checked={appState.drawingMenuVisible} icon={faPencil} tooltip="Hide/show drawing menu"></OlStateButton>
</div>
</div>
<div className="flex flex-wrap content-end justify-center p-4 w-16">
<div className="flex flex-col items-center justify-center gap-2.5">
<OlStateButton onClick={() => window.open("https://github.com/Pax1601/DCSOlympus/wiki")} checked={false} icon={faQuestionCircle} tooltip="Open user guide on separate window"></OlStateButton>
<OlStateButton onClick={events.toggleOptionsMenuVisible} checked={appState.optionsMenuVisible} icon={faCog} tooltip="Hide/show settings menu"></OlStateButton>
</div>
</div>
</nav>
}
</EventsConsumer>
}
</StateConsumer>
return (
<StateConsumer>
{(appState) => (
<EventsConsumer>
{(events) => (
<nav
className={`
flex flex-col z-ui-1 h-full bg-gray-300
dark:bg-olympus-900
`}
>
<div className={`
w-16 flex-1 flex-wrap items-center justify-center p-4
`}>
<div className={`
flex flex-col items-center justify-center gap-2.5
`}>
<OlStateButton
onClick={events.toggleMainMenuVisible}
checked={appState.mainMenuVisible}
icon={faEllipsisV}
tooltip="Hide/show main menu"
></OlStateButton>
<OlStateButton
onClick={events.toggleSpawnMenuVisible}
checked={appState.spawnMenuVisible}
icon={faPlusSquare}
tooltip="Hide/show unit spawn menu"
></OlStateButton>
<OlStateButton
onClick={events.toggleUnitControlMenuVisible}
checked={appState.unitControlMenuVisible}
icon={faGamepad}
tooltip=""
></OlStateButton>
<OlStateButton
onClick={events.toggleMeasureMenuVisible}
checked={appState.measureMenuVisible}
icon={faRuler}
tooltip=""
></OlStateButton>
<OlStateButton
onClick={events.toggleDrawingMenuVisible}
checked={appState.drawingMenuVisible}
icon={faPencil}
tooltip="Hide/show drawing menu"
></OlStateButton>
</div>
</div>
<div className="flex w-16 flex-wrap content-end justify-center p-4">
<div className={`
flex flex-col items-center justify-center gap-2.5
`}>
<OlStateButton
onClick={() =>
window.open("https://github.com/Pax1601/DCSOlympus/wiki")
}
checked={false}
icon={faQuestionCircle}
tooltip="Open user guide on separate window"
></OlStateButton>
<OlStateButton
onClick={events.toggleOptionsMenuVisible}
checked={appState.optionsMenuVisible}
icon={faCog}
tooltip="Hide/show settings menu"
></OlStateButton>
</div>
</div>
</nav>
)}
</EventsConsumer>
)}
</StateConsumer>
);
}

View File

@@ -1,115 +1,179 @@
import React, { useState } from "react";
import { Menu } from "./components/menu";
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { library } from '@fortawesome/fontawesome-svg-core'
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { library } from "@fortawesome/fontawesome-svg-core";
import { OlSearchBar } from "../components/olsearchbar";
import { OlAccordion } from "../components/olaccordion";
import { getApp } from "../../olympusapp";
import { OlUnitEntryList } from "../components/olunitlistentry";
import { UnitSpawnMenu } from "./unitspawnmenu";
import { UnitBlueprint } from "../../interfaces";
import { olButtonsVisibilityAircraft, olButtonsVisibilityGroundunit, olButtonsVisibilityGroundunitSam, olButtonsVisibilityHelicopter, olButtonsVisibilityNavyunit } from "../components/olicons";
import {
olButtonsVisibilityAircraft,
olButtonsVisibilityGroundunit,
olButtonsVisibilityGroundunitSam,
olButtonsVisibilityHelicopter,
olButtonsVisibilityNavyunit,
} from "../components/olicons";
library.add(faPlus);
function filterUnits(blueprints: { [key: string]: UnitBlueprint }, filterString: string) {
var filteredUnits = {};
if (blueprints) {
Object.entries(blueprints).forEach(([key, value]) => {
if (value.enabled && (filterString === "" || value.label.includes(filterString)))
filteredUnits[key] = value;
});
}
return filteredUnits;
function filterUnits(
blueprints: { [key: string]: UnitBlueprint },
filterString: string
) {
var filteredUnits = {};
if (blueprints) {
Object.entries(blueprints).forEach(([key, value]) => {
if (
value.enabled &&
(filterString === "" || value.label.includes(filterString))
)
filteredUnits[key] = value;
});
}
return filteredUnits;
}
export function SpawnMenu(props: {
open: boolean,
onClose: () => void,
children?: JSX.Element | JSX.Element[],
open: boolean;
onClose: () => void;
children?: JSX.Element | JSX.Element[];
}) {
var [blueprint, setBlueprint] = useState(null as (null | UnitBlueprint));
var [filterString, setFilterString] = useState("");
var [blueprint, setBlueprint] = useState(null as null | UnitBlueprint);
var [filterString, setFilterString] = useState("");
/* Filter aircrafts, helicopters, and navyunits */
const filteredAircraft = filterUnits(getApp()?.getAircraftDatabase()?.blueprints, filterString);
const filteredHelicopters = filterUnits(getApp()?.getHelicopterDatabase()?.blueprints, filterString);
const filteredNavyUnits = filterUnits(getApp()?.getNavyUnitDatabase()?.blueprints, filterString);
/* Filter aircrafts, helicopters, and navyunits */
const filteredAircraft = filterUnits(
getApp()?.getAircraftDatabase()?.blueprints,
filterString
);
const filteredHelicopters = filterUnits(
getApp()?.getHelicopterDatabase()?.blueprints,
filterString
);
const filteredNavyUnits = filterUnits(
getApp()?.getNavyUnitDatabase()?.blueprints,
filterString
);
/* Split ground units between air defence and all others */
var filteredAirDefense = {};
var filteredGroundUnits = {};
Object.keys(getApp()?.getGroundUnitDatabase()?.blueprints ?? {}).forEach((key) => {
var blueprint = getApp()?.getGroundUnitDatabase()?.blueprints[key];
var type = blueprint.label;
if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type)) {
filteredAirDefense[key] = blueprint;
} else {
filteredGroundUnits[key] = blueprint;
}
});
filteredAirDefense = filterUnits(filteredAirDefense, filterString);
filteredGroundUnits = filterUnits(filteredGroundUnits, filterString);
/* Split ground units between air defence and all others */
var filteredAirDefense = {};
var filteredGroundUnits = {};
Object.keys(getApp()?.getGroundUnitDatabase()?.blueprints ?? {}).forEach(
(key) => {
var blueprint = getApp()?.getGroundUnitDatabase()?.blueprints[key];
var type = blueprint.label;
if (/\bAAA|SAM\b/.test(type) || /\bmanpad|stinger\b/i.test(type)) {
filteredAirDefense[key] = blueprint;
} else {
filteredGroundUnits[key] = blueprint;
}
}
);
filteredAirDefense = filterUnits(filteredAirDefense, filterString);
filteredGroundUnits = filterUnits(filteredGroundUnits, filterString);
/* When the menu is closed, reset the blueprint */
if (!props.open && blueprint !== null)
setBlueprint(null);
/* When the menu is closed, reset the blueprint */
if (!props.open && blueprint !== null) setBlueprint(null);
return <Menu {...props}
title="Spawn menu"
showBackButton={blueprint !== null}
onBack={() => setBlueprint(null)}
>
<>
{(blueprint === null) && <div className="p-5">
<OlSearchBar onChange={(ev) => setFilterString(ev.target.value)} />
<OlAccordion title={`Aircraft`}>
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
{Object.keys(filteredAircraft).map((key) => {
const blueprint = getApp().getAircraftDatabase().blueprints[key];
return <OlUnitEntryList key={key} icon={olButtonsVisibilityAircraft} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
})}
</div>
</OlAccordion>
<OlAccordion title={`Helicopters`}>
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
{Object.keys(filteredHelicopters).map((key) => {
const blueprint = getApp().getHelicopterDatabase().blueprints[key];
return <OlUnitEntryList key={key} icon={olButtonsVisibilityHelicopter} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
})}
</div>
</OlAccordion>
<OlAccordion title={`SAM & AAA`}>
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
{Object.keys(filteredAirDefense).map((key) => {
const blueprint = getApp().getGroundUnitDatabase().blueprints[key];
return <OlUnitEntryList key={key} icon={olButtonsVisibilityGroundunitSam} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
})}
</div>
</OlAccordion>
<OlAccordion title={`Ground Units`}>
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
{Object.keys(filteredGroundUnits).map((key) => {
const blueprint = getApp().getGroundUnitDatabase().blueprints[key];
return <OlUnitEntryList key={key} icon={olButtonsVisibilityGroundunit} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
})}
</div>
</OlAccordion>
<OlAccordion title={`Ships and submarines`}>
<div className="flex flex-col gap-1 max-h-80 overflow-y-scroll">
{Object.keys(filteredNavyUnits).map((key) => {
const blueprint = getApp().getNavyUnitDatabase().blueprints[key];
return <OlUnitEntryList key={key} icon={olButtonsVisibilityNavyunit} blueprint={blueprint} onClick={() => setBlueprint(blueprint)} />
})}
</div>
</OlAccordion>
<OlAccordion title="Effects (smokes, explosions etc)">
return (
<Menu
{...props}
title="Spawn menu"
showBackButton={blueprint !== null}
onBack={() => setBlueprint(null)}
>
<>
{blueprint === null && (
<div className="p-5">
<OlSearchBar onChange={(ev) => setFilterString(ev.target.value)} />
<OlAccordion title={`Aircraft`}>
<div className="flex max-h-80 flex-col gap-1 overflow-y-scroll">
{Object.keys(filteredAircraft).map((key) => {
const blueprint =
getApp().getAircraftDatabase().blueprints[key];
return (
<OlUnitEntryList
key={key}
icon={olButtonsVisibilityAircraft}
blueprint={blueprint}
onClick={() => setBlueprint(blueprint)}
/>
);
})}
</div>
</OlAccordion>
<OlAccordion title={`Helicopters`}>
<div className="flex max-h-80 flex-col gap-1 overflow-y-scroll">
{Object.keys(filteredHelicopters).map((key) => {
const blueprint =
getApp().getHelicopterDatabase().blueprints[key];
return (
<OlUnitEntryList
key={key}
icon={olButtonsVisibilityHelicopter}
blueprint={blueprint}
onClick={() => setBlueprint(blueprint)}
/>
);
})}
</div>
</OlAccordion>
<OlAccordion title={`SAM & AAA`}>
<div className="flex max-h-80 flex-col gap-1 overflow-y-scroll">
{Object.keys(filteredAirDefense).map((key) => {
const blueprint =
getApp().getGroundUnitDatabase().blueprints[key];
return (
<OlUnitEntryList
key={key}
icon={olButtonsVisibilityGroundunitSam}
blueprint={blueprint}
onClick={() => setBlueprint(blueprint)}
/>
);
})}
</div>
</OlAccordion>
<OlAccordion title={`Ground Units`}>
<div className="flex max-h-80 flex-col gap-1 overflow-y-scroll">
{Object.keys(filteredGroundUnits).map((key) => {
const blueprint =
getApp().getGroundUnitDatabase().blueprints[key];
return (
<OlUnitEntryList
key={key}
icon={olButtonsVisibilityGroundunit}
blueprint={blueprint}
onClick={() => setBlueprint(blueprint)}
/>
);
})}
</div>
</OlAccordion>
<OlAccordion title={`Ships and submarines`}>
<div className="flex max-h-80 flex-col gap-1 overflow-y-scroll">
{Object.keys(filteredNavyUnits).map((key) => {
const blueprint =
getApp().getNavyUnitDatabase().blueprints[key];
return (
<OlUnitEntryList
key={key}
icon={olButtonsVisibilityNavyunit}
blueprint={blueprint}
onClick={() => setBlueprint(blueprint)}
/>
);
})}
</div>
</OlAccordion>
<OlAccordion title="Effects (smokes, explosions etc)"></OlAccordion>
</div>
)}
</OlAccordion>
</div>
}
{!(blueprint === null) && <UnitSpawnMenu blueprint={blueprint} />}
</>
</Menu>
}
{!(blueprint === null) && <UnitSpawnMenu blueprint={blueprint} />}
</>
</Menu>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,89 +1,128 @@
import React, { useState } from 'react';
import { Unit } from '../../unit/unit';
import { ContextActionSet } from '../../unit/contextactionset';
import { OlStateButton } from '../components/olstatebutton';
import { getApp } from '../../olympusapp';
import { ContextAction } from '../../unit/contextaction';
import { CONTEXT_ACTION } from '../../constants/constants';
import { FaInfoCircle } from 'react-icons/fa';
import React, { useState } from "react";
import { Unit } from "../../unit/unit";
import { ContextActionSet } from "../../unit/contextactionset";
import { OlStateButton } from "../components/olstatebutton";
import { getApp } from "../../olympusapp";
import { ContextAction } from "../../unit/contextaction";
import { CONTEXT_ACTION } from "../../constants/constants";
import { FaInfoCircle } from "react-icons/fa";
export function UnitMouseControlBar(props: {
export function UnitMouseControlBar(props: {}) {
var [open, setOpen] = useState(false);
var [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
var [contextActionsSet, setContextActionsSet] = useState(
new ContextActionSet()
);
var [activeContextAction, setActiveContextAction] = useState(
null as null | ContextAction
);
}) {
var [open, setOpen] = useState(false);
var [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
var [contextActionsSet, setContextActionsSet] = useState(new ContextActionSet());
var [activeContextAction, setActiveContextAction] = useState(null as null | ContextAction);
/* When a unit is selected, open the menu */
document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
setOpen(true);
setSelectedUnits(ev.detail as Unit[]);
/* When a unit is selected, open the menu */
document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
setOpen(true);
setSelectedUnits(ev.detail as Unit[]);
updateData();
});
updateData();
})
/* When a unit is deselected, refresh the view */
document.addEventListener("unitDeselection", (ev: CustomEventInit) => {
/* TODO add delay to avoid doing it too many times */
updateData();
});
/* When a unit is deselected, refresh the view */
document.addEventListener("unitDeselection", (ev: CustomEventInit) => {
/* TODO add delay to avoid doing it too many times */
updateData();
})
/* When all units are deselected clean the view */
document.addEventListener("clearSelection", () => {
setOpen(false);
setSelectedUnits([]);
updateData();
});
/* When all units are deselected clean the view */
document.addEventListener("clearSelection", () => {
setOpen(false);
setSelectedUnits([]);
updateData();
})
/* Deselect the context action when exiting state */
document.addEventListener("mapStateChanged", (ev) => {
setOpen(ev.detail === CONTEXT_ACTION);
});
/* Deselect the context action when exiting state */
document.addEventListener("mapStateChanged", (ev) => {
setOpen(ev.detail === CONTEXT_ACTION);
})
/* Update the current values of the shown data */
function updateData() {
var newContextActionSet = new ContextActionSet();
getApp()
.getUnitsManager()
.getSelectedUnits()
.forEach((unit: Unit) => {
unit.appendContextActions(newContextActionSet);
});
/* Update the current values of the shown data */
function updateData() {
var newContextActionSet = new ContextActionSet();
setContextActionsSet(newContextActionSet);
setActiveContextAction(null);
}
getApp().getUnitsManager().getSelectedUnits().forEach((unit: Unit) => {
unit.appendContextActions(newContextActionSet);
})
setContextActionsSet(newContextActionSet);
setActiveContextAction(null);
}
return <> {
open && <>
<div className='flex gap-2 rounded-md absolute top-20 left-[50%] translate-x-[-50%] bg-gray-200 dark:bg-olympus-900 z-ui-2 p-2'>
{
Object.values(contextActionsSet.getContextActions()).map((contextAction) => {
return <OlStateButton checked={contextAction === activeContextAction} icon={contextAction.getIcon()} tooltip={contextAction.getLabel()} onClick={() => {
if (contextAction.getOptions().executeImmediately) {
setActiveContextAction(null);
contextAction.executeCallback(null, null);
} else {
if (activeContextAction != contextAction) {
setActiveContextAction(contextAction);
getApp().getMap().setState(CONTEXT_ACTION, { contextAction: contextAction });
} else {
setActiveContextAction(null);
getApp().getMap().setState(CONTEXT_ACTION, { contextAction: null });
}
}
}} />
})
}
</div>
{ activeContextAction && <div className='flex gap-2 p-4 items-center rounded-md absolute top-36 left-[50%] translate-x-[-50%] bg-gray-200 dark:bg-olympus-800 z-ui-1'>
<FaInfoCircle className="text-blue-500 mr-2 text-sm"/>
<div className='border-l-[1px] px-5 dark:text-gray-400'>
{activeContextAction.getDescription()}
</div>
</div>
}
</>
}
</>
}
return (
<>
{" "}
{open && (
<>
<div
className={`
absolute left-[50%] top-20 flex translate-x-[-50%] gap-2
rounded-md bg-gray-200 p-2 z-ui-2
dark:bg-olympus-900
`}
>
{Object.values(contextActionsSet.getContextActions()).map(
(contextAction) => {
return (
<OlStateButton
checked={contextAction === activeContextAction}
icon={contextAction.getIcon()}
tooltip={contextAction.getLabel()}
onClick={() => {
if (contextAction.getOptions().executeImmediately) {
setActiveContextAction(null);
contextAction.executeCallback(null, null);
} else {
if (activeContextAction != contextAction) {
setActiveContextAction(contextAction);
getApp()
.getMap()
.setState(CONTEXT_ACTION, {
contextAction: contextAction,
});
} else {
setActiveContextAction(null);
getApp()
.getMap()
.setState(CONTEXT_ACTION, { contextAction: null });
}
}
}}
/>
);
}
)}
</div>
{activeContextAction && (
<div
className={`
absolute left-[50%] top-36 flex translate-x-[-50%] items-center
gap-2 rounded-md bg-gray-200 p-4 z-ui-1
dark:bg-olympus-800
`}
>
<FaInfoCircle className="mr-2 text-sm text-blue-500" />
<div
className={`
border-l-[1px] px-5
dark:text-gray-400
`}
>
{activeContextAction.getDescription()}
</div>
</div>
)}
</>
)}
</>
);
}

View File

@@ -4,7 +4,7 @@ import { OlCoalitionToggle } from "../components/olcoalitiontoggle";
import { OlNumberInput } from "../components/olnumberinput";
import { OlLabelToggle } from "../components/ollabeltoggle";
import { OlRangeSlider } from "../components/olrangeslider";
import { OlDropdownItem, OlDropdown } from '../components/oldropdown';
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
import { LoadoutBlueprint, UnitBlueprint } from "../../interfaces";
import { Coalition } from "../../types/types";
import { getApp } from "../../olympusapp";
@@ -12,138 +12,243 @@ import { IDLE, SPAWN_UNIT } from "../../constants/constants";
import { ftToM, getUnitCategoryByBlueprint } from "../../other/utils";
import { LatLng } from "leaflet";
export function UnitSpawnMenu(props: {
blueprint: UnitBlueprint
}) {
/* Compute the min and max values depending on the unit type */
const minNumber = 1;
const maxNumber = 4;
const minAltitude = 0;
const maxAltitude = 30000;
const altitudeStep = 500;
/* State initialization */
var [spawnCoalition, setSpawnCoalition] = useState("blue" as Coalition);
var [spawnNumber, setSpawnNumber] = useState(1);
var [spawnRole, setSpawnRole] = useState("");
var [spawnLoadoutName, setSpawnLoadout] = useState("");
var [spawnAltitude, setSpawnAltitude] = useState((maxAltitude - minAltitude) / 2);
var [spawnAltitudeType, setSpawnAltitudeType] = useState(false);
export function UnitSpawnMenu(props: { blueprint: UnitBlueprint }) {
/* Compute the min and max values depending on the unit type */
const minNumber = 1;
const maxNumber = 4;
const minAltitude = 0;
const maxAltitude = 30000;
const altitudeStep = 500;
/* When the menu is opened show the unit preview on the map as a cursor */
useEffect(() => {
if (props.blueprint !== null) {
getApp()?.getMap()?.setState(SPAWN_UNIT, {
spawnRequestTable: {
category: getUnitCategoryByBlueprint(props.blueprint),
unit: {
unitType: props.blueprint.name,
location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
skill: "High",
liveryID: "",
altitude: ftToM(spawnAltitude),
loadout: props.blueprint.loadouts?.find((loadout) => { return loadout.name === spawnLoadoutName})?.code ?? ""
},
coalition: spawnCoalition
}
});
}
else {
if (getApp()?.getMap()?.getState() === SPAWN_UNIT)
getApp()?.getMap()?.setState(IDLE);
}
})
/* State initialization */
var [spawnCoalition, setSpawnCoalition] = useState("blue" as Coalition);
var [spawnNumber, setSpawnNumber] = useState(1);
var [spawnRole, setSpawnRole] = useState("");
var [spawnLoadoutName, setSpawnLoadout] = useState("");
var [spawnAltitude, setSpawnAltitude] = useState(
(maxAltitude - minAltitude) / 2
);
var [spawnAltitudeType, setSpawnAltitudeType] = useState(false);
/* Get a list of all the roles */
const roles: string[] = [];
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
loadout.roles.forEach((role) => {
!roles.includes(role) && roles.push(role);
})
})
/* When the menu is opened show the unit preview on the map as a cursor */
useEffect(() => {
if (props.blueprint !== null) {
getApp()
?.getMap()
?.setState(SPAWN_UNIT, {
spawnRequestTable: {
category: getUnitCategoryByBlueprint(props.blueprint),
unit: {
unitType: props.blueprint.name,
location: new LatLng(0, 0), // This will be filled when the user clicks on the map to spawn the unit
skill: "High",
liveryID: "",
altitude: ftToM(spawnAltitude),
loadout:
props.blueprint.loadouts?.find((loadout) => {
return loadout.name === spawnLoadoutName;
})?.code ?? "",
},
coalition: spawnCoalition,
},
});
} else {
if (getApp()?.getMap()?.getState() === SPAWN_UNIT)
getApp()?.getMap()?.setState(IDLE);
}
});
/* Initialize the role */
spawnRole === "" && roles.length > 0 && setSpawnRole(roles[0]);
/* Get a list of all the roles */
const roles: string[] = [];
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
loadout.roles.forEach((role) => {
!roles.includes(role) && roles.push(role);
});
});
/* Get a list of all the loadouts */
const loadouts: LoadoutBlueprint[] = [];
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
loadout.roles.includes(spawnRole) && loadouts.push(loadout);
})
/* Initialize the role */
spawnRole === "" && roles.length > 0 && setSpawnRole(roles[0]);
/* Initialize the loadout */
spawnLoadoutName === "" && loadouts.length > 0 && setSpawnLoadout(loadouts[0].name)
const spawnLoadout = props.blueprint.loadouts?.find((loadout) => { return loadout.name === spawnLoadoutName; })
/* Get a list of all the loadouts */
const loadouts: LoadoutBlueprint[] = [];
(props.blueprint as UnitBlueprint).loadouts?.forEach((loadout) => {
loadout.roles.includes(spawnRole) && loadouts.push(loadout);
});
return <div className="flex flex-col">
<OlUnitSummary blueprint={props.blueprint} coalition={spawnCoalition} />
<div className="px-5 pt-6 pb-8 h-fit flex flex-col gap-5">
<div className="flex flex-row content-center justify-between w-full">
<OlCoalitionToggle
coalition={spawnCoalition}
onClick={() => {
spawnCoalition === 'blue' && setSpawnCoalition('neutral');
spawnCoalition === 'neutral' && setSpawnCoalition('red');
spawnCoalition === 'red' && setSpawnCoalition('blue');
}}
/>
<OlNumberInput
value={spawnNumber}
min={minNumber}
max={maxNumber}
onDecrease={() => { setSpawnNumber(Math.max(minNumber, spawnNumber - 1)) }}
onIncrease={() => { setSpawnNumber(Math.min(maxNumber, spawnNumber + 1)) }}
onChange={(ev) => { !isNaN(Number(ev.target.value)) && setSpawnNumber(Math.max(minNumber, Math.min(maxNumber, Number(ev.target.value)))) }}
/>
</div>
<div>
<div className="flex flex-row content-center items-center justify-between">
<div className="flex flex-col">
<span className="font-normal dark:text-white">Altitude</span>
<span className="font-bold dark:text-blue-500">{`${Intl.NumberFormat("en-US").format(spawnAltitude)} FT`}</span>
</div>
<OlLabelToggle toggled={spawnAltitudeType} leftLabel={"AGL"} rightLabel={"ASL"} onClick={() => setSpawnAltitudeType(!spawnAltitudeType)} />
</div>
<OlRangeSlider onChange={(ev) => setSpawnAltitude(Number(ev.target.value))} value={spawnAltitude} min={minAltitude} max={maxAltitude} step={altitudeStep} />
</div>
<div>
<div className="flex flex-row content-center justify-between">
<span className="font-normal dark:text-white h-8">Role</span>
</div>
<OlDropdown label={spawnRole} className="w-full">
{
roles.map((role) => {
return <OlDropdownItem onClick={() => { setSpawnRole(role); setSpawnLoadout(""); }} className="w-full">
{role}
</OlDropdownItem>
})
}
</OlDropdown>
</div>
<div>
<div className="flex flex-row content-center justify-between">
<span className="font-normal dark:text-white h-8">Weapons</span>
</div>
<OlDropdown label={spawnLoadoutName} className="w-full w-max-full">
{
loadouts.map((loadout) => {
return <OlDropdownItem onClick={() => { setSpawnLoadout(loadout.name) }} className="w-full">
<span className="text-left w-full w-max-full text-nowrap text-ellipsis overflow-hidden">
{loadout.name}
</span>
</OlDropdownItem>
})
}
</OlDropdown>
</div>
/* Initialize the loadout */
spawnLoadoutName === "" &&
loadouts.length > 0 &&
setSpawnLoadout(loadouts[0].name);
const spawnLoadout = props.blueprint.loadouts?.find((loadout) => {
return loadout.name === spawnLoadoutName;
});
return (
<div className="flex flex-col">
<OlUnitSummary blueprint={props.blueprint} coalition={spawnCoalition} />
<div className="flex h-fit flex-col gap-5 px-5 pb-8 pt-6">
<div className="flex w-full flex-row content-center justify-between">
<OlCoalitionToggle
coalition={spawnCoalition}
onClick={() => {
spawnCoalition === "blue" && setSpawnCoalition("neutral");
spawnCoalition === "neutral" && setSpawnCoalition("red");
spawnCoalition === "red" && setSpawnCoalition("blue");
}}
/>
<OlNumberInput
value={spawnNumber}
min={minNumber}
max={maxNumber}
onDecrease={() => {
setSpawnNumber(Math.max(minNumber, spawnNumber - 1));
}}
onIncrease={() => {
setSpawnNumber(Math.min(maxNumber, spawnNumber + 1));
}}
onChange={(ev) => {
!isNaN(Number(ev.target.value)) &&
setSpawnNumber(
Math.max(
minNumber,
Math.min(maxNumber, Number(ev.target.value))
)
);
}}
/>
</div>
<div className="dark:bg-olympus-200/30 h-fit p-4 flex flex-col gap-1">
{spawnLoadout && spawnLoadout.items.map((item) => {
return <div className="flex gap-2 content-center">
<div className="my-auto font-bold text-sm rounded-full text-gray-500 dark:bg-[#17212D] w-6 py-0.5 text-center">{item.quantity}</div>
<div className="my-auto text-sm dark:text-gray-300">{item.name}</div>
</div>
<div>
<div
className={`
flex flex-row content-center items-center justify-between
`}
>
<div className="flex flex-col">
<span
className={`
font-normal
dark:text-white
`}
>
Altitude
</span>
<span
className={`
font-bold
dark:text-blue-500
`}
>{`${Intl.NumberFormat("en-US").format(spawnAltitude)} FT`}</span>
</div>
<OlLabelToggle
toggled={spawnAltitudeType}
leftLabel={"AGL"}
rightLabel={"ASL"}
onClick={() => setSpawnAltitudeType(!spawnAltitudeType)}
/>
</div>
<OlRangeSlider
onChange={(ev) => setSpawnAltitude(Number(ev.target.value))}
value={spawnAltitude}
min={minAltitude}
max={maxAltitude}
step={altitudeStep}
/>
</div>
<div>
<div className="flex flex-row content-center justify-between">
<span
className={`
h-8 font-normal
dark:text-white
`}
>
Role
</span>
</div>
<OlDropdown label={spawnRole} className="w-full">
{roles.map((role) => {
return (
<OlDropdownItem
onClick={() => {
setSpawnRole(role);
setSpawnLoadout("");
}}
className={`w-full`}
>
{role}
</OlDropdownItem>
);
})}
</OlDropdown>
</div>
<div>
<div className="flex flex-row content-center justify-between">
<span
className={`
h-8 font-normal
dark:text-white
`}
>
Weapons
</span>
</div>
<OlDropdown
label={spawnLoadoutName}
className={`w-full w-max-full`}
>
{loadouts.map((loadout) => {
return (
<OlDropdownItem
onClick={() => {
setSpawnLoadout(loadout.name);
}}
className={`w-full`}
>
<span
className={`
w-full overflow-hidden text-ellipsis text-nowrap text-left
w-max-full
`}
>
{loadout.name}
</span>
</OlDropdownItem>
);
})}
</OlDropdown>
</div>
</div>
<div
className={`
flex h-fit flex-col gap-1 p-4
dark:bg-olympus-200/30
`}
>
{spawnLoadout &&
spawnLoadout.items.map((item) => {
return (
<div className="flex content-center gap-2">
<div
className={`
my-auto w-6 rounded-full py-0.5 text-center text-sm
font-bold text-gray-500
dark:bg-[#17212D]
`}
>
{item.quantity}
</div>
<div
className={`
my-auto text-sm
dark:text-gray-300
`}
>
{item.name}
</div>
</div>
);
})}
</div>
</div>
);
}

View File

@@ -1,96 +1,95 @@
/* width */
::-webkit-scrollbar {
width: 10px;
width: 10px;
}
/* Track */
::-webkit-scrollbar-track {
}
/* Handle */
::-webkit-scrollbar-thumb {
background: #FFFFFFAA;
border-radius: 10px;
cursor: pointer;
background: #ffffffaa;
border-radius: 10px;
cursor: pointer;
}
/* Hide scrollbar for Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
display: none;
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
.no-scrollbar {
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
-ms-overflow-style: none;
/* IE and Edge */
scrollbar-width: none;
/* Firefox */
}
input[type="range"] {
/* removing default appearance */
-webkit-appearance: none;
appearance: none;
/* creating a custom design */
width: 100%;
cursor: pointer;
outline: none;
border-radius: 15px;
/* overflow: hidden; remove this line*/
/* New additions */
height: 6px;
background: #4B5563;
}
/* Thumb: webkit */
input[type="range"]::-webkit-slider-thumb {
/* removing default appearance */
-webkit-appearance: none;
appearance: none;
/* creating a custom design */
height: 22px;
width: 22px;
background-color: #4B5563;
border-radius: 50%;
border: 1px solid #6B7280;
/* box-shadow: -407px 0 0 400px #4B5563; emove this line */
transition: .2s ease-in-out;
}
/* Thumb: Firefox */
input[type="range"]::-moz-range-thumb {
height: 15px;
width: 15px;
background-color: #4B5563;
border-radius: 50%;
border: none;
border: 1px solid #6B7280;
/* box-shadow: -407px 0 0 400px #4B5563; emove this line */
transition: .2s ease-in-out;
}
/* Hover, active & focus Thumb: Webkit */
input[type="range"]::-webkit-slider-thumb:hover {
box-shadow: 0 0 0 5px #3F83F822
}
input[type="range"]:active::-webkit-slider-thumb {
box-shadow: 0 0 0 7px #3F83F855
}
input[type="range"]:focus::-webkit-slider-thumb {
box-shadow: 0 0 0 7px #3F83F855
}
/* Hover, active & focus Thumb: Firefox */
input[type="range"]::-moz-range-thumb:hover {
box-shadow: 0 0 0 10px #3F83F822
}
input[type="range"]:active::-moz-range-thumb {
box-shadow: 0 0 0 13px #3F83F855
}
input[type="range"]:focus::-moz-range-thumb {
box-shadow: 0 0 0 13px #3F83F855
}
/* removing default appearance */
-webkit-appearance: none;
appearance: none;
/* creating a custom design */
width: 100%;
cursor: pointer;
outline: none;
border-radius: 15px;
/* overflow: hidden; remove this line*/
/* New additions */
height: 6px;
background: #4b5563;
}
/* Thumb: webkit */
input[type="range"]::-webkit-slider-thumb {
/* removing default appearance */
-webkit-appearance: none;
appearance: none;
/* creating a custom design */
height: 22px;
width: 22px;
background-color: #4b5563;
border-radius: 50%;
border: 1px solid #6b7280;
/* box-shadow: -407px 0 0 400px #4B5563; emove this line */
transition: 0.2s ease-in-out;
}
/* Thumb: Firefox */
input[type="range"]::-moz-range-thumb {
height: 15px;
width: 15px;
background-color: #4b5563;
border-radius: 50%;
border: none;
border: 1px solid #6b7280;
/* box-shadow: -407px 0 0 400px #4B5563; emove this line */
transition: 0.2s ease-in-out;
}
/* Hover, active & focus Thumb: Webkit */
input[type="range"]::-webkit-slider-thumb:hover {
box-shadow: 0 0 0 5px #3f83f822;
}
input[type="range"]:active::-webkit-slider-thumb {
box-shadow: 0 0 0 7px #3f83f855;
}
input[type="range"]:focus::-webkit-slider-thumb {
box-shadow: 0 0 0 7px #3f83f855;
}
/* Hover, active & focus Thumb: Firefox */
input[type="range"]::-moz-range-thumb:hover {
box-shadow: 0 0 0 10px #3f83f822;
}
input[type="range"]:active::-moz-range-thumb {
box-shadow: 0 0 0 13px #3f83f855;
}
input[type="range"]:focus::-moz-range-thumb {
box-shadow: 0 0 0 13px #3f83f855;
}

View File

@@ -1,191 +1,234 @@
import React, { useEffect, useState } from 'react'
import './ui.css'
import React, { useEffect, useState } from "react";
import "./ui.css";
import { EventsProvider } from '../eventscontext'
import { StateProvider } from '../statecontext'
import { EventsProvider } from "../eventscontext";
import { StateProvider } from "../statecontext";
import { Header } from './panels/header'
import { SpawnMenu } from './panels/spawnmenu'
import { UnitControlMenu } from './panels/unitcontrolmenu'
import { MainMenu } from './panels/mainmenu'
import { SideBar } from './panels/sidebar';
import { Options } from './panels/options';
import { MapHiddenTypes, MapOptions } from '../types/types'
import { BLUE_COMMANDER, GAME_MASTER, IDLE, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, RED_COMMANDER } from '../constants/constants'
import { getApp, setupApp } from '../olympusapp'
import { LoginModal } from './modals/login'
import { sha256 } from 'js-sha256'
import { MiniMapPanel } from './panels/minimappanel'
import { UnitMouseControlBar } from './panels/unitmousecontrolbar'
import { Header } from "./panels/header";
import { SpawnMenu } from "./panels/spawnmenu";
import { UnitControlMenu } from "./panels/unitcontrolmenu";
import { MainMenu } from "./panels/mainmenu";
import { SideBar } from "./panels/sidebar";
import { Options } from "./panels/options";
import { MapHiddenTypes, MapOptions } from "../types/types";
import {
BLUE_COMMANDER,
GAME_MASTER,
IDLE,
MAP_HIDDEN_TYPES_DEFAULTS,
MAP_OPTIONS_DEFAULTS,
RED_COMMANDER,
} from "../constants/constants";
import { getApp, setupApp } from "../olympusapp";
import { LoginModal } from "./modals/login";
import { sha256 } from "js-sha256";
import { MiniMapPanel } from "./panels/minimappanel";
import { UnitMouseControlBar } from "./panels/unitmousecontrolbar";
export type OlympusState = {
mainMenuVisible: boolean,
spawnMenuVisible: boolean,
unitControlMenuVisible: boolean,
measureMenuVisible: boolean,
drawingMenuVisible: boolean,
optionsMenuVisible: boolean,
mapHiddenTypes: MapHiddenTypes;
mapOptions: MapOptions;
}
mainMenuVisible: boolean;
spawnMenuVisible: boolean;
unitControlMenuVisible: boolean;
measureMenuVisible: boolean;
drawingMenuVisible: boolean;
optionsMenuVisible: boolean;
mapHiddenTypes: MapHiddenTypes;
mapOptions: MapOptions;
};
export function UI() {
var [loginModalVisible, setLoginModalVisible] = useState(true);
var [mainMenuVisible, setMainMenuVisible] = useState(false);
var [spawnMenuVisible, setSpawnMenuVisible] = useState(false);
var [unitControlMenuVisible, setUnitControlMenuVisible] = useState(false);
var [measureMenuVisible, setMeasureMenuVisible] = useState(false);
var [drawingMenuVisible, setDrawingMenuVisible] = useState(false);
var [optionsMenuVisible, setOptionsMenuVisible] = useState(false);
var [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
var [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
var [checkingPassword, setCheckingPassword] = useState(false);
var [loginError, setLoginError] = useState(false);
var [commandMode, setCommandMode] = useState(null as null | string);
var [mapSources, setMapSources] = useState([] as string[]);
var [activeMapSource, setActiveMapSource] = useState("");
var [loginModalVisible, setLoginModalVisible] = useState(true);
var [mainMenuVisible, setMainMenuVisible] = useState(false);
var [spawnMenuVisible, setSpawnMenuVisible] = useState(false);
var [unitControlMenuVisible, setUnitControlMenuVisible] = useState(false);
var [measureMenuVisible, setMeasureMenuVisible] = useState(false);
var [drawingMenuVisible, setDrawingMenuVisible] = useState(false);
var [optionsMenuVisible, setOptionsMenuVisible] = useState(false);
var [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
var [mapOptions, setMapOptions] = useState(MAP_OPTIONS_DEFAULTS);
var [checkingPassword, setCheckingPassword] = useState(false);
var [loginError, setLoginError] = useState(false);
var [commandMode, setCommandMode] = useState(null as null | string);
var [mapSources, setMapSources] = useState([] as string[]);
var [activeMapSource, setActiveMapSource] = useState("");
document.addEventListener("hiddenTypesChanged", (ev) => {
setMapHiddenTypes({ ...getApp().getMap().getHiddenTypes() });
})
document.addEventListener("hiddenTypesChanged", (ev) => {
setMapHiddenTypes({ ...getApp().getMap().getHiddenTypes() });
});
document.addEventListener("mapOptionsChanged", (ev) => {
setMapOptions({ ...getApp().getMap().getOptions() });
})
document.addEventListener("mapOptionsChanged", (ev) => {
setMapOptions({ ...getApp().getMap().getOptions() });
});
document.addEventListener("mapStateChanged", (ev) => {
if ((ev as CustomEvent).detail == IDLE) {
hideAllMenus();
}
})
document.addEventListener("mapStateChanged", (ev) => {
if ((ev as CustomEvent).detail == IDLE) {
hideAllMenus();
}
});
document.addEventListener("mapSourceChanged", (ev) => {
var source = (ev as CustomEvent).detail;
if (source !== activeMapSource)
setActiveMapSource(source);
})
document.addEventListener("mapSourceChanged", (ev) => {
var source = (ev as CustomEvent).detail;
if (source !== activeMapSource) setActiveMapSource(source);
});
document.addEventListener("configLoaded", (ev) => {
let config = getApp().getConfig();
var sources = Object.keys(config.mapMirrors).concat(
Object.keys(config.mapLayers)
);
setMapSources(sources);
setActiveMapSource(sources[0]);
});
document.addEventListener("configLoaded", (ev) => {
let config = getApp().getConfig();
var sources = Object.keys(config.mapMirrors).concat(Object.keys(config.mapLayers));
setMapSources(sources);
setActiveMapSource(sources[0]);
})
function hideAllMenus() {
setMainMenuVisible(false);
setSpawnMenuVisible(false);
setUnitControlMenuVisible(false);
setMeasureMenuVisible(false);
setDrawingMenuVisible(false);
setOptionsMenuVisible(false);
}
function hideAllMenus() {
setMainMenuVisible(false);
setSpawnMenuVisible(false);
setUnitControlMenuVisible(false);
setMeasureMenuVisible(false);
setDrawingMenuVisible(false);
setOptionsMenuVisible(false);
}
function checkPassword(password: string) {
setCheckingPassword(true);
var hash = sha256.create();
getApp().getServerManager().setPassword(hash.update(password).hex());
getApp()
.getServerManager()
.getMission(
(response) => {
const commandMode = response.mission.commandModeOptions.commandMode;
try {
[GAME_MASTER, BLUE_COMMANDER, RED_COMMANDER].includes(commandMode)
? setCommandMode(commandMode)
: setLoginError(true);
} catch {
setLoginError(true);
}
setCheckingPassword(false);
},
() => {
setLoginError(true);
setCheckingPassword(false);
}
);
}
function checkPassword(password: string) {
setCheckingPassword(true);
var hash = sha256.create();
getApp().getServerManager().setPassword(hash.update(password).hex());
getApp().getServerManager().getMission((response) => {
const commandMode = response.mission.commandModeOptions.commandMode;
try {
[GAME_MASTER, BLUE_COMMANDER, RED_COMMANDER].includes(commandMode) ? setCommandMode(commandMode) : setLoginError(true);
} catch {
setLoginError(true);
}
setCheckingPassword(false);
},
() => {
setLoginError(true);
setCheckingPassword(false);
},
)
}
function connect(username: string) {
getApp().getServerManager().setUsername(username);
getApp().getServerManager().startUpdate();
setLoginModalVisible(false);
}
function connect(username: string) {
getApp().getServerManager().setUsername(username);
getApp().getServerManager().startUpdate();
setLoginModalVisible(false);
}
/* Temporary during devel */
//useEffect(() => {
// window.setTimeout(() => {
// checkPassword("admin");
// connect("devel");
// }, 1000)
//}, [])
/* Temporary during devel */
//useEffect(() => {
// window.setTimeout(() => {
// checkPassword("admin");
// connect("devel");
// }, 1000)
//}, [])
return (
<div className="absolute top-0 left-0 h-screen w-screen font-sans overflow-hidden" onLoad={setupApp}>
<StateProvider value={{
mainMenuVisible: mainMenuVisible,
spawnMenuVisible: spawnMenuVisible,
unitControlMenuVisible: unitControlMenuVisible,
measureMenuVisible: measureMenuVisible,
drawingMenuVisible: drawingMenuVisible,
optionsMenuVisible: optionsMenuVisible,
mapOptions: mapOptions,
mapHiddenTypes: mapHiddenTypes,
mapSources: mapSources,
activeMapSource: activeMapSource
}}>
<EventsProvider value={
{
setMainMenuVisible: setMainMenuVisible,
setSpawnMenuVisible: setSpawnMenuVisible,
setUnitControlMenuVisible: setUnitControlMenuVisible,
setDrawingMenuVisible: setDrawingMenuVisible,
setMeasureMenuVisible: setMeasureMenuVisible,
setOptionsMenuVisible: setOptionsMenuVisible,
toggleMainMenuVisible: () => { hideAllMenus(); setMainMenuVisible(!mainMenuVisible) },
toggleSpawnMenuVisible: () => { hideAllMenus(); setSpawnMenuVisible(!spawnMenuVisible) },
toggleUnitControlMenuVisible: () => { hideAllMenus(); setUnitControlMenuVisible(!unitControlMenuVisible) },
toggleMeasureMenuVisible: () => { hideAllMenus(); setMeasureMenuVisible(!measureMenuVisible) },
toggleDrawingMenuVisible: () => { hideAllMenus(); setDrawingMenuVisible(!drawingMenuVisible) },
toggleOptionsMenuVisible: () => { hideAllMenus(); setOptionsMenuVisible(!optionsMenuVisible) },
}
}>
<div className='absolute top-0 left-0 h-full w-full flex flex-col'>
<Header />
<div className='flex h-full'>
{loginModalVisible &&
<>
<div className="fixed top-0 left-0 w-full h-full z-ui-3 bg-[#111111]/95 "></div>
<LoginModal
onLogin={(password) => { checkPassword(password) }}
onContinue={(username) => { connect(username) }}
onBack={() => { setCommandMode(null) }}
checkingPassword={checkingPassword}
loginError={loginError}
commandMode={commandMode}
/>
</>
}
<SideBar />
<MainMenu
open={mainMenuVisible}
onClose={() => setMainMenuVisible(false)}
/>
<SpawnMenu
open={spawnMenuVisible}
onClose={() => setSpawnMenuVisible(false)}
/>
<Options
open={optionsMenuVisible}
onClose={() => setOptionsMenuVisible(false)}
options={mapOptions}
/>
<MiniMapPanel />
<UnitControlMenu />
<div id='map-container' className='h-full w-screen' />
<UnitMouseControlBar />
</div>
</div>
</EventsProvider>
</StateProvider>
</div>
)
return (
<div
className={`
absolute left-0 top-0 h-screen w-screen overflow-hidden font-sans
`}
onLoad={setupApp}
>
<StateProvider
value={{
mainMenuVisible: mainMenuVisible,
spawnMenuVisible: spawnMenuVisible,
unitControlMenuVisible: unitControlMenuVisible,
measureMenuVisible: measureMenuVisible,
drawingMenuVisible: drawingMenuVisible,
optionsMenuVisible: optionsMenuVisible,
mapOptions: mapOptions,
mapHiddenTypes: mapHiddenTypes,
mapSources: mapSources,
activeMapSource: activeMapSource,
}}
>
<EventsProvider
value={{
setMainMenuVisible: setMainMenuVisible,
setSpawnMenuVisible: setSpawnMenuVisible,
setUnitControlMenuVisible: setUnitControlMenuVisible,
setDrawingMenuVisible: setDrawingMenuVisible,
setMeasureMenuVisible: setMeasureMenuVisible,
setOptionsMenuVisible: setOptionsMenuVisible,
toggleMainMenuVisible: () => {
hideAllMenus();
setMainMenuVisible(!mainMenuVisible);
},
toggleSpawnMenuVisible: () => {
hideAllMenus();
setSpawnMenuVisible(!spawnMenuVisible);
},
toggleUnitControlMenuVisible: () => {
hideAllMenus();
setUnitControlMenuVisible(!unitControlMenuVisible);
},
toggleMeasureMenuVisible: () => {
hideAllMenus();
setMeasureMenuVisible(!measureMenuVisible);
},
toggleDrawingMenuVisible: () => {
hideAllMenus();
setDrawingMenuVisible(!drawingMenuVisible);
},
toggleOptionsMenuVisible: () => {
hideAllMenus();
setOptionsMenuVisible(!optionsMenuVisible);
},
}}
>
<div className="absolute left-0 top-0 flex h-full w-full flex-col">
<Header />
<div className="flex h-full">
{loginModalVisible && (
<>
<div className={`
fixed left-0 top-0 h-full w-full z-ui-3 bg-[#111111]/95
`}></div>
<LoginModal
onLogin={(password) => {
checkPassword(password);
}}
onContinue={(username) => {
connect(username);
}}
onBack={() => {
setCommandMode(null);
}}
checkingPassword={checkingPassword}
loginError={loginError}
commandMode={commandMode}
/>
</>
)}
<SideBar />
<MainMenu
open={mainMenuVisible}
onClose={() => setMainMenuVisible(false)}
/>
<SpawnMenu
open={spawnMenuVisible}
onClose={() => setSpawnMenuVisible(false)}
/>
<Options
open={optionsMenuVisible}
onClose={() => setOptionsMenuVisible(false)}
options={mapOptions}
/>
<MiniMapPanel />
<UnitControlMenu />
<div id="map-container" className="h-full w-screen" />
<UnitMouseControlBar />
</div>
</div>
</EventsProvider>
</StateProvider>
</div>
);
}

View File

@@ -3,62 +3,72 @@ import { Unit } from "./unit";
import { LatLng } from "leaflet";
export interface ContextActionOptions {
executeImmediately?: boolean
executeImmediately?: boolean;
}
export type ContextActionCallback = (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => void;
export type ContextActionCallback = (
units: Unit[],
targetUnit: Unit | null,
targetPosition: LatLng | null
) => void;
export class ContextAction {
#id: string = "";
#label: string = "";
#description: string = "";
#callback: ContextActionCallback | null = null;
#units: Unit[] = [];
#icon: IconDefinition;
#options: ContextActionOptions;
#id: string = "";
#label: string = "";
#description: string = "";
#callback: ContextActionCallback | null = null;
#units: Unit[] = [];
#icon: IconDefinition;
#options: ContextActionOptions;
constructor(id: string, label: string, description: string, icon: IconDefinition, callback: ContextActionCallback, options: ContextActionOptions) {
this.#id = id;
this.#label = label;
this.#description = description;
this.#callback = callback;
this.#icon = icon;
this.#options = {
executeImmediately: false,
...options
}
}
constructor(
id: string,
label: string,
description: string,
icon: IconDefinition,
callback: ContextActionCallback,
options: ContextActionOptions
) {
this.#id = id;
this.#label = label;
this.#description = description;
this.#callback = callback;
this.#icon = icon;
this.#options = {
executeImmediately: false,
...options,
};
}
addUnit(unit: Unit) {
this.#units.push(unit);
}
addUnit(unit: Unit) {
this.#units.push(unit);
}
getId() {
return this.#id;
}
getId() {
return this.#id;
}
getLabel() {
return this.#label;
}
getLabel() {
return this.#label;
}
getOptions() {
return this.#options;
}
getOptions() {
return this.#options;
}
getDescription() {
return this.#description;
}
getDescription() {
return this.#description;
}
getCallback() {
return this.#callback;
}
getCallback() {
return this.#callback;
}
getIcon() {
return this.#icon;
}
getIcon() {
return this.#icon;
}
executeCallback(targetUnit: Unit | null, targetPosition: LatLng | null) {
if (this.#callback)
this.#callback(this.#units, targetUnit, targetPosition);
}
executeCallback(targetUnit: Unit | null, targetPosition: LatLng | null) {
if (this.#callback) this.#callback(this.#units, targetUnit, targetPosition);
}
}

View File

@@ -1,20 +1,39 @@
import { ContextAction, ContextActionCallback, ContextActionOptions } from "./contextaction";
import {
ContextAction,
ContextActionCallback,
ContextActionOptions,
} from "./contextaction";
import { Unit } from "./unit";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
export class ContextActionSet {
#contextActions: { [key: string]: ContextAction } = {};
#contextActions: { [key: string]: ContextAction } = {};
addContextAction(unit: Unit, id: string, label: string, description: string, icon: IconDefinition, callback: ContextActionCallback, options?: ContextActionOptions) {
options = options || {};
addContextAction(
unit: Unit,
id: string,
label: string,
description: string,
icon: IconDefinition,
callback: ContextActionCallback,
options?: ContextActionOptions
) {
options = options || {};
if (!(id in this.#contextActions)) {
this.#contextActions[id] = new ContextAction(id, label, description, icon, callback, options);
}
this.#contextActions[id].addUnit(unit);
if (!(id in this.#contextActions)) {
this.#contextActions[id] = new ContextAction(
id,
label,
description,
icon,
callback,
options
);
}
this.#contextActions[id].addUnit(unit);
}
getContextActions() {
return this.#contextActions;
}
}
getContextActions() {
return this.#contextActions;
}
}

View File

@@ -1,37 +1,37 @@
import { getApp } from "../../olympusapp";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
import { UnitDatabase } from "./unitdatabase";
export class AircraftDatabase extends UnitDatabase {
constructor() {
super(window.location.href.split('?')[0].replace("vite/", "") + 'api/databases/units/aircraftdatabase');
}
constructor() {
super(
window.location.href.split("?")[0].replace("vite/", "") +
"api/databases/units/aircraftdatabase"
);
}
getCategory() {
return "Aircraft";
}
getCategory() {
return "Aircraft";
}
getSpawnPointsByName(name: string) {
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
return 0;
getSpawnPointsByName(name: string) {
if (
getApp().getMissionManager().getCommandModeOptions().commandMode ==
GAME_MASTER ||
!getApp().getMissionManager().getCommandModeOptions().restrictSpawns
)
return 0;
const blueprint = this.getByName(name);
if (blueprint?.cost != undefined)
return blueprint?.cost;
if (blueprint?.era == "WW2")
return 20;
else if (blueprint?.era == "Early Cold War")
return 50;
else if (blueprint?.era == "Mid Cold War")
return 100;
else if (blueprint?.era == "Late Cold War")
return 200;
else if (blueprint?.era == "Modern")
return 400;
return 0;
}
const blueprint = this.getByName(name);
if (blueprint?.cost != undefined) return blueprint?.cost;
if (blueprint?.era == "WW2") return 20;
else if (blueprint?.era == "Early Cold War") return 50;
else if (blueprint?.era == "Mid Cold War") return 100;
else if (blueprint?.era == "Late Cold War") return 200;
else if (blueprint?.era == "Modern") return 400;
return 0;
}
}
export var aircraftDatabase = new AircraftDatabase();

View File

@@ -1,4 +1,4 @@
export var citiesDatabase: {lat: number, lng: number, pop: number}[] = [
export var citiesDatabase: { lat: number; lng: number; pop: number }[] = [
{
lat: 41.0136,
lng: 28.955,
@@ -7134,4 +7134,4 @@ export var citiesDatabase: {lat: number, lng: number, pop: number}[] = [
lng: 45.5564,
pop: 1155,
},
]
];

View File

@@ -1,36 +1,37 @@
import { getApp } from "../../olympusapp";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
import { UnitDatabase } from "./unitdatabase";
export class GroundUnitDatabase extends UnitDatabase {
constructor() {
super(window.location.href.split('?')[0].replace("vite/", "") + 'api/databases/units/groundunitdatabase');
}
constructor() {
super(
window.location.href.split("?")[0].replace("vite/", "") +
"api/databases/units/groundunitdatabase"
);
}
getSpawnPointsByName(name: string) {
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
return 0;
getSpawnPointsByName(name: string) {
if (
getApp().getMissionManager().getCommandModeOptions().commandMode ==
GAME_MASTER ||
!getApp().getMissionManager().getCommandModeOptions().restrictSpawns
)
return 0;
const blueprint = this.getByName(name);
if (blueprint?.cost != undefined)
return blueprint?.cost;
if (blueprint?.era == "WW2")
return 20;
else if (blueprint?.era == "Early Cold War")
return 50;
else if (blueprint?.era == "Mid Cold War")
return 100;
else if (blueprint?.era == "Late Cold War")
return 200;
else if (blueprint?.era == "Modern")
return 400;
return 0;
}
const blueprint = this.getByName(name);
if (blueprint?.cost != undefined) return blueprint?.cost;
getCategory() {
return "GroundUnit";
}
if (blueprint?.era == "WW2") return 20;
else if (blueprint?.era == "Early Cold War") return 50;
else if (blueprint?.era == "Mid Cold War") return 100;
else if (blueprint?.era == "Late Cold War") return 200;
else if (blueprint?.era == "Modern") return 400;
return 0;
}
getCategory() {
return "GroundUnit";
}
}
export var groundUnitDatabase = new GroundUnitDatabase();

View File

@@ -1,37 +1,37 @@
import { getApp } from "../../olympusapp";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
import { UnitDatabase } from "./unitdatabase";
export class HelicopterDatabase extends UnitDatabase {
constructor() {
super(window.location.href.split('?')[0].replace("vite/", "") + 'api/databases/units/helicopterdatabase');
}
constructor() {
super(
window.location.href.split("?")[0].replace("vite/", "") +
"api/databases/units/helicopterdatabase"
);
}
getSpawnPointsByName(name: string) {
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
return 0;
getSpawnPointsByName(name: string) {
if (
getApp().getMissionManager().getCommandModeOptions().commandMode ==
GAME_MASTER ||
!getApp().getMissionManager().getCommandModeOptions().restrictSpawns
)
return 0;
const blueprint = this.getByName(name);
if (blueprint?.cost != undefined)
return blueprint?.cost;
if (blueprint?.era == "WW2")
return 20;
else if (blueprint?.era == "Early Cold War")
return 50;
else if (blueprint?.era == "Mid Cold War")
return 100;
else if (blueprint?.era == "Late Cold War")
return 200;
else if (blueprint?.era == "Modern")
return 400;
return 0;
}
const blueprint = this.getByName(name);
if (blueprint?.cost != undefined) return blueprint?.cost;
getCategory() {
return "Helicopter";
}
if (blueprint?.era == "WW2") return 20;
else if (blueprint?.era == "Early Cold War") return 50;
else if (blueprint?.era == "Mid Cold War") return 100;
else if (blueprint?.era == "Late Cold War") return 200;
else if (blueprint?.era == "Modern") return 400;
return 0;
}
getCategory() {
return "Helicopter";
}
}
export var helicopterDatabase = new HelicopterDatabase();

View File

@@ -1,36 +1,37 @@
import { getApp } from "../../olympusapp";
import { GAME_MASTER } from "../../constants/constants";
import { UnitDatabase } from "./unitdatabase"
import { UnitDatabase } from "./unitdatabase";
export class NavyUnitDatabase extends UnitDatabase {
constructor() {
super(window.location.href.split('?')[0].replace("vite/", "") + 'api/databases/units/navyunitdatabase');
}
constructor() {
super(
window.location.href.split("?")[0].replace("vite/", "") +
"api/databases/units/navyunitdatabase"
);
}
getSpawnPointsByName(name: string) {
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns)
return 0;
getSpawnPointsByName(name: string) {
if (
getApp().getMissionManager().getCommandModeOptions().commandMode ==
GAME_MASTER ||
!getApp().getMissionManager().getCommandModeOptions().restrictSpawns
)
return 0;
const blueprint = this.getByName(name);
if (blueprint?.cost != undefined)
return blueprint?.cost;
if (blueprint?.era == "WW2")
return 20;
else if (blueprint?.era == "Early Cold War")
return 50;
else if (blueprint?.era == "Mid Cold War")
return 100;
else if (blueprint?.era == "Late Cold War")
return 200;
else if (blueprint?.era == "Modern")
return 400;
return 0;
}
const blueprint = this.getByName(name);
if (blueprint?.cost != undefined) return blueprint?.cost;
getCategory() {
return "NavyUnit";
}
if (blueprint?.era == "WW2") return 20;
else if (blueprint?.era == "Early Cold War") return 50;
else if (blueprint?.era == "Mid Cold War") return 100;
else if (blueprint?.era == "Late Cold War") return 200;
else if (blueprint?.era == "Modern") return 400;
return 0;
}
getCategory() {
return "NavyUnit";
}
}
export var navyUnitDatabase = new NavyUnitDatabase();

View File

@@ -4,236 +4,244 @@ import { GAME_MASTER } from "../../constants/constants";
import { UnitBlueprint } from "../../interfaces";
export abstract class UnitDatabase {
blueprints: { [key: string]: UnitBlueprint } = {};
#url: string;
blueprints: { [key: string]: UnitBlueprint } = {};
#url: string;
constructor(url: string = "") {
this.#url = url;
this.load(() => {});
}
constructor(url: string = "") {
this.#url = url;
this.load(() => {});
}
load(callback: CallableFunction) {
if (this.#url !== "") {
var xhr = new XMLHttpRequest();
xhr.open('GET', this.#url, true);
xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0");
xhr.responseType = 'json';
xhr.onload = () => {
var status = xhr.status;
if (status === 200) {
this.blueprints = xhr.response;
callback();
} else {
console.error(`Error retrieving database from ${this.#url}`)
}
};
xhr.send();
load(callback: CallableFunction) {
if (this.#url !== "") {
var xhr = new XMLHttpRequest();
xhr.open("GET", this.#url, true);
xhr.setRequestHeader("Cache-Control", "no-cache, no-store, max-age=0");
xhr.responseType = "json";
xhr.onload = () => {
var status = xhr.status;
if (status === 200) {
this.blueprints = xhr.response;
callback();
} else {
console.error(`Error retrieving database from ${this.#url}`);
}
};
xhr.send();
}
}
abstract getCategory(): string;
abstract getCategory(): string;
/* Gets a specific blueprint by name */
getByName(name: string) {
if (name in this.blueprints)
return this.blueprints[name];
return null;
/* Gets a specific blueprint by name */
getByName(name: string) {
if (name in this.blueprints) return this.blueprints[name];
return null;
}
/* Gets a specific blueprint by label */
getByLabel(label: string) {
for (let unit in this.blueprints) {
if (this.blueprints[unit].label === label) return this.blueprints[unit];
}
return null;
}
/* Gets a specific blueprint by label */
getByLabel(label: string) {
for (let unit in this.blueprints) {
if (this.blueprints[unit].label === label)
return this.blueprints[unit];
getBlueprints(includeDisabled: boolean = false) {
if (
getApp().getMissionManager().getCommandModeOptions().commandMode ==
GAME_MASTER ||
!getApp().getMissionManager().getCommandModeOptions().restrictSpawns
) {
var filteredBlueprints: { [key: string]: UnitBlueprint } = {};
for (let unit in this.blueprints) {
const blueprint = this.blueprints[unit];
if (blueprint.enabled || includeDisabled)
filteredBlueprints[unit] = blueprint;
}
return filteredBlueprints;
} else {
var filteredBlueprints: { [key: string]: UnitBlueprint } = {};
for (let unit in this.blueprints) {
const blueprint = this.blueprints[unit];
if (
(blueprint.enabled || includeDisabled) &&
this.getSpawnPointsByName(blueprint.name) <=
getApp().getMissionManager().getAvailableSpawnPoints() &&
getApp()
.getMissionManager()
.getCommandModeOptions()
.eras.includes(blueprint.era) &&
(!getApp().getMissionManager().getCommandModeOptions()
.restrictToCoalition ||
blueprint.coalition ===
getApp().getMissionManager().getCommandedCoalition() ||
blueprint.coalition === undefined)
) {
filteredBlueprints[unit] = blueprint;
}
return null;
}
return filteredBlueprints;
}
}
/* Returns a list of all possible roles in a database */
getRoles() {
var roles: string[] = [];
var filteredBlueprints = this.getBlueprints();
for (let unit in filteredBlueprints) {
var loadouts = filteredBlueprints[unit].loadouts;
if (loadouts) {
for (let loadout of loadouts) {
for (let role of loadout.roles) {
if (role !== "" && !roles.includes(role)) roles.push(role);
}
}
}
}
return roles;
}
/* Returns a list of all possible types in a database */
getTypes(unitFilter?: CallableFunction) {
var filteredBlueprints = this.getBlueprints();
var types: string[] = [];
for (let unit in filteredBlueprints) {
if (
typeof unitFilter === "function" &&
!unitFilter(filteredBlueprints[unit])
)
continue;
var type = filteredBlueprints[unit].type;
if (type && type !== "" && !types.includes(type)) types.push(type);
}
return types;
}
/* Returns a list of all possible periods in a database */
getEras() {
var filteredBlueprints = this.getBlueprints();
var eras: string[] = [];
for (let unit in filteredBlueprints) {
var era = filteredBlueprints[unit].era;
if (era && era !== "" && !eras.includes(era)) eras.push(era);
}
return eras;
}
/* Get all blueprints by range */
getByRange(range: string) {
var filteredBlueprints = this.getBlueprints();
var unitswithrange = [];
var minRange = 0;
var maxRange = 0;
if (range === "Short range") {
minRange = 0;
maxRange = 10000;
} else if (range === "Medium range") {
minRange = 10000;
maxRange = 100000;
} else {
minRange = 100000;
maxRange = 999999;
}
getBlueprints(includeDisabled: boolean = false) {
if (getApp().getMissionManager().getCommandModeOptions().commandMode == GAME_MASTER || !getApp().getMissionManager().getCommandModeOptions().restrictSpawns) {
var filteredBlueprints: { [key: string]: UnitBlueprint } = {};
for (let unit in this.blueprints) {
const blueprint = this.blueprints[unit];
if (blueprint.enabled || includeDisabled)
filteredBlueprints[unit] = blueprint;
}
return filteredBlueprints;
}
else {
var filteredBlueprints: { [key: string]: UnitBlueprint } = {};
for (let unit in this.blueprints) {
const blueprint = this.blueprints[unit];
if ((blueprint.enabled || includeDisabled) && this.getSpawnPointsByName(blueprint.name) <= getApp().getMissionManager().getAvailableSpawnPoints() &&
getApp().getMissionManager().getCommandModeOptions().eras.includes(blueprint.era) &&
(!getApp().getMissionManager().getCommandModeOptions().restrictToCoalition || blueprint.coalition === getApp().getMissionManager().getCommandedCoalition() || blueprint.coalition === undefined)) {
filteredBlueprints[unit] = blueprint;
}
}
return filteredBlueprints;
for (let unit in filteredBlueprints) {
var engagementRange = filteredBlueprints[unit].engagementRange;
if (engagementRange !== undefined) {
if (engagementRange >= minRange && engagementRange < maxRange) {
unitswithrange.push(filteredBlueprints[unit]);
}
}
}
return unitswithrange;
}
/* Returns a list of all possible roles in a database */
getRoles() {
var roles: string[] = [];
var filteredBlueprints = this.getBlueprints();
for (let unit in filteredBlueprints) {
var loadouts = filteredBlueprints[unit].loadouts;
if (loadouts) {
for (let loadout of loadouts) {
for (let role of loadout.roles) {
if (role !== "" && !roles.includes(role))
roles.push(role);
}
}
}
}
return roles;
/* Get all blueprints by type */
getByType(type: string) {
var filteredBlueprints = this.getBlueprints();
var units = [];
for (let unit in filteredBlueprints) {
if (filteredBlueprints[unit].type === type) {
units.push(filteredBlueprints[unit]);
}
}
return units;
}
/* Returns a list of all possible types in a database */
getTypes(unitFilter?:CallableFunction) {
var filteredBlueprints = this.getBlueprints();
var types: string[] = [];
for (let unit in filteredBlueprints) {
if ( typeof unitFilter === "function" && !unitFilter(filteredBlueprints[unit]))
continue;
var type = filteredBlueprints[unit].type;
if (type && type !== "" && !types.includes(type))
types.push(type);
/* Get all blueprints by role */
getByRole(role: string) {
var filteredBlueprints = this.getBlueprints();
var units = [];
for (let unit in filteredBlueprints) {
var loadouts = filteredBlueprints[unit].loadouts;
if (loadouts) {
for (let loadout of loadouts) {
if (
loadout.roles.includes(role) ||
loadout.roles.includes(role.toLowerCase())
) {
units.push(filteredBlueprints[unit]);
break;
}
}
return types;
}
}
return units;
}
/* Returns a list of all possible periods in a database */
getEras() {
var filteredBlueprints = this.getBlueprints();
var eras: string[] = [];
for (let unit in filteredBlueprints) {
var era = filteredBlueprints[unit].era;
if (era && era !== "" && !eras.includes(era))
eras.push(era);
/* Get the names of all the loadouts for a specific unit and for a specific role */
getLoadoutNamesByRole(name: string, role: string) {
var filteredBlueprints = this.getBlueprints();
var loadoutsByRole = [];
var loadouts = filteredBlueprints[name].loadouts;
if (loadouts) {
for (let loadout of loadouts) {
if (loadout.roles.includes(role) || loadout.roles.includes("")) {
loadoutsByRole.push(loadout.name);
}
return eras;
}
}
return loadoutsByRole;
}
/* Get all blueprints by range */
getByRange(range: string) {
var filteredBlueprints = this.getBlueprints();
var unitswithrange = [];
var minRange = 0;
var maxRange = 0;
/* Get the livery names for a specific unit */
getLiveryNamesByName(name: string) {
var liveries = this.blueprints[name].liveries;
if (liveries !== undefined) return Object.values(liveries);
else return [];
}
if (range === "Short range") {
minRange = 0;
maxRange = 10000;
}
else if (range === "Medium range") {
minRange = 10000;
maxRange = 100000;
}
else {
minRange = 100000;
maxRange = 999999;
}
for (let unit in filteredBlueprints) {
var engagementRange = filteredBlueprints[unit].engagementRange;
if (engagementRange !== undefined) {
if (engagementRange >= minRange && engagementRange < maxRange) {
unitswithrange.push(filteredBlueprints[unit]);
}
}
}
return unitswithrange;
/* Get the loadout content from the unit name and loadout name */
getLoadoutByName(name: string, loadoutName: string) {
var loadouts = this.blueprints[name].loadouts;
if (loadouts) {
for (let loadout of loadouts) {
if (loadout.name === loadoutName) return loadout;
}
}
return null;
}
/* Get all blueprints by type */
getByType(type: string) {
var filteredBlueprints = this.getBlueprints();
var units = [];
for (let unit in filteredBlueprints) {
if (filteredBlueprints[unit].type === type) {
units.push(filteredBlueprints[unit]);
}
}
return units;
}
getSpawnPointsByLabel(label: string) {
var blueprint = this.getByLabel(label);
if (blueprint) return this.getSpawnPointsByName(blueprint.name);
else return Infinity;
}
/* Get all blueprints by role */
getByRole(role: string) {
var filteredBlueprints = this.getBlueprints();
var units = [];
for (let unit in filteredBlueprints) {
var loadouts = filteredBlueprints[unit].loadouts;
if (loadouts) {
for (let loadout of loadouts) {
if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase())) {
units.push(filteredBlueprints[unit])
break;
}
}
}
}
return units;
}
getSpawnPointsByName(name: string) {
return Infinity;
}
/* Get the names of all the loadouts for a specific unit and for a specific role */
getLoadoutNamesByRole(name: string, role: string) {
var filteredBlueprints = this.getBlueprints();
var loadoutsByRole = [];
var loadouts = filteredBlueprints[name].loadouts;
if (loadouts) {
for (let loadout of loadouts) {
if (loadout.roles.includes(role) || loadout.roles.includes("")) {
loadoutsByRole.push(loadout.name)
}
}
}
return loadoutsByRole;
}
/* Get the livery names for a specific unit */
getLiveryNamesByName(name: string) {
var liveries = this.blueprints[name].liveries;
if (liveries !== undefined)
return Object.values(liveries);
else
return [];
}
/* Get the loadout content from the unit name and loadout name */
getLoadoutByName(name: string, loadoutName: string) {
var loadouts = this.blueprints[name].loadouts;
if (loadouts) {
for (let loadout of loadouts) {
if (loadout.name === loadoutName)
return loadout;
}
}
return null;
}
getSpawnPointsByLabel(label: string) {
var blueprint = this.getByLabel(label);
if (blueprint)
return this.getSpawnPointsByName(blueprint.name);
else
return Infinity;
}
getSpawnPointsByName(name: string) {
return Infinity;
}
getUnkownUnit(name: string): UnitBlueprint {
return {
name: name,
enabled: true,
coalition: 'neutral',
era: 'N/A',
label: name,
shortLabel: ''
}
}
}
getUnkownUnit(name: string): UnitBlueprint {
return {
name: name,
enabled: true,
coalition: "neutral",
era: "N/A",
label: name,
shortLabel: "",
};
}
}

View File

@@ -1,45 +1,47 @@
import { Unit } from "./unit";
export class Group {
#members: Unit[] = [];
#name: string;
#members: Unit[] = [];
#name: string;
constructor(name: string) {
this.#name = name;
constructor(name: string) {
this.#name = name;
document.addEventListener("unitDeath", (e: any) => {
if (this.#members.includes(e.detail))
this.getLeader()?.onGroupChanged(e.detail);
});
document.addEventListener("unitDeath", (e: any) => {
if (this.#members.includes(e.detail))
this.getLeader()?.onGroupChanged(e.detail);
});
}
getName() {
return this.#name;
}
addMember(member: Unit) {
if (!this.#members.includes(member)) {
this.#members.push(member);
member.setGroup(this);
this.getLeader()?.onGroupChanged(member);
}
}
getName() {
return this.#name;
removeMember(member: Unit) {
if (this.#members.includes(member)) {
delete this.#members[this.#members.indexOf(member)];
member.setGroup(null);
this.getLeader()?.onGroupChanged(member);
}
}
addMember(member: Unit) {
if (!this.#members.includes(member)) {
this.#members.push(member);
member.setGroup(this);
getMembers() {
return this.#members;
}
this.getLeader()?.onGroupChanged(member);
}
}
removeMember(member: Unit) {
if (this.#members.includes(member)) {
delete this.#members[this.#members.indexOf(member)];
member.setGroup(null);
this.getLeader()?.onGroupChanged(member);
}
}
getMembers() {
return this.#members;
}
getLeader() {
return this.#members.find((unit: Unit) => { return (unit.getIsLeader() && unit.getAlive())})
}
}
getLeader() {
return this.#members.find((unit: Unit) => {
return unit.getIsLeader() && unit.getAlive();
});
}
}

View File

@@ -2,65 +2,62 @@
//import { createCheckboxOption } from "../../other/utils";
var categoryMap = {
"Aircraft": "Aircraft",
"Helicopter": "Helicopter",
"GroundUnit": "Ground units",
"NavyUnit": "Naval units"
}
Aircraft: "Aircraft",
Helicopter: "Helicopter",
GroundUnit: "Ground units",
NavyUnit: "Naval units",
};
export abstract class UnitDataFile {
protected data: any;
//protected dialog!: Dialog;
protected data: any;
//protected dialog!: Dialog;
constructor() {}
constructor() { }
buildCategoryCoalitionTable() {
const categories = this.#getCategoriesFromData();
const coalitions = ["blue", "neutral", "red"];
buildCategoryCoalitionTable() {
let headersHTML: string = ``;
let matrixHTML: string = ``;
const categories = this.#getCategoriesFromData();
const coalitions = ["blue", "neutral", "red"];
//categories.forEach((category: string, index) => {
// matrixHTML += `<tr><td>${categoryMap[category as keyof typeof categoryMap]}</td>`;
//
// coalitions.forEach((coalition: string) => {
// if (index === 0)
// headersHTML += `<th data-coalition="${coalition}">${coalition[0].toUpperCase() + coalition.substring(1)}</th>`;
//
// const optionIsValid = this.data[category].hasOwnProperty(coalition);
// let checkboxHTML = createCheckboxOption(``, category, optionIsValid, () => { }, {
// "disabled": !optionIsValid,
// "name": "category-coalition-selection",
// "readOnly": !optionIsValid,
// "value" : `${category}:${coalition}`
// }).outerHTML;
//
// if (optionIsValid)
// checkboxHTML = checkboxHTML.replace(`"checkbox"`, `"checkbox" checked`); // inner and outerHTML screw default checked up
//
// matrixHTML += `<td data-coalition="${coalition}">${checkboxHTML}</td>`;
//
// });
// matrixHTML += "</tr>";
//});
//
//const table = <HTMLTableElement>this.dialog.getElement().querySelector("table.categories-coalitions");
let headersHTML: string = ``;
let matrixHTML: string = ``;
//(<HTMLElement>table.tHead).innerHTML = `<tr><td>&nbsp;</td>${headersHTML}</tr>`;
//(<HTMLElement>table.querySelector(`tbody`)).innerHTML = matrixHTML;
}
//categories.forEach((category: string, index) => {
// matrixHTML += `<tr><td>${categoryMap[category as keyof typeof categoryMap]}</td>`;
//
// coalitions.forEach((coalition: string) => {
// if (index === 0)
// headersHTML += `<th data-coalition="${coalition}">${coalition[0].toUpperCase() + coalition.substring(1)}</th>`;
//
// const optionIsValid = this.data[category].hasOwnProperty(coalition);
// let checkboxHTML = createCheckboxOption(``, category, optionIsValid, () => { }, {
// "disabled": !optionIsValid,
// "name": "category-coalition-selection",
// "readOnly": !optionIsValid,
// "value" : `${category}:${coalition}`
// }).outerHTML;
//
// if (optionIsValid)
// checkboxHTML = checkboxHTML.replace(`"checkbox"`, `"checkbox" checked`); // inner and outerHTML screw default checked up
//
// matrixHTML += `<td data-coalition="${coalition}">${checkboxHTML}</td>`;
//
// });
// matrixHTML += "</tr>";
//});
//
//const table = <HTMLTableElement>this.dialog.getElement().querySelector("table.categories-coalitions");
#getCategoriesFromData() {
const categories = Object.keys(this.data);
categories.sort();
return categories;
}
//(<HTMLElement>table.tHead).innerHTML = `<tr><td>&nbsp;</td>${headersHTML}</tr>`;
//(<HTMLElement>table.querySelector(`tbody`)).innerHTML = matrixHTML;
}
#getCategoriesFromData() {
const categories = Object.keys(this.data);
categories.sort();
return categories;
}
getData() {
return this.data;
}
}
getData() {
return this.data;
}
}

View File

@@ -5,97 +5,99 @@ import { Unit } from "../unit";
import { UnitDataFile } from "./unitdatafile";
export class UnitDataFileExport extends UnitDataFile {
protected data!: any;
//protected dialog: Dialog;
#element!: HTMLElement;
#filename: string = "export.json";
protected data!: any;
//protected dialog: Dialog;
#element!: HTMLElement;
#filename: string = "export.json";
constructor(elementId: string) {
super();
//this.dialog = new Dialog(elementId);
//this.#element = this.dialog.getElement();
constructor(elementId: string) {
super();
//this.dialog = new Dialog(elementId);
//this.#element = this.dialog.getElement();
this.#element
.querySelector(".start-transfer")
?.addEventListener("click", (ev: MouseEventInit) => {
this.#doExport();
});
}
this.#element.querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => {
this.#doExport();
});
}
/**
* Show the form to start the export journey
*/
showForm(units: Unit[]) {
//this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
// el.classList.toggle("hide", el.getAttribute("data-on-error") === "show");
//});
//
//const data: any = {};
//const unitCanBeExported = (unit: Unit) => !["Aircraft", "Helicopter"].includes(unit.getCategory());
//
//units.filter((unit: Unit) => unit.getAlive() && unitCanBeExported(unit)).forEach((unit: Unit) => {
// const category = unit.getCategory();
// const coalition = unit.getCoalition();
//
// if (!data.hasOwnProperty(category)) {
// data[category] = {};
// }
//
// if (!data[category].hasOwnProperty(coalition))
// data[category][coalition] = [];
//
// data[category][coalition].push(unit);
//});
//
//this.data = data;
//this.buildCategoryCoalitionTable();
//this.dialog.show();
//
//const date = new Date();
//this.#filename = `olympus_${getApp().getMissionManager().getTheatre().replace(/[^\w]/gi, "").toLowerCase()}_${date.getFullYear()}${zeroAppend(date.getMonth() + 1, 2)}${zeroAppend(date.getDate(), 2)}_${zeroAppend(date.getHours(), 2)}${zeroAppend(date.getMinutes(), 2)}${zeroAppend(date.getSeconds(), 2)}.json`;
//var input = this.#element.querySelector("#export-filename") as HTMLInputElement;
//input.onchange = (ev: Event) => {
// this.#filename = (ev.currentTarget as HTMLInputElement).value;
//}
//if (input)
// input.value = this.#filename;
}
/**
* Show the form to start the export journey
*/
showForm(units: Unit[]) {
//this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
// el.classList.toggle("hide", el.getAttribute("data-on-error") === "show");
//});
//
//const data: any = {};
//const unitCanBeExported = (unit: Unit) => !["Aircraft", "Helicopter"].includes(unit.getCategory());
//
//units.filter((unit: Unit) => unit.getAlive() && unitCanBeExported(unit)).forEach((unit: Unit) => {
// const category = unit.getCategory();
// const coalition = unit.getCoalition();
//
// if (!data.hasOwnProperty(category)) {
// data[category] = {};
// }
//
// if (!data[category].hasOwnProperty(coalition))
// data[category][coalition] = [];
//
// data[category][coalition].push(unit);
//});
//
//this.data = data;
//this.buildCategoryCoalitionTable();
//this.dialog.show();
//
//const date = new Date();
//this.#filename = `olympus_${getApp().getMissionManager().getTheatre().replace(/[^\w]/gi, "").toLowerCase()}_${date.getFullYear()}${zeroAppend(date.getMonth() + 1, 2)}${zeroAppend(date.getDate(), 2)}_${zeroAppend(date.getHours(), 2)}${zeroAppend(date.getMinutes(), 2)}${zeroAppend(date.getSeconds(), 2)}.json`;
//var input = this.#element.querySelector("#export-filename") as HTMLInputElement;
//input.onchange = (ev: Event) => {
// this.#filename = (ev.currentTarget as HTMLInputElement).value;
//}
//if (input)
// input.value = this.#filename;
}
#doExport() {
let selectedUnits: Unit[] = [];
#doExport() {
let selectedUnits: Unit[] = [];
this.#element.querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach(<HTMLInputElement>(checkbox: HTMLInputElement) => {
if (checkbox instanceof HTMLInputElement) {
const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition"
selectedUnits = selectedUnits.concat(this.data[category][coalition]);
}
});
if (selectedUnits.length === 0) {
alert("Please select at least one option for export.");
return;
this.#element
.querySelectorAll(
`input[type="checkbox"][name="category-coalition-selection"]:checked`
)
.forEach(<HTMLInputElement>(checkbox: HTMLInputElement) => {
if (checkbox instanceof HTMLInputElement) {
const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition"
selectedUnits = selectedUnits.concat(this.data[category][coalition]);
}
});
var unitsToExport: { [key: string]: any } = {};
selectedUnits.forEach((unit: Unit) => {
var data: any = unit.getData();
if (unit.getGroupName() in unitsToExport)
unitsToExport[unit.getGroupName()].push(data);
else
unitsToExport[unit.getGroupName()] = [data];
});
const a = document.createElement("a");
const file = new Blob([JSON.stringify(unitsToExport)], { type: 'text/plain' });
a.href = URL.createObjectURL(file);
var filename = this.#filename;
if (!this.#filename.toLowerCase().endsWith(".json"))
filename += ".json";
a.download = filename;
a.click();
//this.dialog.hide();
if (selectedUnits.length === 0) {
alert("Please select at least one option for export.");
return;
}
}
var unitsToExport: { [key: string]: any } = {};
selectedUnits.forEach((unit: Unit) => {
var data: any = unit.getData();
if (unit.getGroupName() in unitsToExport)
unitsToExport[unit.getGroupName()].push(data);
else unitsToExport[unit.getGroupName()] = [data];
});
const a = document.createElement("a");
const file = new Blob([JSON.stringify(unitsToExport)], {
type: "text/plain",
});
a.href = URL.createObjectURL(file);
var filename = this.#filename;
if (!this.#filename.toLowerCase().endsWith(".json")) filename += ".json";
a.download = filename;
a.click();
//this.dialog.hide();
}
}

View File

@@ -5,146 +5,143 @@ import { UnitData } from "../../interfaces";
import { UnitDataFile } from "./unitdatafile";
export class UnitDataFileImport extends UnitDataFile {
protected data!: any;
//protected dialog: Dialog;
#fileData!: { [key: string]: UnitData[] };
protected data!: any;
//protected dialog: Dialog;
#fileData!: { [key: string]: UnitData[] };
constructor(elementId: string) {
super();
//this.dialog = new Dialog(elementId);
//this.dialog.getElement().querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => {
// this.#doImport();
// this.dialog.hide();
//});
}
constructor(elementId: string) {
super();
//this.dialog = new Dialog(elementId);
//this.dialog.getElement().querySelector(".start-transfer")?.addEventListener("click", (ev: MouseEventInit) => {
// this.#doImport();
// this.dialog.hide();
//});
}
#doImport() {
//let selectedCategories: any = {};
//const unitsManager = getApp().getUnitsManager();
//
//this.dialog.getElement().querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach(<HTMLInputElement>(checkbox: HTMLInputElement) => {
// if (checkbox instanceof HTMLInputElement) {
// const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition"
// selectedCategories[category] = selectedCategories[category] || {};
// selectedCategories[category][coalition] = true;
// }
//});
//
//for (const [groupName, groupData] of Object.entries(this.#fileData)) {
// if (groupName === "" || groupData.length === 0 || !this.#unitGroupDataCanBeImported(groupData))
// continue;
//
// let { category, coalition } = groupData[0];
//
// if (!selectedCategories.hasOwnProperty(category)
// || !selectedCategories[category].hasOwnProperty(coalition)
// || selectedCategories[category][coalition] !== true)
// continue;
//
// let unitsToSpawn = groupData.filter((unitData: UnitData) => this.#unitDataCanBeImported(unitData)).map((unitData: UnitData) => {
// return { unitType: unitData.name, location: unitData.position, liveryID: "", skill: "High" }
// });
//
// unitsManager.spawnUnits(category, unitsToSpawn, coalition, false);
//}
}
#doImport() {
selectFile() {
var input = document.createElement("input");
input.type = "file";
input.addEventListener("change", (e: any) => {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = (e: any) => {
try {
this.#fileData = JSON.parse(e.target.result);
//let selectedCategories: any = {};
//const unitsManager = getApp().getUnitsManager();
//
//this.dialog.getElement().querySelectorAll(`input[type="checkbox"][name="category-coalition-selection"]:checked`).forEach(<HTMLInputElement>(checkbox: HTMLInputElement) => {
// if (checkbox instanceof HTMLInputElement) {
// const [category, coalition] = checkbox.value.split(":"); // e.g. "category:coalition"
// selectedCategories[category] = selectedCategories[category] || {};
// selectedCategories[category][coalition] = true;
// }
//});
//
//for (const [groupName, groupData] of Object.entries(this.#fileData)) {
// if (groupName === "" || groupData.length === 0 || !this.#unitGroupDataCanBeImported(groupData))
// continue;
//
// let { category, coalition } = groupData[0];
//
// if (!selectedCategories.hasOwnProperty(category)
// || !selectedCategories[category].hasOwnProperty(coalition)
// || selectedCategories[category][coalition] !== true)
// continue;
//
// let unitsToSpawn = groupData.filter((unitData: UnitData) => this.#unitDataCanBeImported(unitData)).map((unitData: UnitData) => {
// return { unitType: unitData.name, location: unitData.position, liveryID: "", skill: "High" }
// });
//
// unitsManager.spawnUnits(category, unitsToSpawn, coalition, false);
//}
}
//const validator = new ImportFileJSONSchemaValidator();
//if (!validator.validate(this.#fileData)) {
// const errors = validator.getErrors().reduce((acc:any, error:any) => {
// let errorString = error.instancePath.substring(1) + ": " + error.message;
// if (error.params) {
// const {allowedValues} = error.params;
// if (allowedValues)
// errorString += ": " + allowedValues.join(', ');
// }
// acc.push(errorString);
// return acc;
// }, [] as string[]);
// this.#showFileDataErrors(errors);
//} else {
// this.#showForm();
//}
} catch (e: any) {
this.#showFileDataErrors([e]);
}
};
reader.readAsText(file);
});
input.click();
}
selectFile() {
var input = document.createElement("input");
input.type = "file";
input.addEventListener("change", (e: any) => {
var file = e.target.files[0];
if (!file) {
return;
}
var reader = new FileReader();
reader.onload = (e: any) => {
try {
this.#fileData = JSON.parse(e.target.result);
#showFileDataErrors(reasons: string[]) {
//this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
// el.classList.toggle("hide", el.getAttribute("data-on-error") === "hide");
//});
//
//const reasonsList = this.dialog.getElement().querySelector(".import-error-reasons");
//if (reasonsList instanceof HTMLElement)
// reasonsList.innerHTML = `<li>${reasons.join("</li><li>")}</li>`;
//
//this.dialog.show();
}
//const validator = new ImportFileJSONSchemaValidator();
//if (!validator.validate(this.#fileData)) {
// const errors = validator.getErrors().reduce((acc:any, error:any) => {
// let errorString = error.instancePath.substring(1) + ": " + error.message;
// if (error.params) {
// const {allowedValues} = error.params;
// if (allowedValues)
// errorString += ": " + allowedValues.join(', ');
// }
// acc.push(errorString);
// return acc;
// }, [] as string[]);
// this.#showFileDataErrors(errors);
//} else {
// this.#showForm();
//}
} catch(e:any) {
this.#showFileDataErrors([e]);
}
};
reader.readAsText(file);
})
input.click();
}
#showForm() {
//this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
// el.classList.toggle("hide", el.getAttribute("data-on-error") === "show");
//});
//
//const data: any = {};
//
//for (const [group, units] of Object.entries(this.#fileData)) {
// if (group === "" || units.length === 0)
// continue;
//
// if (units.some((unit: UnitData) => !this.#unitDataCanBeImported(unit)))
// continue;
//
// const category = units[0].category;
//
// if (!data.hasOwnProperty(category)) {
// data[category] = {};
// }
//
// units.forEach((unit: UnitData) => {
// if (!data[category].hasOwnProperty(unit.coalition))
// data[category][unit.coalition] = [];
//
// data[category][unit.coalition].push(unit);
// });
//
//}
//
//this.data = data;
//this.buildCategoryCoalitionTable();
//this.dialog.show();
}
#showFileDataErrors( reasons:string[]) {
//this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
// el.classList.toggle("hide", el.getAttribute("data-on-error") === "hide");
//});
//
//const reasonsList = this.dialog.getElement().querySelector(".import-error-reasons");
//if (reasonsList instanceof HTMLElement)
// reasonsList.innerHTML = `<li>${reasons.join("</li><li>")}</li>`;
//
//this.dialog.show();
}
#unitDataCanBeImported(unitData: UnitData) {
return unitData.alive && this.#unitGroupDataCanBeImported([unitData]);
}
#showForm() {
//this.dialog.getElement().querySelectorAll("[data-on-error]").forEach((el:Element) => {
// el.classList.toggle("hide", el.getAttribute("data-on-error") === "show");
//});
//
//const data: any = {};
//
//for (const [group, units] of Object.entries(this.#fileData)) {
// if (group === "" || units.length === 0)
// continue;
//
// if (units.some((unit: UnitData) => !this.#unitDataCanBeImported(unit)))
// continue;
//
// const category = units[0].category;
//
// if (!data.hasOwnProperty(category)) {
// data[category] = {};
// }
//
// units.forEach((unit: UnitData) => {
// if (!data[category].hasOwnProperty(unit.coalition))
// data[category][unit.coalition] = [];
//
// data[category][unit.coalition].push(unit);
// });
//
//}
//
//this.data = data;
//this.buildCategoryCoalitionTable();
//this.dialog.show();
}
#unitDataCanBeImported(unitData: UnitData) {
return unitData.alive && this.#unitGroupDataCanBeImported([unitData]);
}
#unitGroupDataCanBeImported(unitGroupData: UnitData[]) {
return unitGroupData.every((unitData: UnitData) => {
return !["Aircraft", "Helicopter"].includes(unitData.category);
}) && unitGroupData.some((unitData: UnitData) => unitData.alive);
}
}
#unitGroupDataCanBeImported(unitGroupData: UnitData[]) {
return (
unitGroupData.every((unitData: UnitData) => {
return !["Aircraft", "Helicopter"].includes(unitData.category);
}) && unitGroupData.some((unitData: UnitData) => unitData.alive)
);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,316 +1,439 @@
import { LatLng, DivIcon, Map } from 'leaflet';
import { getApp } from '../olympusapp';
import { enumToCoalition, mToFt, msToKnots, rad2deg, zeroAppend } from '../other/utils';
import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { DLINK, DataIndexes, GAME_MASTER, IRST, OPTIC, RADAR, VISUAL } from '../constants/constants';
import { DataExtractor } from '../server/dataextractor';
import { ObjectIconOptions } from '../interfaces';
import { LatLng, DivIcon, Map } from "leaflet";
import { getApp } from "../olympusapp";
import {
enumToCoalition,
mToFt,
msToKnots,
rad2deg,
zeroAppend,
} from "../other/utils";
import { CustomMarker } from "../map/markers/custommarker";
import { SVGInjector } from "@tanem/svg-injector";
import {
DLINK,
DataIndexes,
GAME_MASTER,
IRST,
OPTIC,
RADAR,
VISUAL,
} from "../constants/constants";
import { DataExtractor } from "../server/dataextractor";
import { ObjectIconOptions } from "../interfaces";
export class Weapon extends CustomMarker {
ID: number;
ID: number;
#alive: boolean = false;
#coalition: string = "neutral";
#name: string = "";
#position: LatLng = new LatLng(0, 0, 0);
#speed: number = 0;
#heading: number = 0;
#alive: boolean = false;
#coalition: string = "neutral";
#name: string = "";
#position: LatLng = new LatLng(0, 0, 0);
#speed: number = 0;
#heading: number = 0;
#hidden: boolean = false;
#detectionMethods: number[] = [];
#hidden: boolean = false;
#detectionMethods: number[] = [];
getAlive() {return this.#alive};
getCoalition() {return this.#coalition};
getName() {return this.#name};
getPosition() {return this.#position};
getSpeed() {return this.#speed};
getHeading() {return this.#heading};
static getConstructor(type: string) {
if (type === "Missile") return Missile;
if (type === "Bomb") return Bomb;
getAlive() {
return this.#alive;
}
getCoalition() {
return this.#coalition;
}
getName() {
return this.#name;
}
getPosition() {
return this.#position;
}
getSpeed() {
return this.#speed;
}
getHeading() {
return this.#heading;
}
static getConstructor(type: string) {
if (type === "Missile") return Missile;
if (type === "Bomb") return Bomb;
}
constructor(ID: number) {
super(new LatLng(0, 0), { riseOnHover: true, keyboard: false });
this.ID = ID;
/* Update the marker when the options change */
document.addEventListener("mapOptionsChanged", (ev: CustomEventInit) => {
this.#updateMarker();
});
}
getCategory() {
// Overloaded by child classes
return "";
}
/********************** Unit data *************************/
setData(dataExtractor: DataExtractor) {
var updateMarker = !getApp().getMap().hasLayer(this);
var datumIndex = 0;
while (datumIndex != DataIndexes.endOfData) {
datumIndex = dataExtractor.extractUInt8();
switch (datumIndex) {
case DataIndexes.category:
dataExtractor.extractString();
break;
case DataIndexes.alive:
this.setAlive(dataExtractor.extractBool());
updateMarker = true;
break;
case DataIndexes.coalition:
this.#coalition = enumToCoalition(dataExtractor.extractUInt8());
break;
case DataIndexes.name:
this.#name = dataExtractor.extractString();
break;
case DataIndexes.position:
this.#position = dataExtractor.extractLatLng();
updateMarker = true;
break;
case DataIndexes.speed:
this.#speed = dataExtractor.extractFloat64();
updateMarker = true;
break;
case DataIndexes.heading:
this.#heading = dataExtractor.extractFloat64();
updateMarker = true;
break;
}
}
constructor(ID: number) {
super(new LatLng(0, 0), { riseOnHover: true, keyboard: false });
if (updateMarker) this.#updateMarker();
}
this.ID = ID;
/* Update the marker when the options change */
document.addEventListener("mapOptionsChanged", (ev: CustomEventInit) => {
this.#updateMarker();
});
getData() {
return {
category: this.getCategory(),
ID: this.ID,
alive: this.#alive,
coalition: this.#coalition,
name: this.#name,
position: this.#position,
speed: this.#speed,
heading: this.#heading,
};
}
getMarkerCategory(): string {
return "";
}
getIconOptions(): ObjectIconOptions {
// Default values, overloaded by child classes if needed
return {
showState: false,
showVvi: false,
showHealth: false,
showHotgroup: false,
showUnitIcon: true,
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: true,
showCallsign: true,
rotateToHeading: false,
};
}
setAlive(newAlive: boolean) {
this.#alive = newAlive;
}
belongsToCommandedCoalition() {
if (
getApp().getMissionManager().getCommandModeOptions().commandMode !==
GAME_MASTER &&
getApp().getMissionManager().getCommandedCoalition() !== this.#coalition
)
return false;
return true;
}
getType() {
return "";
}
/********************** Icon *************************/
createIcon(): void {
/* Set the icon */
var icon = new DivIcon({
className: "leaflet-unit-icon",
iconAnchor: [25, 25],
iconSize: [50, 50],
});
this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("unit");
el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`);
el.setAttribute("data-coalition", this.#coalition);
// Generate and append elements depending on active options
// Velocity vector
if (this.getIconOptions().showVvi) {
var vvi = document.createElement("div");
vvi.classList.add("unit-vvi");
vvi.toggleAttribute("data-rotate-to-heading");
el.append(vvi);
}
getCategory() {
// Overloaded by child classes
return "";
// Main icon
if (this.getIconOptions().showUnitIcon) {
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute(
"data-rotate-to-heading",
this.getIconOptions().rotateToHeading
);
el.append(unitIcon);
}
/********************** Unit data *************************/
setData(dataExtractor: DataExtractor) {
var updateMarker = !getApp().getMap().hasLayer(this);
this.getElement()?.appendChild(el);
}
var datumIndex = 0;
while (datumIndex != DataIndexes.endOfData) {
datumIndex = dataExtractor.extractUInt8();
switch (datumIndex) {
case DataIndexes.category: dataExtractor.extractString(); break;
case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break;
case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break;
case DataIndexes.name: this.#name = dataExtractor.extractString(); break;
case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break;
case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break;
case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break;
}
}
if (updateMarker)
this.#updateMarker();
}
/********************** Visibility *************************/
updateVisibility() {
const hiddenUnits = getApp().getMap().getHiddenTypes();
var hidden =
hiddenUnits[this.getMarkerCategory()] ||
hiddenUnits[this.#coalition] ||
(!this.belongsToCommandedCoalition() &&
this.#detectionMethods.length == 0);
getData() {
return {
category: this.getCategory(),
ID: this.ID,
alive: this.#alive,
coalition: this.#coalition,
name: this.#name,
position: this.#position,
speed: this.#speed,
heading: this.#heading
}
}
this.setHidden(hidden || !this.#alive);
}
getMarkerCategory(): string {
return "";
}
setHidden(hidden: boolean) {
this.#hidden = hidden;
getIconOptions(): ObjectIconOptions {
// Default values, overloaded by child classes if needed
return {
showState: false,
showVvi: false,
showHealth: false,
showHotgroup: false,
showUnitIcon: true,
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: true,
showCallsign: true,
rotateToHeading: false
}
}
setAlive(newAlive: boolean) {
this.#alive = newAlive;
}
belongsToCommandedCoalition() {
if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER && getApp().getMissionManager().getCommandedCoalition() !== this.#coalition)
return false;
return true;
}
getType() {
return "";
}
/********************** Icon *************************/
createIcon(): void {
/* Set the icon */
var icon = new DivIcon({
className: 'leaflet-unit-icon',
iconAnchor: [25, 25],
iconSize: [50, 50],
/* Add the marker if not present */
if (!getApp().getMap().hasLayer(this) && !this.getHidden()) {
if (getApp().getMap().isZooming())
this.once("zoomend", () => {
this.addTo(getApp().getMap());
});
this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("unit");
el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`);
el.setAttribute("data-coalition", this.#coalition);
// Generate and append elements depending on active options
// Velocity vector
if (this.getIconOptions().showVvi) {
var vvi = document.createElement("div");
vvi.classList.add("unit-vvi");
vvi.toggleAttribute("data-rotate-to-heading");
el.append(vvi);
}
// Main icon
if (this.getIconOptions().showUnitIcon) {
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", this.getIconOptions().rotateToHeading);
el.append(unitIcon);
}
this.getElement()?.appendChild(el);
else this.addTo(getApp().getMap());
}
/********************** Visibility *************************/
updateVisibility() {
const hiddenUnits = getApp().getMap().getHiddenTypes();
var hidden = (hiddenUnits[this.getMarkerCategory()]) ||
(hiddenUnits[this.#coalition]) ||
(!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0);
this.setHidden(hidden || !this.#alive);
/* Hide the marker if necessary*/
if (getApp().getMap().hasLayer(this) && this.getHidden()) {
getApp().getMap().removeLayer(this);
}
}
setHidden(hidden: boolean) {
this.#hidden = hidden;
getHidden() {
return this.#hidden;
}
/* Add the marker if not present */
if (!getApp().getMap().hasLayer(this) && !this.getHidden()) {
if (getApp().getMap().isZooming())
this.once("zoomend", () => {this.addTo(getApp().getMap())})
else
this.addTo(getApp().getMap());
}
/* Hide the marker if necessary*/
if (getApp().getMap().hasLayer(this) && this.getHidden()) {
getApp().getMap().removeLayer(this);
}
setDetectionMethods(newDetectionMethods: number[]) {
if (!this.belongsToCommandedCoalition()) {
/* Check if the detection methods of this unit have changed */
if (
this.#detectionMethods.length !== newDetectionMethods.length ||
this.getDetectionMethods().some(
(value) => !newDetectionMethods.includes(value)
)
) {
/* Force a redraw of the unit to reflect the new status of the detection methods */
this.setHidden(true);
this.#detectionMethods = newDetectionMethods;
this.#updateMarker();
}
}
}
getHidden() {
return this.#hidden;
}
setDetectionMethods(newDetectionMethods: number[]) {
if (!this.belongsToCommandedCoalition()) {
/* Check if the detection methods of this unit have changed */
if (this.#detectionMethods.length !== newDetectionMethods.length || this.getDetectionMethods().some(value => !newDetectionMethods.includes(value))) {
/* Force a redraw of the unit to reflect the new status of the detection methods */
this.setHidden(true);
this.#detectionMethods = newDetectionMethods;
this.#updateMarker();
}
}
}
getDetectionMethods() {
return this.#detectionMethods;
}
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
return this;
}
#updateMarker() {
this.updateVisibility();
/* Draw the marker */
if (!this.getHidden()) {
if (this.getLatLng().lat !== this.#position.lat || this.getLatLng().lng !== this.#position.lng) {
this.setLatLng(new LatLng(this.#position.lat, this.#position.lng));
}
var element = this.getElement();
if (element != null) {
/* Draw the velocity vector */
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`);
/* Set dead/alive flag */
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive);
/* Set altitude and speed */
if (element.querySelector(".unit-altitude"))
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + zeroAppend(Math.floor(mToFt(this.#position.alt as number) / 100), 3);
if (element.querySelector(".unit-speed"))
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.#speed))) + "GS";
/* Rotate elements according to heading */
element.querySelectorAll("[data-rotate-to-heading]").forEach(el => {
const headingDeg = rad2deg(this.#heading);
let currentStyle = el.getAttribute("style") || "";
el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`);
});
}
/* Set vertical offset for altitude stacking */
var pos = getApp().getMap().latLngToLayerPoint(this.getLatLng()).round();
this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y);
}
getDetectionMethods() {
return this.#detectionMethods;
}
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
return this;
}
#updateMarker() {
this.updateVisibility();
/* Draw the marker */
if (!this.getHidden()) {
if (
this.getLatLng().lat !== this.#position.lat ||
this.getLatLng().lng !== this.#position.lng
) {
this.setLatLng(new LatLng(this.#position.lat, this.#position.lng));
}
var element = this.getElement();
if (element != null) {
/* Draw the velocity vector */
element
.querySelector(".unit-vvi")
?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`);
/* Set dead/alive flag */
element
.querySelector(".unit")
?.toggleAttribute("data-is-dead", !this.#alive);
/* Set altitude and speed */
if (element.querySelector(".unit-altitude"))
(<HTMLElement>element.querySelector(".unit-altitude")).innerText =
"FL" +
zeroAppend(
Math.floor(mToFt(this.#position.alt as number) / 100),
3
);
if (element.querySelector(".unit-speed"))
(<HTMLElement>element.querySelector(".unit-speed")).innerText =
String(Math.floor(msToKnots(this.#speed))) + "GS";
/* Rotate elements according to heading */
element.querySelectorAll("[data-rotate-to-heading]").forEach((el) => {
const headingDeg = rad2deg(this.#heading);
let currentStyle = el.getAttribute("style") || "";
el.setAttribute(
"style",
currentStyle + `transform:rotate(${headingDeg}deg);`
);
});
}
/* Set vertical offset for altitude stacking */
var pos = getApp().getMap().latLngToLayerPoint(this.getLatLng()).round();
this.setZIndexOffset(
1000 + Math.floor(this.#position.alt as number) - pos.y
);
}
}
}
export class Missile extends Weapon {
constructor(ID: number) {
super(ID);
}
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Missile";
}
getCategory() {
return "Missile";
}
getMarkerCategory() {
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC))
return "missile";
else
return "aircraft";
}
getMarkerCategory() {
if (
this.belongsToCommandedCoalition() ||
this.getDetectionMethods().includes(VISUAL) ||
this.getDetectionMethods().includes(OPTIC)
)
return "missile";
else return "aircraft";
}
getIconOptions() {
return {
showState: false,
showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showHealth: false,
showHotgroup: false,
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showCallsign: false,
rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)
};
}
getIconOptions() {
return {
showState: false,
showVvi:
!this.belongsToCommandedCoalition() &&
!this.getDetectionMethods().some((value) =>
[VISUAL, OPTIC].includes(value)
) &&
this.getDetectionMethods().some((value) =>
[RADAR, IRST, DLINK].includes(value)
),
showHealth: false,
showHotgroup: false,
showUnitIcon:
this.belongsToCommandedCoalition() ||
this.getDetectionMethods().some((value) =>
[VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value)
),
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary:
!this.belongsToCommandedCoalition() &&
!this.getDetectionMethods().some((value) =>
[VISUAL, OPTIC].includes(value)
) &&
this.getDetectionMethods().some((value) =>
[RADAR, IRST, DLINK].includes(value)
),
showCallsign: false,
rotateToHeading:
this.belongsToCommandedCoalition() ||
this.getDetectionMethods().includes(VISUAL) ||
this.getDetectionMethods().includes(OPTIC),
};
}
}
export class Bomb extends Weapon {
constructor(ID: number) {
super(ID);
}
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Bomb";
}
getCategory() {
return "Bomb";
}
getMarkerCategory() {
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC))
return "bomb";
else
return "aircraft";
}
getMarkerCategory() {
if (
this.belongsToCommandedCoalition() ||
this.getDetectionMethods().includes(VISUAL) ||
this.getDetectionMethods().includes(OPTIC)
)
return "bomb";
else return "aircraft";
}
getIconOptions() {
return {
showState: false,
showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showHealth: false,
showHotgroup: false,
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showCallsign: false,
rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)
};
}
getIconOptions() {
return {
showState: false,
showVvi:
!this.belongsToCommandedCoalition() &&
!this.getDetectionMethods().some((value) =>
[VISUAL, OPTIC].includes(value)
) &&
this.getDetectionMethods().some((value) =>
[RADAR, IRST, DLINK].includes(value)
),
showHealth: false,
showHotgroup: false,
showUnitIcon:
this.belongsToCommandedCoalition() ||
this.getDetectionMethods().some((value) =>
[VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value)
),
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary:
!this.belongsToCommandedCoalition() &&
!this.getDetectionMethods().some((value) =>
[VISUAL, OPTIC].includes(value)
) &&
this.getDetectionMethods().some((value) =>
[RADAR, IRST, DLINK].includes(value)
),
showCallsign: false,
rotateToHeading:
this.belongsToCommandedCoalition() ||
this.getDetectionMethods().includes(VISUAL) ||
this.getDetectionMethods().includes(OPTIC),
};
}
}

View File

@@ -4,106 +4,113 @@ import { DataIndexes } from "../constants/constants";
import { DataExtractor } from "../server/dataextractor";
import { Contact } from "../interfaces";
/** The WeaponsManager handles the creation and update of weapons. Data is strictly updated by the server ONLY. */
export class WeaponsManager {
#weapons: { [ID: number]: Weapon };
#weapons: { [ID: number]: Weapon };
constructor() {
this.#weapons = {};
constructor() {
this.#weapons = {};
document.addEventListener("commandModeOptionsChanged", () => {Object.values(this.#weapons).forEach((weapon: Weapon) => weapon.updateVisibility())});
document.addEventListener("commandModeOptionsChanged", () => {
Object.values(this.#weapons).forEach((weapon: Weapon) =>
weapon.updateVisibility()
);
});
}
/**
*
* @returns All the existing weapons, both active and destroyed
*/
getWeapons() {
return this.#weapons;
}
/** Get a weapon by ID
*
* @param ID ID of the weapon
* @returns Weapon object, or null if input ID does not exist
*/
getWeaponByID(ID: number) {
if (ID in this.#weapons) return this.#weapons[ID];
else return null;
}
/** Add a new weapon to the manager
*
* @param ID ID of the new weapon
* @param category Either "Missile" or "Bomb". Determines what class will be used to create the new unit accordingly.
*/
addWeapon(ID: number, category: string) {
if (category) {
/* The name of the weapon category is exactly the same as the constructor name */
var constructor = Weapon.getConstructor(category);
if (constructor != undefined) {
this.#weapons[ID] = new constructor(ID);
}
}
}
/**
*
* @returns All the existing weapons, both active and destroyed
*/
getWeapons() {
return this.#weapons;
}
/** Get a weapon by ID
*
* @param ID ID of the weapon
* @returns Weapon object, or null if input ID does not exist
*/
getWeaponByID(ID: number) {
if (ID in this.#weapons)
return this.#weapons[ID];
else
return null;
}
/** Add a new weapon to the manager
*
* @param ID ID of the new weapon
* @param category Either "Missile" or "Bomb". Determines what class will be used to create the new unit accordingly.
*/
addWeapon(ID: number, category: string) {
if (category){
/* The name of the weapon category is exactly the same as the constructor name */
var constructor = Weapon.getConstructor(category);
if (constructor != undefined) {
this.#weapons[ID] = new constructor(ID);
}
}
}
/** Update the data of all the weapons. The data is directly decoded from the binary buffer received from the REST Server. This is necessary for performance and bandwidth reasons.
*
* @param buffer The arraybuffer, encoded according to the ICD defined in: TODO Add reference to ICD
* @returns The decoded updateTime of the data update.
*/
update(buffer: ArrayBuffer) {
/* Extract the data from the arraybuffer. Since data is encoded dynamically (not all data is always present, but rather only the data that was actually updated since the last request).
/** Update the data of all the weapons. The data is directly decoded from the binary buffer received from the REST Server. This is necessary for performance and bandwidth reasons.
*
* @param buffer The arraybuffer, encoded according to the ICD defined in: TODO Add reference to ICD
* @returns The decoded updateTime of the data update.
*/
update(buffer: ArrayBuffer) {
/* Extract the data from the arraybuffer. Since data is encoded dynamically (not all data is always present, but rather only the data that was actually updated since the last request).
No a prori casting can be performed. On the contrary, the array is decoded incrementally, depending on the DataIndexes of the data. The actual data decoding is performed by the Weapon class directly.
Every time a piece of data is decoded the decoder seeker is incremented. */
var dataExtractor = new DataExtractor(buffer);
var dataExtractor = new DataExtractor(buffer);
var updateTime = Number(dataExtractor.extractUInt64());
var updateTime = Number(dataExtractor.extractUInt64());
/* Run until all data is extracted or an error occurs */
while (dataExtractor.getSeekPosition() < buffer.byteLength) {
/* Extract the weapon ID */
const ID = dataExtractor.extractUInt32();
/* Run until all data is extracted or an error occurs */
while (dataExtractor.getSeekPosition() < buffer.byteLength) {
/* Extract the weapon ID */
const ID = dataExtractor.extractUInt32();
/* If the ID of the weapon does not yet exist, create the weapon, if the category is known. If it isn't, some data must have been lost and we need to wait for another update */
if (!(ID in this.#weapons)) {
const datumIndex = dataExtractor.extractUInt8();
if (datumIndex == DataIndexes.category) {
const category = dataExtractor.extractString();
this.addWeapon(ID, category);
}
else {
/* Inconsistent data, we need to wait for a refresh */
return updateTime;
}
}
/* Update the data of the weapon */
this.#weapons[ID]?.setData(dataExtractor);
/* If the ID of the weapon does not yet exist, create the weapon, if the category is known. If it isn't, some data must have been lost and we need to wait for another update */
if (!(ID in this.#weapons)) {
const datumIndex = dataExtractor.extractUInt8();
if (datumIndex == DataIndexes.category) {
const category = dataExtractor.extractString();
this.addWeapon(ID, category);
} else {
/* Inconsistent data, we need to wait for a refresh */
return updateTime;
}
return updateTime;
}
/* Update the data of the weapon */
this.#weapons[ID]?.setData(dataExtractor);
}
return updateTime;
}
/** For a given weapon, it returns if and how it is being detected by other units. NOTE: this function will return how a weapon is being detected, i.e. how other units are detecting it. It will not return
* what the weapon is detecting (mostly because weapons can't detect units).
*
* @param weapon The unit of which to retrieve the "detected" methods.
* @returns Array of detection methods
*/
getWeaponDetectedMethods(weapon: Weapon) {
var detectionMethods: number[] = [];
var units = getApp().getUnitsManager().getUnits();
for (let idx in units) {
if (units[idx].getAlive() && units[idx].getIsLeader() && units[idx].getCoalition() !== "neutral" && units[idx].getCoalition() != weapon.getCoalition())
{
units[idx].getContacts().forEach((contact: Contact) => {
if (contact.ID == weapon.ID && !detectionMethods.includes(contact.detectionMethod))
detectionMethods.push(contact.detectionMethod);
});
}
}
return detectionMethods;
/** For a given weapon, it returns if and how it is being detected by other units. NOTE: this function will return how a weapon is being detected, i.e. how other units are detecting it. It will not return
* what the weapon is detecting (mostly because weapons can't detect units).
*
* @param weapon The unit of which to retrieve the "detected" methods.
* @returns Array of detection methods
*/
getWeaponDetectedMethods(weapon: Weapon) {
var detectionMethods: number[] = [];
var units = getApp().getUnitsManager().getUnits();
for (let idx in units) {
if (
units[idx].getAlive() &&
units[idx].getIsLeader() &&
units[idx].getCoalition() !== "neutral" &&
units[idx].getCoalition() != weapon.getCoalition()
) {
units[idx].getContacts().forEach((contact: Contact) => {
if (
contact.ID == weapon.ID &&
!detectionMethods.includes(contact.detectionMethod)
)
detectionMethods.push(contact.detectionMethod);
});
}
}
}
return detectionMethods;
}
}