feat: Allow user generated notifications (#2401)
This commit is contained in:
@@ -8,9 +8,9 @@ import {
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { IconController } from '../components-lib/icon-controller';
|
||||
import { InternalIcon } from '../config/schema/common/icon.js';
|
||||
import { HomeAssistant } from '../ha/types';
|
||||
import iconStyle from '../scss/icon.scss';
|
||||
import { Icon } from '../types.js';
|
||||
import { contentsChanged } from '../utils/basic.js';
|
||||
|
||||
@customElement('advanced-camera-card-icon')
|
||||
@@ -19,7 +19,7 @@ export class AdvancedCameraCardIcon extends LitElement {
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
@property({ attribute: false, hasChanged: contentsChanged })
|
||||
public icon?: Icon;
|
||||
public icon?: InternalIcon;
|
||||
|
||||
// Note: This attribute will allow non-active entity state styles (e.g. 'off',
|
||||
// 'unavailable') to be overriden from outside the icon itself. This is useful
|
||||
|
||||
@@ -2,9 +2,9 @@ import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { NextPreviousControlConfig } from '../config/schema/common/controls/next-previous.js';
|
||||
import { Icon } from '../config/schema/common/icon.js';
|
||||
import { HomeAssistant } from '../ha/types.js';
|
||||
import controlStyle from '../scss/next-previous-control.scss';
|
||||
import { Icon } from '../types.js';
|
||||
import { renderTask } from '../utils/task.js';
|
||||
import { createFetchThumbnailTask } from '../utils/thumbnail.js';
|
||||
|
||||
|
||||
@@ -2,17 +2,29 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { createRef, Ref, ref } from 'lit/directives/ref.js';
|
||||
import overlayMessageStyle from '../scss/overlay-message.scss';
|
||||
import { MetadataField, OverlayMessage, OverlayMessageControl } from '../types.js';
|
||||
import { dispatchDismissOverlayMessageEvent } from '../utils/overlay-message.js';
|
||||
import { actionHandler } from '../action-handler-directive.js';
|
||||
import { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request';
|
||||
import {
|
||||
Notification,
|
||||
NotificationControl,
|
||||
NotificationDetail,
|
||||
} from '../config/schema/actions/types.js';
|
||||
import notificationStyle from '../scss/notification.scss';
|
||||
import {
|
||||
getActionConfigGivenAction,
|
||||
hasAction,
|
||||
stopEventFromActivatingCardWideActions,
|
||||
} from '../utils/action.js';
|
||||
import { arrayify } from '../utils/basic.js';
|
||||
import { dispatchDismissNotificationEvent } from '../utils/notification.js';
|
||||
import './icon.js';
|
||||
|
||||
@customElement('advanced-camera-card-overlay-message')
|
||||
export class AdvancedCameraCardOverlayMessage extends LitElement {
|
||||
@customElement('advanced-camera-card-notification')
|
||||
export class AdvancedCameraCardNotification extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public message: OverlayMessage | null = null;
|
||||
public notification: Notification | null = null;
|
||||
|
||||
private _refMessage: Ref<HTMLElement> = createRef();
|
||||
private _refNotification: Ref<HTMLElement> = createRef();
|
||||
|
||||
public connectedCallback(): void {
|
||||
super.connectedCallback();
|
||||
@@ -29,20 +41,20 @@ export class AdvancedCameraCardOverlayMessage extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult | void {
|
||||
if (!this.message) {
|
||||
if (!this.notification) {
|
||||
return;
|
||||
}
|
||||
|
||||
const heading = this.message.heading;
|
||||
const details = this.message.details ?? [];
|
||||
const text = this.message.text;
|
||||
const controls = this.message.controls ?? [];
|
||||
const heading = this.notification.heading;
|
||||
const details = this.notification.details ?? [];
|
||||
const text = this.notification.text;
|
||||
const controls = this.notification.controls ?? [];
|
||||
|
||||
return html`
|
||||
<div class="backdrop" @click=${this._dismiss}></div>
|
||||
<div
|
||||
class="message"
|
||||
${ref(this._refMessage)}
|
||||
class="notification"
|
||||
${ref(this._refNotification)}
|
||||
@animationend=${this._handleAnimationEnd}
|
||||
>
|
||||
<div class="details">
|
||||
@@ -64,60 +76,70 @@ export class AdvancedCameraCardOverlayMessage extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderControl(control: OverlayMessageControl): TemplateResult {
|
||||
const emphasisClass = control.emphasis ? `emphasis-${control.emphasis}` : '';
|
||||
private _renderControl(control: NotificationControl): TemplateResult {
|
||||
const severityClass = control.severity ? `severity-${control.severity}` : '';
|
||||
return html`
|
||||
<div
|
||||
class="control ${emphasisClass}"
|
||||
title=${control.title}
|
||||
@click=${async () => this._handleControlClick(control)}
|
||||
class="control ${severityClass}"
|
||||
title=${control.tooltip ?? ''}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: hasAction(control.actions?.hold_action),
|
||||
hasDoubleClick: hasAction(control.actions?.double_tap_action),
|
||||
})}
|
||||
@action=${(ev: CustomEvent) => this._handleControlAction(ev, control)}
|
||||
>
|
||||
${control.icon
|
||||
? html`<advanced-camera-card-icon
|
||||
.icon=${control.icon}
|
||||
.icon=${{ icon: control.icon }}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private async _handleControlClick(control: OverlayMessageControl): Promise<void> {
|
||||
const result = await control.callback();
|
||||
if (result === null) {
|
||||
// null = close the message
|
||||
private _handleControlAction(
|
||||
ev: CustomEvent<{ action: string }>,
|
||||
control: NotificationControl,
|
||||
): void {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
|
||||
const action = getActionConfigGivenAction(ev.detail.action, control.actions);
|
||||
if (action) {
|
||||
dispatchActionExecutionRequest(this, {
|
||||
actions: arrayify(action),
|
||||
});
|
||||
}
|
||||
if (control.dismiss !== false) {
|
||||
this._dismiss();
|
||||
} else {
|
||||
// Updated message = keep open and refresh
|
||||
this.message = result;
|
||||
}
|
||||
}
|
||||
|
||||
private _renderDetail(detail: MetadataField, isHeading = false): TemplateResult {
|
||||
private _renderDetail(detail: NotificationDetail, isHeading = false): TemplateResult {
|
||||
const classes = {
|
||||
detail: true,
|
||||
heading: isHeading,
|
||||
[`emphasis-${detail.emphasis}`]: !!detail.emphasis,
|
||||
[`severity-${detail.severity}`]: !!detail.severity,
|
||||
};
|
||||
return html`
|
||||
<div class="${classMap(classes)}">
|
||||
${detail.icon
|
||||
? html`<advanced-camera-card-icon
|
||||
title=${detail.hint ?? ''}
|
||||
.icon=${detail.icon}
|
||||
title=${detail.tooltip ?? ''}
|
||||
.icon=${{ icon: detail.icon }}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
<span title=${detail.title}>${detail.title}</span>
|
||||
<span title=${detail.text}>${detail.text}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _dismiss = (): void => {
|
||||
this._refMessage.value?.classList.add('exiting');
|
||||
this._refNotification.value?.classList.add('exiting');
|
||||
};
|
||||
|
||||
private _handleAnimationEnd = (ev: AnimationEvent): void => {
|
||||
if (ev.animationName === 'slideDown') {
|
||||
dispatchDismissOverlayMessageEvent(this);
|
||||
dispatchDismissNotificationEvent(this);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -136,12 +158,12 @@ export class AdvancedCameraCardOverlayMessage extends LitElement {
|
||||
};
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return unsafeCSS(overlayMessageStyle);
|
||||
return unsafeCSS(notificationStyle);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'advanced-camera-card-overlay-message': AdvancedCameraCardOverlayMessage;
|
||||
'advanced-camera-card-notification': AdvancedCameraCardNotification;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from 'lit';
|
||||
import { customElement, property, state } from 'lit/decorators.js';
|
||||
import { styleMap } from 'lit/directives/style-map.js';
|
||||
import { InternalIcon } from '../../config/schema/common/icon.js';
|
||||
import { MenuSubmenuSelect } from '../../config/schema/elements/custom/menu/submenu-select.js';
|
||||
import { MenuSubmenuItem } from '../../config/schema/elements/custom/menu/submenu.js';
|
||||
import { computeDomain } from '../../ha/compute-domain.js';
|
||||
@@ -17,7 +18,6 @@ import { isHassDifferent } from '../../ha/is-hass-different.js';
|
||||
import { EntityRegistryManager } from '../../ha/registry/entity/types.js';
|
||||
import { HomeAssistant } from '../../ha/types.js';
|
||||
import menuButtonStyle from '../../scss/menu-button.scss';
|
||||
import { Icon } from '../../types.js';
|
||||
import { createSelectOptionAction } from '../../utils/action.js';
|
||||
import '../icon.js';
|
||||
import './index.js';
|
||||
@@ -37,7 +37,7 @@ export class AdvancedCameraCardSubmenuSelectButton extends LitElement {
|
||||
private _optionTitles?: Record<string, string>;
|
||||
|
||||
private _generatedSubmenuItems?: MenuSubmenuItem[];
|
||||
private _generatedIcon?: Icon;
|
||||
private _generatedIcon?: InternalIcon;
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues): boolean {
|
||||
// No need to update the submenu unless the select entity has changed.
|
||||
|
||||
@@ -8,11 +8,12 @@ import {
|
||||
} from 'lit';
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { CameraManager } from '../../camera-manager/manager';
|
||||
import { MediaDetailsController } from '../../components-lib/media/details-controller';
|
||||
import { NotificationDetail } from '../../config/schema/actions/types';
|
||||
import { HomeAssistant } from '../../ha/types';
|
||||
import thumbnailDetailsStyle from '../../scss/thumbnail-details.scss';
|
||||
import { MetadataField } from '../../types';
|
||||
import { ViewItem } from '../../view/item';
|
||||
import '../icon';
|
||||
|
||||
@@ -42,7 +43,10 @@ export class AdvancedCameraCardThumbnailDetails extends LitElement {
|
||||
const heading = this._controller.getHeading();
|
||||
const details = this._controller.getDetails();
|
||||
|
||||
const renderDetail = (detail: MetadataField, heading = false): TemplateResult => {
|
||||
const renderDetail = (
|
||||
detail: NotificationDetail,
|
||||
heading = false,
|
||||
): TemplateResult => {
|
||||
return html`<div
|
||||
class=${classMap({
|
||||
heading,
|
||||
@@ -50,12 +54,12 @@ export class AdvancedCameraCardThumbnailDetails extends LitElement {
|
||||
>
|
||||
${detail.icon
|
||||
? html` <advanced-camera-card-icon
|
||||
severity=${detail.emphasis}
|
||||
title=${detail.hint ?? ''}
|
||||
.icon=${detail.icon}
|
||||
severity=${ifDefined(detail.severity)}
|
||||
title=${detail.tooltip ?? ''}
|
||||
.icon=${{ icon: detail.icon }}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
<span title=${detail.title}>${detail.title}</span>
|
||||
<span title=${detail.text}>${detail.text}</span>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,24 +9,27 @@ import {
|
||||
import { customElement, property } from 'lit/decorators.js';
|
||||
import { classMap } from 'lit/directives/class-map.js';
|
||||
import { CameraManager } from '../../../camera-manager/manager';
|
||||
import { dispatchActionExecutionRequest } from '../../../card-controller/actions/utils/execution-request';
|
||||
import { ViewItemManager } from '../../../card-controller/view/item-manager';
|
||||
import { ViewManagerEpoch } from '../../../card-controller/view/types';
|
||||
import {
|
||||
MediaDetailsController,
|
||||
OverlayControlsContext,
|
||||
NotificationControlsContext,
|
||||
} from '../../../components-lib/media/details-controller';
|
||||
import { ThumbnailFeatureController } from '../../../components-lib/thumbnail/feature/controller';
|
||||
import { HomeAssistant } from '../../../ha/types';
|
||||
import { localize } from '../../../localize/localize';
|
||||
import thumbnailFeatureStyle from '../../../scss/thumbnail-feature.scss';
|
||||
import { stopEventFromActivatingCardWideActions } from '../../../utils/action';
|
||||
import {
|
||||
createNotificationAction,
|
||||
stopEventFromActivatingCardWideActions,
|
||||
} from '../../../utils/action';
|
||||
import {
|
||||
downloadMedia,
|
||||
navigateToTimeline,
|
||||
toggleFavorite,
|
||||
toggleReviewed,
|
||||
} from '../../../utils/media-actions';
|
||||
import { dispatchShowOverlayMessageEvent } from '../../../utils/overlay-message';
|
||||
import { ViewItem } from '../../../view/item';
|
||||
import { ViewItemClassifier } from '../../../view/item-classifier';
|
||||
import '../../icon.js';
|
||||
@@ -80,7 +83,7 @@ export class AdvancedCameraCardThumbnailFeature extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _getControlContext(): OverlayControlsContext {
|
||||
private _getControlContext(): NotificationControlsContext {
|
||||
return {
|
||||
hass: this.hass,
|
||||
viewItemManager: this.viewItemManager,
|
||||
@@ -207,10 +210,13 @@ export class AdvancedCameraCardThumbnailFeature extends LitElement {
|
||||
stopEventFromActivatingCardWideActions(ev);
|
||||
const detailsController = new MediaDetailsController();
|
||||
detailsController.calculate(this.cameraManager, this.item);
|
||||
dispatchShowOverlayMessageEvent(
|
||||
this,
|
||||
detailsController.getMessage(this._getControlContext()),
|
||||
);
|
||||
dispatchActionExecutionRequest(this, {
|
||||
actions: [
|
||||
createNotificationAction(
|
||||
detailsController.getNotification(this._getControlContext()),
|
||||
),
|
||||
],
|
||||
});
|
||||
}}
|
||||
></advanced-camera-card-icon>`
|
||||
: ''}
|
||||
|
||||
Reference in New Issue
Block a user