Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Chartfox support #8519

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,30 @@
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features",
"editor.formatOnSave": true
"editor.formatOnSave": false
},
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.formatOnSave": true
"editor.formatOnSave": false
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.formatOnSave": true
"editor.formatOnSave": false
},
"[javascriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.formatOnSave": true
"editor.formatOnSave": false
},
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.formatOnSave": true
"editor.formatOnSave": false
},
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
"source.fixAll.eslint":"never"
},
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
Expand Down
54 changes: 54 additions & 0 deletions fbw-a32nx/src/systems/simbridge-client/src/components/Viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,60 @@ export class Viewer {
throw new Error('File name or page number missing');
}

/**
* Used to retrieve a streamable image of specified page within a given PDF URL
* @param url required field, URL link to the pdf
* @param pageNumber required field, The page of the PDF file
* @returns a Blob
*/
public static async getPDFPageFromUrl(url: string, pageNumber: number): Promise<Blob> {
if (!ClientState.getInstance().isConnected()) {
throw new Error('SimBridge is not connected.');
}
if (url || pageNumber) {
const encodedUrl = encodeURIComponent(url);
const response = await fetchWithTimeout(`${getSimBridgeUrl()}/api/v1/utility/pdf/fromUrl?encodedUrl=${encodedUrl}&pagenumber=${pageNumber}`);
if (response.ok) {
return response.blob();
}
throw new Error(`SimBridge Error: ${response.status}`);
}
throw new Error('File name or page number missing');
}

/**
* Used to retrieve a URL to the rendered image of the PDF page at the specified URL.
* It internally calls getPDFPageFromUrl and then calls createObjectURL().
* @see https://developer.mozilla.org/en-US/docs/web/api/url/createobjecturl
* @param url required field, URL link to the pdf
* @param pageNumber required field, The page of the PDF file
* @returns url to the image (object blob) of the PDF page
*/
public static async getImageUrlFromPdfUrl(url: string, pageNumber: number): Promise<string> {
const blob = await Viewer.getPDFPageFromUrl(url, pageNumber);
return URL.createObjectURL(blob);
}

/**
* Retrieve the number of pages within a PDF file at the specified URL
* @param url required field, URL link to the pdf
* @returns A number
*/
public static async getPDFPageCountFromUrl(url: string): Promise<number> {
if (!ClientState.getInstance().isConnected()) {
throw new Error('SimBridge is not connected.');
}
if (url) {
const encodedUrl = encodeURIComponent(url);
const response = await fetchWithTimeout(`${getSimBridgeUrl()}/api/v1/utility/pdf/fromUrl/numpages?encodedUrl=${encodedUrl}`);
if (response.ok) {
return response.json();
}
throw new Error(`SimBridge Error: ${response.status}`);
}
throw new Error('File name or page number missing');
}

/**
* Used to retrieve a list of filenames within the PDF folder
* @returns an Array of strings
Expand Down
Original file line number Diff line number Diff line change
@@ -1,99 +1,9 @@
// Copyright (c) 2023-2024 FlyByWire Simulations
// SPDX-License-Identifier: GPL-3.0

export type ChartFoxChart = {
name: string,
type: string,
runway: null | string,
url: string,
}
import React, { useContext } from 'react';
import { ChartFoxData } from 'shared/src/chartfox/client';

export type ChartFoxAirportCharts = {
arrival: ChartFoxChart[],
approach: ChartFoxChart[],
airport: ChartFoxChart[],
departure: ChartFoxChart[],
reference: ChartFoxChart[],
}
export const ChartFoxContext = React.createContext<ChartFoxData>(undefined!);

export class ChartFoxClient {
private static token = process.env.CHARTFOX_SECRET;

public static sufficientEnv() {
return !!ChartFoxClient.token;
}

public async getChartList(icao: string): Promise<ChartFoxAirportCharts> {
if (ChartFoxClient.sufficientEnv()) {
if (icao.length === 4) {
try {
const chartJsonResp = await fetch(`https://chartfox.org/api/charts/grouped/${icao}?token=${ChartFoxClient.token}`, { method: 'POST' });

if (chartJsonResp.ok) {
const chartJson = await chartJsonResp.json();

const groundLayoutArray: ChartFoxChart[] = chartJson.charts['2'].charts.map((charts) => ({
name: charts.name,
type: charts.type,
runway: charts.runway,
url: charts.url,
}));

const generalArray: ChartFoxChart[] = chartJson.charts['0'].charts.map((charts) => ({
name: charts.name,
type: charts.type,
runway: charts.runway,
url: charts.url,
}));

const textualArray: ChartFoxChart[] = chartJson.charts['1'].charts.map((charts) => ({
name: charts.name,
type: charts.type,
runway: charts.runway,
url: charts.url,
}));

const sidArray: ChartFoxChart[] = chartJson.charts['6'].charts.map((charts) => ({
name: charts.name,
type: charts.type,
runway: charts.runway,
url: charts.url,
}));

const starArray: ChartFoxChart[] = chartJson.charts['7'].charts.map((charts) => ({
name: charts.name,
type: charts.type,
runway: charts.runway,
url: charts.url,
}));

const approachArray: ChartFoxChart[] = chartJson.charts['8'].charts.map((charts) => ({
name: charts.name,
type: charts.type,
runway: charts.runway,
url: charts.url,
}));

return {
arrival: starArray,
approach: approachArray,
airport: groundLayoutArray,
departure: sidArray,
reference: generalArray.concat(textualArray),
};
}
} catch (_) {
console.log('Token Authentication Failed. #CF101');
}
}
}

return {
arrival: [],
approach: [],
airport: [],
departure: [],
reference: [],
};
}
}
export const useChartFox = () => useContext(ChartFoxContext);
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (c) 2023-2024 FlyByWire Simulations
// SPDX-License-Identifier: GPL-3.0

import React from 'react';
import { ChartFoxStatus } from '@flybywiresim/fbw-sdk';
import { useHistory } from 'react-router-dom';
import { t } from '../../../Localization/translation';
import { useChartFox } from '../ChartFox';

export const ChartFoxAuthUIWrapper = ({ children }) => {
const chartFox = useChartFox();

if (chartFox.status === ChartFoxStatus.LoggedIn) {
return children;
}

return <ChartFoxAuthRedirectUI />;
};

export const ChartFoxAuthRedirectUI = () => {
const history = useHistory();

const handleGoToThirdPartySettings = () => {
history.push('/settings/3rd-party-options');
};

return (
<div className="flex h-content-section-reduced items-center justify-center rounded-lg border-2 border-theme-accent">
<div className="flex flex-col items-center justify-center space-y-4">
<h1>{t('NavigationAndCharts.ChartFox.GoToThirdPartyOptions.Title')}</h1>

<button
type="button"
className="flex w-52 items-center justify-center space-x-4 rounded-md border-2
border-theme-highlight bg-theme-highlight py-2 text-theme-body transition
duration-100 hover:bg-theme-body hover:text-theme-highlight"
onClick={handleGoToThirdPartySettings}
>
{t('NavigationAndCharts.ChartFox.GoToThirdPartyOptions.Button')}
</button>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const sampleData = {};
84 changes: 47 additions & 37 deletions fbw-common/src/systems/instruments/src/EFB/Efb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import React, { useEffect, useState } from 'react';
import {
useSimVar, useInterval, useInteractionEvent, usePersistentNumberProperty, usePersistentProperty, NavigraphClient,
SentryConsentState, SENTRY_CONSENT_KEY,
ChartFoxClient, SentryConsentState, SENTRY_CONSENT_KEY,
FailureDefinition,
ChartFoxStatus,
} from '@flybywiresim/fbw-sdk';
import { Redirect, Route, Switch, useHistory } from 'react-router-dom';
import { Battery } from 'react-bootstrap-icons';
Expand All @@ -14,12 +15,13 @@ import { distanceTo } from 'msfs-geo';
import { ErrorBoundary } from 'react-error-boundary';
import { MemoryRouter as Router } from 'react-router';
import { Provider } from 'react-redux';
import { Tooltip } from './UtilComponents/TooltipWrapper';
import { Error as ErrorIcon } from './Assets/Error';
import { FailuresOrchestratorProvider } from './failures-orchestrator-provider';
import { AlertModal, ModalContainer, ModalProvider, useModals } from './UtilComponents/Modals/Modals';
import { FbwLogo } from './UtilComponents/FbwLogo';
import { Tooltip } from './UtilComponents/TooltipWrapper';
import { NavigraphContext } from './Apis/Navigraph/Navigraph';
import { ChartFoxContext } from './Apis/ChartFox/ChartFox';
import { StatusBar } from './StatusBar/StatusBar';
import { ToolBar } from './ToolBar/ToolBar';
import { Dashboard } from './Dashboard/Dashboard';
Expand Down Expand Up @@ -99,6 +101,12 @@ export const Efb = () => {

const [navigraph] = useState(() => new NavigraphClient());

const [chartFoxStatus, setChartFoxStatus] = useState(ChartFoxStatus.LoggedOut);
const handleChartFoxUpdate = ({ status }) => {
setChartFoxStatus(status);
};
const [chartFoxClient] = useState(() => new ChartFoxClient({ onUpdate: handleChartFoxUpdate }));

const dispatch = useAppDispatch();

const [dc2BusIsPowered] = useSimVar('L:A32NX_ELEC_DC_2_BUS_IS_POWERED', 'bool');
Expand Down Expand Up @@ -315,43 +323,45 @@ export const Efb = () => {
case PowerStates.LOADED:
return (
<NavigraphContext.Provider value={navigraph}>
<ModalContainer />
<PowerContext.Provider value={{ powerState, setPowerState }}>
<div className="bg-theme-body" style={{ transform: `translateY(-${offsetY}px)` }}>
<Tooltip posX={posX} posY={posY} shown={shown} text={text} />

<ToastContainer
position="top-center"
draggableDirection="y"
limit={2}
/>
<StatusBar
batteryLevel={batteryLevel.level}
isCharging={dc2BusIsPowered === 1}
/>
<div className="flex flex-row">
<ToolBar />
<div className="h-screen w-screen pr-6 pt-14">
<Switch>
<Route exact path="/">
<Redirect to="/dashboard" />
</Route>
<Route path="/dashboard" component={Dashboard} />
<Route path="/dispatch" component={Dispatch} />
<Route path="/ground" component={Ground} />
<Route path="/performance" component={Performance} />
<Route path="/navigation" component={Navigation} />
<Route path="/atc" component={ATC} />
<Route path="/failures" component={Failures} />
<Route path="/checklists" component={Checklists} />
<Route path="/presets" component={Presets} />
<Route path="/settings" component={Settings} />
<Route path="/settings/flypad" component={FlyPadPage} />
</Switch>
<ChartFoxContext.Provider value={{ client: chartFoxClient, status: chartFoxStatus }}>
<ModalContainer />
<PowerContext.Provider value={{ powerState, setPowerState }}>
<div className="bg-theme-body" style={{ transform: `translateY(-${offsetY}px)` }}>
<Tooltip posX={posX} posY={posY} shown={shown} text={text} />

<ToastContainer
position="top-center"
draggableDirection="y"
limit={2}
/>
<StatusBar
batteryLevel={batteryLevel.level}
isCharging={dc2BusIsPowered === 1}
/>
<div className="flex flex-row">
<ToolBar />
<div className="h-screen w-screen pr-6 pt-14">
<Switch>
<Route exact path="/">
<Redirect to="/dashboard" />
</Route>
<Route path="/dashboard" component={Dashboard} />
<Route path="/dispatch" component={Dispatch} />
<Route path="/ground" component={Ground} />
<Route path="/performance" component={Performance} />
<Route path="/navigation" component={Navigation} />
<Route path="/atc" component={ATC} />
<Route path="/failures" component={Failures} />
<Route path="/checklists" component={Checklists} />
<Route path="/presets" component={Presets} />
<Route path="/settings" component={Settings} />
<Route path="/settings/flypad" component={FlyPadPage} />
</Switch>
</div>
</div>
</div>
</div>
</PowerContext.Provider>
</PowerContext.Provider>
</ChartFoxContext.Provider>
</NavigraphContext.Provider>
);
default:
Expand Down
Loading