Skip to content

Commit

Permalink
Merge pull request #5 from Navigraph/chore/ui-improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
SkySails authored May 14, 2024
2 parents 2d8906f + 759fd04 commit 7b6c460
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 37 deletions.
56 changes: 56 additions & 0 deletions examples/gauge/Components/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ComponentProps, DisplayComponent, FSComponent, Subscribable, UUID, VNode } from "@microsoft/msfs-sdk"

interface InputProps extends ComponentProps {
value?: string
class?: string | Subscribable<string>
textarea?: boolean
}

export class Input extends DisplayComponent<InputProps> {
private readonly inputId = UUID.GenerateUuid()
private readonly inputRef = FSComponent.createRef<HTMLInputElement>()

get value() {
return this.inputRef.instance.value
}

onAfterRender(node: VNode): void {
super.onAfterRender(node)

this.inputRef.instance.onfocus = this.onInputFocus
this.inputRef.instance.onblur = this.onInputBlur
}

private getInputProps() {
return { value: this.props.value, class: this.props.class }
}

/**
* Method to handle when input focus is set
* @param e The focus event.
*/
private onInputFocus = (e: FocusEvent): void => {
e.preventDefault()

Coherent.trigger("FOCUS_INPUT_FIELD", this.inputId, "", "", this.inputRef.instance.value, false)
Coherent.on("mousePressOutsideView", () => this.inputRef.instance.blur())
}

/**
* Method to handle on input blur
*/
private onInputBlur = (): void => {
Coherent.trigger("UNFOCUS_INPUT_FIELD", "")
Coherent.off("mousePressOutsideView")
}

render() {
if (this.props.textarea)
return (
<textarea style="width:350px;height:100px;" ref={this.inputRef} {...this.getInputProps()}>
{this.props.value}
</textarea>
)
return <input ref={this.inputRef} {...this.getInputProps()} />
}
}
37 changes: 32 additions & 5 deletions examples/gauge/Components/InterfaceSample.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
width: 100%;
height: 100%;
position: relative;
top: 100px;
}

.loading-container {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
font-size: x-large;
text-align: center;
}

.button {
Expand All @@ -19,16 +28,21 @@
}

.qr-code {
width: 300px;
height: 300px;
margin-top: 10px;
width: 250px;
height: 250px;
display: none;
background: white;
padding: 10px;
border-radius: 5px;
}

.horizontal {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
justify-content: space-evenly;
align-items: flex-start;
padding: 2rem;
}

.vertical {
Expand All @@ -49,3 +63,16 @@
justify-content: center;
align-items: center;
}

pre {
background: #272727;
border-radius: 6px;
padding: 10px;

min-width: 300px;
min-height: 200px;
}

h4 {
margin-bottom: 15px;
}
160 changes: 130 additions & 30 deletions examples/gauge/Components/InterfaceSample.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
import { ComponentProps, DisplayComponent, EventBus, FSComponent, VNode } from "@microsoft/msfs-sdk"
import { CancelToken } from "navigraph/auth"
import { packages } from "../Lib/navigraph"
import { AuthService } from "../Services/AuthService"
import "./InterfaceSample.css"
import {
ComponentProps,
DisplayComponent,
EventBus,
FSComponent,
MappedSubject,
Subject,
VNode,
} from "@microsoft/msfs-sdk"
import {
DownloadProgressPhase,
NavigraphEventType,
NavigraphNavigationDataInterface,
} from "@navigraph/msfs-navigation-data-interface"
import { NavigationDataStatus } from "@navigraph/msfs-navigation-data-interface/types/meta"
import { CancelToken } from "navigraph/auth"
import { packages } from "../Lib/navigraph"
import { AuthService } from "../Services/AuthService"
import { Dropdown } from "./Dropdown"
import { Input } from "./Input"
import "./InterfaceSample.css"

interface InterfaceSampleProps extends ComponentProps {
bus: EventBus
Expand All @@ -21,8 +31,15 @@ export class InterfaceSample extends DisplayComponent<InterfaceSampleProps> {
private readonly qrCodeRef = FSComponent.createRef<HTMLImageElement>()
private readonly dropdownRef = FSComponent.createRef<Dropdown>()
private readonly downloadButtonRef = FSComponent.createRef<HTMLButtonElement>()
private readonly executeButtonRef = FSComponent.createRef<HTMLButtonElement>()
private readonly inputRef = FSComponent.createRef<HTMLInputElement>()
private readonly icaoInputRef = FSComponent.createRef<Input>()
private readonly executeIcaoButtonRef = FSComponent.createRef<HTMLButtonElement>()
private readonly sqlInputRef = FSComponent.createRef<Input>()
private readonly executeSqlButtonRef = FSComponent.createRef<HTMLButtonElement>()
private readonly outputRef = FSComponent.createRef<HTMLPreElement>()
private readonly loadingRef = FSComponent.createRef<HTMLDivElement>()
private readonly authContainerRef = FSComponent.createRef<HTMLDivElement>()

private readonly navigationDataStatus = Subject.create<NavigationDataStatus | null>(null)

private cancelSource = CancelToken.source()

Expand Down Expand Up @@ -53,50 +70,123 @@ export class InterfaceSample extends DisplayComponent<InterfaceSampleProps> {
})
}

public render(): VNode {
public renderDatabaseStatus(): VNode | void {
return (
<div class="auth-container">
<div class="horizontal">
<div class="vertical">
<div ref={this.textRef}>Loading</div>
<div ref={this.loginButtonRef} class="button" />
<div ref={this.navigationDataTextRef} />
<img ref={this.qrCodeRef} class="qr-code" />
<>
<div
class={MappedSubject.create(([status]) => {
return status ? "vertical" : "hidden"
}, this.navigationDataStatus)}
>
<div>{this.navigationDataStatus.map(s => `Install method: ${s?.status}`)}</div>
<div>
{this.navigationDataStatus.map(
s => `Installed format: ${s?.installedFormat} revision ${s?.installedRevision}`,
)}
</div>
<div class="vertical">
<Dropdown ref={this.dropdownRef} />
<div ref={this.downloadButtonRef} class="button">
Download
<div>{this.navigationDataStatus.map(s => `Installed path: ${s?.installedPath}`)}</div>
<div>{this.navigationDataStatus.map(s => `Installed cycle: ${s?.installedCycle}`)}</div>
<div>{this.navigationDataStatus.map(s => `Latest cycle: ${s?.latestCycle}`)}</div>
<div>{this.navigationDataStatus.map(s => `Validity period: ${s?.validityPeriod}`)}</div>
</div>
<div class={this.navigationDataStatus.map(status => (status ? "hidden" : "visible"))}>Loading status...</div>
</>
)
}

public render(): VNode {
return (
<>
<div class="loading-container" ref={this.loadingRef}>
Waiting for navigation data interface to initialize... If building for the first time, this may take a few
minutes
</div>
<div class="auth-container" ref={this.authContainerRef} style={{ display: "none" }}>
<div class="horizontal">
<div class="vertical">
<h4>Step 1 - Sign in</h4>
<div ref={this.textRef}>Loading</div>
<div ref={this.loginButtonRef} class="button" />
<div ref={this.navigationDataTextRef} />
<img ref={this.qrCodeRef} class="qr-code" />
</div>
<div class="vertical">
<h4>Step 2 - Select Database</h4>
<Dropdown ref={this.dropdownRef} />
<div ref={this.downloadButtonRef} class="button">
Download
</div>
{this.renderDatabaseStatus()}
</div>
<input ref={this.inputRef} type="text" id="sql" name="sql" value="ESSA" class="text-field" />
<div ref={this.executeButtonRef} class="button">
Execute SQL
</div>

<h4 style="text-align: center;">Step 3 - Query the database</h4>
<div class="horizontal">
<div class="vertical">
<Input ref={this.icaoInputRef} value="TNCM" class="text-field" />
<div ref={this.executeIcaoButtonRef} class="button">
Fetch Airport
</div>
<div style="height:30px;"></div>
<Input
ref={this.sqlInputRef}
textarea
value="SELECT airport_name FROM tbl_airports WHERE airport_identifier = 'TNCM'"
class="text-field"
/>
<div ref={this.executeSqlButtonRef} class="button">
Execute SQL
</div>
</div>
<pre ref={this.outputRef} id="output">
The output of the query will show up here
</pre>
</div>
</div>
</div>
</>
)
}

public onBeforeRender(): void {
super.onBeforeRender()
}

public onAfterRender(node: VNode): void {
super.onAfterRender(node)

// Populate status when ready
this.navigationDataInterface.onReady(() => {
this.navigationDataInterface
.get_navigation_data_install_status()
.then(status => this.navigationDataStatus.set(status))
.catch(e => console.error(e))

// show the auth container
this.authContainerRef.instance.style.display = "block"
this.loadingRef.instance.style.display = "none"
})

this.loginButtonRef.instance.addEventListener("click", () => this.handleClick())
this.downloadButtonRef.instance.addEventListener("click", () => this.handleDownloadClick())

this.executeButtonRef.instance.addEventListener("click", () => {
this.executeIcaoButtonRef.instance.addEventListener("click", () => {
console.time("query")
this.navigationDataInterface
.get_airport(this.inputRef.instance.value)
.get_airport(this.icaoInputRef.instance.value)
.then(airport => {
console.info(airport)
console.timeEnd("query")
this.outputRef.instance.textContent = JSON.stringify(airport, null, 2)
})
.catch(e => console.error(e))
.finally(() => console.timeEnd("query"))
})

this.executeSqlButtonRef.instance.addEventListener("click", () => {
console.time("query")
this.navigationDataInterface
.execute_sql(this.sqlInputRef.instance.value, [])
.then(result => {
console.info(result)
this.outputRef.instance.textContent = JSON.stringify(result, null, 2)
})
.catch(e => console.error(e))
.finally(() => console.timeEnd("query"))
})

AuthService.user.sub(user => {
Expand All @@ -105,6 +195,7 @@ export class InterfaceSample extends DisplayComponent<InterfaceSampleProps> {
this.qrCodeRef.instance.style.display = "none"
this.loginButtonRef.instance.textContent = "Log out"
this.textRef.instance.textContent = `Welcome, ${user.preferred_username}`
this.displayMessage("")

this.handleLogin()
} else {
Expand All @@ -120,6 +211,7 @@ export class InterfaceSample extends DisplayComponent<InterfaceSampleProps> {
await AuthService.signOut()
} else {
this.cancelSource = CancelToken.source() // Reset any previous cancellations
this.displayMessage("Authenticating.. Scan code (or click it) to sign in")
await AuthService.signIn(p => {
if (p) {
this.qrCodeRef.instance.src = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${p.verification_uri_complete}`
Expand All @@ -131,6 +223,7 @@ export class InterfaceSample extends DisplayComponent<InterfaceSampleProps> {
}, this.cancelSource.token)
}
} catch (err) {
this.qrCodeRef.instance.style.display = "none"
if (err instanceof Error) this.displayError(err.message)
else this.displayError(`Unknown error: ${String(err)}`)
}
Expand Down Expand Up @@ -160,6 +253,13 @@ export class InterfaceSample extends DisplayComponent<InterfaceSampleProps> {

// Download navigation data to work dir
await this.navigationDataInterface.download_navigation_data(pkg.file.url)

// Update navigation data status
this.navigationDataInterface
.get_navigation_data_install_status()
.then(status => this.navigationDataStatus.set(status))
.catch(e => console.error(e))

this.displayMessage("Navigation data downloaded")
} catch (err) {
if (err instanceof Error) this.displayError(err.message)
Expand Down
15 changes: 15 additions & 0 deletions examples/gauge/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
interface CoherentEngine {
/**
* Asynchronously call a C++ handler and retrieve the result
* @param name name of the C++ handler to be called
* @param args any extra parameters to be passed to the C++ handler
* @return promise for the result of the C++ function
*/
call(name: "PLAY_INSTRUMENT_SOUND", soundName: string): Promise<void>
call(name: string, ...args: unknown[]): Promise<unknown>

on(name: "SetInputTextFromOS" | "mousePressOutsideView", cb: () => void): void
off(name: "SetInputTextFromOS" | "mousePressOutsideView", cb?: () => void): void

trigger(name: "FOCUS_INPUT_FIELD" | "UNFOCUS_INPUT_FIELD", ...args: unknown[])
}
4 changes: 2 additions & 2 deletions src/js/types/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ export enum InstallStatus {
export interface NavigationDataStatus {
status: InstallStatus
installedFormat: string | null
installedRegion: string | null
installedRevision: string | null
installedCycle: string | null
installedPath: string | null
validityPeriod: string | null
lastestCycle: string | null
latestCycle: string | null
}

0 comments on commit 7b6c460

Please sign in to comment.