Compare commits

...

2 Commits

Author SHA1 Message Date
8975ea17af SB fetch and entry 2025-06-11 23:49:35 +02:00
896a459bba Pax Station Entry 2025-06-11 17:30:36 +02:00
16 changed files with 334 additions and 491 deletions

View File

@ -1,6 +1,11 @@
import { FC, StrictMode, useCallback, useEffect, useState } from 'react'; import { FC, useCallback, useEffect, useState } from 'react';
import Pax from './components/pax/Pax'; import Pax from './components/pax/Pax';
import { COHERENT_COMBUS_WASM_CALL, COMM_BUS_LIVE_DATA_EVENT, TFDI_SIMBRIEF_USERNAME_EVENT } from './constants'; import {
COHERENT_COMBUS_WASM_CALL,
COMM_BUS_LIVE_DATA_EVENT,
TFDI_SIMBRIEF_USERNAME_CALL,
TFDI_SIMBRIEF_USERNAME_EVENT,
} from './constants';
import { WASMDataPax } from './types/WASMData'; import { WASMDataPax } from './types/WASMData';
interface IAppProps { interface IAppProps {
@ -23,21 +28,20 @@ const App: FC<IAppProps> = ({ commBus }) => {
useEffect(() => { useEffect(() => {
console.log('Initializing CommBus'); console.log('Initializing CommBus');
commBus.on(COMM_BUS_LIVE_DATA_EVENT, usernameCallback); commBus.on(TFDI_SIMBRIEF_USERNAME_EVENT, usernameCallback);
commBus.on(COMM_BUS_LIVE_DATA_EVENT, wasmCallback); commBus.on(COMM_BUS_LIVE_DATA_EVENT, wasmCallback);
setTimeout(() => { setTimeout(() => {
Coherent.call(COHERENT_COMBUS_WASM_CALL, TFDI_SIMBRIEF_USERNAME_EVENT, 'null'); Coherent.call(COHERENT_COMBUS_WASM_CALL, TFDI_SIMBRIEF_USERNAME_CALL, 'null');
}, 1000); }, 1000);
return () => { return () => {
commBus.off('receiveSimBriefUsername', usernameCallback); commBus.off(TFDI_SIMBRIEF_USERNAME_EVENT, usernameCallback);
commBus.off(COMM_BUS_LIVE_DATA_EVENT, wasmCallback); commBus.off(COMM_BUS_LIVE_DATA_EVENT, wasmCallback);
}; };
}, []); }, []);
return ( return (
<StrictMode>
<div className="flex w-full justify-center pt-2 bg-zinc-900"> <div className="flex w-full justify-center pt-2 bg-zinc-900">
<div className="flex w-3/4 flex-col items-center"> <div className="flex w-3/4 flex-col items-center">
{isReady && WASMData ? ( {isReady && WASMData ? (
@ -51,7 +55,6 @@ const App: FC<IAppProps> = ({ commBus }) => {
)} )}
</div> </div>
</div> </div>
</StrictMode>
); );
}; };

View File

@ -1,111 +1,43 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useRef, useState } from 'react';
import { PaxConfig, PayloadPax } from '../../configs/pax'; import { emptyAircraft } from '../../configs/shared';
import { Fuel, SharedConfig } from '../../configs/shared'; import { COHERENT_COMBUS_WASM_CALL, COMM_BUS_UPDATE_TARGET_EVENT } from '../../constants';
import { WASMDataPax } from '../../types/WASMData';
import { LoadingState } from '../../types/general';
import { ImportFlightPlan } from '../../utils/TFDISBImport'; import { ImportFlightPlan } from '../../utils/TFDISBImport';
import CGSelect from '../CGSelect/CGSelect'; import CGSelect from '../CGSelect/CGSelect';
import ActionBar from '../actionbar/ActionBar'; import ActionBar from '../actionbar/ActionBar';
interface StationEntryProps { interface SBEntryProps {
unit: 'kg' | 'lbs'; WASMData: WASMDataPax;
isER: boolean; loadingState: LoadingState;
initialPayload: PayloadPax;
fuelLive: Fuel;
payloadLive: PayloadPax;
loadingState: 'preview' | 'accepted' | 'loaded';
username: string; username: string;
setLoadingState: (newState: StationEntryProps['loadingState']) => void; setLoadingState: (newState: LoadingState) => void;
updateView: (payload: PayloadPax) => void;
loadAircraft: () => void; loadAircraft: () => void;
} }
const SBEntryPax: FC<StationEntryProps> = ({ const SBEntryPax: FC<SBEntryProps> = ({ WASMData, loadingState, username, setLoadingState, loadAircraft }) => {
unit, const [CGTarget, setCGTarget] = useState(WASMData.targetPayload.CGTarget);
isER, const [fuel, setFuel] = useState(Math.round(WASMData.livePayload.fuel));
initialPayload, const [fuelEnabled, setFuelEnabled] = useState(true);
fuelLive,
payloadLive,
loadingState,
username,
setLoadingState,
updateView,
loadAircraft,
}) => {
const [targetZFWCG, setTargetZFWCG] = useState(SharedConfig.CGLimits.default);
const [fuel, setFuel] = useState(
Math.round(
fuelLive.main1 +
fuelLive.main1Tip +
fuelLive.main2 +
fuelLive.main3 +
fuelLive.main3Tip +
fuelLive.upperAux +
fuelLive.lowerAux +
fuelLive.tail +
fuelLive.forwardAux1 +
fuelLive.forwardAux2
)
);
const [ZFW, setZFW] = useState(
Math.round(
PaxConfig.weights.base[unit].total +
(isER ? SharedConfig.erExtraWeight[unit] * 2 : 0) +
payloadLive.empty +
initialPayload.business1Left +
initialPayload.business1Center +
initialPayload.business1Right +
initialPayload.business2Left +
initialPayload.business2Center +
initialPayload.business2Right +
initialPayload.economy1Left +
initialPayload.economy1Center +
initialPayload.economy1Right +
initialPayload.economy2Left +
initialPayload.economy2Center +
initialPayload.economy2Right +
initialPayload.forwardCargo +
initialPayload.rearCargo
)
);
const [SBPlan, setSBPlan] = useState<any>(); const [SBPlan, setSBPlan] = useState<any>();
const [SBInFlight, setSBInFlight] = useState(false); const [SBInFlight, setSBInFlight] = useState(false);
const _ZFW = () => { const numPax = useRef(0);
if (loadingState !== 'loaded') return ZFW; const cargo = useRef(0);
return Math.round( const ZFW = () => {
payloadLive.empty + if (loadingState !== 'loaded') return Math.round(WASMData.targetPayload.total);
payloadLive.pilot +
payloadLive.firstOfficer + return Math.round(WASMData.livePayload.total);
payloadLive.engineer +
payloadLive.cabinCrewFront +
payloadLive.business1Left +
payloadLive.business1Center +
payloadLive.business1Right +
payloadLive.business2Left +
payloadLive.business2Center +
payloadLive.business2Right +
payloadLive.economy1Left +
payloadLive.economy1Center +
payloadLive.economy1Right +
payloadLive.economy2Left +
payloadLive.economy2Center +
payloadLive.economy2Right +
payloadLive.cabinCrewRear +
payloadLive.forwardCargo +
payloadLive.rearCargo +
payloadLive.leftAuxPax +
payloadLive.rightAuxPax
);
}; };
const ZFWValid = () => { const ZFWValid = () => {
return _ZFW() <= PaxConfig.maxZWF[unit]; return ZFW() <= WASMData.limits.maxZFW;
}; };
const GW = () => { const GW = () => {
return fuel + _ZFW(); return fuel + ZFW();
}; };
const GWValid = () => { const GWValid = () => {
return GW() <= (isER ? SharedConfig.maxTOW.er[unit] : SharedConfig.maxTOW.norm[unit]); return GW() <= WASMData.limits.maxTOW;
}; };
const handleInput = (input: string, maxValue: number, setter: (value: number) => void) => { const handleInput = (input: string, maxValue: number, setter: (value: number) => void) => {
@ -125,49 +57,89 @@ const SBEntryPax: FC<StationEntryProps> = ({
const handleSB = async () => { const handleSB = async () => {
setSBInFlight(true); setSBInFlight(true);
const SBResponse = await ImportFlightPlan(username, PaxConfig, unit, isER); const SBResponse = await ImportFlightPlan(
username,
WASMData.limits.maxZFW,
WASMData.limits.maxTOW,
WASMData.limits.maxFuel,
WASMData.userData.isImperial
);
if (SBResponse.type === 'error') { if (SBResponse.type === 'error') {
console.error('TODO: ERROR', SBResponse.message); console.error('TODO: ERROR', SBResponse.message);
setSBInFlight(false); setSBInFlight(false);
return; return;
} }
const __ZFW = Math.round( cargo.current = parseFloat(SBResponse.message.cargo) ?? 0;
PaxConfig.weights.base[unit].total + numPax.current = parseInt(SBResponse.message.pax) ?? 0;
(isER ? SharedConfig.erExtraWeight[unit] * 2 : 0) +
payloadLive.empty +
SBResponse.message.pax * (PaxConfig.weights.pax[unit] + PaxConfig.weights.baggage[unit]) +
SBResponse.message.cargo
);
const _fuel = SBResponse.message.fuel;
updateView( updateData();
PaxConfig.distribute(__ZFW, targetZFWCG, payloadLive.empty, fuelLive, unit, isER, SBResponse.message.pax)
);
setSBPlan(SBResponse.message); setSBPlan(SBResponse.message);
setZFW(__ZFW); setFuel(parseFloat(SBResponse.message.fuel) ?? 0);
setFuel(_fuel);
setSBInFlight(false); setSBInFlight(false);
}; };
useEffect( useEffect(
() => () =>
setFuel((prev) => setFuel((prev) => {
prev > (isER ? SharedConfig.maxFuel.er[unit] : SharedConfig.maxFuel.norm[unit]) if (prev > WASMData.limits.maxFuel) return WASMData.limits.maxFuel;
? isER return prev;
? SharedConfig.maxFuel.er[unit] }),
: SharedConfig.maxFuel.norm[unit] [WASMData.userData.isER]
: prev
),
[isER]
); );
useEffect(() => {
setFuelEnabled(Math.round(WASMData.livePayload.fuel) === fuel);
}, [WASMData.livePayload.fuel]);
const updateData = (_CGTarget?: number) => {
Coherent.call(
COHERENT_COMBUS_WASM_CALL,
COMM_BUS_UPDATE_TARGET_EVENT,
JSON.stringify({
mode: 0,
cargo: cargo.current ?? 0,
numPax: numPax.current ?? 0,
CGTarget: _CGTarget ?? CGTarget,
})
);
};
return ( 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'}
/>
<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(false);
}}
disabled={loadingState !== 'preview' || !fuelEnabled}
>
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="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"> <div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>Planned ZFW ({unit})</label> <label>Planned ZFW ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input <input
type="text" type="text"
placeholder="" placeholder=""
@ -177,7 +149,7 @@ const SBEntryPax: FC<StationEntryProps> = ({
/> />
</div> </div>
<div className="relative flex w-full items-center justify-between bg-zinc-700 p-2 px-4"> <div className="relative flex w-full items-center justify-between bg-zinc-700 p-2 px-4">
<label>Planned ZFW ({unit})</label> <label>Planned ZFW ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input <input
type="text" type="text"
placeholder="" placeholder=""
@ -188,22 +160,24 @@ const SBEntryPax: FC<StationEntryProps> = ({
</div> </div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-600 p-2 px-4"> <div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-600 p-2 px-4">
<label> <label>
Target ZFWCG ({SharedConfig.CGLimits.min} - {SharedConfig.CGLimits.max}) Target ZFWCG ({WASMData.limits.minCG} - {WASMData.limits.maxCG})
</label> </label>
<CGSelect <CGSelect
value={targetZFWCG} minCG={WASMData.limits.minCG}
maxCG={WASMData.limits.maxCG}
value={CGTarget}
disabled={loadingState !== 'preview'} disabled={loadingState !== 'preview'}
increase={() => increase={() =>
setTargetZFWCG((prev) => { setCGTarget((prev) => {
const _new = prev + 0.1; const _new = prev + 0.1;
updateView(PaxConfig.distribute(ZFW, _new, payloadLive.empty, fuelLive, unit, isER)); updateData(_new);
return _new; return _new;
}) })
} }
decrease={() => decrease={() =>
setTargetZFWCG((prev) => { setCGTarget((prev) => {
const _new = prev - 0.1; const _new = prev - 0.1;
updateView(PaxConfig.distribute(ZFW, _new, payloadLive.empty, fuelLive, unit, isER)); updateData(_new);
return _new; return _new;
}) })
} }
@ -211,53 +185,22 @@ const SBEntryPax: FC<StationEntryProps> = ({
</div> </div>
</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-md bg-zinc-600 p-2 px-4">
<label>Fuel ({unit})</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,
isER ? SharedConfig.maxFuel.er[unit] : SharedConfig.maxFuel.norm[unit],
setFuel
)
}
disabled
/>
<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', unit === 'kg' ? fuel * 2.20462262185 : fuel);
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
}}
disabled={loadingState !== 'preview' || SBInFlight}
>
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="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"> <div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label> <label>
{loadingState !== 'loaded' ? 'Expected' : 'Actual'} ZFW ({unit}) {loadingState !== 'loaded' ? 'Expected' : 'Actual'} ZFW ({WASMData.userData.isImperial ? 'lbs' : 'kg'})
</label> </label>
<input <input
type="text" type="text"
placeholder="" 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`} 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 disabled
value={_ZFW()} value={ZFW()}
/> />
</div> </div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4"> <div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label> <label>
{loadingState !== 'loaded' ? 'Expected' : 'Actual'} GW ({unit}) {loadingState !== 'loaded' ? 'Expected' : 'Actual'} GW ({WASMData.userData.isImperial ? 'lbs' : 'kg'})
</label> </label>
<input <input
type="text" type="text"
@ -272,7 +215,6 @@ const SBEntryPax: FC<StationEntryProps> = ({
<ActionBar <ActionBar
loadingState={loadingState} loadingState={loadingState}
acceptDisabled={!GWValid() || SBInFlight} acceptDisabled={!GWValid() || SBInFlight}
//TODO: Make GSX optional (accepted state for NON GSX)
accept={() => setLoadingState('loaded')} accept={() => setLoadingState('loaded')}
reject={() => setLoadingState('preview')} reject={() => setLoadingState('preview')}
importSB={handleSB} importSB={handleSB}
@ -284,7 +226,7 @@ const SBEntryPax: FC<StationEntryProps> = ({
unload={() => { unload={() => {
setLoadingState('preview'); setLoadingState('preview');
PaxConfig.unload(unit, isER); emptyAircraft();
}} }}
/> />
</> </>

View File

@ -1,7 +1,8 @@
import { FC } from 'react'; import { FC } from 'react';
import { LoadingState } from '../../types/general';
interface ActionBarProps { interface ActionBarProps {
loadingState: 'preview' | 'accepted' | 'loaded'; loadingState: LoadingState;
acceptDisabled: boolean; acceptDisabled: boolean;
accept: () => void; accept: () => void;
reject: () => void; reject: () => void;

View File

@ -1,6 +1,9 @@
import { FC, useState } from 'react'; import { FC, useState } from 'react';
import { LoadingState } from '../../types/general';
import { WASMDataPax } from '../../types/WASMData'; import { WASMDataPax } from '../../types/WASMData';
import Profile from '../profile/Profile'; import Profile from '../profile/Profile';
import SBEntryPax from '../SBEntry/SBEntryPax';
import StationEntryPax from '../stationEntry/StationEntryPax';
import Tabbar from '../tabbar/Tabbar'; import Tabbar from '../tabbar/Tabbar';
import ZFWEntryPax from '../zfwEntry/ZFWEntryPax'; import ZFWEntryPax from '../zfwEntry/ZFWEntryPax';
@ -11,24 +14,24 @@ interface PaxProps {
const Pax: FC<PaxProps> = ({ WASMData, username }) => { const Pax: FC<PaxProps> = ({ WASMData, username }) => {
const [selectedTab, setSelectedTab] = useState(0); const [selectedTab, setSelectedTab] = useState(0);
const [loadingState, setLoadingState] = useState<'preview' | 'accepted' | 'loaded'>('preview'); const [loadingState, setLoadingState] = useState<LoadingState>('preview');
const upper1 = (overrideState: 'preview' | 'accepted' | 'loaded' = loadingState) => { const upper1 = (overrideState: LoadingState = loadingState) => {
if (overrideState !== 'loaded') return WASMData.targetPayload.business1; if (overrideState !== 'loaded') return WASMData.targetPayload.business1;
return WASMData.livePayload.business1; return WASMData.livePayload.business1;
}; };
const upper2 = (overrideState: 'preview' | 'accepted' | 'loaded' = loadingState) => { const upper2 = (overrideState: LoadingState = loadingState) => {
if (overrideState !== 'loaded') return WASMData.targetPayload.business2; if (overrideState !== 'loaded') return WASMData.targetPayload.business2;
return WASMData.livePayload.business2; return WASMData.livePayload.business2;
}; };
const upper3 = (overrideState: 'preview' | 'accepted' | 'loaded' = loadingState) => { const upper3 = (overrideState: LoadingState = loadingState) => {
if (overrideState !== 'loaded') return WASMData.targetPayload.economy1; if (overrideState !== 'loaded') return WASMData.targetPayload.economy1;
return WASMData.livePayload.economy1; return WASMData.livePayload.economy1;
}; };
const upper4 = (overrideState: 'preview' | 'accepted' | 'loaded' = loadingState) => { const upper4 = (overrideState: LoadingState = loadingState) => {
if (overrideState !== 'loaded') return WASMData.targetPayload.economy2; if (overrideState !== 'loaded') return WASMData.targetPayload.economy2;
return WASMData.livePayload.economy2; return WASMData.livePayload.economy2;
@ -100,58 +103,37 @@ const Pax: FC<PaxProps> = ({ WASMData, username }) => {
selectedTab={selectedTab} selectedTab={selectedTab}
setSelectedTab={setSelectedTab} setSelectedTab={setSelectedTab}
/> />
{/*
{username && selectedTab === 0 && ( {username && selectedTab === 0 && (
<SBEntryPax <SBEntryPax
unit={unit} WASMData={WASMData}
isER={isER}
initialPayload={payload}
fuelLive={fuelLive}
payloadLive={payloadLive}
loadingState={loadingState} loadingState={loadingState}
username={username} username={username}
setLoadingState={setLoadingState} setLoadingState={setLoadingState}
updateView={(_payload) => {
setPayload(_payload);
}}
loadAircraft={() => { loadAircraft={() => {
PaxConfig.setBaseWeight(unit, isER); console.log('SET WEIGHT SB');
PaxConfig.setWeights(payload, unit);
}} }}
/> />
)} )}
*/}
{((username && selectedTab === 1) || (!username && selectedTab === 0)) && ( {((username && selectedTab === 1) || (!username && selectedTab === 0)) && (
<ZFWEntryPax <ZFWEntryPax
WASMData={WASMData} WASMData={WASMData}
loadingState={loadingState} loadingState={loadingState}
setLoadingState={setLoadingState} setLoadingState={setLoadingState}
loadAircraft={() => { loadAircraft={() => {
console.log('SET WEIGHT'); console.log('SET WEIGHT ZFW');
}} }}
/> />
)} )}
{/*
{((username && selectedTab === 2) || (!username && selectedTab === 1)) && ( {((username && selectedTab === 2) || (!username && selectedTab === 1)) && (
<StationEntryPax <StationEntryPax
unit={unit} WASMData={WASMData}
isER={isER}
initialPayload={payload}
fuelLive={fuelLive}
payloadLive={payloadLive}
loadingState={loadingState} loadingState={loadingState}
setLoadingState={setLoadingState} setLoadingState={setLoadingState}
updateView={(_payload) => {
setPayload(_payload);
}}
loadAircraft={() => { loadAircraft={() => {
PaxConfig.setBaseWeight(unit, isER); console.log('SET WEIGHT STATIONS');
PaxConfig.setWeights(payload, unit);
}} }}
/> />
)} )}
*/}
</> </>
); );
}; };

View File

@ -1,117 +1,40 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { PaxConfig, PayloadPax } from '../../configs/pax'; import { emptyAircraft } from '../../configs/shared';
import { Fuel, SharedConfig } from '../../configs/shared'; import { COHERENT_COMBUS_WASM_CALL, COMM_BUS_UPDATE_TARGET_EVENT } from '../../constants';
import { LoadingState } from '../../types/general';
import { WASMDataPax } from '../../types/WASMData';
import ActionBar from '../actionbar/ActionBar'; import ActionBar from '../actionbar/ActionBar';
interface StationEntryProps { interface StationEntryProps {
unit: 'kg' | 'lbs'; WASMData: WASMDataPax;
isER: boolean; loadingState: LoadingState;
initialPayload: PayloadPax; setLoadingState: (newState: LoadingState) => void;
fuelLive: Fuel;
payloadLive: PayloadPax;
loadingState: 'preview' | 'accepted' | 'loaded';
setLoadingState: (newState: StationEntryProps['loadingState']) => void;
updateView: (payload: PayloadPax) => void;
loadAircraft: () => void; loadAircraft: () => void;
} }
const StationEntryPax: FC<StationEntryProps> = ({ const StationEntryPax: FC<StationEntryProps> = ({ WASMData, loadingState, setLoadingState, loadAircraft }) => {
unit, const [business1, setBusiness1] = useState(WASMData.targetPayload.business1);
isER, const [business2, setBusiness2] = useState(WASMData.targetPayload.business2);
initialPayload, const [economy1, setEconomy1] = useState(WASMData.targetPayload.economy1);
fuelLive, const [economy2, setEconomy2] = useState(WASMData.targetPayload.economy2);
payloadLive, const [forwardCargo, setForwardCargo] = useState(WASMData.targetPayload.forwardCargo);
loadingState, const [rearCargo, setRearCargo] = useState(WASMData.targetPayload.rearCargo);
setLoadingState, const [fuel, setFuel] = useState(Math.round(WASMData.livePayload.fuel));
updateView, const [fuelEnabled, setFuelEnabled] = useState(true);
loadAircraft,
}) => {
const [business1, setBusiness1] = useState(
PaxConfig.weightToPax(
initialPayload.business1Left + initialPayload.business1Center + initialPayload.business1Right,
unit
)
);
const [business2, setBusiness2] = useState(
PaxConfig.weightToPax(
initialPayload.business2Left + initialPayload.business2Center + initialPayload.business2Right,
unit
)
);
const [economy1, setEconomy1] = useState(
PaxConfig.weightToPax(
initialPayload.economy1Left + initialPayload.economy1Center + initialPayload.economy1Right,
unit
)
);
const [economy2, setEconomy2] = useState(
PaxConfig.weightToPax(
initialPayload.economy2Left + initialPayload.economy2Center + initialPayload.economy2Right,
unit
)
);
const [forwardCargo, setForwardCargo] = useState(initialPayload.forwardCargo);
const [rearCargo, setRearCargo] = useState(initialPayload.rearCargo);
const [fuel, setFuel] = useState(
Math.round(
fuelLive.main1 +
fuelLive.main1Tip +
fuelLive.main2 +
fuelLive.main3 +
fuelLive.main3Tip +
fuelLive.upperAux +
fuelLive.lowerAux +
fuelLive.tail +
fuelLive.forwardAux1 +
fuelLive.forwardAux2
)
);
const ZFW = () => { const ZFW = () => {
if (loadingState !== 'loaded') if (loadingState !== 'loaded') return Math.round(WASMData.targetPayload.total);
return Math.round(
(business1 + business2 + economy1 + economy2) * PaxConfig.weights.pax[unit] +
forwardCargo +
rearCargo +
PaxConfig.weights.base[unit].total +
(isER ? SharedConfig.erExtraWeight[unit] * 2 : 0) +
payloadLive.empty
);
return Math.round( return Math.round(WASMData.livePayload.total);
payloadLive.empty +
payloadLive.pilot +
payloadLive.firstOfficer +
payloadLive.engineer +
payloadLive.cabinCrewFront +
payloadLive.business1Left +
payloadLive.business1Center +
payloadLive.business1Right +
payloadLive.business2Left +
payloadLive.business2Center +
payloadLive.business2Right +
payloadLive.economy1Left +
payloadLive.economy1Center +
payloadLive.economy1Right +
payloadLive.economy2Left +
payloadLive.economy2Center +
payloadLive.economy2Right +
payloadLive.cabinCrewRear +
payloadLive.forwardCargo +
payloadLive.rearCargo +
payloadLive.leftAuxPax +
payloadLive.rightAuxPax
);
}; };
const ZFWValid = () => { const ZFWValid = () => {
return ZFW() <= PaxConfig.maxZWF[unit]; return ZFW() <= WASMData.limits.maxZFW;
}; };
const GW = () => { const GW = () => {
return fuel + ZFW(); return fuel + ZFW();
}; };
const GWValid = () => { const GWValid = () => {
return GW() <= (isER ? SharedConfig.maxTOW.er[unit] : SharedConfig.maxTOW.norm[unit]); return GW() <= WASMData.limits.maxTOW;
}; };
const handleInput = (input: string, maxValue: number, setter: (value: number) => void) => { const handleInput = (input: string, maxValue: number, setter: (value: number) => void) => {
@ -128,37 +51,67 @@ const StationEntryPax: FC<StationEntryProps> = ({
} }
}; };
useEffect(() => _updateView(), [business1, business2, economy1, economy2, forwardCargo, rearCargo]); useEffect(() => updateData(), [business1, business2, economy1, economy2, forwardCargo, rearCargo]);
useEffect( useEffect(
() => () =>
setFuel((prev) => setFuel((prev) => {
prev > (isER ? SharedConfig.maxFuel.er[unit] : SharedConfig.maxFuel.norm[unit]) if (prev > WASMData.limits.maxFuel) return WASMData.limits.maxFuel;
? isER return prev;
? SharedConfig.maxFuel.er[unit] }),
: SharedConfig.maxFuel.norm[unit] [WASMData.userData.isER]
: prev
),
[isER]
); );
useEffect(() => {
setFuelEnabled(Math.round(WASMData.livePayload.fuel) === fuel);
}, [WASMData.livePayload.fuel]);
const _updateView = () => { const updateData = () => {
const payload = PaxConfig.generateDistribution( Coherent.call(
payloadLive.empty, COHERENT_COMBUS_WASM_CALL,
COMM_BUS_UPDATE_TARGET_EVENT,
JSON.stringify({
mode: 2,
business1, business1,
business2, business2,
economy1, economy1,
economy2, economy2,
forwardCargo, forwardCargo,
rearCargo, rearCargo,
unit, })
isER
); );
updateView(payload);
}; };
return ( 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'}
/>
<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(false);
}}
disabled={loadingState !== 'preview' || !fuelEnabled}
>
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="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"> <div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label>Business</label> <label>Business</label>
@ -167,7 +120,7 @@ const StationEntryPax: FC<StationEntryProps> = ({
placeholder="" 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" 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={business1} value={business1}
onChange={(e) => handleInput(e.target.value, PaxConfig.stationMax.business1, setBusiness1)} onChange={(e) => handleInput(e.target.value, WASMData.limits.business1, setBusiness1)}
disabled={loadingState !== 'preview'} disabled={loadingState !== 'preview'}
/> />
</div> </div>
@ -178,7 +131,7 @@ const StationEntryPax: FC<StationEntryProps> = ({
placeholder="" 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" 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={business2} value={business2}
onChange={(e) => handleInput(e.target.value, PaxConfig.stationMax.business2, setBusiness2)} onChange={(e) => handleInput(e.target.value, WASMData.limits.business2, setBusiness2)}
disabled={loadingState !== 'preview'} disabled={loadingState !== 'preview'}
/> />
</div> </div>
@ -189,7 +142,7 @@ const StationEntryPax: FC<StationEntryProps> = ({
placeholder="" 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" 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={economy1} value={economy1}
onChange={(e) => handleInput(e.target.value, PaxConfig.stationMax.economy1, setEconomy1)} onChange={(e) => handleInput(e.target.value, WASMData.limits.economy1, setEconomy1)}
disabled={loadingState !== 'preview'} disabled={loadingState !== 'preview'}
/> />
</div> </div>
@ -200,69 +153,38 @@ const StationEntryPax: FC<StationEntryProps> = ({
placeholder="" 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" 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={economy2} value={economy2}
onChange={(e) => handleInput(e.target.value, PaxConfig.stationMax.economy2, setEconomy2)} onChange={(e) => handleInput(e.target.value, WASMData.limits.economy2, setEconomy2)}
disabled={loadingState !== 'preview'} disabled={loadingState !== 'preview'}
/> />
</div> </div>
<div className="relative flex w-full items-center justify-between bg-zinc-600 p-2 px-4"> <div className="relative flex w-full items-center justify-between bg-zinc-600 p-2 px-4">
<label>Forward Cargo ({unit})</label> <label>Forward Cargo ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input <input
type="text" type="text"
placeholder="" 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" 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={forwardCargo} value={forwardCargo}
onChange={(e) => handleInput(e.target.value, SharedConfig.stationMax.forward[unit], setForwardCargo)} onChange={(e) => handleInput(e.target.value, WASMData.limits.forwardCargo, setForwardCargo)}
disabled={loadingState !== 'preview'} disabled={loadingState !== 'preview'}
/> />
</div> </div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4"> <div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label>Aft Cargo ({unit})</label> <label>Aft Cargo ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
<input <input
type="text" type="text"
placeholder="" 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" 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={rearCargo} value={rearCargo}
onChange={(e) => handleInput(e.target.value, SharedConfig.stationMax.rear[unit], setRearCargo)} onChange={(e) => handleInput(e.target.value, WASMData.limits.rearCargo, setRearCargo)}
disabled={loadingState !== 'preview'} disabled={loadingState !== 'preview'}
/> />
</div> </div>
</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-md bg-zinc-600 p-2 px-4">
<label>Fuel ({unit})</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,
isER ? SharedConfig.maxFuel.er[unit] : SharedConfig.maxFuel.norm[unit],
setFuel
)
}
disabled={loadingState !== 'preview'}
/>
<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', unit === 'kg' ? fuel * 2.20462262185 : fuel);
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
}}
disabled={loadingState !== 'preview'}
>
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="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"> <div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label> <label>
{loadingState !== 'loaded' ? 'Expected' : 'Actual'} ZFW ({unit}) {loadingState !== 'loaded' ? 'Expected' : 'Actual'} ZFW ({WASMData.userData.isImperial ? 'lbs' : 'kg'})
</label> </label>
<input <input
type="text" type="text"
@ -274,7 +196,7 @@ const StationEntryPax: FC<StationEntryProps> = ({
</div> </div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4"> <div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">
<label> <label>
{loadingState !== 'loaded' ? 'Expected' : 'Actual'} GW ({unit}) {loadingState !== 'loaded' ? 'Expected' : 'Actual'} GW ({WASMData.userData.isImperial ? 'lbs' : 'kg'})
</label> </label>
<input <input
type="text" type="text"
@ -299,7 +221,7 @@ const StationEntryPax: FC<StationEntryProps> = ({
unload={() => { unload={() => {
setLoadingState('preview'); setLoadingState('preview');
PaxConfig.unload(unit, isER); emptyAircraft();
}} }}
/> />
</> </>

View File

@ -2,32 +2,33 @@ import { FC, useEffect, useState } from 'react';
import { emptyAircraft } from '../../configs/shared'; import { emptyAircraft } from '../../configs/shared';
import { COHERENT_COMBUS_WASM_CALL, COMM_BUS_UPDATE_TARGET_EVENT } from '../../constants'; import { COHERENT_COMBUS_WASM_CALL, COMM_BUS_UPDATE_TARGET_EVENT } from '../../constants';
import { WASMDataPax } from '../../types/WASMData'; import { WASMDataPax } from '../../types/WASMData';
import { LoadingState } from '../../types/general';
import CGSelect from '../CGSelect/CGSelect'; import CGSelect from '../CGSelect/CGSelect';
import ActionBar from '../actionbar/ActionBar'; import ActionBar from '../actionbar/ActionBar';
interface StationEntryProps { interface ZFWEntryProps {
WASMData: WASMDataPax; WASMData: WASMDataPax;
loadingState: 'preview' | 'accepted' | 'loaded'; loadingState: LoadingState;
setLoadingState: (newState: StationEntryProps['loadingState']) => void; setLoadingState: (newState: LoadingState) => void;
loadAircraft: () => void; loadAircraft: () => void;
} }
const ZFWEntryPax: FC<StationEntryProps> = ({ WASMData, loadingState, setLoadingState, loadAircraft }) => { const ZFWEntryPax: FC<ZFWEntryProps> = ({ WASMData, loadingState, setLoadingState, loadAircraft }) => {
const [targetZFWCG, setTargetZFWCG] = useState(WASMData.targetPayload.ZFWCG); const [CGTarget, setCGTarget] = useState(WASMData.targetPayload.CGTarget);
const [fuel, setFuel] = useState(Math.round(WASMData.livePayload.fuel)); const [fuel, setFuel] = useState(Math.round(WASMData.livePayload.fuel));
const [ZFW, setZFW] = useState(Math.round(Math.max(WASMData.limits.minZFW, WASMData.targetPayload.total))); const [ZFWTarget, setZFWTarget] = useState(Math.round(WASMData.targetPayload.total));
const [fuelEnabled, setFuelEnabled] = useState(true);
const _ZFW = () => { const ZFW = () => {
if (loadingState !== 'loaded') return ZFW; if (loadingState !== 'loaded') return ZFWTarget;
return Math.round(WASMData.livePayload.total); return Math.round(WASMData.livePayload.total);
}; };
const ZFWValid = () => { const ZFWValid = () => {
return _ZFW() <= WASMData.limits.maxZFW; return ZFW() <= WASMData.limits.maxZFW;
}; };
const GW = () => { const GW = () => {
return fuel + _ZFW(); return fuel + ZFW();
}; };
const GWValid = () => { const GWValid = () => {
return GW() <= WASMData.limits.maxTOW; return GW() <= WASMData.limits.maxTOW;
@ -51,24 +52,24 @@ const ZFWEntryPax: FC<StationEntryProps> = ({ WASMData, loadingState, setLoading
const converted = parseInt(input); const converted = parseInt(input);
if (converted) { if (converted) {
if (converted < 0) setZFW(Math.round(WASMData.targetPayload.empty + WASMData.targetPayload.crew)); if (converted < 0) setZFWTarget(Math.round(WASMData.targetPayload.empty + WASMData.targetPayload.crew));
else if (converted > WASMData.limits.maxZFW) setZFW(WASMData.limits.maxZFW); else if (converted > WASMData.limits.maxZFW) setZFWTarget(WASMData.limits.maxZFW);
else setZFW(converted); else setZFWTarget(converted);
} }
}; };
const handleBlur = (input: string) => { const handleBlur = (input: string) => {
const minZFW = Math.round(WASMData.targetPayload.empty + WASMData.targetPayload.crew); const minZFW = Math.round(WASMData.targetPayload.empty + WASMData.targetPayload.crew);
if (!input) { if (!input) {
setZFW(minZFW); setZFWTarget(minZFW);
return; return;
} }
const converted = parseInt(input); const converted = parseInt(input);
if (converted) { if (converted) {
if (converted < minZFW) setZFW(minZFW); if (converted < minZFW) setZFWTarget(minZFW);
else if (converted > WASMData.limits.maxZFW) setZFW(WASMData.limits.maxZFW); else if (converted > WASMData.limits.maxZFW) setZFWTarget(WASMData.limits.maxZFW);
else setZFW(converted); else setZFWTarget(converted);
} }
updateData(converted); updateData(converted);
@ -82,61 +83,24 @@ const ZFWEntryPax: FC<StationEntryProps> = ({ WASMData, loadingState, setLoading
}), }),
[WASMData.userData.isER] [WASMData.userData.isER]
); );
useEffect(() => {
setFuelEnabled(Math.round(WASMData.livePayload.fuel) === fuel);
}, [WASMData.livePayload.fuel]);
const updateData = (ZFWTarget?: number, CGTarget?: number) => { const updateData = (_ZFWTarget?: number, _CGTarget?: number) => {
Coherent.call( Coherent.call(
COHERENT_COMBUS_WASM_CALL, COHERENT_COMBUS_WASM_CALL,
COMM_BUS_UPDATE_TARGET_EVENT, COMM_BUS_UPDATE_TARGET_EVENT,
JSON.stringify({ JSON.stringify({
mode: 1, mode: 1,
ZFWTarget: ZFWTarget ?? ZFW, ZFWTarget: _ZFWTarget ?? ZFWTarget,
CGTarget: CGTarget ?? targetZFWCG, CGTarget: _CGTarget ?? CGTarget,
}) })
); );
}; };
return ( 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-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={ZFW}
onChange={(e) => handleInputZFW(e.target.value)}
onBlur={(e) => handleBlur(e.target.value)}
disabled={loadingState !== 'preview'}
/>
</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={targetZFWCG}
disabled={loadingState !== 'preview'}
increase={() =>
setTargetZFWCG((prev) => {
const _new = prev + 0.1;
updateData(undefined, _new);
return _new;
})
}
decrease={() =>
setTargetZFWCG((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="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"> <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> <label>Fuel ({WASMData.userData.isImperial ? 'lbs' : 'kg'})</label>
@ -158,14 +122,55 @@ const ZFWEntryPax: FC<StationEntryProps> = ({ WASMData, loadingState, setLoading
WASMData.userData.isImperial ? fuel : fuel * 2.20462262185 WASMData.userData.isImperial ? fuel : fuel * 2.20462262185
); );
SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true); SimVar.SetSimVarValue('L:MD11_EFB_READ_READY', 'bool', true);
setFuelEnabled(false);
}} }}
disabled={loadingState !== 'preview' || fuel === Math.round(WASMData.livePayload.fuel)} disabled={loadingState !== 'preview' || !fuelEnabled}
> >
Load Fuel Load Fuel
</button> </button>
</div> </div>
</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'}
/>
</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'}
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="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"> <div className="relative flex w-full items-center justify-between rounded-t-md bg-zinc-600 p-2 px-4">
<label> <label>
@ -176,7 +181,7 @@ const ZFWEntryPax: FC<StationEntryProps> = ({ WASMData, loadingState, setLoading
placeholder="" 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`} 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 disabled
value={_ZFW()} value={ZFW()}
/> />
</div> </div>
<div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4"> <div className="relative flex w-full items-center justify-between rounded-b-md bg-zinc-700 p-2 px-4">

View File

@ -1,5 +1,8 @@
export const COHERENT_COMBUS_WASM_CALL = 'COMM_BUS_WASM_CALLBACK'; export const COHERENT_COMBUS_WASM_CALL = 'COMM_BUS_WASM_CALLBACK';
export const TFDI_SIMBRIEF_USERNAME_EVENT = 'requestSimBriefUsername';
export const TFDI_SIMBRIEF_USERNAME_CALL = 'requestSimBriefUsername';
export const TFDI_SIMBRIEF_USERNAME_EVENT = 'receiveSimBriefUsername';
export const COMM_BUS_LIVE_DATA_EVENT = 'khofmann_tfdi_md-11_load_manager_live_data'; export const COMM_BUS_LIVE_DATA_EVENT = 'khofmann_tfdi_md-11_load_manager_live_data';
export const COMM_BUS_UPDATE_TARGET_EVENT = 'khofmann_tfdi_md-11_load_manager_update_target'; export const COMM_BUS_UPDATE_TARGET_EVENT = 'khofmann_tfdi_md-11_load_manager_update_target';

View File

@ -6,7 +6,14 @@ export interface WASMDataPax {
limits: LimitsPax; limits: LimitsPax;
} }
interface TargetPayloadPax { interface TargetPayload {
CGTarget: number;
ZFWCG: number;
TOCG: number;
total: number;
}
interface TargetPayloadPax extends TargetPayload {
empty: number; empty: number;
crew: number; crew: number;
business1: number; business1: number;
@ -15,16 +22,13 @@ interface TargetPayloadPax {
economy2: number; economy2: number;
forwardCargo: number; forwardCargo: number;
rearCargo: number; rearCargo: number;
ZFWCG: number;
TOCG: number;
total: number;
} }
interface LivePayloadPax extends TargetPayloadPax { interface LivePayloadPax extends TargetPayloadPax {
fuel: number; fuel: number;
} }
interface TargetPayloadF { interface TargetPayloadF extends TargetPayload {
empty: number; empty: number;
crew: number; crew: number;
upper1: number; upper1: number;
@ -33,9 +37,6 @@ interface TargetPayloadF {
upper4: number; upper4: number;
lowerForward: number; lowerForward: number;
lowerRear: number; lowerRear: number;
ZFWCG: number;
TOCG: number;
total: number;
} }
interface LivePayloadF extends TargetPayloadF { interface LivePayloadF extends TargetPayloadF {

View File

@ -0,0 +1 @@
export type LoadingState = 'preview' | 'accepted' | 'loaded';

View File

@ -1,11 +0,0 @@
import { CSSProperties, DOMAttributes, ReactNode } from 'react';
type CustomElement<T> = Partial<T & DOMAttributes<T> & { children: ReactNode }>;
declare global {
namespace JSX {
interface IntrinsicElements {
['virtual-scroll']: CustomElement<{ class?: string; direction: 'x' | 'y'; style?: CSSProperties }>;
}
}
}

View File

@ -1,6 +1,3 @@
import { PaxConfig } from '../configs/pax';
import { SharedConfig } from '../configs/shared';
const getSimBriefFlightPlan = async (simBriefUsername: string) => { const getSimBriefFlightPlan = async (simBriefUsername: string) => {
const flightPlanURL = `https://www.simbrief.com/api/xml.fetcher.php?username=${simBriefUsername}&json=1`; const flightPlanURL = `https://www.simbrief.com/api/xml.fetcher.php?username=${simBriefUsername}&json=1`;
let response: Response; let response: Response;
@ -19,9 +16,10 @@ const getSimBriefFlightPlan = async (simBriefUsername: string) => {
export const ImportFlightPlan = async ( export const ImportFlightPlan = async (
username: string, username: string,
config: typeof PaxConfig, maxZFW: number,
unit: 'kg' | 'lbs', maxTOW: number,
isER: boolean maxFuel: number,
isImperial: boolean
) => { ) => {
const flightPlan = await getSimBriefFlightPlan(username); const flightPlan = await getSimBriefFlightPlan(username);
if (!flightPlan.success) { if (!flightPlan.success) {
@ -41,23 +39,17 @@ export const ImportFlightPlan = async (
} }
let convFactor = 1; let convFactor = 1;
if (data.params.units === 'kgs' && unit === 'lbs') convFactor = 2.20462262185; if (data.params.units === 'kgs' && isImperial) convFactor = 2.20462262185;
if (data.params.units === 'lbs' && unit === 'kg') convFactor = 1 / 2.20462262185; if (data.params.units === 'lbs' && !isImperial) convFactor = 1 / 2.20462262185;
return { return {
type: 'data', type: 'data',
message: { message: {
plannedZFW: Math.min(config.maxZWF[unit], Math.round(data.weights.est_zfw * convFactor)), plannedZFW: Math.min(maxZFW, Math.round(data.weights.est_zfw * convFactor)),
plannedGW: Math.min( plannedGW: Math.min(maxTOW, Math.round(data.weights.est_ramp * convFactor)),
isER ? SharedConfig.maxTOW.er[unit] : SharedConfig.maxTOW.norm[unit],
Math.round(data.weights.est_ramp * convFactor)
),
pax: data.weights.pax_count_actual, pax: data.weights.pax_count_actual,
cargo: Math.round(data.weights.freight_added * convFactor), cargo: Math.round(data.weights.freight_added * convFactor),
fuel: Math.min( fuel: Math.min(maxFuel, Math.round(data.fuel.plan_ramp * convFactor)),
isER ? SharedConfig.maxFuel.er[unit] : SharedConfig.maxFuel.norm[unit],
Math.round(data.fuel.plan_ramp * convFactor)
),
}, },
}; };
}; };

View File

@ -35,7 +35,7 @@ extern "C" MSFS_CALLBACK void module_init(void) {
targetFPayloadData = new fPayloadData_t(); targetFPayloadData = new fPayloadData_t();
liveFuelData = new FuelData_t(); liveFuelData = new FuelData_t();
targetFPayloadData->ZFWCG = targetPaxPayloadData->ZFWCG = 21; targetFPayloadData->CGTarget = targetPaxPayloadData->CGTarget = 21;
#pragma region SimConnect #pragma region SimConnect
@ -454,13 +454,14 @@ int receiveData(const char* buf) {
double CGTarget = document["CGTarget"].GetDouble(); double CGTarget = document["CGTarget"].GetDouble();
if (UserData->isCargo) { if (UserData->isCargo) {
targetPaxPayloadData->CGTarget = CGTarget;
} }
else { else {
if (!document.HasMember("numPax")) return -1; if (!document.HasMember("numPax")) return -1;
unsigned short numPax = document["numPax"].GetInt(); unsigned short numPax = document["numPax"].GetInt();
distribute(targetPaxPayloadData, liveFuelData, numPax, cargo, CGTarget, UserData->isImperial); targetPaxPayloadData->CGTarget = CGTarget;
distribute(targetPaxPayloadData, liveFuelData, numPax, cargo, UserData->isImperial);
} }
break; break;
} }
@ -471,10 +472,11 @@ int receiveData(const char* buf) {
double CGTarget = document["CGTarget"].GetDouble(); double CGTarget = document["CGTarget"].GetDouble();
if (UserData->isCargo) { if (UserData->isCargo) {
targetPaxPayloadData->CGTarget = CGTarget;
} }
else { else {
distribute(targetPaxPayloadData, liveFuelData, ZFWTarget, CGTarget, UserData->isImperial); targetPaxPayloadData->CGTarget = CGTarget;
distribute(targetPaxPayloadData, liveFuelData, ZFWTarget, UserData->isImperial);
} }
break; break;
} }
@ -495,7 +497,6 @@ int receiveData(const char* buf) {
targetPaxPayloadData->rearCargo = document["rearCargo"].GetDouble(); targetPaxPayloadData->rearCargo = document["rearCargo"].GetDouble();
generatePayload(targetPaxPayloadData, UserData->isImperial); generatePayload(targetPaxPayloadData, UserData->isImperial);
normalisePayload(targetPaxPayloadData, UserData->isImperial);
} }
break; break;
} }
@ -557,8 +558,8 @@ void sendData () {
// CGs // CGs
//TODO: Enable for F //TODO: Enable for F
//calculateCGs(liveFPayloadData, liveFuelData, &liveFPayloadData->ZFWCG, &liveFPayloadData->TOCG, true); //calculateCGs(liveFPayloadData, liveFuelData, &liveFPayloadData->ZFWCG, &liveFPayloadData->TOCG, true);
targetPayload.AddMember("ZFWCG", liveFPayloadData->ZFWCG, allocator); livePayload.AddMember("ZFWCG", liveFPayloadData->ZFWCG, allocator);
targetPayload.AddMember("TOCG", liveFPayloadData->TOCG, allocator); livePayload.AddMember("TOCG", liveFPayloadData->TOCG, allocator);
} }
// Pax only (converted to passengers) // Pax only (converted to passengers)
else { else {
@ -613,6 +614,7 @@ void sendData () {
targetPayload.AddMember("lowerForward", targetFPayloadData->lowerForward, allocator); targetPayload.AddMember("lowerForward", targetFPayloadData->lowerForward, allocator);
targetPayload.AddMember("lowerRear", targetFPayloadData->lowerRear, allocator); targetPayload.AddMember("lowerRear", targetFPayloadData->lowerRear, allocator);
targetPayload.AddMember("total", targetFPayloadData->total, allocator); targetPayload.AddMember("total", targetFPayloadData->total, allocator);
targetPayload.AddMember("CGTarget", targetFPayloadData->CGTarget, allocator);
// CGs // CGs
//TODO: Enable for F //TODO: Enable for F
//calculateCGs(targetFPayloadData, liveFuelData, &targetFPayloadData->ZFWCG, &targetFPayloadData->TOCG, UserData->isImperial); //calculateCGs(targetFPayloadData, liveFuelData, &targetFPayloadData->ZFWCG, &targetFPayloadData->TOCG, UserData->isImperial);
@ -632,6 +634,7 @@ void sendData () {
targetPayload.AddMember("forwardCargo", targetPaxPayloadData->forwardCargo, allocator); targetPayload.AddMember("forwardCargo", targetPaxPayloadData->forwardCargo, allocator);
targetPayload.AddMember("rearCargo", targetPaxPayloadData->rearCargo, allocator); targetPayload.AddMember("rearCargo", targetPaxPayloadData->rearCargo, allocator);
targetPayload.AddMember("total", targetPaxPayloadData->total, allocator); targetPayload.AddMember("total", targetPaxPayloadData->total, allocator);
targetPayload.AddMember("CGTarget", targetPaxPayloadData->CGTarget, allocator);
// CGs // CGs
calculateCGs(targetPaxPayloadData, liveFuelData, &targetPaxPayloadData->ZFWCG, &targetPaxPayloadData->TOCG, UserData->isImperial); calculateCGs(targetPaxPayloadData, liveFuelData, &targetPaxPayloadData->ZFWCG, &targetPaxPayloadData->TOCG, UserData->isImperial);
targetPayload.AddMember("ZFWCG", targetPaxPayloadData->ZFWCG, allocator); targetPayload.AddMember("ZFWCG", targetPaxPayloadData->ZFWCG, allocator);

View File

@ -1,18 +1,18 @@
#include "pax.h" #include "pax.h"
//ZFW Entry, fill pax first (pax+bag), rest is cargo //ZFW Entry, fill pax first (pax+bag), rest is cargo
void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const fuel, const double ZFWTarget, const double CGTarget, const bool isImperial) { void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const fuel, const double ZFWTarget, const bool isImperial) {
// Find payload, num pax and extra cargo // Find payload, num pax and extra cargo
double payload = ZFWTarget - targetPayload->empty - targetPayload->pilot - targetPayload->firstOfficer - targetPayload->engineer - double payload = ZFWTarget - targetPayload->empty - targetPayload->pilot - targetPayload->firstOfficer - targetPayload->engineer -
targetPayload->cabinCrewFront - targetPayload->cabinCrewRear - targetPayload->leftAux - targetPayload->rightAux; targetPayload->cabinCrewFront - targetPayload->cabinCrewRear - targetPayload->leftAux - targetPayload->rightAux;
unsigned short numPax = std::max(0.0, std::min((double)MAX_PAX, floor(payload / (PAX_WEIGHT(isImperial) + BAG_WEIGHT(isImperial))))); unsigned short numPax = std::max(0.0, std::min((double)MAX_PAX, floor(payload / (PAX_WEIGHT(isImperial) + BAG_WEIGHT(isImperial)))));
unsigned int cargo = round(payload - numPax * PAX_WEIGHT(isImperial) - numPax * BAG_WEIGHT(isImperial)); unsigned int cargo = round(payload - numPax * PAX_WEIGHT(isImperial) - numPax * BAG_WEIGHT(isImperial));
distribute(targetPayload, fuel, numPax, cargo, CGTarget, isImperial); distribute(targetPayload, fuel, numPax, cargo, isImperial);
} }
//SimBrief Entry, SB pax count and total cargo //SimBrief Entry, SB pax count and total cargo
void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const fuel, unsigned short numPax, unsigned int cargo, const double CGTarget, const bool isImperial) { void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const fuel, unsigned short numPax, unsigned int cargo, const bool isImperial) {
// Clear // Clear
targetPayload->paxCount.business1 = targetPayload->paxCount.business2 = targetPayload->paxCount.economy1 = targetPayload->paxCount.economy2 = targetPayload->paxCount.business1 = targetPayload->paxCount.business2 = targetPayload->paxCount.economy1 = targetPayload->paxCount.economy2 =
targetPayload->paxCount.total = 0; targetPayload->paxCount.total = 0;
@ -108,7 +108,7 @@ void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const f
calculateCGs(targetPayload, fuel, &targetPayload->ZFWCG, &targetPayload->TOCG, isImperial); calculateCGs(targetPayload, fuel, &targetPayload->ZFWCG, &targetPayload->TOCG, isImperial);
// in front of target // in front of target
if (targetPayload->ZFWCG < CGTarget - CG_TOLERANCE) { if (targetPayload->ZFWCG < targetPayload->CGTarget - CG_TOLERANCE) {
if (targetPayload->paxCount.business1 > 0) { if (targetPayload->paxCount.business1 > 0) {
targetPayload->paxCount.business1--; targetPayload->paxCount.business1--;
} }
@ -136,7 +136,7 @@ void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const f
} }
} }
// behind target // behind target
else if (targetPayload->ZFWCG > CGTarget + CG_TOLERANCE) { else if (targetPayload->ZFWCG > targetPayload->CGTarget + CG_TOLERANCE) {
if (targetPayload->paxCount.economy2 > 0) { if (targetPayload->paxCount.economy2 > 0) {
targetPayload->paxCount.economy2--; targetPayload->paxCount.economy2--;
} }
@ -176,7 +176,7 @@ void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const f
calculateCGs(targetPayload, fuel, &targetPayload->ZFWCG, &targetPayload->TOCG, isImperial); calculateCGs(targetPayload, fuel, &targetPayload->ZFWCG, &targetPayload->TOCG, isImperial);
// in front of target // in front of target
if (targetPayload->ZFWCG < CGTarget - CG_TOLERANCE) { if (targetPayload->ZFWCG < targetPayload->CGTarget - CG_TOLERANCE) {
if (targetPayload->forwardCargo > 0 && targetPayload->rearCargo < MAX_REAR_CARGO(isImperial)) { if (targetPayload->forwardCargo > 0 && targetPayload->rearCargo < MAX_REAR_CARGO(isImperial)) {
if (targetPayload->forwardCargo > BAG_WEIGHT(isImperial) && if (targetPayload->forwardCargo > BAG_WEIGHT(isImperial) &&
targetPayload->rearCargo < MAX_FRONT_CARGO(isImperial) - BAG_WEIGHT(isImperial)) { targetPayload->rearCargo < MAX_FRONT_CARGO(isImperial) - BAG_WEIGHT(isImperial)) {
@ -193,7 +193,7 @@ void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const f
} }
} }
// behind target // behind target
else if (targetPayload->ZFWCG > CGTarget + CG_TOLERANCE) { else if (targetPayload->ZFWCG > targetPayload->CGTarget + CG_TOLERANCE) {
if (targetPayload->rearCargo > 0 && targetPayload->forwardCargo < MAX_FRONT_CARGO(isImperial)) { if (targetPayload->rearCargo > 0 && targetPayload->forwardCargo < MAX_FRONT_CARGO(isImperial)) {
if (targetPayload->rearCargo > BAG_WEIGHT(isImperial) && if (targetPayload->rearCargo > BAG_WEIGHT(isImperial) &&
targetPayload->forwardCargo < MAX_REAR_CARGO(isImperial) - BAG_WEIGHT(isImperial)) { targetPayload->forwardCargo < MAX_REAR_CARGO(isImperial) - BAG_WEIGHT(isImperial)) {
@ -233,7 +233,7 @@ void generatePayload(paxPayloadData_t* const targetPayload, const bool isImperia
// Normalise to Pounds // Normalise to Pounds
// MANDATORY BEFORE SETTING WEIGHTS // MANDATORY BEFORE SETTING WEIGHTS
// ENSURE ONLY EVER CALLED ONCE PER SET CYCLE // USE ON COPY OF GLOBAL STATE ONLY
void normalisePayload(paxPayloadData_t* const targetPayload, const bool isImperial) { void normalisePayload(paxPayloadData_t* const targetPayload, const bool isImperial) {
targetPayload->empty = TO_POUNDS(isImperial, targetPayload->empty); targetPayload->empty = TO_POUNDS(isImperial, targetPayload->empty);
targetPayload->pilot = TO_POUNDS(isImperial, targetPayload->pilot); targetPayload->pilot = TO_POUNDS(isImperial, targetPayload->pilot);

View File

@ -17,9 +17,9 @@
#include "types.h" #include "types.h"
//ZFW Entry, fill pax first (pax+bag), rest is cargo //ZFW Entry, fill pax first (pax+bag), rest is cargo
void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const fuel, const double ZFWTarget, const double CGTarget, bool isImperial); void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const fuel, const double ZFWTarget, bool isImperial);
//SimBrief Entry, SB pax count and total cargo //SimBrief Entry, SB pax count and total cargo
void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const fuel, unsigned short numPax, unsigned int cargo, const double CGTarget, bool isImperial); void distribute(paxPayloadData_t* const targetPayload, const FuelData_t* const fuel, unsigned short numPax, unsigned int cargo, bool isImperial);
// Updates pax stations with their respective weights // Updates pax stations with their respective weights
// Used internally and used for Station Entry (pax only, cargo is ste directly) // Used internally and used for Station Entry (pax only, cargo is ste directly)
// STATION WEIGHTS ARE NOT NORMALISED TO POUNDS // STATION WEIGHTS ARE NOT NORMALISED TO POUNDS

View File

@ -144,6 +144,7 @@ typedef struct {
// Additional properties // Additional properties
double empty; double empty;
double total; double total;
double CGTarget;
double ZFWCG; double ZFWCG;
double TOCG; double TOCG;
struct paxCount { struct paxCount {
@ -176,6 +177,7 @@ typedef struct {
// Additional properties // Additional properties
double empty; double empty;
double total; double total;
double CGTarget;
double ZFWCG; double ZFWCG;
double TOCG; double TOCG;
} fPayloadData_t; } fPayloadData_t;

View File

@ -10,12 +10,9 @@ Coherent.call("COMM_BUS_WASM_CALLBACK", "khofmann_tfdi_md-11_load_manager_update
TODO: TODO:
- JS - JS
- Connect to WASM as Data Source (sans SB username) - GSX State for displaying status fo prog. loading
- Setting target - Persist SB data across page changes
- SB
- Stations
- GSX State?
- Duplicate Input pages for F - Duplicate Input pages for F
- WASM - WASM
- Setting of payload - Setting of payload
- GSX synced setting - GSX synced setting of payload