From acc5b1345e6875181d9c86e15473151547602e94 Mon Sep 17 00:00:00 2001 From: Neil Enns Date: Wed, 21 Aug 2024 09:46:53 -0700 Subject: [PATCH 1/2] Make long press trigger a reset Fixes #250 --- src/actions/atisLetter.ts | 19 ++++++-- src/actions/stationStatus.ts | 22 +++++++--- src/actions/trackAudioStatus.ts | 17 ++++++-- src/managers/action.ts | 77 +++++++++++++++++++++++++-------- src/utils/constants.ts | 5 +++ 5 files changed, 109 insertions(+), 31 deletions(-) create mode 100644 src/utils/constants.ts diff --git a/src/actions/atisLetter.ts b/src/actions/atisLetter.ts index e621fb3..14be960 100644 --- a/src/actions/atisLetter.ts +++ b/src/actions/atisLetter.ts @@ -1,18 +1,21 @@ import { action, DidReceiveSettingsEvent, - KeyDownEvent, + KeyUpEvent, SingletonAction, WillAppearEvent, WillDisappearEvent, } from "@elgato/streamdeck"; import actionManager from "@managers/action"; +import { LONG_PRESS_THRESHOLD } from "@utils/constants"; @action({ UUID: "com.neil-enns.trackaudio.atisletter" }) /** * Represents the status of a TrackAudio station */ export class AtisLetter extends SingletonAction { + private _keyDownStart = 0; + // When the action is added to a profile it gets saved in the ActionManager // instance for use elsewhere in the code. The default title is also set // to something useful. @@ -35,8 +38,18 @@ export class AtisLetter extends SingletonAction { actionManager.updateAtisLetter(ev.action, ev.payload.settings); } - onKeyDown(ev: KeyDownEvent): Promise | void { - actionManager.atisLetterKeyDown(ev.action); + onKeyDown(): Promise | void { + this._keyDownStart = Date.now(); + } + + onKeyUp(ev: KeyUpEvent): Promise | void { + const pressLength = Date.now() - this._keyDownStart; + + if (pressLength > LONG_PRESS_THRESHOLD) { + actionManager.atisLetterLongPress(ev.action.id); + } else { + actionManager.atisLetterShortPress(ev.action.id); + } } } diff --git a/src/actions/stationStatus.ts b/src/actions/stationStatus.ts index 90603cf..f30205a 100644 --- a/src/actions/stationStatus.ts +++ b/src/actions/stationStatus.ts @@ -2,18 +2,21 @@ import { ListenTo } from "@controllers/stationStatus"; import { action, DidReceiveSettingsEvent, - KeyDownEvent, + KeyUpEvent, SingletonAction, WillAppearEvent, WillDisappearEvent, } from "@elgato/streamdeck"; import actionManager from "@managers/action"; +import { LONG_PRESS_THRESHOLD } from "@utils/constants"; @action({ UUID: "com.neil-enns.trackaudio.stationstatus" }) /** * Represents the status of a TrackAudio station */ export class StationStatus extends SingletonAction { + private _keyDownStart = 0; + // When the action is added to a profile it gets saved in the ActionManager // instance for use elsewhere in the code. The default title is also set // to something useful. @@ -36,11 +39,18 @@ export class StationStatus extends SingletonAction { actionManager.updateStation(ev.action, ev.payload.settings); } - // When the key is pressed send the request to toggle the current action to the ActionManager. - // That will take care of figuing out the frequency and listenTo value and sending - // the appropriate message to TrackAudio via a websocket. - onKeyDown(ev: KeyDownEvent): void | Promise { - actionManager.toggleFrequency(ev.action.id); + onKeyDown(): void | Promise { + this._keyDownStart = Date.now(); + } + + onKeyUp(ev: KeyUpEvent): Promise | void { + const pressLength = Date.now() - this._keyDownStart; + + if (pressLength > LONG_PRESS_THRESHOLD) { + actionManager.stationStatusLongPress(ev.action.id); + } else { + actionManager.stationStatusShortPress(ev.action.id); + } } } diff --git a/src/actions/trackAudioStatus.ts b/src/actions/trackAudioStatus.ts index cf432b2..72ebd0a 100644 --- a/src/actions/trackAudioStatus.ts +++ b/src/actions/trackAudioStatus.ts @@ -1,18 +1,21 @@ import { action, DidReceiveSettingsEvent, - KeyDownEvent, + KeyUpEvent, SingletonAction, WillAppearEvent, WillDisappearEvent, } from "@elgato/streamdeck"; import actionManager from "@managers/action"; +import { LONG_PRESS_THRESHOLD } from "@utils/constants"; @action({ UUID: "com.neil-enns.trackaudio.trackaudiostatus" }) /** * Represents the status of the websocket connection to TrackAudio */ export class TrackAudioStatus extends SingletonAction { + private _keyDownStart = 0; + // When the action is added to a profile it gets saved in the ActionManager // instance for use elsewhere in the code. onWillAppear( @@ -34,8 +37,16 @@ export class TrackAudioStatus extends SingletonAction actionManager.updateTrackAudioStatus(ev.action, ev.payload.settings); } - onKeyDown(ev: KeyDownEvent): Promise | void { - actionManager.trackAudioStatusKeyDown(ev.action); + onKeyDown(): Promise | void { + this._keyDownStart = Date.now(); + } + + onKeyUp(ev: KeyUpEvent): Promise | void { + const pressLength = Date.now() - this._keyDownStart; + + if (pressLength > LONG_PRESS_THRESHOLD) { + actionManager.trackAudioStatusLongPress(ev.action); + } } } diff --git a/src/managers/action.ts b/src/managers/action.ts index 2448d41..e9b83fd 100644 --- a/src/managers/action.ts +++ b/src/managers/action.ts @@ -133,12 +133,37 @@ class ActionManager extends EventEmitter { this.emit("actionAdded", controller); } + /** + * Called when a station status action has a long press. Resets the + * station status and refreshses its state. + * @param actionId The ID of the action that had the long press + */ + public stationStatusLongPress(actionId: string) { + const savedAction = this.getStationStatusControllers().find( + (entry) => entry.action.id === actionId + ); + + if (!savedAction) { + return; + } + + savedAction.reset(); + trackAudioManager.refreshStationState(savedAction.callsign); + + savedAction.action.showOk().catch((error: unknown) => { + handleAsyncException( + "Unable to show OK on station status button:", + error + ); + }); + } + /** * Called when a TrackAudio status action keydown event is triggered. * Forces a refresh of the TrackAudio status. * @param action The action */ - public trackAudioStatusKeyDown(action: Action): void { + public trackAudioStatusLongPress(action: Action) { this.resetAll(); trackAudioManager.refreshVoiceConnectedState(); // This also causes a refresh of the station states @@ -151,30 +176,41 @@ class ActionManager extends EventEmitter { } /** - * Called when an ATIS letter action keydown event is triggered. If the - * action is in the isUpdated state then it clears the state. If the - * station is not in the isUpdated state then forces a VATSIM data refresh. - * @param action The action + * Called when an ATIS letter action has a short press. Clears the state. + * @param actionId The ID of the action that had the short press */ - public atisLetterKeyDown(action: Action): void { + public atisLetterShortPress(actionId: string) { const savedAction = this.getAtisLetterControllers().find( - (entry) => entry.action.id === action.id + (entry) => entry.action.id === actionId ); if (!savedAction) { return; } - if (savedAction.isUpdated) { - savedAction.isUpdated = false; - } else { - savedAction.action.showOk().catch((error: unknown) => { - handleAsyncException("Unable to show OK on ATIS button:", error); - }); - vatsimManager.refresh(); - } + savedAction.isUpdated = false; } + /** + * Called when an ATIS letter action has a long press. Refreshses the ATIS. + * @param actionId The ID of the action that had the long press + */ + public atisLetterLongPress(actionId: string) { + const savedAction = this.getAtisLetterControllers().find( + (entry) => entry.action.id === actionId + ); + + if (!savedAction) { + return; + } + + savedAction.reset(); + vatsimManager.refresh(); + + savedAction.action.showOk().catch((error: unknown) => { + handleAsyncException("Unable to show OK on ATIS button:", error); + }); + } /** * Resets the ATIS letter on all ATIS letter actions to undefined. */ @@ -588,11 +624,14 @@ class ActionManager extends EventEmitter { } /** - * Toggles the tx, rx, xc, or spkr state of a frequency bound to a StreamDeck action. - * @param id The action id to toggle the state of + * Handles a short press of a station status action. Toggles the + * the tx, rx, xc, or spkr state of a frequency bound to a StreamDeck action. + * @param actionId The action id to toggle the state of */ - public toggleFrequency(id: string): void { - const foundAction = this.actions.find((entry) => entry.action.id === id); + public stationStatusShortPress(actionId: string): void { + const foundAction = this.actions.find( + (entry) => entry.action.id === actionId + ); if (!foundAction || !isStationStatusController(foundAction)) { return; diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..99f7d52 --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,5 @@ +/** + * The length of time in ms that has to pass for an action press to + * count as a long press. + */ +export const LONG_PRESS_THRESHOLD = 500; From 47a1a12cf3fe0bded988d3dd9a66af1c44e00337 Mon Sep 17 00:00:00 2001 From: Neil Enns Date: Wed, 21 Aug 2024 09:56:11 -0700 Subject: [PATCH 2/2] Add long press to hotline, code cleanup --- README.md | 8 +-- src/actions/atisLetter.ts | 4 +- src/actions/hotline.ts | 20 ++++++-- src/actions/stationStatus.ts | 4 +- src/managers/action.ts | 97 ++++++++++++++++++++++-------------- 5 files changed, 85 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 1aea8fc..3b43c7f 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ After installation the plugin actions are available under the TrackAudio categor The station status action displays the current status of a single station's button in TrackAudio, including whether communication is currently active. Pressing the action will toggle the equivalent button in TrackAudio, convenient for listening to other frequencies while controlling with the ability to quickly turn off listening -to those frequencies when things get busy. +to those frequencies when things get busy. A long press of the action will refresh the action's state. For example, if you are controlling `LMT_TWR` and have TrackAudio set up like this: @@ -79,6 +79,7 @@ be added with `XCA` enabled and the hotline station should be added with `RX` en hotline action with the primary and hotline station callsigns. Once configured, pressing the action will toggle `TX` active between your primary and hotline frequencies. +A long press of the action will refresh the action's state. ### Hotline action settings @@ -99,7 +100,8 @@ Once configured, pressing the action will toggle `TX` active between your primar ## Configuring a TrackAudio status action The TrackAudio status action shows the status of the connection between StreamDeck and TrackAudio, and whether -the voice connection in TrackAudio is up. Pressing the action will force a state refresh. +the voice connection in TrackAudio is up. A long press of the action will force a refresh of all the StreamDeck +TrackAudio actions. ### TrackAudio status action settings @@ -118,7 +120,7 @@ the voice connection in TrackAudio is up. Pressing the action will force a state The ATIS letter action shows the current AITS letter for a station, refreshed automatically every minute. When the ATIS letter updates the action will show an orange background until the action is pressed to reset the -state. Pressing the action when it is not in the updated state will force a refresh of the ATIS information. +state. A long press of the action will force a refresh of the ATIS information. See the [SVG template documentation](https://github.com/neilenns/streamdeck-trackaudio/wiki/SVG-templates) for an example template that shows the title small and station letter big. diff --git a/src/actions/atisLetter.ts b/src/actions/atisLetter.ts index 14be960..6f90bfa 100644 --- a/src/actions/atisLetter.ts +++ b/src/actions/atisLetter.ts @@ -46,9 +46,9 @@ export class AtisLetter extends SingletonAction { const pressLength = Date.now() - this._keyDownStart; if (pressLength > LONG_PRESS_THRESHOLD) { - actionManager.atisLetterLongPress(ev.action.id); + actionManager.atisLetterLongPress(ev.action); } else { - actionManager.atisLetterShortPress(ev.action.id); + actionManager.atisLetterShortPress(ev.action); } } } diff --git a/src/actions/hotline.ts b/src/actions/hotline.ts index f8679a5..d0bae0d 100644 --- a/src/actions/hotline.ts +++ b/src/actions/hotline.ts @@ -1,18 +1,21 @@ import { action, DidReceiveSettingsEvent, - KeyDownEvent, + KeyUpEvent, SingletonAction, WillAppearEvent, WillDisappearEvent, } from "@elgato/streamdeck"; import actionManager from "@managers/action"; +import { LONG_PRESS_THRESHOLD } from "@utils/constants"; @action({ UUID: "com.neil-enns.trackaudio.hotline" }) /** * Represents the status of a TrackAudio station */ export class Hotline extends SingletonAction { + private _keyDownStart = 0; + // When the action is added to a profile it gets saved in the ActionManager // instance for use elsewhere in the code. The default title is also set // to something useful. @@ -35,9 +38,18 @@ export class Hotline extends SingletonAction { actionManager.updateHotline(ev.action, ev.payload.settings); } - // When the key is pressed send the request to toggle the hotline. - onKeyDown(ev: KeyDownEvent): void | Promise { - actionManager.toggleHotline(ev.action.id); + onKeyDown(): void | Promise { + this._keyDownStart = Date.now(); + } + + onKeyUp(ev: KeyUpEvent): Promise | void { + const pressLength = Date.now() - this._keyDownStart; + + if (pressLength > LONG_PRESS_THRESHOLD) { + actionManager.hotlineLongPress(ev.action); + } else { + actionManager.hotlineShortPress(ev.action); + } } } diff --git a/src/actions/stationStatus.ts b/src/actions/stationStatus.ts index f30205a..46098c5 100644 --- a/src/actions/stationStatus.ts +++ b/src/actions/stationStatus.ts @@ -47,9 +47,9 @@ export class StationStatus extends SingletonAction { const pressLength = Date.now() - this._keyDownStart; if (pressLength > LONG_PRESS_THRESHOLD) { - actionManager.stationStatusLongPress(ev.action.id); + actionManager.stationStatusLongPress(ev.action); } else { - actionManager.stationStatusShortPress(ev.action.id); + actionManager.stationStatusShortPress(ev.action); } } } diff --git a/src/managers/action.ts b/src/managers/action.ts index e9b83fd..aa888fe 100644 --- a/src/managers/action.ts +++ b/src/managers/action.ts @@ -133,31 +133,6 @@ class ActionManager extends EventEmitter { this.emit("actionAdded", controller); } - /** - * Called when a station status action has a long press. Resets the - * station status and refreshses its state. - * @param actionId The ID of the action that had the long press - */ - public stationStatusLongPress(actionId: string) { - const savedAction = this.getStationStatusControllers().find( - (entry) => entry.action.id === actionId - ); - - if (!savedAction) { - return; - } - - savedAction.reset(); - trackAudioManager.refreshStationState(savedAction.callsign); - - savedAction.action.showOk().catch((error: unknown) => { - handleAsyncException( - "Unable to show OK on station status button:", - error - ); - }); - } - /** * Called when a TrackAudio status action keydown event is triggered. * Forces a refresh of the TrackAudio status. @@ -179,9 +154,9 @@ class ActionManager extends EventEmitter { * Called when an ATIS letter action has a short press. Clears the state. * @param actionId The ID of the action that had the short press */ - public atisLetterShortPress(actionId: string) { + public atisLetterShortPress(action: Action) { const savedAction = this.getAtisLetterControllers().find( - (entry) => entry.action.id === actionId + (entry) => entry.action.id === action.id ); if (!savedAction) { @@ -195,9 +170,9 @@ class ActionManager extends EventEmitter { * Called when an ATIS letter action has a long press. Refreshses the ATIS. * @param actionId The ID of the action that had the long press */ - public atisLetterLongPress(actionId: string) { + public atisLetterLongPress(action: Action) { const savedAction = this.getAtisLetterControllers().find( - (entry) => entry.action.id === actionId + (entry) => entry.action.id === action.id ); if (!savedAction) { @@ -207,7 +182,7 @@ class ActionManager extends EventEmitter { savedAction.reset(); vatsimManager.refresh(); - savedAction.action.showOk().catch((error: unknown) => { + action.showOk().catch((error: unknown) => { handleAsyncException("Unable to show OK on ATIS button:", error); }); } @@ -578,13 +553,15 @@ class ActionManager extends EventEmitter { } /** - * Toggles the tx on both the primary and hotline frequency. - * @param id The action id to toggle the state of + * Handles the short press of a hotline action. Toggles the tx on both the primary and hotline frequency. + * @param actionId The action id to toggle the state of */ - public toggleHotline(id: string): void { - const foundAction = this.actions.find((entry) => entry.action.id === id); + public hotlineShortPress(action: Action): void { + const foundAction = this.getHotlineControllers().find( + (entry) => entry.action.id === action.id + ); - if (!foundAction || !isHotlineController(foundAction)) { + if (!foundAction) { return; } @@ -623,14 +600,35 @@ class ActionManager extends EventEmitter { }); } + public hotlineLongPress(action: Action) { + const foundAction = this.getHotlineControllers().find( + (entry) => entry.action.id === action.id + ); + + if (!foundAction) { + return; + } + + foundAction.reset(); + trackAudioManager.refreshStationState(foundAction.primaryCallsign); + trackAudioManager.refreshStationState(foundAction.hotlineCallsign); + + action.showOk().catch((error: unknown) => { + handleAsyncException( + "Unable to show OK status on TrackAudio action: ", + error + ); + }); + } + /** * Handles a short press of a station status action. Toggles the * the tx, rx, xc, or spkr state of a frequency bound to a StreamDeck action. * @param actionId The action id to toggle the state of */ - public stationStatusShortPress(actionId: string): void { + public stationStatusShortPress(action: Action): void { const foundAction = this.actions.find( - (entry) => entry.action.id === actionId + (entry) => entry.action.id === action.id ); if (!foundAction || !isStationStatusController(foundAction)) { @@ -655,6 +653,31 @@ class ActionManager extends EventEmitter { }); } + /** + * Called when a station status action has a long press. Resets the + * station status and refreshses its state. + * @param actionId The ID of the action that had the long press + */ + public stationStatusLongPress(action: Action) { + const savedAction = this.getStationStatusControllers().find( + (entry) => entry.action.id === action.id + ); + + if (!savedAction) { + return; + } + + savedAction.reset(); + trackAudioManager.refreshStationState(savedAction.callsign); + + action.showOk().catch((error: unknown) => { + handleAsyncException( + "Unable to show OK on station status button:", + error + ); + }); + } + /** * Updates the connection state on all TrackAudio status buttons to the current connected states * and updates the background image to the appropriate state image.