feat: Add loading spinner on card initialization (#1549)
* feat: Add loading spinner on card initialization * Minor doc fix.
This commit is contained in:
@@ -31,12 +31,17 @@ export class InitializationManager {
|
||||
// initialization" (above) are followed.
|
||||
protected _initializationQueue = new PQueue({ concurrency: 1 });
|
||||
protected _initializer: Initializer;
|
||||
protected _everInitialized = false;
|
||||
|
||||
constructor(api: CardInitializerAPI, initializer?: Initializer) {
|
||||
this._api = api;
|
||||
this._initializer = initializer ?? new Initializer();
|
||||
}
|
||||
|
||||
public wasEverInitialized(): boolean {
|
||||
return this._everInitialized;
|
||||
}
|
||||
|
||||
public isInitializedMandatory(): boolean {
|
||||
const config = this._api.getConfigManager().getConfig();
|
||||
if (!config) {
|
||||
@@ -113,6 +118,7 @@ export class InitializationManager {
|
||||
return;
|
||||
}
|
||||
|
||||
this._everInitialized = true;
|
||||
this._api.getCardElementManager().update();
|
||||
}
|
||||
|
||||
|
||||
+32
-27
@@ -12,6 +12,7 @@ import { CardController } from './card-controller/controller';
|
||||
import { MenuButtonController } from './components-lib/menu-button-controller';
|
||||
import './components/elements.js';
|
||||
import { FrigateCardElements } from './components/elements.js';
|
||||
import './components/loading.js';
|
||||
import './components/menu.js';
|
||||
import { FrigateCardMenu } from './components/menu.js';
|
||||
import './components/message.js';
|
||||
@@ -183,7 +184,6 @@ class FrigateCard extends LitElement {
|
||||
|
||||
if (!this._controller.getInitializationManager().isInitializedMandatory()) {
|
||||
this._controller.getInitializationManager().initializeMandatory();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -338,6 +338,11 @@ class FrigateCard extends LitElement {
|
||||
|
||||
const actions = this._controller.getActionsManager().getMergedActions();
|
||||
const cameraManager = this._controller.getCameraManager();
|
||||
const renderLoadingSpinner =
|
||||
this._config?.performance?.features.animated_progress_indicator !== false;
|
||||
const showLoadingSpinner =
|
||||
!this._controller.getInitializationManager().wasEverInitialized() &&
|
||||
!this._controller.getMessageManager().hasMessage();
|
||||
|
||||
// Caution: Keep the main div and the menu next to one another in order to
|
||||
// ensure the hover menu styling continues to work.
|
||||
@@ -366,35 +371,35 @@ class FrigateCard extends LitElement {
|
||||
}
|
||||
@frigate-card:focus=${() => this.focus()}
|
||||
>
|
||||
${renderLoadingSpinner
|
||||
? html`<frigate-card-loading .show=${showLoadingSpinner}>
|
||||
</frigate-card-loading>`
|
||||
: ''}
|
||||
${this._renderMenuStatusContainer('top')}
|
||||
<div ${ref(this._refMain)} class="${classMap(mainClasses)}">
|
||||
${this._renderMenuStatusContainer('overlay')}
|
||||
${
|
||||
// Always want to render <frigate-card-views> even if there's a message, to
|
||||
// ensure live preload is always present (even if not displayed).
|
||||
html`<frigate-card-views
|
||||
${ref(this._refViews)}
|
||||
.hass=${this._hass}
|
||||
.viewManagerEpoch=${this._controller.getViewManager().getEpoch()}
|
||||
.cameraManager=${cameraManager}
|
||||
.resolvedMediaCache=${this._controller.getResolvedMediaCache()}
|
||||
.nonOverriddenConfig=${this._controller
|
||||
.getConfigManager()
|
||||
.getNonOverriddenConfig()}
|
||||
.overriddenConfig=${this._controller.getConfigManager().getConfig()}
|
||||
.cardWideConfig=${this._controller.getConfigManager().getCardWideConfig()}
|
||||
.rawConfig=${this._controller.getConfigManager().getRawConfig()}
|
||||
.configManager=${this._controller.getConfigManager()}
|
||||
.conditionsManagerEpoch=${this._controller
|
||||
.getConditionsManager()
|
||||
?.getEpoch()}
|
||||
.hide=${!!this._controller.getMessageManager().hasMessage()}
|
||||
.microphoneManager=${this._controller.getMicrophoneManager()}
|
||||
.triggeredCameraIDs=${this._config?.view.triggers.show_trigger_status
|
||||
? this._controller.getTriggersManager().getTriggeredCameraIDs()
|
||||
: undefined}
|
||||
></frigate-card-views>`
|
||||
}
|
||||
<frigate-card-views
|
||||
${ref(this._refViews)}
|
||||
.hass=${this._hass}
|
||||
.viewManagerEpoch=${this._controller.getViewManager().getEpoch()}
|
||||
.cameraManager=${cameraManager}
|
||||
.resolvedMediaCache=${this._controller.getResolvedMediaCache()}
|
||||
.nonOverriddenConfig=${this._controller
|
||||
.getConfigManager()
|
||||
.getNonOverriddenConfig()}
|
||||
.overriddenConfig=${this._controller.getConfigManager().getConfig()}
|
||||
.cardWideConfig=${this._controller.getConfigManager().getCardWideConfig()}
|
||||
.rawConfig=${this._controller.getConfigManager().getRawConfig()}
|
||||
.configManager=${this._controller.getConfigManager()}
|
||||
.conditionsManagerEpoch=${this._controller
|
||||
.getConditionsManager()
|
||||
?.getEpoch()}
|
||||
.hide=${!!this._controller.getMessageManager().hasMessage()}
|
||||
.microphoneManager=${this._controller.getMicrophoneManager()}
|
||||
.triggeredCameraIDs=${this._config?.view.triggers.show_trigger_status
|
||||
? this._controller.getTriggersManager().getTriggeredCameraIDs()
|
||||
: undefined}
|
||||
></frigate-card-views>
|
||||
${
|
||||
// Keep message rendering to last to show messages that may have been
|
||||
// generated during the render.
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
html,
|
||||
unsafeCSS,
|
||||
} from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import irisLogo from '../images/camera-iris.svg';
|
||||
import controlStyle from '../scss/loading.scss';
|
||||
import { Timer } from '../utils/timer';
|
||||
|
||||
// Number of seconds after the loading spinner is hidden before rendering this
|
||||
// component as empty. Should be longer than the opacity css transition time.
|
||||
const LOADING_EMPTY_SECONDS = 2;
|
||||
|
||||
@customElement('frigate-card-loading')
|
||||
export class FrigateCardLoading extends LitElement {
|
||||
@property({ attribute: true, reflect: true, type: Boolean })
|
||||
public show = false;
|
||||
|
||||
@state()
|
||||
protected _empty = false;
|
||||
|
||||
protected _timer = new Timer();
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return this._empty ? html`` : html` <img src="${irisLogo}" /> `;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues): void {
|
||||
if (changedProps.has('show') && !this.show) {
|
||||
this._timer.start(LOADING_EMPTY_SECONDS, () => {
|
||||
this._empty = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(controlStyle);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'frigate-card-loading': FrigateCardLoading;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
border-radius: var(--ha-card-border-radius, 4px);
|
||||
|
||||
height: var(--frigate-card-height);
|
||||
min-height: 100px;
|
||||
|
||||
// Ensure all clicks at the top level work.
|
||||
pointer-events: all;
|
||||
@@ -29,6 +30,12 @@
|
||||
--frigate-card-height: auto;
|
||||
}
|
||||
|
||||
frigate-card-loading {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
:host([dark]) {
|
||||
filter: brightness(75%);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
:host {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
transition: opacity 1s;
|
||||
}
|
||||
|
||||
:host([show]) {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
:host(:not([show])) {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 40%;
|
||||
height: 40%;
|
||||
|
||||
opacity: 0.2;
|
||||
|
||||
filter: invert(100%);
|
||||
|
||||
animation: rotate 8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user