mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Reformatted project using prettier plugin
This commit is contained in:
@@ -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 |
@@ -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;
|
||||
|
||||
50
frontend/react/src/dom.d.ts
vendored
50
frontend/react/src/dom.d.ts
vendored
@@ -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 {};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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 */
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
//});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
`}
|
||||
/>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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} />}
|
||||
</>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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: "",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> </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> </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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user