Skip to content

Commit

Permalink
feat: wx source, accounts, about settings, error modal
Browse files Browse the repository at this point in the history
  • Loading branch information
ninjomcs committed Sep 30, 2024
1 parent d2a6bbf commit fe3adf9
Show file tree
Hide file tree
Showing 16 changed files with 527 additions and 125 deletions.
2 changes: 1 addition & 1 deletion src/instruments/src/EFB/apps/FZPro/Flight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SimbriefClient, SimbriefOfp } from "@microsoft/msfs-sdk";
import { FlightContext } from "./FlightPlan";
import { useSetting } from "../../hooks/useSettings";
import ScrollContainer from "react-indiana-drag-scroll";
import { ModalContext } from ".";
import { ModalContext } from "../../";
import { CancelConfirmModal } from "../../components/CancelConfirmModal";

export const Flight: FC<{ onUplink: (ofp: SimbriefOfp) => void; onClose: () => void }> = ({ onUplink, onClose }) => {
Expand Down
25 changes: 2 additions & 23 deletions src/instruments/src/EFB/apps/FZPro/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,41 +22,20 @@ import { FZProContext, sourceToLabel } from "./AppContext";
import { InfoWx } from "./info-wx";
import { facilityLoader, getAirportIcaoFromIdent } from "../../lib/facility";

export const ModalContext = createContext<{ modal: ReactNode | null; setModal: (modal: ReactNode | null) => void }>({
modal: null,
setModal: () => {},
});

export const FZPro: FC = () => {
const { user, initialized } = useNavigraphAuth();

const [modal, setModal] = useState<ReactNode | null>(null);

return (
<RootContainer>
<FlightProvider>
<ModalContext.Provider value={{ modal, setModal }}>
{!initialized && <div>Loading</div>}

{initialized && !user ? <SignInPrompt /> : user && <App />}
{!initialized && <div>Loading</div>}

{modal && <ModalOverlay onClick={() => setModal(null)} />}

{modal}
</ModalContext.Provider>
{initialized && !user ? <SignInPrompt /> : user && <App />}
</FlightProvider>
</RootContainer>
);
};

const ModalOverlay = styled.div`
position: absolute;
width: 100vw;
height: 100vh;
background: black;
opacity: 0.6;
`;

const App: FC = () => {
const [currentChart, setCurrentChart] = useState<Chart | null>(null);
const [selectedAirport, setSelectedAirport] = useState<string | null>(null);
Expand Down
8 changes: 5 additions & 3 deletions src/instruments/src/EFB/apps/FZPro/info-wx/Weather.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { AirportFacility } from "@microsoft/msfs-sdk";
import { getIdentFromIcao } from "instruments/src/EFB/lib/facility";
import { FZProContext } from "../AppContext";
import ScrollContainer from "react-indiana-drag-scroll";
import { SettingsContext } from "../../Settings/SettingsContext";

export const Weather: FC<{ airport: AirportFacility }> = ({ airport }) => {
const [metarLoading, setMetarLoading] = useState<boolean>(false);
const [tafLoading, setTafLoading] = useState<boolean>(false);
const [atisLoading, setAtisLoading] = useState<boolean>(false);

const { metar, setMetar, taf, setTaf, atis, setAtis, weatherLastUpdated, setWeatherLastUpdated } = useContext(FZProContext);
const { metarSource, tafSource, atisSource } = useContext(SettingsContext);

const lastUpdatedString = () => {
if (weatherLastUpdated) {
Expand All @@ -32,9 +34,9 @@ export const Weather: FC<{ airport: AirportFacility }> = ({ airport }) => {

const fetchData = async () => {
setAllLoading(true);
setMetar(await WeatherData.fetchMetar(airport, "msfs"));
setTaf(await WeatherData.fetchTaf(getIdentFromIcao(airport.icao), "aviationweather"));
setAtis(await WeatherData.fetchAtis(getIdentFromIcao(airport.icao), "faa"));
setMetar(await WeatherData.fetchMetar(airport, metarSource));
setTaf(await WeatherData.fetchTaf(getIdentFromIcao(airport.icao), tafSource));
setAtis(await WeatherData.fetchAtis(getIdentFromIcao(airport.icao), atisSource));
setAllLoading(false)
setWeatherLastUpdated(new Date(Date.now()));
};
Expand Down
69 changes: 69 additions & 0 deletions src/instruments/src/EFB/apps/Settings/About.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { FC, ReactNode, useContext } from "react";

import { ContentPageContainer } from "./components/ContentPageContainer";
import { ItemGroup } from "../../components/ItemGroup";
import styled from "styled-components";
import { ListItem } from "../../components/ListItem";

import saltyLogo from "../../img/salty-logo.svg";

export const About: FC = () => (
<ContentPageContainer title="About" backProps={{ label: "General", path: "/settings/general" }}>
<ItemGroup>
<ListItem>
<Name>Name</Name>
<Value>saltPad</Value>
</ListItem>
<ListItem>
<Name>saltPadOS Version</Name>
<Value>1.0</Value>
</ListItem>
<ListItem>
<Name>74S Version</Name>
<Value>0.7.0</Value>
</ListItem>
</ItemGroup>
<LicenseContainer>
<div className="margin">Copyright (C) 2024 Salty Simulations and its contributors</div>
<div className="margin">
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published
by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
</div>
<div>
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
</div>
<div className="me">Written with &lt;3 by Natalie &lt;[email protected]&gt;</div>
</LicenseContainer>
<img src={saltyLogo} width={350} />
</ContentPageContainer>
);



const Name = styled.div`
margin: 0 25px;
`;

const Value = styled.div`
margin: 0 25px;
color: gray;
`;

const LicenseContainer = styled.div`
padding: 25px;
background: white;
border-radius: 25px;
margin-bottom: 50px;
text-align: start;
font-size: 22px;
border: 1px solid lightgray;
.me {
margin-top: 16px;
}
.margin {
margin-bottom: 8px;
}
`;
74 changes: 72 additions & 2 deletions src/instruments/src/EFB/apps/Settings/Acars.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import React, { FC } from "react";
import React, { FC, ReactNode, useContext } from "react";

import { ContentPageContainer } from "./components/ContentPageContainer";
import { ItemGroup } from "../../components/ItemGroup";
import { NavigationItem } from "./components/NavigationItem";
import { SettingsContext } from "./SettingsContext";
import { SelectableItem } from "./components/SelectableItem";
import { AtisSource, MetarSource, TafSource } from "../../lib/weather";
import { BackButtonProps } from "./components/BackButton";

// TODO: add sub options
export const Acars: FC = () => (
<ContentPageContainer title="ACARS">
<ItemGroup>
Expand All @@ -15,3 +18,70 @@ export const Acars: FC = () => (
</ContentPageContainer>
);

export const MetarSourceOptions: FC = () => {
const { metarSource, setMetarSource } = useContext(SettingsContext);

const sources: Record<MetarSource, string> = {
msfs: "MSFS",
aviationweather: "AviationWeather",
vatsim: "Vatsim",
ivao: "IVAO",
pilotedge: "PilotEdge",
} as const;

return (
<SourceOptions
title="METAR Source"
backProps={{ label: "ACARS", path: "/settings/acars" }}
items={(Object.keys(sources) as MetarSource[]).map((source, i) => (
<SelectableItem label={sources[source]} selected={metarSource === source} onClick={() => setMetarSource(source)} key={i} />
))}
/>
);
};

export const TafSourceOptions: FC = () => {
const { tafSource, setTafSource } = useContext(SettingsContext);

const sources: Record<TafSource, string> = {
aviationweather: "AviationWeather",
faa: "FAA",
} as const;

return (
<SourceOptions
title="TAF Source"
backProps={{ label: "ACARS", path: "/settings/acars" }}
items={(Object.keys(sources) as TafSource[]).map((source, i) => (
<SelectableItem label={sources[source]} selected={tafSource === source} onClick={() => setTafSource(source)} key={i} />
))}
/>
);
};

export const AtisSourceOptions: FC = () => {
const { atisSource, setAtisSource } = useContext(SettingsContext);

const sources: Record<AtisSource, string> = {
vatsim: "Vatsim",
ivao: "IVAO",
pilotedge: "PilotEdge",
faa: "FAA",
} as const;

return (
<SourceOptions
title="ATIS Source"
backProps={{ label: "ACARS", path: "/settings/acars" }}
items={(Object.keys(sources) as AtisSource[]).map((source, i) => (
<SelectableItem label={sources[source]} selected={atisSource === source} onClick={() => setAtisSource(source)} key={i} />
))}
/>
);
};

const SourceOptions: FC<{ title: string; backProps: BackButtonProps | undefined; items: ReactNode[] }> = ({ title, backProps, items }) => (
<ContentPageContainer title={title} backProps={backProps}>
<ItemGroup>{items}</ItemGroup>
</ContentPageContainer>
);
46 changes: 46 additions & 0 deletions src/instruments/src/EFB/apps/Settings/Accounts.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { FC, useContext } from "react";

import { ContentPageContainer } from "./components/ContentPageContainer";
import { ItemGroup } from "../../components/ItemGroup";
import { NavigationItem } from "./components/NavigationItem";
import { ListItem } from "../../components/ListItem";
import { Input } from "../../components/Input";
import { useSetting } from "../../hooks/useSettings";
import { SimbriefClient } from "@microsoft/msfs-sdk";
import { ModalContext } from "../..";
import { InfoModal } from "../../components/InfoModal";

export const Accounts: FC = () => {
const [simbriefUsername, setSimbriefUsername] = useSetting("boeingMsfsSimbriefUsername");
const [, setSimbriefId] = useSetting("boeingMsfsSimbriefUserID");
const { setModal } = useContext(ModalContext);

const handleSimbriefInput = async (input: string) => {
try {
const userId = await SimbriefClient.getSimbriefUserIDFromUsername(input);
setSimbriefUsername(input);
setSimbriefId(userId);
} catch (_) {
setModal(<InfoModal title="Error" description={`Invalid SimBrief username "${input}"`} />);
}
};

return (
<ContentPageContainer title="Accounts">
<ItemGroup>
<ListItem>
<div className="side">SimBrief Username</div>
<Input
placeholder={simbriefUsername ? simbriefUsername.toString().toLowerCase() : "..."}
centerPlaceholder={false}
placeholderAlign="right"
style={{ borderBottom: "none", textAlign: "right", margin: "0 15px", fontSize: "26px" }}
onFocusOut={handleSimbriefInput}
clearOnFocusOut
/>
</ListItem>
<NavigationItem name="Navigraph" path="/fzpro" />
</ItemGroup>
</ContentPageContainer>
);
};
9 changes: 8 additions & 1 deletion src/instruments/src/EFB/apps/Settings/Categories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const Categories: FC = () => {
return (
<StyledCategories>
<SettingsHeader>Settings</SettingsHeader>
<SignIn />
<SignIn selected={selectedCategory === "accounts"} onClick={() => selectCategory("accounts")} />
<CategoryGroup>
<Category name="General" icon={general} selected={selectedCategory === "general"} selectCategory={() => selectCategory("general")} />
<Category
Expand All @@ -52,6 +52,12 @@ export const Categories: FC = () => {
selected={selectedCategory === "aircraft"}
selectCategory={() => selectCategory("aircraft")}
/>
<Category
name="ACARS"
icon={acars}
selected={selectedCategory === "acars"}
selectCategory={() => selectCategory("acars")}
/>
</CategoryGroup>
</StyledCategories>
);
Expand All @@ -69,6 +75,7 @@ const StyledCategories = styled.div`
margin-top: 100px;
display: flex;
flex-direction: column;
flex-shrink: 0;
`;

const CategoryGroup = styled.div`
Expand Down
57 changes: 57 additions & 0 deletions src/instruments/src/EFB/apps/Settings/SettingsContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { FC, ReactNode, useState } from "react";
import { usePersistentProperty, usePersistentPropertyWithDefault } from "react-msfs";
import { AtisSource, MetarSource, TafSource } from "../../lib/weather";

type SettingsContextProps = {
metarSource: MetarSource;
setMetarSource: (source: MetarSource) => void;
tafSource: TafSource;
setTafSource: (source: TafSource) => void;
atisSource: AtisSource;
setAtisSource: (source: AtisSource) => void;
};

const defaults: Pick<SettingsContextProps, "metarSource" | "tafSource" | "atisSource"> = {
metarSource: "msfs",
tafSource: "aviationweather",
atisSource: "vatsim",
};

export const SettingsContext = React.createContext<SettingsContextProps>({
metarSource: defaults.metarSource,
setMetarSource: () => {},
tafSource: defaults.tafSource,
setTafSource: () => {},
atisSource: defaults.atisSource,
setAtisSource: () => {},
});

export const SettingsContextProvider: FC<{ children: ReactNode | ReactNode[] }> = ({ children }) => {
const [metarSource, setMetarSource] = usePersistentPropertyWithDefault("SALTY_METAR_SOURCE", defaults.metarSource) as [
MetarSource,
(s: MetarSource) => void
];
const [tafSource, setTafSource] = usePersistentPropertyWithDefault("SALTY_TAF_SOURCE", defaults.tafSource) as [
TafSource,
(s: TafSource) => void];

const [atisSource, setAtisSource] = usePersistentPropertyWithDefault("SALTY_ATIS_SOURCE", defaults.atisSource) as [
AtisSource,
(s: AtisSource) => void
];

return (
<SettingsContext.Provider
value={{
metarSource,
setMetarSource,
tafSource,
setTafSource,
atisSource,
setAtisSource,
}}
>
{children}
</SettingsContext.Provider>
);
};
Loading

0 comments on commit fe3adf9

Please sign in to comment.