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

Fix flickering problem in NMEA injector page #2452

Merged
merged 2 commits into from Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 14 additions & 6 deletions core/frontend/src/components/nmea-injector/NMEAInjector.vue
Expand Up @@ -98,9 +98,9 @@
<script lang="ts">
import Vue from 'vue'

import { OneMoreTime } from '@/one-more-time'
import nmea_injector from '@/store/nmea-injector'
import { NMEASocket } from '@/types/nmea-injector'
import { callPeriodically, stopCallingPeriodically } from '@/utils/helper_functions'

import SpinningLogo from '../common/SpinningLogo.vue'
import NMEASocketCard from './NMEASocketCard.vue'
Expand All @@ -116,11 +116,13 @@ export default Vue.extend({
data() {
return {
show_creation_dialog: false,
updating_nmea_sockets_debounced: false,
fetch_nmea_sockets_task: new OneMoreTime({ delay: 5000, disposeWith: this }),
}
},
computed: {
updating_nmea_sockets(): boolean {
return nmea_injector.updating_nmea_sockets
return nmea_injector.updating_nmea_sockets && this.updating_nmea_sockets_debounced
},
available_nmea_sockets(): NMEASocket[] {
return nmea_injector.available_nmea_sockets
Expand All @@ -130,12 +132,18 @@ export default Vue.extend({
},
},
mounted() {
callPeriodically(nmea_injector.fetchAvailableNMEASockets, 5000)
},
beforeDestroy() {
stopCallingPeriodically(nmea_injector.fetchAvailableNMEASockets)
this.fetch_nmea_sockets_task.setAction(() => this.fetchAvailableNMEASockets())
},
methods: {
async fetchAvailableNMEASockets(): Promise<void> {
this.updating_nmea_sockets_debounced = false

await nmea_injector.fetchAvailableNMEASockets()

setTimeout(() => {
this.updating_nmea_sockets_debounced = true
}, 300)
},
openCreationDialog(): void {
this.show_creation_dialog = true
},
Expand Down
76 changes: 69 additions & 7 deletions core/frontend/src/one-more-time.ts
@@ -1,3 +1,8 @@
/**
* Represents a function that can be OneMoreTime valid action
*/
type OneMoreTimeAction = (() => Promise<void>) | (() => void) | undefined;

/**
* Used to configure the OneMoreTime instance
*/
Expand All @@ -21,36 +26,93 @@ export interface OneMoreTimeOptions {
* @type {boolean}
*/
autostart?: boolean

/**
* Callback function to be called in case of an error during action execution.
* Provides a way to handle errors.
* @param {unknown} error The error object caught during action execution.
*/
onError?: (error: unknown) => void

/**
* Reference to some object instance that can be used as a source to dispose the
* OneMoreTime instance.
*/
disposeWith?: unknown
}

/**
* After multiple years and attempts
* This is a RAII class that takes advantage of the new disposable feature
* to create a promise that repeats itself until it's disposed
*/
export default class OneMoreTime {
export class OneMoreTime {
private isDisposed = false

/**
* Constructs an instance of OneMoreTime, optionally starting the action immediately.
* @param {OneMoreTimeOptions} options Configuration options for the instance.
* @param {OneMoreTimeAction} action The action to be executed repeatedly.
*/
constructor(
private readonly action: () => Promise<void>,
private readonly options: OneMoreTimeOptions = {}
private readonly options: OneMoreTimeOptions = {},
private action?: OneMoreTimeAction,
) {
this.watchDisposeWith()
// One more time
if (options.autostart ?? true) {
// eslint-disable-next-line no-return-await
(async () => await this.start())()
this.softStart()
}

private watchDisposeWith(): void {
if (this.options.disposeWith) {
const ref = new WeakRef(this.options.disposeWith)

const id = setInterval(() => {
// Check if object does not exist anymore or if it was destroyed by vue
// eslint-disable-next-line
if (!ref.deref() || ref.deref()._isDestroyed) {
this.stop()
clearInterval(id)
}
}, 1000)
}
}

/**
* Starts the action if `autostart` is enabled and an action is defined.
* @returns {void}
*/
softStart(): void {
if (this.action && (this.options.autostart ?? true)) {
this.start()
}
}

/**
* Sets a new action to be executed and starts it if `autostart` is true.
* This allows dynamically changing the action during the lifecycle of the instance.
* @param {OneMoreTimeAction} action The new action to set and possibly start.
* @returns {void}
*/
setAction(action: OneMoreTimeAction): void {
this.action = action

this.softStart()
}

/**
* Begins or resumes the execution of the set action at intervals specified by `delay`.
*/
async start(): Promise<void> {
// Come on, alright
if (this.isDisposed) return

try {
// One more time, we're gonna celebrate
await this.action()
await this.action?.()
} catch (error) {
console.error('Error in self-calling promise:', error)
this.options.onError?.(error)
// Oh yeah, alright, don't stop the dancing
// eslint-disable-next-line no-promise-executor-return
await new Promise((resolve) => setTimeout(resolve, this.options.errorDelay))
Expand Down