Refactor timers into a simple tested object.
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
DirectiveParameters,
|
||||
} from 'lit/directive.js';
|
||||
import { stopEventFromActivatingCardWideActions } from './utils/action.js';
|
||||
import { Timer } from './utils/timer.js';
|
||||
|
||||
interface ActionHandler extends HTMLElement {
|
||||
holdTime: number;
|
||||
@@ -25,14 +26,13 @@ interface FrigateCardActionHandlerOptions extends ActionHandlerOptions {
|
||||
}
|
||||
|
||||
class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
public holdTime = 400;
|
||||
public holdTime = 0.4;
|
||||
|
||||
protected timer?: number;
|
||||
protected holdTimer = new Timer();
|
||||
protected doubleClickTimer = new Timer();
|
||||
|
||||
protected held = false;
|
||||
|
||||
private dblClickTimeout?: number;
|
||||
|
||||
public connectedCallback(): void {
|
||||
[
|
||||
'touchcancel',
|
||||
@@ -46,10 +46,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
document.addEventListener(
|
||||
ev,
|
||||
() => {
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = undefined;
|
||||
}
|
||||
this.holdTimer.stop();
|
||||
},
|
||||
{ passive: true },
|
||||
);
|
||||
@@ -79,9 +76,9 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
|
||||
const start = (): void => {
|
||||
this.held = false;
|
||||
this.timer = window.setTimeout(() => {
|
||||
this.holdTimer.start(this.holdTime, () => {
|
||||
this.held = true;
|
||||
}, this.holdTime);
|
||||
});
|
||||
|
||||
fireEvent(element, 'action', { action: 'start_tap' });
|
||||
};
|
||||
@@ -103,8 +100,7 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(this.timer);
|
||||
this.timer = undefined;
|
||||
this.holdTimer.stop();
|
||||
|
||||
fireEvent(element, 'action', { action: 'end_tap' });
|
||||
|
||||
@@ -113,15 +109,13 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
} else if (options?.hasDoubleClick) {
|
||||
if (
|
||||
(ev.type === 'click' && (ev as MouseEvent).detail < 2) ||
|
||||
!this.dblClickTimeout
|
||||
!this.doubleClickTimer.isRunning()
|
||||
) {
|
||||
this.dblClickTimeout = window.setTimeout(() => {
|
||||
this.dblClickTimeout = undefined;
|
||||
fireEvent(element, 'action', { action: 'tap' });
|
||||
}, 250);
|
||||
this.doubleClickTimer.start(0.25, () =>
|
||||
fireEvent(element, 'action', { action: 'tap' }),
|
||||
);
|
||||
} else {
|
||||
clearTimeout(this.dblClickTimeout);
|
||||
this.dblClickTimeout = undefined;
|
||||
this.doubleClickTimer.stop();
|
||||
fireEvent(element, 'action', { action: 'double_tap' });
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ReactiveController, ReactiveControllerHost } from 'lit';
|
||||
import { Timer } from './utils/timer';
|
||||
|
||||
export class CachedValueController<T> implements ReactiveController {
|
||||
protected _value?: T;
|
||||
@@ -7,7 +8,7 @@ export class CachedValueController<T> implements ReactiveController {
|
||||
protected _callback: () => T;
|
||||
protected _timerStartCallback?: () => void;
|
||||
protected _timerStopCallback?: () => void;
|
||||
protected _timerID?: number;
|
||||
protected _timer = new Timer();
|
||||
|
||||
constructor(
|
||||
host: ReactiveControllerHost,
|
||||
@@ -56,11 +57,10 @@ export class CachedValueController<T> implements ReactiveController {
|
||||
* Disable the timer.
|
||||
*/
|
||||
public stopTimer(): void {
|
||||
if (this._timerID !== undefined) {
|
||||
window.clearInterval(this._timerID);
|
||||
if (this._timer.isRunning()) {
|
||||
this._timer.stop();
|
||||
this._timerStopCallback?.();
|
||||
}
|
||||
this._timerID = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,15 +71,15 @@ export class CachedValueController<T> implements ReactiveController {
|
||||
|
||||
if (this._timerSeconds > 0) {
|
||||
this._timerStartCallback?.();
|
||||
this._timerID = window.setInterval(() => {
|
||||
this._timer.startRepeated(this._timerSeconds, () => {
|
||||
this.updateValue();
|
||||
this._host.requestUpdate();
|
||||
}, this._timerSeconds * 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public hasTimer(): boolean {
|
||||
return !!this._timerID;
|
||||
return this._timer.isRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+16
-45
@@ -99,6 +99,7 @@ import {
|
||||
hasSubstream,
|
||||
} from './utils/substream';
|
||||
import { View } from './view/view.js';
|
||||
import { Timer } from './utils/timer';
|
||||
|
||||
/** A note on media callbacks:
|
||||
*
|
||||
@@ -196,12 +197,9 @@ class FrigateCard extends LitElement {
|
||||
protected _refElements: Ref<FrigateCardElements> = createRef();
|
||||
protected _refViews: Ref<FrigateCardViews> = createRef();
|
||||
|
||||
// user interaction timer ("screensaver" functionality, return to default
|
||||
// view after user interaction).
|
||||
protected _interactionTimerID: number | null = null;
|
||||
|
||||
// Automated refreshes of the default view.
|
||||
protected _updateTimerID: number | null = null;
|
||||
protected _interactionTimer = new Timer();
|
||||
protected _updateTimer = new Timer();
|
||||
protected _untriggerTimer = new Timer();
|
||||
|
||||
// Information about loaded media items.
|
||||
protected _currentMediaLoadedInfo: MediaLoadedInfo | null = null;
|
||||
@@ -227,7 +225,6 @@ class FrigateCard extends LitElement {
|
||||
protected _boundFullscreenHandler = this._fullscreenHandler.bind(this);
|
||||
|
||||
protected _triggers: Map<string, Date> = new Map();
|
||||
protected _untriggerTimerID: number | null = null;
|
||||
|
||||
protected _mediaPlayers?: string[];
|
||||
|
||||
@@ -1006,7 +1003,7 @@ class FrigateCard extends LitElement {
|
||||
const needDarkMode =
|
||||
this._getConfig().view.dark_mode === 'on' ||
|
||||
(this._getConfig().view.dark_mode === 'auto' &&
|
||||
(!this._interactionTimerID || this._hass?.themes.darkMode));
|
||||
(!this._interactionTimer.isRunning() || this._hass?.themes.darkMode));
|
||||
|
||||
if (needDarkMode) {
|
||||
this.setAttribute('dark', '');
|
||||
@@ -1144,7 +1141,7 @@ class FrigateCard extends LitElement {
|
||||
* @returns
|
||||
*/
|
||||
protected _isTriggered(): boolean {
|
||||
return !!this._triggers.size || !!this._untriggerTimerID;
|
||||
return !!this._triggers.size || this._untriggerTimer.isRunning();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1153,7 +1150,7 @@ class FrigateCard extends LitElement {
|
||||
protected _untrigger(): void {
|
||||
const wasTriggered = this._isTriggered();
|
||||
this._triggers.clear();
|
||||
this._clearUntriggerTimer();
|
||||
this._untriggerTimer.stop();
|
||||
|
||||
if (wasTriggered) {
|
||||
this.requestUpdate();
|
||||
@@ -1164,9 +1161,7 @@ class FrigateCard extends LitElement {
|
||||
* Start the untrigger timer.
|
||||
*/
|
||||
protected _startUntriggerTimer(): void {
|
||||
this._clearUntriggerTimer();
|
||||
|
||||
this._untriggerTimerID = window.setTimeout(() => {
|
||||
this._untriggerTimer.start(this._getConfig().view.scan.untrigger_seconds, () => {
|
||||
this._untrigger();
|
||||
if (
|
||||
this._isAutomatedViewUpdateAllowed() &&
|
||||
@@ -1174,17 +1169,7 @@ class FrigateCard extends LitElement {
|
||||
) {
|
||||
this._changeView();
|
||||
}
|
||||
}, this._getConfig().view.scan.untrigger_seconds * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the user interaction ('screensaver') timer.
|
||||
*/
|
||||
protected _clearUntriggerTimer(): void {
|
||||
if (this._untriggerTimerID) {
|
||||
window.clearTimeout(this._untriggerTimerID);
|
||||
this._untriggerTimerID = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected _handleThrownError(error: unknown) {
|
||||
@@ -1811,34 +1796,23 @@ class FrigateCard extends LitElement {
|
||||
this._startInteractionTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the user interaction ('screensaver') timer.
|
||||
*/
|
||||
protected _clearInteractionTimer(): void {
|
||||
if (this._interactionTimerID) {
|
||||
window.clearTimeout(this._interactionTimerID);
|
||||
this._interactionTimerID = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the user interaction ('screensaver') timer to reset the view to
|
||||
* default `view.timeout_seconds` after user interaction.
|
||||
*/
|
||||
protected _startInteractionTimer(): void {
|
||||
this._clearInteractionTimer();
|
||||
this._interactionTimer.stop();
|
||||
|
||||
// Interactions reset the trigger state.
|
||||
this._untrigger();
|
||||
|
||||
if (this._getConfig().view.timeout_seconds) {
|
||||
this._interactionTimerID = window.setTimeout(() => {
|
||||
this._clearInteractionTimer();
|
||||
this._interactionTimer.start(this._getConfig().view.timeout_seconds, () => {
|
||||
if (this._isAutomatedViewUpdateAllowed()) {
|
||||
this._changeView();
|
||||
this._setLightOrDarkMode();
|
||||
}
|
||||
}, this._getConfig().view.timeout_seconds * 1000);
|
||||
});
|
||||
}
|
||||
this._setLightOrDarkMode();
|
||||
}
|
||||
@@ -1848,12 +1822,9 @@ class FrigateCard extends LitElement {
|
||||
* `view.update_seconds`.
|
||||
*/
|
||||
protected _startUpdateTimer(): void {
|
||||
if (this._updateTimerID) {
|
||||
window.clearTimeout(this._updateTimerID);
|
||||
this._updateTimerID = null;
|
||||
}
|
||||
this._updateTimer.stop();
|
||||
if (this._getConfig().view.update_seconds) {
|
||||
this._updateTimerID = window.setTimeout(() => {
|
||||
this._updateTimer.start(this._getConfig().view.update_seconds, () => {
|
||||
if (this._isAutomatedViewUpdateAllowed()) {
|
||||
this._changeView();
|
||||
} else {
|
||||
@@ -1861,7 +1832,7 @@ class FrigateCard extends LitElement {
|
||||
// interval.
|
||||
this._startUpdateTimer();
|
||||
}
|
||||
}, this._getConfig().view.update_seconds * 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1872,7 +1843,7 @@ class FrigateCard extends LitElement {
|
||||
protected _isAutomatedViewUpdateAllowed(ignoreTriggers?: boolean): boolean {
|
||||
return (
|
||||
(ignoreTriggers || !this._isTriggered()) &&
|
||||
(this._getConfig().view.update_force || !this._interactionTimerID)
|
||||
(this._getConfig().view.update_force || !this._interactionTimer.isRunning())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
dispatchMediaPlayEvent,
|
||||
} from '../../utils/media-info.js';
|
||||
import { dispatchErrorMessageEvent } from '../message.js';
|
||||
import { Timer } from '../../utils/timer.js';
|
||||
|
||||
// Number of seconds a signed URL is valid for.
|
||||
const JSMPEG_URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60;
|
||||
@@ -41,7 +42,7 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
|
||||
|
||||
protected _jsmpegCanvasElement?: HTMLCanvasElement;
|
||||
protected _jsmpegVideoPlayer?: JSMpeg.VideoElement;
|
||||
protected _refreshPlayerTimerID?: number;
|
||||
protected _refreshPlayerTimer = new Timer();
|
||||
|
||||
public async play(): Promise<void> {
|
||||
return this._jsmpegVideoPlayer?.play();
|
||||
@@ -145,10 +146,7 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
|
||||
* Reset / destroy the player.
|
||||
*/
|
||||
protected _resetPlayer(): void {
|
||||
if (this._refreshPlayerTimerID) {
|
||||
window.clearTimeout(this._refreshPlayerTimerID);
|
||||
this._refreshPlayerTimerID = undefined;
|
||||
}
|
||||
this._refreshPlayerTimer.stop();
|
||||
if (this._jsmpegVideoPlayer) {
|
||||
try {
|
||||
this._jsmpegVideoPlayer.destroy();
|
||||
@@ -213,9 +211,10 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi
|
||||
}
|
||||
|
||||
await this._createJSMPEGPlayer(address);
|
||||
this._refreshPlayerTimerID = window.setTimeout(() => {
|
||||
this.requestUpdate();
|
||||
}, (JSMPEG_URL_SIGN_EXPIRY_SECONDS - JSMPEG_URL_SIGN_REFRESH_THRESHOLD_SECONDS) * 1000);
|
||||
this._refreshPlayerTimer.start(
|
||||
JSMPEG_URL_SIGN_EXPIRY_SECONDS - JSMPEG_URL_SIGN_REFRESH_THRESHOLD_SECONDS,
|
||||
() => this.requestUpdate(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,6 +22,7 @@ import './carousel.js';
|
||||
import { FrigateCardNextPreviousControl } from './next-prev-control.js';
|
||||
import { FrigateCardTitleControl } from './title-control.js';
|
||||
import debounce from 'lodash-es/debounce';
|
||||
import { Timer } from '../utils/timer';
|
||||
|
||||
interface CarouselMediaLoadedInfo {
|
||||
slide: number;
|
||||
@@ -126,7 +127,7 @@ export class FrigateCardMediaCarousel extends LitElement {
|
||||
protected _nextControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
||||
protected _previousControlRef: Ref<FrigateCardNextPreviousControl> = createRef();
|
||||
protected _titleControlRef: Ref<FrigateCardTitleControl> = createRef();
|
||||
protected _titleTimerID: number | null = null;
|
||||
protected _titleTimer = new Timer();
|
||||
|
||||
protected _boundAutoPlayHandler = this.autoPlay.bind(this);
|
||||
protected _boundAutoUnmuteHandler = this.autoUnmute.bind(this);
|
||||
@@ -231,13 +232,10 @@ export class FrigateCardMediaCarousel extends LitElement {
|
||||
*/
|
||||
protected _titleHandler(): void {
|
||||
const show = () => {
|
||||
this._titleTimerID = null;
|
||||
this._titleTimer.stop();
|
||||
this._titleControlRef.value?.show();
|
||||
};
|
||||
|
||||
if (this._titleTimerID) {
|
||||
window.clearTimeout(this._titleTimerID);
|
||||
}
|
||||
if (this._titleControlRef.value?.isVisible()) {
|
||||
// If it's already visible, update it immediately (but also update it
|
||||
// after the timer expires to ensure it re-positions if necessary, see
|
||||
@@ -248,7 +246,7 @@ export class FrigateCardMediaCarousel extends LitElement {
|
||||
// Allow a brief pause after the media loads, but before the title is
|
||||
// displayed. This allows for a pleasant appearance/disappear of the title,
|
||||
// and allows for the browser to finish rendering the carousel.
|
||||
this._titleTimerID = window.setTimeout(show, 0.5 * 1000);
|
||||
this._titleTimer.start(0.5, show);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-7
@@ -1,4 +1,5 @@
|
||||
import { FrigateCardMediaPlayer } from '../types';
|
||||
import { Timer } from './timer';
|
||||
|
||||
// The number of seconds to hide the video controls for after loading (in order
|
||||
// to give a cleaner UI appearance, see:
|
||||
@@ -15,19 +16,17 @@ const MEDIA_SEEK_CONTROLS_HIDE_SECONDS = 1;
|
||||
export const hideMediaControlsTemporarily = (
|
||||
element: HTMLElement & {
|
||||
controls: boolean;
|
||||
_controlsHideTimeoutID?: number;
|
||||
_controlsHideTimer?: Timer;
|
||||
},
|
||||
seconds = MEDIA_SEEK_CONTROLS_HIDE_SECONDS,
|
||||
): void => {
|
||||
element.controls = false;
|
||||
|
||||
if (element._controlsHideTimeoutID) {
|
||||
window.clearTimeout(element._controlsHideTimeoutID);
|
||||
}
|
||||
element._controlsHideTimeoutID = window.setTimeout(() => {
|
||||
element._controlsHideTimer ??= new Timer();
|
||||
element._controlsHideTimer.start(seconds, () => {
|
||||
element.controls = true;
|
||||
delete element._controlsHideTimeoutID;
|
||||
}, seconds * 1000);
|
||||
delete element._controlsHideTimer;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+5
-13
@@ -1,8 +1,9 @@
|
||||
import { errorToConsole } from "./basic";
|
||||
import { errorToConsole } from './basic';
|
||||
import { Timer } from './timer';
|
||||
|
||||
export class MicrophoneController {
|
||||
protected _stream?: MediaStream | null;
|
||||
protected _timerID: number | null = null;
|
||||
protected _timer = new Timer();
|
||||
|
||||
// We keep mute state separate from the stream state so that mute/unmute can
|
||||
// be expressed before the stream is created -- and when it's create it will
|
||||
@@ -68,20 +69,11 @@ export class MicrophoneController {
|
||||
return !this._stream || this._stream.getTracks().every((track) => !track.enabled);
|
||||
}
|
||||
|
||||
protected _clearTimer(): void {
|
||||
if (this._timerID) {
|
||||
window.clearTimeout(this._timerID);
|
||||
this._timerID = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected _startTimer(): void {
|
||||
if (this._disconnectSeconds) {
|
||||
this._clearTimer();
|
||||
this._timerID = window.setTimeout(() => {
|
||||
this._clearTimer();
|
||||
this._timer.start(this._disconnectSeconds, () => {
|
||||
this.disconnect();
|
||||
}, this._disconnectSeconds * 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
export class Timer {
|
||||
protected _timer: number | null = null;
|
||||
|
||||
public stop(): void {
|
||||
if (this._timer) {
|
||||
window.clearTimeout(this._timer);
|
||||
this._timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
public isRunning(): boolean {
|
||||
return this._timer !== null;
|
||||
}
|
||||
|
||||
public start(seconds: number, func: () => void): void {
|
||||
this.stop();
|
||||
this._timer = window.setTimeout(() => {
|
||||
this._timer = null;
|
||||
func();
|
||||
}, seconds * 1000);
|
||||
}
|
||||
|
||||
public startRepeated(seconds: number, func: () => void): void {
|
||||
this.stop();
|
||||
this._timer = window.setInterval(() => {
|
||||
func();
|
||||
}, seconds * 1000);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user