fix: Various editor / diagnostic improvements (#2363)

- Closes: #2360 
 - Closes: #2328
This commit is contained in:
Dermot Duffy
2026-02-21 09:31:10 -08:00
committed by GitHub
parent 6d5a76ac0a
commit df3614f793
13 changed files with 313 additions and 58 deletions
+1 -9
View File
@@ -32,15 +32,7 @@ class ActionHandler extends HTMLElement implements ActionHandlerInterface {
private started = false;
public connectedCallback(): void {
[
'touchcancel',
'mouseout',
'mouseup',
'touchmove',
'mousewheel',
'wheel',
'scroll',
].forEach((ev) => {
['mouseup', 'mousewheel', 'scroll', 'touchcancel', 'wheel'].forEach((ev) => {
document.addEventListener(
ev,
() => {
+29 -1
View File
@@ -1,8 +1,10 @@
import { LitElement, ReactiveControllerHost } from 'lit';
import { ActionEventTarget } from '../action-handler-directive';
import { isCardInPanel } from '../ha/panel';
import { LovelaceCard } from '../ha/types';
import { setOrRemoveAttribute } from '../utils/basic';
import { isBeingCasted } from '../utils/casting';
import { isAncestorInEventPath } from '../utils/event-ancestor';
import { CardMediaReviewEventTarget } from '../utils/review';
import { ViewItem } from '../view/item';
import { ActionExecutionRequestEventTarget } from './actions/utils/execution-request';
@@ -12,7 +14,8 @@ import { CardElementAPI } from './types';
export type ScrollCallback = () => void;
export type MenuToggleCallback = () => void;
export type CardHTMLElement = LitElement &
export type CardHTMLElement = LovelaceCard &
LitElement &
ReactiveControllerHost &
ActionEventTarget &
ActionExecutionRequestEventTarget &
@@ -141,6 +144,10 @@ export class CardElementManager {
'popstate',
this._api.getQueryStringManager().requestExecution,
);
window.addEventListener(
'advanced-camera-card:editor:diagnostics',
this._editorDiagnosticsHandler,
);
this._api.getConditionStateManager()?.setState({
userAgent: navigator.userAgent,
@@ -218,6 +225,10 @@ export class CardElementManager {
'popstate',
this._api.getQueryStringManager().requestExecution,
);
window.removeEventListener(
'advanced-camera-card:editor:diagnostics',
this._editorDiagnosticsHandler,
);
}
private _handleMediaReviewed = (ev: CustomEvent<ViewItem>): void => {
@@ -230,4 +241,21 @@ export class CardElementManager {
this.update();
}
};
protected _editorDiagnosticsHandler = (ev: Event): void => {
const toggleDiagnostics = (): void => {
const viewManager = this._api.getViewManager();
if (viewManager.getView()?.view === 'diagnostics') {
viewManager.setViewDefault();
} else {
viewManager.setViewByParameters({
params: { view: 'diagnostics' },
});
}
};
if (isAncestorInEventPath(this._element, ev, 'hui-dialog-edit-card')) {
toggleDiagnostics();
}
};
}
+29 -20
View File
@@ -271,6 +271,7 @@ import { localize } from './localize/localize.js';
import editorStyle from './scss/editor.scss';
import { arrayMove, prettifyTitle } from './utils/basic.js';
import { getCameraID } from './utils/camera.js';
import { fireAdvancedCameraCardEvent } from './utils/fire-advanced-camera-card-event.js';
import { getFolderID } from './utils/folder.js';
const MENU_CAMERAS = 'cameras';
@@ -2965,26 +2966,6 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
[]) as RawAdvancedCameraCardConfigArray;
return html`
${this._configUpgradeable
? html` <div class="upgrade">
<span>${localize('editor.upgrade_available')}</span>
<span>
<mwc-button
raised
label="${localize('editor.upgrade')}"
@click=${() => {
if (this._config) {
const upgradedConfig = copyConfig(this._config);
upgradeConfig(upgradedConfig);
this._updateConfig(upgradedConfig);
}
}}
>
</mwc-button>
</span>
</div>
<br />`
: html``}
<div class="card-config">
${this._renderOptionSetHeader('cameras')}
${this._expandedMenus[MENU_OPTIONS] === 'cameras'
@@ -3597,6 +3578,34 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
</div>`
: ''}`
: html``}
<div class="action-buttons">
${this._configUpgradeable
? html`<ha-button
appearance="filled"
variant="warning"
title=${localize('editor.upgrade_available')}
aria-label=${localize('editor.upgrade_available')}
@click=${() => {
if (this._config) {
const upgradedConfig = copyConfig(this._config);
upgradeConfig(upgradedConfig);
this._updateConfig(upgradedConfig);
}
}}
>
${localize('editor.upgrade')}
</ha-button>`
: ''}
<ha-button
title=${localize('editor.toggle_diagnostics')}
aria-label=${localize('editor.toggle_diagnostics')}
@click=${() => {
fireAdvancedCameraCardEvent(this, 'editor:diagnostics');
}}
>
${localize('editor.toggle_diagnostics')}
</ha-button>
</div>
</div>
`;
}
-1
View File
@@ -22,7 +22,6 @@ export const sideLoadHomeAssistantElements = async (): Promise<boolean> => {
'ha-spinner',
'ha-state-icon',
'ha-web-rtc-player',
'mwc-button',
'mwc-list-item',
'state-badge',
];
+1
View File
@@ -189,6 +189,7 @@ export interface LovelaceCard extends HTMLElement {
hass?: HomeAssistant;
isPanel?: boolean;
editMode?: boolean;
preview?: boolean;
getCardSize(): number | Promise<number>;
setConfig(config: LovelaceCardConfig): void;
}
+2 -1
View File
@@ -688,11 +688,12 @@
"profiles_secondary": "Choose pre-configured sets of defaults",
"remote_control": "Remote Control",
"remote_control_secondary": "Options for remote controlling the card",
"toggle_diagnostics": "Toggle diagnostics",
"status_bar": "Status bar",
"status_bar_secondary": "Status bar look & feel options",
"timeline": "Timeline",
"timeline_secondary": "Event timeline options",
"upgrade": "Upgrade",
"upgrade": "Automatic Upgrade",
"upgrade_available": "An automatic card configuration upgrade is available",
"view": "View",
"view_secondary": "What the card should show and how to show it"
+6 -9
View File
@@ -36,16 +36,9 @@
.submenu + .option {
margin-top: 10px;
}
div.upgrade {
width: auto;
border: 1px dotted var(--primary-color);
margin: 10px;
.action-buttons {
display: flex;
justify-content: space-between;
align-items: center;
}
div.upgrade span {
padding: 10px;
flex-wrap: wrap;
}
.submenu-header {
@@ -150,3 +143,7 @@ advanced-camera-card-message::part(icon) {
margin-top: 0;
pointer-events: none;
}
ha-button {
margin: 10px;
}
+21
View File
@@ -0,0 +1,21 @@
/**
* Walk up `element`'s ancestor chain (through shadow boundaries) looking for
* an ancestor with the given tag name. Returns true if one is found and that
* same element also appears in the event's composedPath — indicating the event
* originated from within the same subtree as the element.
*/
export const isAncestorInEventPath = (
element: Element,
ev: Event,
tagName: string,
): boolean => {
const composedPath = ev.composedPath();
let node: Node | null = element;
while (node) {
if (node instanceof Element && node.tagName.toLowerCase() === tagName) {
return composedPath.includes(node);
}
node = node instanceof ShadowRoot ? node.host : node.parentNode;
}
return false;
};