Skip to content

Commit c9c1bf7

Browse files
committed
feat: files app
1 parent 1a54adf commit c9c1bf7

File tree

14 files changed

+546
-76
lines changed

14 files changed

+546
-76
lines changed
99.4 KB
Binary file not shown.

src/instruments/src/EFB/apps/FZPro/components/ChartControls.tsx

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/instruments/src/EFB/apps/FZPro/enroute-charts/Controls.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import styled from "styled-components";
22
import React, { FC, useContext } from "react";
33
import { useMap } from "react-leaflet";
4-
import { ChartControlContainer, ChartControlItem } from "../components/ChartControls";
4+
import { ChartControlContainer, ChartControlItem } from "../../../components/charts/ChartControls";
55
import { IoIosMoon, IoIosSunny } from "react-icons/io";
66
import { AiOutlineZoomIn, AiOutlineZoomOut } from "react-icons/ai";
77
import { FZProContext } from "../AppContext";

src/instruments/src/EFB/apps/FZPro/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import { Chart, ChartCategory } from "navigraph/charts";
55
import { useNavigraphAuth } from "../../hooks/useNavigraphAuth";
66
import styled from "styled-components";
77

8-
import { AirportChartViewer } from "./AirportChartViewer";
8+
import { AirportChartViewer } from "../../components/charts/AirportChartViewer";
99
import { SignInPrompt } from "./SignInPrompt";
1010
import { TopBar } from "./TopBar";
11-
import { DocumentLoading } from "./DocumentLoading";
11+
import { DocumentLoading } from "../../components/charts/DocumentLoading";
1212
import { ChartSelector } from "./ChartSelector";
1313
import { Sidebar } from "./Sidebar";
1414
import { AirportSelector } from "./AirportSelector";
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React, { FC, ReactNode, useState } from "react";
2+
import { View, Files } from ".";
3+
4+
type FilesContextProps = {
5+
view?: View;
6+
setView: (view?: View) => void;
7+
files?: Files;
8+
setFiles: (files?: Files) => void;
9+
ofp?: string;
10+
setOfp: (ofp?: string) => void;
11+
ofpSelected: boolean;
12+
setOfpSelected: (selected: boolean) => void;
13+
};
14+
15+
export const FilesContext = React.createContext<FilesContextProps>({
16+
view: undefined,
17+
setView: () => {},
18+
files: undefined,
19+
setFiles: () => {},
20+
ofp: undefined,
21+
setOfp: () => {},
22+
ofpSelected: false,
23+
setOfpSelected: () => {},
24+
});
25+
26+
export const FilesContextProvider: FC<{ children: ReactNode | ReactNode[] }> = ({ children }) => {
27+
const [files, setFiles] = useState<Files>();
28+
const [view, setView] = useState<View>();
29+
const [ofp, setOfp] = useState<string>();
30+
const [ofpSelected, setOfpSelected] = useState<boolean>(false);
31+
32+
return (
33+
<FilesContext.Provider value={{ files, setFiles, view, setView, ofp, setOfp, ofpSelected, setOfpSelected }}>
34+
{children}
35+
</FilesContext.Provider>
36+
);
37+
};
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import React, { FC } from "react";
2+
import styled from "styled-components";
3+
import { BsFileEarmark, BsFiletypeJpg, BsFiletypePdf, BsFiletypePng } from "react-icons/bs";
4+
import { IoIosRefresh } from "react-icons/io";
5+
import { AiOutlineCloudDownload } from "react-icons/ai";
6+
import ScrollContainer from "react-indiana-drag-scroll";
7+
import { Files } from ".";
8+
9+
type SidebarProps = {
10+
files?: Files;
11+
selected?: string;
12+
onSelect: (name: string) => void;
13+
ofpSelected: boolean;
14+
onSelectOfp: () => void;
15+
};
16+
17+
export const Sidebar: FC<SidebarProps> = ({ files, selected, onSelect, ofpSelected, onSelectOfp }) => {
18+
const getFileTypeIcon = (name: string, props: { fill: string; size: number }) => {
19+
if (name.endsWith(".pdf")) {
20+
return <BsFiletypePdf {...props} />;
21+
} else if (name.endsWith(".png")) {
22+
return <BsFiletypePng {...props} />;
23+
} else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
24+
return <BsFiletypeJpg {...props} />;
25+
}
26+
return <BsFileEarmark {...props} />;
27+
};
28+
29+
return (
30+
<StyledSidebar>
31+
<Title>Files</Title>
32+
<Category>
33+
<div>SimBrief</div>
34+
<AiOutlineCloudDownload size={33} fill="#4FA0FC" />
35+
</Category>
36+
<Entry selected={ofpSelected} onClick={onSelectOfp}>
37+
<BsFileEarmark fill={ofpSelected ? "white" : "#4FA0FC"} size={32} />
38+
<div>OFP</div>
39+
</Entry>
40+
<ScrollContainer style={{ width: "100%" }}>
41+
{files?.pdfs.length !== 0 && (
42+
<>
43+
<Category>
44+
<div>Local Documents</div>
45+
<IoIosRefresh size={32} fill="#4FA0FC" />
46+
</Category>
47+
{files?.pdfs?.map((file, i) => (
48+
<Entry selected={selected === file} key={i} onClick={() => onSelect(file)}>
49+
{getFileTypeIcon(file, { fill: selected === file ? "white" : "#4FA0FC", size: 32 })}
50+
<div>{file}</div>
51+
</Entry>
52+
))}
53+
</>
54+
)}
55+
{files?.images.length !== 0 && (
56+
<>
57+
<Category>
58+
<div>Local Images</div>
59+
<IoIosRefresh size={32} fill="#4FA0FC" />
60+
</Category>
61+
{files?.images.map((image, i) => (
62+
<Entry selected={selected === image} key={i} onClick={() => onSelect(image)}>
63+
{getFileTypeIcon(image, { fill: selected === image ? "white" : "#4FA0FC", size: 32 })}
64+
<div>{image}</div>
65+
</Entry>
66+
))}
67+
</>
68+
)}
69+
</ScrollContainer>
70+
</StyledSidebar>
71+
);
72+
};
73+
74+
const Entry = styled.div<{ selected: boolean }>`
75+
display: flex;
76+
align-items: center;
77+
padding: 12px 8px;
78+
width: 100%;
79+
flex-wrap: nowrap;
80+
overflow: hidden;
81+
background: ${({ selected }) => (selected ? "#4fa0fc" : "transparent")};
82+
color: ${({ selected }) => (selected ? "white" : "black")};
83+
border-radius: 15px;
84+
85+
* {
86+
margin: 0 4px;
87+
flex-shrink: 0;
88+
}
89+
`;
90+
91+
const Category = styled.div`
92+
font-size: 26px;
93+
font-weight: 700;
94+
padding: 14px 10px;
95+
width: 100%;
96+
display: flex;
97+
justify-content: space-between;
98+
align-items: center;
99+
`;
100+
101+
const StyledSidebar = styled.div`
102+
height: 100%;
103+
width: 400px;
104+
display: flex;
105+
flex-direction: column;
106+
background: #f2f1f6;
107+
padding: 25px 17px;
108+
color: black;
109+
font-size: 24px;
110+
border-right: 1px solid lightgray;
111+
`;
112+
113+
const Title = styled.div`
114+
font-size: 50px;
115+
font-weight: 700;
116+
padding: 50px 0 15px 8px;
117+
`;
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import React, { FC, useContext, useEffect, useRef, useState } from "react";
2+
import styled from "styled-components";
3+
import { ModalContext } from "../..";
4+
import { InfoModal } from "../../components/InfoModal";
5+
import { SimBridge } from "../../lib/simbridge";
6+
import { AirportChartViewer } from "../../components/charts/AirportChartViewer";
7+
import { Sidebar } from "./Sidebar";
8+
import { DocumentLoading } from "../../components/charts/DocumentLoading";
9+
import ScrollContainer from "react-indiana-drag-scroll";
10+
import { useSetting } from "../../hooks/useSettings";
11+
import { FilesContext } from "./FilesContext";
12+
13+
export type View = {
14+
name: string;
15+
blob: Blob;
16+
pages?: number;
17+
currentPage?: number;
18+
};
19+
20+
export type Files = { pdfs: string[]; images: string[] };
21+
22+
export const Files: FC = () => {
23+
const [documentLoading, setDocumentLoading] = useState<boolean>(false);
24+
25+
const { setModal } = useContext(ModalContext);
26+
const { view, setView, files, setFiles, ofp, setOfp, ofpSelected, setOfpSelected } = useContext(FilesContext);
27+
28+
const contentSectionRef = useRef<HTMLDivElement>(null);
29+
30+
const [simbriefUsername] = useSetting("boeingMsfsSimbriefUsername");
31+
32+
const getFiles = async () => setFiles({ pdfs: await SimBridge.getPDFList(), images: await SimBridge.getImageList() });
33+
34+
const handleSelect = async (name: string) => {
35+
setOfpSelected(false);
36+
setView(undefined);
37+
setDocumentLoading(true);
38+
39+
try {
40+
setView(
41+
files?.images.includes(name)
42+
? { name, blob: await SimBridge.getImage(name) }
43+
: { name, blob: await SimBridge.getPDFPage(name, 1), pages: await SimBridge.getPDFPageNum(name), currentPage: 1 }
44+
);
45+
} catch (e: unknown) {
46+
e instanceof Error && setModal(<InfoModal title="Error" description={e.message} />);
47+
}
48+
49+
setDocumentLoading(false);
50+
};
51+
52+
const handlePageChange = async (page: number) => {
53+
try {
54+
view && setView({ ...view, blob: await SimBridge.getPDFPage(view.name, page), currentPage: page });
55+
} catch (e: unknown) {
56+
e instanceof Error && setModal(<InfoModal title="Error" description={e.message} />);
57+
}
58+
};
59+
60+
const handleSelectOfp = async () => {
61+
setDocumentLoading(true);
62+
setView(undefined);
63+
64+
if (!ofp) {
65+
try {
66+
const ofp = await fetch(`https://www.simbrief.com/api/xml.fetcher.php?username=${simbriefUsername}&json=1`);
67+
const json = await ofp.json();
68+
const html = json.text.plan_html;
69+
setOfp(html.replace(/^<div [^>]+>/, "").replace(/<\/div>$/, ""));
70+
} catch (_) {
71+
setModal(<InfoModal title="Error" description="Couldn't fetch OFP" />);
72+
}
73+
}
74+
75+
setOfpSelected(true);
76+
setDocumentLoading(false);
77+
};
78+
79+
useEffect(() => {
80+
!files && getFiles();
81+
}, []);
82+
83+
return (
84+
<Container>
85+
<Sidebar
86+
files={files}
87+
selected={view?.name}
88+
onSelect={handleSelect}
89+
ofpSelected={ofpSelected}
90+
onSelectOfp={handleSelectOfp}
91+
/>
92+
<ContentSection ref={contentSectionRef}>
93+
{!view && !ofpSelected && !documentLoading && <SelectAFile>Select a file</SelectAFile>}
94+
{view && contentSectionRef.current && (
95+
<AirportChartViewer
96+
chartImage={view.blob}
97+
canvasWidth={contentSectionRef.current.clientWidth}
98+
canvasHeight={contentSectionRef.current.clientHeight}
99+
currentPage={view.currentPage}
100+
pages={view.pages}
101+
setPage={handlePageChange}
102+
theme="os"
103+
/>
104+
)}
105+
{documentLoading && <DocumentLoading />}
106+
{ofpSelected && ofp && (
107+
<ScrollContainer style={{ width: "100%", height: "100%" }}>
108+
<Ofp dangerouslySetInnerHTML={{ __html: ofp }} />
109+
</ScrollContainer>
110+
)}
111+
</ContentSection>
112+
</Container>
113+
);
114+
};
115+
116+
const Ofp = styled.div`
117+
width: 100%;
118+
height: 100%;
119+
color: black;
120+
padding: 50px 60px 10px 60px;
121+
font-size: 24px;
122+
line-height: 24px;
123+
124+
* {
125+
font-family: "Inconsolata";
126+
}
127+
`;
128+
129+
const SelectAFile = styled.div`
130+
flex: 1;
131+
height: 100%;
132+
display: flex;
133+
justify-content: center;
134+
align-items: center;
135+
color: lightgray;
136+
font-size: 28px;
137+
`;
138+
139+
const Container = styled.div`
140+
width: 100vw;
141+
height: 100vh;
142+
display: flex;
143+
background: white;
144+
`;
145+
146+
const ContentSection = styled.div`
147+
flex: 1;
148+
position: relative;
149+
`;

src/instruments/src/EFB/apps/Home/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export const HomeScreen: FC = () => (
6666
icon={<BsGearWideConnected style={{ fill: "#2C3233", transform: "scale(3.5)" }} />}
6767
route="/settings/general"
6868
/>
69-
<App bg="white" icon={<IoFolder style={{ fill: "218FF3", transform: "scale(3.5)" }} />} />
69+
<App bg="white" icon={<IoFolder style={{ fill: "218FF3", transform: "scale(3.5)" }} />} route="/files" />
7070
</FavoriteAppsContainer>
7171
</Home>
7272
);

0 commit comments

Comments
 (0)