Freighter

This commit is contained in:
2025-06-15 01:57:28 +02:00
parent cbd7d4e0ae
commit 9da2abdad7
32 changed files with 1372 additions and 242 deletions
@@ -0,0 +1,247 @@
import { FC, useEffect, useRef, useState } from 'react';
import {
COHERENT_COMM_BUS_WASM_CALL,
COMM_BUS_UPDATE_TARGET_EVENT,
GSX_SERVICE_CALLED,
GSX_SERVICE_FINISHED,
} from '../../constants';
import { WASMDataF } from '../../types/WASMData';
import { LoadingState } from '../../types/general';
import { ImportFlightPlan } from '../../utils/TFDISBImport';
import { inRangeOf, loadAircraft, unloadAircraft } from '../../utils/utils';
import CGSelect from '../CGSelect/CGSelect';
import ActionBar from '../actionbar/ActionBar';
interface SBEntryProps {
WASMData: WASMDataF;
loadingState: LoadingState;
username: string;
setLoadingState: (newState: LoadingState) => void;
}
const SBEntryF: FC<SBEntryProps> = ({ WASMData, loadingState, username, setLoadingState }) => {
const [CGTarget, setCGTarget] = useState(WASMData.targetPayload.CGTarget);
const [fuel, setFuel] = useState(Math.round(WASMData.livePayload.fuel));
const [fuelEnabled, setFuelEnabled] = useState(true);
//eslint-disable-next-line @typescript-eslint/no-explicit-any
const [SBPlan, setSBPlan] = useState<any>();
const [SBInFlight, setSBInFlight] = useState(false);
const cargo = useRef(0);
const ZFW = () => {
if (loadingState !== 'loaded' && !GSXActive()) return Math.round(WASMData.targetPayload.total);
return Math.round(WASMData.livePayload.total);
};
const ZFWValid = () => {
return ZFW() <= WASMData.limits.maxZFW;
};
const GW = () => {
return fuel + ZFW();
};
const GWValid = () => {
return GW() <= WASMData.limits.maxTOW;
};
const GSXActive = () => {
return (
(WASMData.GSX.boardingState >= GSX_SERVICE_CALLED || WASMData.GSX.deboardingState >= GSX_SERVICE_CALLED) &&
WASMData.GSX.deboardingState !== GSX_SERVICE_FINISHED
);
};
const handleInput = (input: string, maxValue: number, setter: (value: number) => void) => {
if (!input) {
setter(0);
return;
}
const converted = parseInt(input);
if (converted) {
if (converted < 0) setter(0);
else if (converted > maxValue) setter(maxValue);
else setter(converted);
}
};
const handleSB = async () => {
setSBInFlight(true);
const SBResponse = await ImportFlightPlan(
username,
WASMData.limits.maxZFW,
WASMData.limits.maxTOW,
WASMData.limits.maxFuel,
WASMData.userData.isImperial
);
if (SBResponse.type === 'error') {
console.error('TODO: ERROR', SBResponse.message);
setSBInFlight(false);
return;
}
cargo.current = parseFloat(SBResponse.message.cargo) ?? 0;
updateData();
setSBPlan(SBResponse.message);
setFuel(parseFloat(SBResponse.message.fuel) ?? 0);
setSBInFlight(false);
};
useEffect(
() =>
setFuel((prev) => {
if (prev > WASMData.limits.maxFuel) return WASMData.limits.maxFuel;
return prev;
}),
[WASMData.userData.isER]
);
useEffect(() => {
setFuelEnabled(inRangeOf(Math.round(WASMData.livePayload.fuel), fuel));
}, [WASMData.livePayload.fuel]);
const updateData = (_CGTarget?: number) => {
Coherent.call(
COHERENT_COMM_BUS_WASM_CALL,
COMM_BUS_UPDATE_TARGET_EVENT,
JSON.stringify({
mode: 0,
cargo: cargo.current ?? 0,
CGTarget: _CGTarget ?? CGTarget,
})
);
};
return (
<>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-md bg-zinc-600 p-2 px-4">
<label>Fuel ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right`}
value={fuel}
onChange={(e) => handleInput(e.target.value, WASMData.limits.maxFuel, setFuel)}
disabled={loadingState !== 'preview' || GSXActive()}
/>
<button
className="middle none center rounded-lg bg-green-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-green-500/20 transition-all hover:shadow-lg hover:shadow-green-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={() => {
SimVar.SetSimVarValue(
'L:MD11_EFB_PAYLOAD_FUEL',
'lbs',
WASMData.userData.isImperial ? fuel : fuel * 2.20462262185
);
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
setFuelEnabled(WASMData.livePayload.fuel === fuel);
}}
disabled={loadingState !== 'preview' || !fuelEnabled || GSXActive()}
>
Load Fuel
</button>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>Planned ZFW ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={SBPlan?.plannedZFW ?? 0}
disabled
/>
</div>
<div className="relative flex w-full items-center justify-between bg-zinc-700 p-2 px-4">
<label>Planned GW ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={SBPlan?.plannedGW ?? 0}
disabled
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-600 p-2 px-4">
<label>
Target ZFWCG ({WASMData.limits.minCG} - {WASMData.limits.maxCG})
</label>
<CGSelect
minCG={WASMData.limits.minCG}
maxCG={WASMData.limits.maxCG}
value={CGTarget}
disabled={loadingState !== 'preview' || GSXActive()}
increase={() =>
setCGTarget((prev) => {
const _new = prev + 0.1;
updateData(_new);
return _new;
})
}
decrease={() =>
setCGTarget((prev) => {
const _new = prev - 0.1;
updateData(_new);
return _new;
})
}
/>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>
{loadingState !== 'loaded' && !GSXActive() ? 'Expected' : 'Actual'} ZFW (
{WASMData.userData.isImperial ? 'lbs' : 'kg'})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${ZFWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={ZFW()}
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label>
{loadingState !== 'loaded' && !GSXActive() ? 'Expected' : 'Actual'} GW (
{WASMData.userData.isImperial ? 'lbs' : 'kg'})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${GWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={GW()}
/>
</div>
</div>
<ActionBar
loadingState={loadingState}
loadDisabled={!GWValid() || SBInFlight}
GSXSync={WASMData.options.GSXSync}
GSXActive={GSXActive()}
importSB={handleSB}
load={() => {
setLoadingState('loaded');
loadAircraft();
}}
unload={() => {
setLoadingState('preview');
unloadAircraft();
}}
/>
</>
);
};
export default SBEntryF;
@@ -23,6 +23,7 @@ const SBEntryPax: FC<SBEntryProps> = ({ WASMData, loadingState, username, setLoa
const [CGTarget, setCGTarget] = useState(WASMData.targetPayload.CGTarget);
const [fuel, setFuel] = useState(Math.round(WASMData.livePayload.fuel));
const [fuelEnabled, setFuelEnabled] = useState(true);
//eslint-disable-next-line @typescript-eslint/no-explicit-any
const [SBPlan, setSBPlan] = useState<any>();
const [SBInFlight, setSBInFlight] = useState(false);
@@ -139,7 +140,7 @@ const SBEntryPax: FC<SBEntryProps> = ({ WASMData, loadingState, username, setLoa
WASMData.userData.isImperial ? fuel : fuel * 2.20462262185
);
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
setFuelEnabled(false);
setFuelEnabled(WASMData.livePayload.fuel === fuel);
}}
disabled={loadingState !== 'preview' || !fuelEnabled || GSXActive()}
>
@@ -160,7 +161,7 @@ const SBEntryPax: FC<SBEntryProps> = ({ WASMData, loadingState, username, setLoa
/>
</div>
<div className="relative flex w-full items-center justify-between bg-zinc-700 p-2 px-4">
<label>Planned ZFW ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<label>Planned GW ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input
type="text"
placeholder=""
@@ -0,0 +1,128 @@
import { FC, useState } from 'react';
import { GSX_SERVICE_CALLED, GSX_SERVICE_FINISHED } from '../../constants';
import { LoadingState } from '../../types/general';
import { WASMDataF } from '../../types/WASMData';
import Profile from '../profile/Profile';
import SBEntryF from '../SBEntry/SBEntryF';
import StationEntryF from '../stationEntry/StationEntryF';
import Tabbar from '../tabbar/Tabbar';
import ZFWEntryF from '../zfwEntry/ZFWEntryF';
interface FreighterProps {
WASMData: WASMDataF;
username?: string;
}
const Freighter: FC<FreighterProps> = ({ WASMData, username }) => {
const [selectedTab, setSelectedTab] = useState(0);
const [loadingState, setLoadingState] = useState<LoadingState>('preview');
const upper1 = (overrideState: LoadingState = loadingState) => {
if (overrideState !== 'loaded') return Math.round(WASMData.targetPayload.upper1);
return Math.round(WASMData.livePayload.upper1);
};
const upper2 = (overrideState: LoadingState = loadingState) => {
if (overrideState !== 'loaded') return Math.round(WASMData.targetPayload.upper2);
return Math.round(WASMData.livePayload.upper2);
};
const upper3 = (overrideState: LoadingState = loadingState) => {
if (overrideState !== 'loaded') return Math.round(WASMData.targetPayload.upper3);
return Math.round(WASMData.livePayload.upper3);
};
const upper4 = (overrideState: LoadingState = loadingState) => {
if (overrideState !== 'loaded') return Math.round(WASMData.targetPayload.upper4);
return Math.round(WASMData.livePayload.upper4);
};
const lower1 = () => {
if (loadingState !== 'loaded' && !GSXActive()) return Math.round(WASMData.targetPayload.lowerForward);
return Math.round(WASMData.livePayload.lowerForward);
};
const lower2 = () => {
if (loadingState !== 'loaded' && !GSXActive()) return Math.round(WASMData.targetPayload.lowerRear);
return Math.round(WASMData.livePayload.lowerRear);
};
const OEW = () => {
if (loadingState !== 'loaded' && !GSXActive()) return Math.round(WASMData.targetPayload.empty);
return Math.round(WASMData.livePayload.empty);
};
const crew = () => {
if (loadingState !== 'loaded' && !GSXActive()) return Math.round(WASMData.targetPayload.crew);
return Math.round(WASMData.livePayload.crew);
};
const GSXActive = () => {
return (
(WASMData.GSX.boardingState >= GSX_SERVICE_CALLED || WASMData.GSX.deboardingState >= GSX_SERVICE_CALLED) &&
WASMData.GSX.deboardingState !== GSX_SERVICE_FINISHED
);
};
const CGs = (): [string, boolean, string, boolean] => {
if (loadingState !== 'loaded' && !GSXActive()) {
return [
WASMData.targetPayload.ZFWCG.toFixed(1),
WASMData.targetPayload.ZFWCG < WASMData.limits.minCG || WASMData.targetPayload.ZFWCG > WASMData.limits.maxCG,
WASMData.targetPayload.TOCG.toFixed(1),
WASMData.targetPayload.TOCG < WASMData.limits.minCG || WASMData.targetPayload.TOCG > WASMData.limits.maxCG,
];
}
return [
WASMData.livePayload.ZFWCG.toFixed(1),
WASMData.livePayload.ZFWCG < WASMData.limits.minCG || WASMData.livePayload.ZFWCG > WASMData.limits.maxCG,
WASMData.livePayload.TOCG.toFixed(1),
WASMData.livePayload.TOCG < WASMData.limits.minCG || WASMData.livePayload.TOCG > WASMData.limits.maxCG,
];
};
return (
<>
<Profile
type="PAX"
isER={WASMData.userData.isER}
upper1={`${upper1(GSXActive() ? 'loaded' : loadingState)}`}
upper2={`${upper2(GSXActive() ? 'loaded' : loadingState)}`}
upper3={`${upper3(GSXActive() ? 'loaded' : loadingState)}`}
upper4={`${upper4(GSXActive() ? 'loaded' : loadingState)}`}
lower1={`${lower1()}`}
lower2={`${lower2()}`}
OEW={`${OEW()}`}
crew={`${crew()}`}
unit={WASMData.userData.isImperial ? 'LBS' : 'KG'}
inPreview={loadingState !== 'loaded' && !GSXActive()}
CGs={CGs()}
/>
<Tabbar
tabs={
username ? ['Simbrief', 'ZFW', 'Passengers & Cargo', 'Options'] : ['ZFW', 'Passengers & Cargo', 'Options']
}
selectedTab={selectedTab}
setSelectedTab={setSelectedTab}
/>
{username && selectedTab === 0 && (
<SBEntryF
WASMData={WASMData}
loadingState={loadingState}
username={username}
setLoadingState={setLoadingState}
/>
)}
{((username && selectedTab === 1) || (!username && selectedTab === 0)) && (
<ZFWEntryF WASMData={WASMData} loadingState={loadingState} setLoadingState={setLoadingState} />
)}
{((username && selectedTab === 2) || (!username && selectedTab === 1)) && (
<StationEntryF WASMData={WASMData} loadingState={loadingState} setLoadingState={setLoadingState} />
)}
</>
);
};
export default Freighter;
@@ -0,0 +1,244 @@
import { FC, useEffect, useState } from 'react';
import {
COHERENT_COMM_BUS_WASM_CALL,
COMM_BUS_UPDATE_TARGET_EVENT,
GSX_SERVICE_CALLED,
GSX_SERVICE_FINISHED,
} from '../../constants';
import { LoadingState } from '../../types/general';
import { WASMDataF } from '../../types/WASMData';
import { inRangeOf, loadAircraft, unloadAircraft } from '../../utils/utils';
import ActionBar from '../actionbar/ActionBar';
interface StationEntryProps {
WASMData: WASMDataF;
loadingState: LoadingState;
setLoadingState: (newState: LoadingState) => void;
}
const StationEntryF: FC<StationEntryProps> = ({ WASMData, loadingState, setLoadingState }) => {
const [upper1, setUpper1] = useState(WASMData.targetPayload.upper1);
const [upper2, setUpper2] = useState(WASMData.targetPayload.upper2);
const [upper3, setUpper3] = useState(WASMData.targetPayload.upper3);
const [upper4, setUpper4] = useState(WASMData.targetPayload.upper4);
const [lowerForward, setLowerForward] = useState(WASMData.targetPayload.lowerForward);
const [lowerRear, setLowerRear] = useState(WASMData.targetPayload.lowerRear);
const [fuel, setFuel] = useState(Math.round(WASMData.livePayload.fuel));
const [fuelEnabled, setFuelEnabled] = useState(true);
const ZFW = () => {
if (loadingState !== 'loaded' && !GSXActive()) return Math.round(WASMData.targetPayload.total);
return Math.round(WASMData.livePayload.total);
};
const ZFWValid = () => {
return ZFW() <= WASMData.limits.maxZFW;
};
const GW = () => {
return fuel + ZFW();
};
const GWValid = () => {
return GW() <= WASMData.limits.maxTOW;
};
const GSXActive = () => {
return (
(WASMData.GSX.boardingState >= GSX_SERVICE_CALLED || WASMData.GSX.deboardingState >= GSX_SERVICE_CALLED) &&
WASMData.GSX.deboardingState !== GSX_SERVICE_FINISHED
);
};
const handleInput = (input: string, maxValue: number, setter: (value: number) => void) => {
if (!input) {
setter(0);
return;
}
const converted = parseInt(input);
if (converted) {
if (converted < 0) setter(0);
else if (converted > maxValue) setter(maxValue);
else setter(converted);
}
};
useEffect(() => updateData(), [upper1, upper2, upper3, upper4, lowerForward, lowerRear]);
useEffect(
() =>
setFuel((prev) => {
if (prev > WASMData.limits.maxFuel) return WASMData.limits.maxFuel;
return prev;
}),
[WASMData.userData.isER]
);
useEffect(() => {
setFuelEnabled(inRangeOf(Math.round(WASMData.livePayload.fuel), fuel));
}, [WASMData.livePayload.fuel]);
const updateData = () => {
Coherent.call(
COHERENT_COMM_BUS_WASM_CALL,
COMM_BUS_UPDATE_TARGET_EVENT,
JSON.stringify({
mode: 2,
business1: upper1,
business2: upper2,
economy1: upper3,
economy2: upper4,
forwardCargo: lowerForward,
rearCargo: lowerRear,
})
);
};
return (
<>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-md bg-zinc-600 p-2 px-4">
<label>Fuel ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right`}
value={fuel}
onChange={(e) => handleInput(e.target.value, WASMData.limits.maxFuel, setFuel)}
disabled={loadingState !== 'preview' || GSXActive()}
/>
<button
className="middle none center rounded-lg bg-green-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-green-500/20 transition-all hover:shadow-lg hover:shadow-green-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={() => {
SimVar.SetSimVarValue(
'L:MD11_EFB_PAYLOAD_FUEL',
'lbs',
WASMData.userData.isImperial ? fuel : fuel * 2.20462262185
);
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
setFuelEnabled(WASMData.livePayload.fuel === fuel);
}}
disabled={loadingState !== 'preview' || !fuelEnabled || GSXActive()}
>
Load Fuel
</button>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>Upper 1</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={upper1}
onChange={(e) => handleInput(e.target.value, WASMData.limits.upper1, setUpper1)}
disabled={loadingState !== 'preview' || GSXActive()}
/>
</div>
<div className="relative flex w-full items-center justify-between bg-zinc-700 p-2 px-4">
<label>Upper 2</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={upper2}
onChange={(e) => handleInput(e.target.value, WASMData.limits.upper2, setUpper2)}
disabled={loadingState !== 'preview' || GSXActive()}
/>
</div>
<div className="relative flex w-full items-center justify-between bg-zinc-600 p-2 px-4">
<label>Upper 3</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={upper3}
onChange={(e) => handleInput(e.target.value, WASMData.limits.upper3, setUpper3)}
disabled={loadingState !== 'preview' || GSXActive()}
/>
</div>
<div className="relative flex w-full items-center justify-between bg-zinc-700 p-2 px-4">
<label>Upper 4</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={upper4}
onChange={(e) => handleInput(e.target.value, WASMData.limits.upper4, setUpper4)}
disabled={loadingState !== 'preview' || GSXActive()}
/>
</div>
<div className="relative flex w-full items-center justify-between bg-zinc-600 p-2 px-4">
<label>Forward Cargo ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={lowerForward}
onChange={(e) => handleInput(e.target.value, WASMData.limits.lowerForward, setLowerForward)}
disabled={loadingState !== 'preview' || GSXActive()}
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label>Aft Cargo ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={lowerRear}
onChange={(e) => handleInput(e.target.value, WASMData.limits.lowerRear, setLowerRear)}
disabled={loadingState !== 'preview' || GSXActive()}
/>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>
{loadingState !== 'loaded' && !GSXActive() ? 'Expected' : 'Actual'} ZFW (
{WASMData.userData.isImperial ? 'lbs' : 'kg'})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${ZFWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={ZFW()}
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label>
{loadingState !== 'loaded' && !GSXActive() ? 'Expected' : 'Actual'} GW (
{WASMData.userData.isImperial ? 'lbs' : 'kg'})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${GWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={GW()}
/>
</div>
</div>
<ActionBar
loadingState={loadingState}
loadDisabled={!ZFWValid() || !GWValid()}
GSXSync={WASMData.options.GSXSync}
GSXActive={GSXActive()}
load={() => {
setLoadingState('loaded');
loadAircraft();
}}
unload={() => {
setLoadingState('preview');
unloadAircraft();
}}
/>
</>
);
};
export default StationEntryF;
@@ -114,7 +114,7 @@ const StationEntryPax: FC<StationEntryProps> = ({ WASMData, loadingState, setLoa
WASMData.userData.isImperial ? fuel : fuel * 2.20462262185
);
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
setFuelEnabled(false);
setFuelEnabled(WASMData.livePayload.fuel === fuel);
}}
disabled={loadingState !== 'preview' || !fuelEnabled || GSXActive()}
>
@@ -0,0 +1,234 @@
import { FC, useEffect, useState } from 'react';
import {
COHERENT_COMM_BUS_WASM_CALL,
COMM_BUS_UPDATE_TARGET_EVENT,
GSX_SERVICE_CALLED,
GSX_SERVICE_FINISHED,
} from '../../constants';
import { WASMDataF } from '../../types/WASMData';
import { LoadingState } from '../../types/general';
import { inRangeOf, loadAircraft, unloadAircraft } from '../../utils/utils';
import CGSelect from '../CGSelect/CGSelect';
import ActionBar from '../actionbar/ActionBar';
interface ZFWEntryProps {
WASMData: WASMDataF;
loadingState: LoadingState;
setLoadingState: (newState: LoadingState) => void;
}
const ZFWEntryF: FC<ZFWEntryProps> = ({ WASMData, loadingState, setLoadingState }) => {
const [CGTarget, setCGTarget] = useState(WASMData.targetPayload.CGTarget);
const [fuel, setFuel] = useState(Math.round(WASMData.livePayload.fuel));
const [ZFWTarget, setZFWTarget] = useState(Math.round(WASMData.targetPayload.total));
const [fuelEnabled, setFuelEnabled] = useState(true);
const ZFW = () => {
if (loadingState !== 'loaded' && !GSXActive()) return ZFWTarget;
return Math.round(WASMData.livePayload.total);
};
const ZFWValid = () => {
return ZFW() <= WASMData.limits.maxZFW;
};
const GW = () => {
return fuel + ZFW();
};
const GWValid = () => {
return GW() <= WASMData.limits.maxTOW;
};
const GSXActive = () => {
return (
(WASMData.GSX.boardingState >= GSX_SERVICE_CALLED || WASMData.GSX.deboardingState >= GSX_SERVICE_CALLED) &&
WASMData.GSX.deboardingState !== GSX_SERVICE_FINISHED
);
};
const handleInput = (input: string, maxValue: number, setter: (value: number) => void) => {
if (!input) {
setter(0);
return;
}
const converted = parseInt(input);
if (converted) {
if (converted < 0) setter(0);
else if (converted > maxValue) setter(maxValue);
else setter(converted);
}
};
const handleInputZFW = (input: string) => {
if (!input) return;
const converted = parseInt(input);
if (converted) {
if (converted < 0) setZFWTarget(Math.round(WASMData.targetPayload.empty + WASMData.targetPayload.crew));
else if (converted > WASMData.limits.maxZFW) setZFWTarget(WASMData.limits.maxZFW);
else setZFWTarget(converted);
}
};
const handleBlur = (input: string) => {
const minZFW = Math.round(WASMData.targetPayload.empty + WASMData.targetPayload.crew);
if (!input) {
setZFWTarget(minZFW);
return;
}
const converted = parseInt(input);
if (converted) {
if (converted < minZFW) setZFWTarget(minZFW);
else if (converted > WASMData.limits.maxZFW) setZFWTarget(WASMData.limits.maxZFW);
else setZFWTarget(converted);
}
updateData(converted);
};
useEffect(
() =>
setFuel((prev) => {
if (prev > WASMData.limits.maxFuel) return WASMData.limits.maxFuel;
return prev;
}),
[WASMData.userData.isER]
);
useEffect(() => {
setFuelEnabled(inRangeOf(Math.round(WASMData.livePayload.fuel), fuel));
}, [WASMData.livePayload.fuel]);
const updateData = (_ZFWTarget?: number, _CGTarget?: number) => {
Coherent.call(
COHERENT_COMM_BUS_WASM_CALL,
COMM_BUS_UPDATE_TARGET_EVENT,
JSON.stringify({
mode: 1,
ZFWTarget: _ZFWTarget ?? ZFWTarget,
CGTarget: _CGTarget ?? CGTarget,
})
);
};
return (
<>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-md bg-zinc-600 p-2 px-4">
<label>Fuel ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right`}
value={fuel}
onChange={(e) => handleInput(e.target.value, WASMData.limits.maxFuel, setFuel)}
disabled={loadingState !== 'preview' || GSXActive()}
/>
<button
className="middle none center rounded-lg bg-green-600 px-6 py-3 font-sans text-xs font-bold uppercase text-white shadow-md shadow-green-500/20 transition-all hover:shadow-lg hover:shadow-green-500/40 focus:opacity-[0.85] focus:shadow-none active:opacity-[0.85] active:shadow-none disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none"
data-ripple-light="true"
onClick={() => {
SimVar.SetSimVarValue(
'L:MD11_EFB_PAYLOAD_FUEL',
'lbs',
WASMData.userData.isImperial ? fuel : fuel * 2.20462262185
);
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
setFuelEnabled(WASMData.livePayload.fuel === fuel);
}}
disabled={loadingState !== 'preview' || !fuelEnabled || GSXActive()}
>
Load Fuel
</button>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>Target ZFW ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input
type="text"
placeholder=""
className="w-1/2 rounded-lg border border-white bg-zinc-700 px-3 py-2 text-right focus:border-blue-600 focus:ring-blue-600"
value={ZFWTarget}
onChange={(e) => handleInputZFW(e.target.value)}
onBlur={(e) => handleBlur(e.target.value)}
disabled={loadingState !== 'preview' || GSXActive()}
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label>
Target ZFWCG ({WASMData.limits.minCG} - {WASMData.limits.maxCG})
</label>
<CGSelect
minCG={WASMData.limits.minCG}
maxCG={WASMData.limits.maxCG}
value={CGTarget}
disabled={loadingState !== 'preview' || GSXActive()}
increase={() =>
setCGTarget((prev) => {
const _new = prev + 0.1;
updateData(undefined, _new);
return _new;
})
}
decrease={() =>
setCGTarget((prev) => {
const _new = prev - 0.1;
updateData(undefined, _new);
return _new;
})
}
/>
</div>
</div>
<div className="block flex w-full flex-col opacity-100 transition-opacity duration-150 ease-linear mb-4">
<div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>
{loadingState !== 'loaded' && !GSXActive() ? 'Expected' : 'Actual'} ZFW (
{WASMData.userData.isImperial ? 'lbs' : 'kg'})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${ZFWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={ZFW()}
/>
</div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label>
{loadingState !== 'loaded' && !GSXActive() ? 'Expected' : 'Actual'} GW (
{WASMData.userData.isImperial ? 'lbs' : 'kg'})
</label>
<input
type="text"
placeholder=""
className={`w-1/2 rounded-lg border ${GWValid() ? 'border-white' : 'border-red-500 text-red-500'} bg-zinc-700 px-3 py-2 text-right`}
disabled
value={GW()}
/>
</div>
</div>
<ActionBar
loadingState={loadingState}
loadDisabled={!GWValid()}
GSXSync={WASMData.options.GSXSync}
GSXActive={GSXActive()}
load={() => {
setLoadingState('loaded');
loadAircraft();
}}
unload={() => {
setLoadingState('preview');
unloadAircraft();
}}
/>
</>
);
};
export default ZFWEntryF;
@@ -133,7 +133,7 @@ const ZFWEntryPax: FC<ZFWEntryProps> = ({ WASMData, loadingState, setLoadingStat
WASMData.userData.isImperial ? fuel : fuel * 2.20462262185
);
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
setFuelEnabled(false);
setFuelEnabled(WASMData.livePayload.fuel === fuel);
}}
disabled={loadingState !== 'preview' || !fuelEnabled || GSXActive()}
>