mirror of
https://github.com/home-assistant/frontend.git
synced 2026-04-29 22:13:23 +00:00
Compare commits
12 Commits
master
...
devtools-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e07cf6b52a | ||
|
|
2ca3378d35 | ||
|
|
95228d23d5 | ||
|
|
05f23e45e1 | ||
|
|
d6fd9db5c3 | ||
|
|
4a67b57ef8 | ||
|
|
c91040a4d7 | ||
|
|
e3e4fb1a08 | ||
|
|
b78a1218c8 | ||
|
|
5d55e0a1b2 | ||
|
|
9359572f41 | ||
|
|
34f48acf09 |
@@ -18,7 +18,7 @@ import "./events-list";
|
||||
class HaPanelDevEvent extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@state() private _eventType = "";
|
||||
|
||||
@@ -94,6 +94,7 @@ class HaPanelDevEvent extends LitElement {
|
||||
|
||||
<event-subscribe-card
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.selectedEventType=${this._selectedEventType}
|
||||
></event-subscribe-card>
|
||||
</div>
|
||||
@@ -158,6 +159,8 @@ class HaPanelDevEvent extends LitElement {
|
||||
padding: var(--ha-space-4);
|
||||
max-width: 1200px;
|
||||
margin: auto;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host {
|
||||
@@ -165,10 +168,26 @@ class HaPanelDevEvent extends LitElement {
|
||||
-webkit-user-select: initial;
|
||||
-moz-user-select: initial;
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:host([narrow]) {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
:host([narrow]) .content {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.flex {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:host([narrow]) .flex {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
@@ -180,11 +199,19 @@ class HaPanelDevEvent extends LitElement {
|
||||
}
|
||||
|
||||
event-subscribe-card {
|
||||
display: block;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
margin-top: var(--ha-space-4);
|
||||
direction: var(--direction);
|
||||
}
|
||||
|
||||
:host([narrow]) event-subscribe-card {
|
||||
flex: none;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,39 @@
|
||||
import {
|
||||
mdiChevronDoubleLeft,
|
||||
mdiChevronDoubleRight,
|
||||
mdiChevronLeft,
|
||||
mdiChevronRight,
|
||||
mdiInformationOutline,
|
||||
} from "@mdi/js";
|
||||
import type { HassEvent } from "home-assistant-js-websocket";
|
||||
import type { TemplateResult, PropertyValues } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { formatTime } from "../../../../common/datetime/format_time";
|
||||
import { formatTimeWithSeconds } from "../../../../common/datetime/format_time";
|
||||
import "../../../../components/ha-alert";
|
||||
import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import "../../../../components/ha-yaml-editor";
|
||||
import "../../../../components/input/ha-input";
|
||||
import type { HaInput } from "../../../../components/input/ha-input";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
|
||||
const MAX_BUFFERED_EVENTS = 100;
|
||||
|
||||
interface SubscribedEvent {
|
||||
id: number;
|
||||
event: HassEvent;
|
||||
}
|
||||
|
||||
@customElement("event-subscribe-card")
|
||||
class EventSubscribeCard extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public selectedEventType = "";
|
||||
|
||||
@state() private _eventType = "";
|
||||
@@ -24,13 +42,12 @@ class EventSubscribeCard extends LitElement {
|
||||
|
||||
@state() private _eventFilter = "";
|
||||
|
||||
@state() private _events: {
|
||||
id: number;
|
||||
event: HassEvent;
|
||||
}[] = [];
|
||||
@state() private _events: SubscribedEvent[] = [];
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _viewedEventId?: number;
|
||||
|
||||
private _eventCount = 0;
|
||||
|
||||
@state() _ignoredEventsCount = 0;
|
||||
@@ -113,43 +130,161 @@ class EventSubscribeCard extends LitElement {
|
||||
</ha-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<div class="events">
|
||||
${repeat(
|
||||
this._events,
|
||||
(event) => event.id,
|
||||
(event) => html`
|
||||
<div class="event">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.developer-tools.tabs.events.event_fired",
|
||||
{ name: event.id }
|
||||
)}
|
||||
${formatTime(
|
||||
new Date(event.event.time_fired),
|
||||
this.hass!.locale,
|
||||
this.hass!.config
|
||||
)}:
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${event.event}
|
||||
read-only
|
||||
></ha-yaml-editor>
|
||||
</div>
|
||||
`
|
||||
${this._renderEventsCard()}
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderEventsCard(): TemplateResult {
|
||||
if (!this._events.length) {
|
||||
const message = this._subscribed
|
||||
? this.hass!.localize(
|
||||
"ui.panel.config.developer-tools.tabs.events.waiting_for_events"
|
||||
)
|
||||
: this.hass!.localize(
|
||||
"ui.panel.config.developer-tools.tabs.events.subscribe_prompt"
|
||||
);
|
||||
return html`
|
||||
<ha-card class="events-card">
|
||||
<div class="empty-state">${message}</div>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
const bufferTotal = this._events.length;
|
||||
const index = this._resolveViewedIndex();
|
||||
const event = this._events[index];
|
||||
const position = event.id + 1;
|
||||
|
||||
const bufferPosition = bufferTotal - index;
|
||||
const atNewest = index === 0;
|
||||
|
||||
const hasRolledOver = this._events[bufferTotal - 1].id > 0;
|
||||
|
||||
return html`
|
||||
<ha-card class="events-card">
|
||||
<div class="events-toolbar">
|
||||
<ha-icon-button
|
||||
.path=${mdiChevronDoubleLeft}
|
||||
.disabled=${index >= bufferTotal - 1}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.developer-tools.tabs.events.oldest_event"
|
||||
)}
|
||||
@click=${this._showOldest}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
.path=${mdiChevronLeft}
|
||||
.disabled=${index >= bufferTotal - 1}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.developer-tools.tabs.events.older_event"
|
||||
)}
|
||||
@click=${this._showOlder}
|
||||
></ha-icon-button>
|
||||
<div class="event-info">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.developer-tools.tabs.events.event_fired",
|
||||
{
|
||||
name: position,
|
||||
time: formatTimeWithSeconds(
|
||||
new Date(event.event.time_fired),
|
||||
this.hass!.locale,
|
||||
this.hass!.config
|
||||
),
|
||||
}
|
||||
)}
|
||||
<span class="counter">(${bufferPosition} / ${bufferTotal})</span>
|
||||
${hasRolledOver
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
id="buffer-info"
|
||||
class="buffer-info"
|
||||
.path=${mdiInformationOutline}
|
||||
></ha-svg-icon>
|
||||
<ha-tooltip for="buffer-info" placement="bottom">
|
||||
<span class="buffer-tooltip">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.developer-tools.tabs.events.buffer_disclaimer",
|
||||
{ count: MAX_BUFFERED_EVENTS }
|
||||
)}
|
||||
</span>
|
||||
</ha-tooltip>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-icon-button
|
||||
.path=${mdiChevronRight}
|
||||
.disabled=${atNewest}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.developer-tools.tabs.events.newer_event"
|
||||
)}
|
||||
@click=${this._showNewer}
|
||||
></ha-icon-button>
|
||||
<ha-icon-button
|
||||
.path=${mdiChevronDoubleRight}
|
||||
.disabled=${atNewest}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.developer-tools.tabs.events.newest_event"
|
||||
)}
|
||||
@click=${this._showNewest}
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.value=${event.event}
|
||||
auto-update
|
||||
read-only
|
||||
></ha-yaml-editor>
|
||||
</ha-card>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: InputEvent): void {
|
||||
private _resolveViewedIndex(): number {
|
||||
if (this._viewedEventId === undefined) {
|
||||
return 0;
|
||||
}
|
||||
const found = this._events.findIndex((e) => e.id === this._viewedEventId);
|
||||
|
||||
// Fall back to the oldest available event when the viewed one has aged out.
|
||||
return found === -1 ? this._events.length - 1 : found;
|
||||
}
|
||||
|
||||
private _showOldest() {
|
||||
if (!this._events.length) {
|
||||
return;
|
||||
}
|
||||
this._viewedEventId = this._events[this._events.length - 1].id;
|
||||
}
|
||||
|
||||
private _showOlder() {
|
||||
if (!this._events.length) {
|
||||
return;
|
||||
}
|
||||
const next = Math.min(
|
||||
this._resolveViewedIndex() + 1,
|
||||
this._events.length - 1
|
||||
);
|
||||
this._viewedEventId = this._events[next].id;
|
||||
}
|
||||
|
||||
private _showNewest() {
|
||||
if (!this._events.length) {
|
||||
return;
|
||||
}
|
||||
this._viewedEventId = this._events[0].id;
|
||||
}
|
||||
|
||||
private _showNewer() {
|
||||
if (!this._events.length) {
|
||||
return;
|
||||
}
|
||||
const next = Math.max(this._resolveViewedIndex() - 1, 0);
|
||||
this._viewedEventId = this._events[next].id;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: InputEvent) {
|
||||
this._eventType = (ev.target as HaInput).value ?? "";
|
||||
this._error = undefined;
|
||||
}
|
||||
|
||||
private _filterChanged(ev: InputEvent): void {
|
||||
private _filterChanged(ev: InputEvent) {
|
||||
this._eventFilter = (ev.target as HaInput).value ?? "";
|
||||
}
|
||||
|
||||
@@ -160,7 +295,7 @@ class EventSubscribeCard extends LitElement {
|
||||
|
||||
const searchStr = this._eventFilter;
|
||||
|
||||
function visit(node) {
|
||||
function visit(node: unknown) {
|
||||
// Handle primitives directly
|
||||
if (node === null || typeof node !== "object") {
|
||||
return String(node).includes(searchStr);
|
||||
@@ -203,55 +338,116 @@ class EventSubscribeCard extends LitElement {
|
||||
return;
|
||||
}
|
||||
const tail =
|
||||
this._events.length > 30
|
||||
? this._events.slice(0, 29)
|
||||
this._events.length >= MAX_BUFFERED_EVENTS
|
||||
? this._events.slice(0, MAX_BUFFERED_EVENTS - 1)
|
||||
: this._events;
|
||||
const id = this._eventCount++;
|
||||
this._events = [
|
||||
{
|
||||
event,
|
||||
id: this._eventCount++,
|
||||
id,
|
||||
},
|
||||
...tail,
|
||||
];
|
||||
if (this._viewedEventId === undefined) {
|
||||
this._viewedEventId = id;
|
||||
}
|
||||
}, this._eventType);
|
||||
} catch (error: any) {
|
||||
} catch (error) {
|
||||
this._error = this.hass!.localize(
|
||||
"ui.panel.config.developer-tools.tabs.events.subscribe_failed",
|
||||
{ error: error.message || "Unknown error" }
|
||||
{
|
||||
error:
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: this.hass!.localize(
|
||||
"ui.panel.config.developer-tools.tabs.events.unknown_error"
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _clearEvents(): void {
|
||||
private _clearEvents() {
|
||||
this._events = [];
|
||||
this._eventCount = 0;
|
||||
this._ignoredEventsCount = 0;
|
||||
this._error = undefined;
|
||||
this._viewedEventId = undefined;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
}
|
||||
ha-input {
|
||||
margin-bottom: var(--ha-space-2);
|
||||
}
|
||||
.error-message {
|
||||
margin-top: var(--ha-space-2);
|
||||
}
|
||||
.event {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
padding-top: var(--ha-space-2);
|
||||
padding-bottom: var(--ha-space-2);
|
||||
margin: var(--ha-space-4) 0;
|
||||
}
|
||||
.event:last-child {
|
||||
border-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
pre {
|
||||
font-family: var(--ha-font-family-code);
|
||||
}
|
||||
ha-card {
|
||||
margin-bottom: var(--ha-space-1);
|
||||
margin-bottom: var(--ha-space-2);
|
||||
}
|
||||
.events-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 620px;
|
||||
padding: var(--ha-space-2);
|
||||
}
|
||||
:host([narrow]) .events-card {
|
||||
height: auto;
|
||||
min-height: 360px;
|
||||
}
|
||||
.events-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--ha-space-8);
|
||||
color: var(--primary-text-color);
|
||||
text-align: center;
|
||||
font-size: var(--ha-font-size-xl);
|
||||
line-height: var(--ha-line-height-normal);
|
||||
}
|
||||
.event-info {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-size: var(--ha-font-size-m);
|
||||
}
|
||||
.counter {
|
||||
color: var(--secondary-text-color);
|
||||
margin-left: var(--ha-space-2);
|
||||
}
|
||||
.buffer-info {
|
||||
color: var(--secondary-text-color);
|
||||
margin-left: var(--ha-space-1);
|
||||
vertical-align: middle;
|
||||
--mdc-icon-size: 16px;
|
||||
}
|
||||
.buffer-tooltip {
|
||||
white-space: pre-line;
|
||||
display: block;
|
||||
max-width: 320px;
|
||||
}
|
||||
ha-yaml-editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
margin-top: var(--ha-space-2);
|
||||
--code-mirror-height: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -3781,7 +3781,7 @@
|
||||
"type": "Event type",
|
||||
"data": "Event data (YAML, optional)",
|
||||
"fire_event": "Fire event",
|
||||
"event_fired": "Event {name} fired",
|
||||
"event_fired": "Event {name} fired at {time}",
|
||||
"active_listeners": "Active listeners",
|
||||
"count_listeners": "({count} {count, plural,\n one {listener}\n other {listeners}\n})",
|
||||
"listen_to_events": "Listen to events",
|
||||
@@ -3795,7 +3795,15 @@
|
||||
"clear_events": "Clear events",
|
||||
"alert_event_type": "Event type is a mandatory field",
|
||||
"notification_event_fired": "Event {type} successfully fired!",
|
||||
"subscribe_failed": "Failed to subscribe to event: {error}"
|
||||
"subscribe_failed": "Failed to subscribe to event: {error}",
|
||||
"unknown_error": "Unknown error",
|
||||
"oldest_event": "Oldest event",
|
||||
"older_event": "Older event",
|
||||
"newer_event": "Newer event",
|
||||
"newest_event": "Newest event",
|
||||
"waiting_for_events": "Waiting for events…",
|
||||
"subscribe_prompt": "Subscribe to an event type above to see events here.",
|
||||
"buffer_disclaimer": "Showing the latest {count} events. Older events are discarded as new ones arrive.\n\nTip: Subscribe to a more specific event type, or use the filter to narrow results."
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
|
||||
Reference in New Issue
Block a user