fix: Move upgrade button to top of editor (#2687)
This commit is contained in:
@@ -277,7 +277,15 @@ export class CardElementManager {
|
||||
const toggleDiagnostics = (): void => {
|
||||
const viewManager = this._api.getViewManager();
|
||||
if (viewManager.getView()?.view === 'diagnostics') {
|
||||
viewManager.setViewDefault();
|
||||
if (viewManager.canSetViewDefault()) {
|
||||
// Clear the view when no default one can be built, to return the card
|
||||
// to what it showed before diagnostics (e.g. an initialization error).
|
||||
viewManager.setViewDefault();
|
||||
} else {
|
||||
// Won't be a blank card: will show the issue that stopped camera
|
||||
// initialization, or the loading indicator if that is still running.
|
||||
viewManager.reset();
|
||||
}
|
||||
} else {
|
||||
viewManager.setViewByParameters({
|
||||
params: { view: 'diagnostics' },
|
||||
|
||||
@@ -74,6 +74,7 @@ export interface ViewManagerInterface {
|
||||
hasView(): boolean;
|
||||
reset(): void;
|
||||
|
||||
canSetViewDefault(): boolean;
|
||||
setViewDefault(options?: ViewFactoryOptions): void;
|
||||
setViewByParameters(options?: ViewFactoryOptions): void;
|
||||
|
||||
|
||||
@@ -62,6 +62,12 @@ export class ViewManager implements ViewManagerInterface {
|
||||
}
|
||||
}
|
||||
|
||||
// Whether a default view will be built at all. A view-creation request made
|
||||
// while this is false is silently ignored.
|
||||
public canSetViewDefault(): boolean {
|
||||
return this._isAllowedToProposeView();
|
||||
}
|
||||
|
||||
setViewDefault = (options?: ViewFactoryOptions): void =>
|
||||
this._setViewGeneric(
|
||||
this._viewFactory.getViewDefault.bind(this._viewFactory),
|
||||
@@ -116,7 +122,11 @@ export class ViewManager implements ViewManagerInterface {
|
||||
viewFactoryFunc: (options?: ViewFactoryOptions) => View | null,
|
||||
options?: ViewFactoryOptions,
|
||||
): void {
|
||||
if (!this._isAllowedToProposeView()) {
|
||||
const isDiagnosticsRequest = options?.params?.view === 'diagnostics';
|
||||
|
||||
// Diagnostics requires no camera, and is what the user is asked to open
|
||||
// when the card is broken.
|
||||
if (!isDiagnosticsRequest && !this._isAllowedToProposeView()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -131,8 +141,13 @@ export class ViewManager implements ViewManagerInterface {
|
||||
// does not linger invisibly and re-pop on the next evaluation cycle,
|
||||
// and that a stale media_query failure from an abandoned gallery /
|
||||
// viewer doesn't follow the user into an unrelated view.
|
||||
this._api.getIssueManager().reset('view_incompatible');
|
||||
this._api.getIssueManager().reset('media_query');
|
||||
//
|
||||
// Diagnostics is the exception: those issues are part of what its report
|
||||
// shows, so opening it must not erase them.
|
||||
if (!isDiagnosticsRequest) {
|
||||
this._api.getIssueManager().reset('view_incompatible');
|
||||
this._api.getIssueManager().reset('media_query');
|
||||
}
|
||||
} catch (e) {
|
||||
if (!this._view) {
|
||||
view = this._getFailSafeView(viewFactoryFunc);
|
||||
|
||||
+8
-2
@@ -413,6 +413,12 @@ export class AdvancedCameraCard extends LitElement {
|
||||
this._config?.performance?.features.card_loading_indicator !== false &&
|
||||
!fullCardIssue;
|
||||
|
||||
// Always render diagnostics. The issue itself remains and will be rendered
|
||||
// outside of the diagostics view.
|
||||
const issueToRender = this._controller.getViewManager().getView()?.is('diagnostics')
|
||||
? null
|
||||
: fullCardIssue;
|
||||
|
||||
// Caution: Keep the main div and the menu next to one another in order to
|
||||
// ensure the hover menu styling continues to work.
|
||||
return this._renderInDialogIfNecessary(
|
||||
@@ -485,7 +491,7 @@ export class AdvancedCameraCard extends LitElement {
|
||||
.cardWideConfig=${this._controller.getConfigManager().getCardWideConfig()}
|
||||
.rawConfig=${this._controller.getConfigManager().getRawConfig()}
|
||||
.configManager=${this._controller.getConfigManager()}
|
||||
.hide=${!!fullCardIssue}
|
||||
.hide=${!!issueToRender}
|
||||
.microphoneManager=${this._controller.getMicrophoneManager()}
|
||||
.microphoneState=${this._controller.getMicrophoneManager().getState()}
|
||||
.call=${this._controller.getCallManager().getCall() ?? undefined}
|
||||
@@ -500,7 +506,7 @@ export class AdvancedCameraCard extends LitElement {
|
||||
.getStateManager()
|
||||
.getIssuePresence()}
|
||||
></advanced-camera-card-views>
|
||||
${fullCardIssue ? renderNotificationBlock(fullCardIssue.notification) : ''}
|
||||
${issueToRender ? renderNotificationBlock(issueToRender.notification) : ''}
|
||||
</div>
|
||||
${this._renderMenuStatusContainer('bottom')}
|
||||
${this._config?.elements &&
|
||||
|
||||
@@ -13,7 +13,10 @@ import {
|
||||
import { setProfiles } from '../../config/profiles/set-profiles';
|
||||
import type { EditorMode } from '../../config/schema/editor';
|
||||
import { profilesSchema, type ProfileType } from '../../config/schema/profiles';
|
||||
import { configDefaults } from '../../config/schema/types';
|
||||
import {
|
||||
advancedCameraCardConfigSchema,
|
||||
configDefaults,
|
||||
} from '../../config/schema/types';
|
||||
import type {
|
||||
RawAdvancedCameraCardConfig,
|
||||
RawAdvancedCameraCardConfigArray,
|
||||
@@ -35,9 +38,14 @@ import { getEditorCameraTitle, getEditorFolderTitle } from './titles';
|
||||
type EditorControllerHost = ReactiveControllerHost & EventTarget;
|
||||
|
||||
// A card-state notice the editor shows as a banner above the sections.
|
||||
interface EditorNotice {
|
||||
export interface EditorNotice {
|
||||
type: 'info' | 'warning';
|
||||
message: string;
|
||||
|
||||
button?: {
|
||||
label: string;
|
||||
callback: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
// Interpret a raw configuration value as an array of configuration objects.
|
||||
@@ -65,6 +73,7 @@ export class EditorController implements ReactiveController {
|
||||
private _defaults = copyConfig(configDefaults);
|
||||
|
||||
private _configUpgradeable = false;
|
||||
private _configValid = false;
|
||||
private _initialized = false;
|
||||
private _hass?: HomeAssistant;
|
||||
|
||||
@@ -108,6 +117,7 @@ export class EditorController implements ReactiveController {
|
||||
private _applyConfig(config: RawAdvancedCameraCardConfig): void {
|
||||
this._config = config;
|
||||
this._configUpgradeable = isConfigUpgradeable(config);
|
||||
this._configValid = advancedCameraCardConfigSchema.safeParse(config).success;
|
||||
this._editorMode = getEditorMode(config);
|
||||
|
||||
// The defaults are rebuilt from scratch so that removing (or breaking) a
|
||||
@@ -124,6 +134,17 @@ export class EditorController implements ReactiveController {
|
||||
public getNotices(): EditorNotice[] {
|
||||
const notices: EditorNotice[] = [];
|
||||
|
||||
if (this._configUpgradeable) {
|
||||
notices.push({
|
||||
type: 'warning',
|
||||
message: localize('editor.upgrade_available'),
|
||||
button: {
|
||||
label: localize('editor.upgrade'),
|
||||
callback: () => this.upgrade(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (this._profiles.includes('low-performance')) {
|
||||
notices.push({
|
||||
type: 'warning',
|
||||
@@ -196,8 +217,8 @@ export class EditorController implements ReactiveController {
|
||||
return this._hass;
|
||||
}
|
||||
|
||||
public isConfigUpgradeable(): boolean {
|
||||
return this._configUpgradeable;
|
||||
public isConfigValid(): boolean {
|
||||
return this._configValid;
|
||||
}
|
||||
|
||||
public upgrade(): void {
|
||||
|
||||
+31
-30
@@ -7,6 +7,7 @@ import {
|
||||
type TemplateResult,
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
|
||||
import './components/editor/cameras.js';
|
||||
import './components/editor/doc-link.js';
|
||||
@@ -248,7 +249,7 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
|
||||
${this._controller.getEditorMode() === 'simple'
|
||||
? this._renderSimple(hass, input)
|
||||
: this._renderFull(hass, input)}
|
||||
${this._renderActionButtons()}
|
||||
${this._renderDiagnosticsButton()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -318,39 +319,39 @@ export class AdvancedCameraCardEditor extends LitElement implements LovelaceCard
|
||||
// Card-state notices rendered as banners above the sections (matching how
|
||||
// native Home Assistant editors surface such notes).
|
||||
private _renderNotices(): TemplateResult {
|
||||
return html`${this._controller
|
||||
.getNotices()
|
||||
.map(
|
||||
(notice) =>
|
||||
html`<ha-alert alert-type=${notice.type}>${notice.message}</ha-alert>`,
|
||||
)}`;
|
||||
}
|
||||
|
||||
private _renderActionButtons(): TemplateResult {
|
||||
return html`
|
||||
<div class="action-buttons">
|
||||
${this._controller.isConfigUpgradeable()
|
||||
return html`${this._controller.getNotices().map((notice) => {
|
||||
const button = notice.button;
|
||||
return html`<ha-alert alert-type=${notice.type}>
|
||||
${notice.message}
|
||||
${button
|
||||
? html`<ha-button
|
||||
appearance="filled"
|
||||
variant="warning"
|
||||
title=${localize('editor.upgrade_available')}
|
||||
aria-label=${localize('editor.upgrade_available')}
|
||||
@click=${() => this._controller.upgrade()}
|
||||
slot="action"
|
||||
appearance="outlined"
|
||||
variant=${ifDefined(notice.type === 'warning' ? 'warning' : undefined)}
|
||||
@click=${() => button.callback()}
|
||||
>
|
||||
${localize('editor.upgrade')}
|
||||
${button.label}
|
||||
</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>
|
||||
`;
|
||||
</ha-alert>`;
|
||||
})}`;
|
||||
}
|
||||
|
||||
private _renderDiagnosticsButton(): TemplateResult {
|
||||
return html`${
|
||||
// Diagnostics won't render on a card with a broken config.
|
||||
this._controller.isConfigValid()
|
||||
? html`<ha-button
|
||||
title=${localize('editor.toggle_diagnostics')}
|
||||
aria-label=${localize('editor.toggle_diagnostics')}
|
||||
@click=${() => {
|
||||
fireAdvancedCameraCardEvent(this, 'editor:diagnostics');
|
||||
}}
|
||||
>
|
||||
${localize('editor.toggle_diagnostics')}
|
||||
</ha-button>`
|
||||
: ''
|
||||
}`;
|
||||
}
|
||||
|
||||
// `ha-form` and `ha-selector` lazily `import()` their per-type sub-elements
|
||||
|
||||
@@ -784,7 +784,7 @@
|
||||
"timeline": "Timeline",
|
||||
"timeline_secondary": "Event timeline options",
|
||||
"toggle_diagnostics": "Toggle diagnostics",
|
||||
"upgrade": "Automatic Upgrade",
|
||||
"upgrade": "Upgrade",
|
||||
"upgrade_available": "An automatic card configuration upgrade is available",
|
||||
"view": "View",
|
||||
"view_secondary": "What the card should show and how to show it"
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
// The alert sizes the slot its button sits in to `min-content`, which would
|
||||
// otherwise break the label across lines.
|
||||
.card-config > ha-alert ha-button {
|
||||
width: max-content;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// The switch between the editors, set apart from what it switches: it is a
|
||||
// control over the editor rather than one of the card settings below it. Its
|
||||
// icon sits beside the form so the row reads as one of the editor's rows, each
|
||||
@@ -60,11 +67,6 @@
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
ha-button {
|
||||
.card-config > ha-button {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user