mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-14 12:56:37 +00:00
Add support for custom tile features (#15411
* Move isSupported to class * Use getConfigElement to display edit button * Remove custom element for test * Don't use class static functions * Refactor render * Add support for custom tile features in editor * Add missing relative position * Rename custom tile features options * PR feedbacks + split offical and custom types * Merge is custom type function
This commit is contained in:
parent
88205a94d6
commit
8ed4914232
@ -1,6 +1,5 @@
|
|||||||
import { refine, string } from "superstruct";
|
import { refine, string } from "superstruct";
|
||||||
|
import { isCustomType } from "../../data/lovelace_custom_cards";
|
||||||
export const isCustomType = (value: string) => value.startsWith("custom:");
|
|
||||||
|
|
||||||
export const customType = () =>
|
export const customType = () =>
|
||||||
refine(string(), "custom element type", isCustomType);
|
refine(string(), "custom element type", isCustomType);
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import type { HassEntity } from "home-assistant-js-websocket";
|
||||||
|
|
||||||
export interface CustomCardEntry {
|
export interface CustomCardEntry {
|
||||||
type: string;
|
type: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
@ -6,8 +8,16 @@ export interface CustomCardEntry {
|
|||||||
documentationURL?: string;
|
documentationURL?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CustomTileFeatureEntry {
|
||||||
|
type: string;
|
||||||
|
name?: string;
|
||||||
|
supported?: (stateObj: HassEntity) => boolean;
|
||||||
|
configurable?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CustomCardsWindow {
|
export interface CustomCardsWindow {
|
||||||
customCards?: CustomCardEntry[];
|
customCards?: CustomCardEntry[];
|
||||||
|
customTileFeatures?: CustomTileFeatureEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CUSTOM_TYPE_PREFIX = "custom:";
|
export const CUSTOM_TYPE_PREFIX = "custom:";
|
||||||
@ -17,8 +27,18 @@ const customCardsWindow = window as CustomCardsWindow;
|
|||||||
if (!("customCards" in customCardsWindow)) {
|
if (!("customCards" in customCardsWindow)) {
|
||||||
customCardsWindow.customCards = [];
|
customCardsWindow.customCards = [];
|
||||||
}
|
}
|
||||||
|
if (!("customTileFeatures" in customCardsWindow)) {
|
||||||
|
customCardsWindow.customTileFeatures = [];
|
||||||
|
}
|
||||||
|
|
||||||
export const customCards = customCardsWindow.customCards!;
|
export const customCards = customCardsWindow.customCards!;
|
||||||
|
export const customTileFeatures = customCardsWindow.customTileFeatures!;
|
||||||
|
|
||||||
export const getCustomCardEntry = (type: string) =>
|
export const getCustomCardEntry = (type: string) =>
|
||||||
customCards.find((card) => card.type === type);
|
customCards.find((card) => card.type === type);
|
||||||
|
|
||||||
|
export const isCustomType = (type: string) =>
|
||||||
|
type.startsWith(CUSTOM_TYPE_PREFIX);
|
||||||
|
|
||||||
|
export const stripCustomPrefix = (type: string) =>
|
||||||
|
type.slice(CUSTOM_TYPE_PREFIX.length);
|
||||||
|
@ -40,7 +40,6 @@ import { findEntities } from "../common/find-entities";
|
|||||||
import { handleAction } from "../common/handle-action";
|
import { handleAction } from "../common/handle-action";
|
||||||
import "../components/hui-timestamp-display";
|
import "../components/hui-timestamp-display";
|
||||||
import { createTileFeatureElement } from "../create-element/create-tile-feature-element";
|
import { createTileFeatureElement } from "../create-element/create-tile-feature-element";
|
||||||
import { supportsTileFeature } from "../tile-features/tile-features";
|
|
||||||
import { LovelaceTileFeatureConfig } from "../tile-features/types";
|
import { LovelaceTileFeatureConfig } from "../tile-features/types";
|
||||||
import {
|
import {
|
||||||
LovelaceCard,
|
LovelaceCard,
|
||||||
@ -309,10 +308,6 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
: undefined;
|
: undefined;
|
||||||
const badge = computeTileBadge(stateObj, this.hass);
|
const badge = computeTileBadge(stateObj, this.hass);
|
||||||
|
|
||||||
const supportedFeatures = this._config.features?.filter((feature) =>
|
|
||||||
supportsTileFeature(stateObj, feature.type)
|
|
||||||
);
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-card style=${styleMap(style)} class=${classMap({ active })}>
|
<ha-card style=${styleMap(style)} class=${classMap({ active })}>
|
||||||
${this._shouldRenderRipple ? html`<mwc-ripple></mwc-ripple>` : null}
|
${this._shouldRenderRipple ? html`<mwc-ripple></mwc-ripple>` : null}
|
||||||
@ -373,15 +368,11 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
></ha-tile-info>
|
></ha-tile-info>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${supportedFeatures?.length
|
|
||||||
? html`
|
|
||||||
<div class="features">
|
<div class="features">
|
||||||
${supportedFeatures.map((featureConf) =>
|
${this._config.features?.map((featureConf) =>
|
||||||
this.renderFeature(featureConf, stateObj)
|
this.renderFeature(featureConf, stateObj)
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
`
|
|
||||||
: null}
|
|
||||||
</ha-card>
|
</ha-card>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -412,7 +403,7 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
(element as LovelaceTileFeature).stateObj = stateObj;
|
(element as LovelaceTileFeature).stateObj = stateObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`<div class="feature">${element}</div>`;
|
return html`${element}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
@ -502,6 +493,9 @@ export class HuiTileCard extends LitElement implements LovelaceCard {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
.features {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,10 @@ import {
|
|||||||
LovelaceViewConfig,
|
LovelaceViewConfig,
|
||||||
LovelaceViewElement,
|
LovelaceViewElement,
|
||||||
} from "../../../data/lovelace";
|
} from "../../../data/lovelace";
|
||||||
import { CUSTOM_TYPE_PREFIX } from "../../../data/lovelace_custom_cards";
|
import {
|
||||||
|
isCustomType,
|
||||||
|
stripCustomPrefix,
|
||||||
|
} from "../../../data/lovelace_custom_cards";
|
||||||
import type { HuiErrorCard } from "../cards/hui-error-card";
|
import type { HuiErrorCard } from "../cards/hui-error-card";
|
||||||
import type { ErrorCardConfig } from "../cards/types";
|
import type { ErrorCardConfig } from "../cards/types";
|
||||||
import { LovelaceElement, LovelaceElementConfig } from "../elements/types";
|
import { LovelaceElement, LovelaceElementConfig } from "../elements/types";
|
||||||
@ -153,9 +156,7 @@ const _lazyCreate = <T extends keyof CreateElementConfigTypes>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const _getCustomTag = (type: string) =>
|
const _getCustomTag = (type: string) =>
|
||||||
type.startsWith(CUSTOM_TYPE_PREFIX)
|
isCustomType(type) ? stripCustomPrefix(type) : undefined;
|
||||||
? type.slice(CUSTOM_TYPE_PREFIX.length)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
export const createLovelaceElement = <T extends keyof CreateElementConfigTypes>(
|
export const createLovelaceElement = <T extends keyof CreateElementConfigTypes>(
|
||||||
tagSuffix: T,
|
tagSuffix: T,
|
||||||
|
@ -17,10 +17,7 @@ import {
|
|||||||
union,
|
union,
|
||||||
} from "superstruct";
|
} from "superstruct";
|
||||||
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent, HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||||
import {
|
import { customType } from "../../../../common/structs/is-custom-type";
|
||||||
customType,
|
|
||||||
isCustomType,
|
|
||||||
} from "../../../../common/structs/is-custom-type";
|
|
||||||
import { entityId } from "../../../../common/structs/is-entity-id";
|
import { entityId } from "../../../../common/structs/is-entity-id";
|
||||||
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
import { computeRTLDirection } from "../../../../common/util/compute_rtl";
|
||||||
import "../../../../components/entity/state-badge";
|
import "../../../../components/entity/state-badge";
|
||||||
@ -50,6 +47,7 @@ import {
|
|||||||
} from "../types";
|
} from "../types";
|
||||||
import { configElementStyle } from "./config-elements-style";
|
import { configElementStyle } from "./config-elements-style";
|
||||||
import { buttonEntityConfigStruct } from "../structs/button-entity-struct";
|
import { buttonEntityConfigStruct } from "../structs/button-entity-struct";
|
||||||
|
import { isCustomType } from "../../../../data/lovelace_custom_cards";
|
||||||
|
|
||||||
const buttonEntitiesRowConfigStruct = object({
|
const buttonEntitiesRowConfigStruct = object({
|
||||||
type: literal("button"),
|
type: literal("button"),
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { mdiDelete, mdiDrag, mdiListBox, mdiPencil, mdiPlus } from "@mdi/js";
|
||||||
mdiDelete,
|
|
||||||
mdiDrag,
|
|
||||||
mdiListBox,
|
|
||||||
mdiPencil,
|
|
||||||
mdiPlus,
|
|
||||||
mdiWindowShutter,
|
|
||||||
} from "@mdi/js";
|
|
||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
@ -14,9 +7,17 @@ import type { SortableEvent } from "sortablejs";
|
|||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||||
import "../../../../components/entity/ha-entity-picker";
|
import "../../../../components/entity/ha-entity-picker";
|
||||||
import "../../../../components/ha-icon-button";
|
|
||||||
import "../../../../components/ha-button";
|
import "../../../../components/ha-button";
|
||||||
|
import "../../../../components/ha-icon-button";
|
||||||
|
import "../../../../components/ha-list-item";
|
||||||
import "../../../../components/ha-svg-icon";
|
import "../../../../components/ha-svg-icon";
|
||||||
|
import {
|
||||||
|
CustomTileFeatureEntry,
|
||||||
|
customTileFeatures,
|
||||||
|
CUSTOM_TYPE_PREFIX,
|
||||||
|
isCustomType,
|
||||||
|
stripCustomPrefix,
|
||||||
|
} from "../../../../data/lovelace_custom_cards";
|
||||||
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
import { sortableStyles } from "../../../../resources/ha-sortable-style";
|
||||||
import {
|
import {
|
||||||
loadSortable,
|
loadSortable,
|
||||||
@ -24,19 +25,40 @@ import {
|
|||||||
} from "../../../../resources/sortable.ondemand";
|
} from "../../../../resources/sortable.ondemand";
|
||||||
import { HomeAssistant } from "../../../../types";
|
import { HomeAssistant } from "../../../../types";
|
||||||
import { getTileFeatureElementClass } from "../../create-element/create-tile-feature-element";
|
import { getTileFeatureElementClass } from "../../create-element/create-tile-feature-element";
|
||||||
import {
|
import { supportsCoverOpenCloseTileFeature } from "../../tile-features/hui-cover-open-close-tile-feature";
|
||||||
isTileFeatureEditable,
|
import { supportsCoverTiltTileFeature } from "../../tile-features/hui-cover-tilt-tile-feature";
|
||||||
supportsTileFeature,
|
import { supportsLightBrightnessTileFeature } from "../../tile-features/hui-light-brightness-tile-feature";
|
||||||
} from "../../tile-features/tile-features";
|
import { supportsVacuumCommandTileFeature } from "../../tile-features/hui-vacuum-commands-tile-feature";
|
||||||
import { LovelaceTileFeatureConfig } from "../../tile-features/types";
|
import { LovelaceTileFeatureConfig } from "../../tile-features/types";
|
||||||
|
|
||||||
const FEATURES_TYPE: LovelaceTileFeatureConfig["type"][] = [
|
type FeatureType = LovelaceTileFeatureConfig["type"];
|
||||||
|
type SupportsFeature = (stateObj: HassEntity) => boolean;
|
||||||
|
|
||||||
|
const FEATURE_TYPES: FeatureType[] = [
|
||||||
"cover-open-close",
|
"cover-open-close",
|
||||||
"cover-tilt",
|
"cover-tilt",
|
||||||
"light-brightness",
|
"light-brightness",
|
||||||
"vacuum-commands",
|
"vacuum-commands",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const EDITABLES_FEATURE_TYPES = new Set<FeatureType>(["vacuum-commands"]);
|
||||||
|
|
||||||
|
const SUPPORTS_FEATURE_TYPES: Record<FeatureType, SupportsFeature | undefined> =
|
||||||
|
{
|
||||||
|
"cover-open-close": supportsCoverOpenCloseTileFeature,
|
||||||
|
"cover-tilt": supportsCoverTiltTileFeature,
|
||||||
|
"light-brightness": supportsLightBrightnessTileFeature,
|
||||||
|
"vacuum-commands": supportsVacuumCommandTileFeature,
|
||||||
|
};
|
||||||
|
|
||||||
|
const CUSTOM_FEATURE_ENTRIES: Record<
|
||||||
|
string,
|
||||||
|
CustomTileFeatureEntry | undefined
|
||||||
|
> = {};
|
||||||
|
customTileFeatures.forEach((feature) => {
|
||||||
|
CUSTOM_FEATURE_ENTRIES[feature.type] = feature;
|
||||||
|
});
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HASSDomEvents {
|
interface HASSDomEvents {
|
||||||
"features-changed": {
|
"features-changed": {
|
||||||
@ -64,6 +86,45 @@ export class HuiTileCardFeaturesEditor extends LitElement {
|
|||||||
this._destroySortable();
|
this._destroySortable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _supportsFeatureType(type: string): boolean {
|
||||||
|
if (!this.stateObj) return false;
|
||||||
|
|
||||||
|
if (isCustomType(type)) {
|
||||||
|
const customType = stripCustomPrefix(type);
|
||||||
|
const customFeatureEntry = CUSTOM_FEATURE_ENTRIES[customType];
|
||||||
|
if (!customFeatureEntry?.supported) return true;
|
||||||
|
try {
|
||||||
|
return customFeatureEntry.supported(this.stateObj);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const supportsFeature = SUPPORTS_FEATURE_TYPES[type];
|
||||||
|
return !supportsFeature || supportsFeature(this.stateObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isFeatureTypeEditable(type: string) {
|
||||||
|
if (isCustomType(type)) {
|
||||||
|
const customType = stripCustomPrefix(type);
|
||||||
|
const customFeatureEntry = CUSTOM_FEATURE_ENTRIES[customType];
|
||||||
|
return customFeatureEntry?.configurable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EDITABLES_FEATURE_TYPES.has(type as FeatureType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getFeatureTypeLabel(type: string) {
|
||||||
|
if (isCustomType(type)) {
|
||||||
|
const customType = stripCustomPrefix(type);
|
||||||
|
const customFeatureEntry = CUSTOM_FEATURE_ENTRIES[customType];
|
||||||
|
return customFeatureEntry?.name || type;
|
||||||
|
}
|
||||||
|
return this.hass!.localize(
|
||||||
|
`ui.panel.lovelace.editor.card.tile.features.types.${type}.label`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private _getKey(feature: LovelaceTileFeatureConfig) {
|
private _getKey(feature: LovelaceTileFeatureConfig) {
|
||||||
if (!this._featuresKeys.has(feature)) {
|
if (!this._featuresKeys.has(feature)) {
|
||||||
this._featuresKeys.set(feature, Math.random().toString());
|
this._featuresKeys.set(feature, Math.random().toString());
|
||||||
@ -72,19 +133,32 @@ export class HuiTileCardFeaturesEditor extends LitElement {
|
|||||||
return this._featuresKeys.get(feature)!;
|
return this._featuresKeys.get(feature)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _supportedFeatureTypes() {
|
protected firstUpdated() {
|
||||||
if (!this.stateObj) return [];
|
this._createSortable();
|
||||||
|
}
|
||||||
|
|
||||||
return FEATURES_TYPE.filter((type) =>
|
private _getSupportedFeaturesType() {
|
||||||
supportsTileFeature(this.stateObj!, type)
|
const featuresTypes = FEATURE_TYPES as string[];
|
||||||
|
const customFeaturesTypes = customTileFeatures.map(
|
||||||
|
(feature) => `${CUSTOM_TYPE_PREFIX}${feature.type}`
|
||||||
);
|
);
|
||||||
|
return featuresTypes
|
||||||
|
.concat(customFeaturesTypes)
|
||||||
|
.filter((type) => this._supportsFeatureType(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult {
|
||||||
if (!this.features || !this.hass) {
|
if (!this.features || !this.hass) {
|
||||||
return html``;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const supportedFeaturesType = this._getSupportedFeaturesType();
|
||||||
|
|
||||||
|
const types = supportedFeaturesType.filter((type) => !isCustomType(type));
|
||||||
|
const customTypes = supportedFeaturesType.filter((type) =>
|
||||||
|
isCustomType(type)
|
||||||
|
);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-expansion-panel outlined>
|
<ha-expansion-panel outlined>
|
||||||
<h3 slot="header">
|
<h3 slot="header">
|
||||||
@ -94,8 +168,7 @@ export class HuiTileCardFeaturesEditor extends LitElement {
|
|||||||
)}
|
)}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
${this._supportedFeatureTypes.length === 0 &&
|
${supportedFeaturesType.length === 0 && this.features.length === 0
|
||||||
this.features.length === 0
|
|
||||||
? html`
|
? html`
|
||||||
<ha-alert type="info">
|
<ha-alert type="info">
|
||||||
${this.hass!.localize(
|
${this.hass!.localize(
|
||||||
@ -108,30 +181,32 @@ export class HuiTileCardFeaturesEditor extends LitElement {
|
|||||||
${repeat(
|
${repeat(
|
||||||
this.features,
|
this.features,
|
||||||
(featureConf) => this._getKey(featureConf),
|
(featureConf) => this._getKey(featureConf),
|
||||||
(featureConf, index) => html`
|
(featureConf, index) => {
|
||||||
|
const type = featureConf.type;
|
||||||
|
const supported = this._supportsFeatureType(type);
|
||||||
|
const editable = this._isFeatureTypeEditable(type);
|
||||||
|
return html`
|
||||||
<div class="feature">
|
<div class="feature">
|
||||||
<div class="handle">
|
<div class="handle">
|
||||||
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
<ha-svg-icon .path=${mdiDrag}></ha-svg-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-content">
|
<div class="feature-content">
|
||||||
<div>
|
<div>
|
||||||
<span>
|
<span> ${this._getFeatureTypeLabel(type)} </span>
|
||||||
${this.hass!.localize(
|
${this.stateObj && !supported
|
||||||
`ui.panel.lovelace.editor.card.tile.features.types.${featureConf.type}.label`
|
? html`
|
||||||
)}
|
<span class="secondary">
|
||||||
</span>
|
|
||||||
${this.stateObj &&
|
|
||||||
!supportsTileFeature(this.stateObj, featureConf.type)
|
|
||||||
? html`<span class="secondary">
|
|
||||||
${this.hass!.localize(
|
${this.hass!.localize(
|
||||||
"ui.panel.lovelace.editor.card.tile.features.not_compatible"
|
"ui.panel.lovelace.editor.card.tile.features.not_compatible"
|
||||||
)}
|
)}
|
||||||
</span>`
|
</span>
|
||||||
|
`
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${isTileFeatureEditable(featureConf.type)
|
${editable
|
||||||
? html`<ha-icon-button
|
? html`
|
||||||
|
<ha-icon-button
|
||||||
.label=${this.hass!.localize(
|
.label=${this.hass!.localize(
|
||||||
`ui.panel.lovelace.editor.card.tile.features.edit`
|
`ui.panel.lovelace.editor.card.tile.features.edit`
|
||||||
)}
|
)}
|
||||||
@ -139,7 +214,9 @@ export class HuiTileCardFeaturesEditor extends LitElement {
|
|||||||
class="edit-icon"
|
class="edit-icon"
|
||||||
.index=${index}
|
.index=${index}
|
||||||
@click=${this._editFeature}
|
@click=${this._editFeature}
|
||||||
></ha-icon-button>`
|
.disabled=${!supported}
|
||||||
|
></ha-icon-button>
|
||||||
|
`
|
||||||
: null}
|
: null}
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.label=${this.hass!.localize(
|
.label=${this.hass!.localize(
|
||||||
@ -151,10 +228,11 @@ export class HuiTileCardFeaturesEditor extends LitElement {
|
|||||||
@click=${this._removeFeature}
|
@click=${this._removeFeature}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
</div>
|
</div>
|
||||||
`
|
`;
|
||||||
|
}
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
${this._supportedFeatureTypes.length > 0
|
${supportedFeaturesType.length > 0
|
||||||
? html`
|
? html`
|
||||||
<ha-button-menu
|
<ha-button-menu
|
||||||
fixed
|
fixed
|
||||||
@ -170,16 +248,22 @@ export class HuiTileCardFeaturesEditor extends LitElement {
|
|||||||
>
|
>
|
||||||
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
<ha-svg-icon .path=${mdiPlus} slot="icon"></ha-svg-icon>
|
||||||
</ha-button>
|
</ha-button>
|
||||||
${this._supportedFeatureTypes.map(
|
${types.map(
|
||||||
(featureType) => html`<mwc-list-item .value=${featureType}>
|
(type) => html`
|
||||||
<ha-svg-icon
|
<ha-list-item .value=${type}>
|
||||||
slot="graphic"
|
${this._getFeatureTypeLabel(type)}
|
||||||
.path=${mdiWindowShutter}
|
</ha-list-item>
|
||||||
></ha-svg-icon>
|
`
|
||||||
${this.hass!.localize(
|
|
||||||
`ui.panel.lovelace.editor.card.tile.features.types.${featureType}.label`
|
|
||||||
)}
|
)}
|
||||||
</mwc-list-item>`
|
${types.length > 0 && customTypes.length > 0
|
||||||
|
? html`<li divider role="separator"></li>`
|
||||||
|
: null}
|
||||||
|
${customTypes.map(
|
||||||
|
(type) => html`
|
||||||
|
<ha-list-item .value=${type}>
|
||||||
|
${this._getFeatureTypeLabel(type)}
|
||||||
|
</ha-list-item>
|
||||||
|
`
|
||||||
)}
|
)}
|
||||||
</ha-button-menu>
|
</ha-button-menu>
|
||||||
`
|
`
|
||||||
@ -189,10 +273,6 @@ export class HuiTileCardFeaturesEditor extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected firstUpdated(): void {
|
|
||||||
this._createSortable();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _createSortable() {
|
private async _createSortable() {
|
||||||
const Sortable = await loadSortable();
|
const Sortable = await loadSortable();
|
||||||
this._sortable = new Sortable(
|
this._sortable = new Sortable(
|
||||||
@ -228,7 +308,9 @@ export class HuiTileCardFeaturesEditor extends LitElement {
|
|||||||
|
|
||||||
if (index == null) return;
|
if (index == null) return;
|
||||||
|
|
||||||
const value = this._supportedFeatureTypes[index];
|
const value = this._getSupportedFeaturesType()[index];
|
||||||
|
if (!value) return;
|
||||||
|
|
||||||
const elClass = await getTileFeatureElementClass(value);
|
const elClass = await getTileFeatureElementClass(value);
|
||||||
|
|
||||||
let newFeature: LovelaceTileFeatureConfig;
|
let newFeature: LovelaceTileFeatureConfig;
|
||||||
@ -340,6 +422,10 @@ export class HuiTileCardFeaturesEditor extends LitElement {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li[divider] {
|
||||||
|
border-bottom-color: var(--divider-color);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
CUSTOM_TYPE_PREFIX,
|
|
||||||
getCustomCardEntry,
|
getCustomCardEntry,
|
||||||
|
isCustomType,
|
||||||
|
stripCustomPrefix,
|
||||||
} from "../../../data/lovelace_custom_cards";
|
} from "../../../data/lovelace_custom_cards";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { documentationUrl } from "../../../util/documentation-url";
|
import { documentationUrl } from "../../../util/documentation-url";
|
||||||
@ -9,9 +10,8 @@ export const getCardDocumentationURL = (
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
type: string
|
type: string
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
if (type.startsWith(CUSTOM_TYPE_PREFIX)) {
|
if (isCustomType(type)) {
|
||||||
return getCustomCardEntry(type.slice(CUSTOM_TYPE_PREFIX.length))
|
return getCustomCardEntry(stripCustomPrefix(type))?.documentationURL;
|
||||||
?.documentationURL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${documentationUrl(hass, "/lovelace/")}${type}`;
|
return `${documentationUrl(hass, "/lovelace/")}${type}`;
|
||||||
|
@ -2,6 +2,7 @@ import { mdiStop } from "@mdi/js";
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import {
|
import {
|
||||||
computeCloseIcon,
|
computeCloseIcon,
|
||||||
computeOpenIcon,
|
computeOpenIcon,
|
||||||
@ -19,6 +20,15 @@ import { HomeAssistant } from "../../../types";
|
|||||||
import { LovelaceTileFeature } from "../types";
|
import { LovelaceTileFeature } from "../types";
|
||||||
import { CoverOpenCloseTileFeatureConfig } from "./types";
|
import { CoverOpenCloseTileFeatureConfig } from "./types";
|
||||||
|
|
||||||
|
export const supportsCoverOpenCloseTileFeature = (stateObj: HassEntity) => {
|
||||||
|
const domain = computeDomain(stateObj.entity_id);
|
||||||
|
return (
|
||||||
|
domain === "cover" &&
|
||||||
|
(supportsFeature(stateObj, CoverEntityFeature.OPEN) ||
|
||||||
|
supportsFeature(stateObj, CoverEntityFeature.CLOSE))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("hui-cover-open-close-tile-feature")
|
@customElement("hui-cover-open-close-tile-feature")
|
||||||
class HuiCoverOpenCloseTileFeature
|
class HuiCoverOpenCloseTileFeature
|
||||||
extends LitElement
|
extends LitElement
|
||||||
@ -64,9 +74,14 @@ class HuiCoverOpenCloseTileFeature
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult | null {
|
||||||
if (!this._config || !this.hass || !this.stateObj) {
|
if (
|
||||||
return html``;
|
!this._config ||
|
||||||
|
!this.hass ||
|
||||||
|
!this.stateObj ||
|
||||||
|
!supportsCoverOpenCloseTileFeature(this.stateObj)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
|
@ -2,6 +2,7 @@ import { mdiArrowBottomLeft, mdiArrowTopRight, mdiStop } from "@mdi/js";
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
import "../../../components/ha-control-button";
|
import "../../../components/ha-control-button";
|
||||||
import "../../../components/ha-control-button-group";
|
import "../../../components/ha-control-button-group";
|
||||||
@ -15,6 +16,15 @@ import { HomeAssistant } from "../../../types";
|
|||||||
import { LovelaceTileFeature } from "../types";
|
import { LovelaceTileFeature } from "../types";
|
||||||
import { CoverTiltTileFeatureConfig } from "./types";
|
import { CoverTiltTileFeatureConfig } from "./types";
|
||||||
|
|
||||||
|
export const supportsCoverTiltTileFeature = (stateObj: HassEntity) => {
|
||||||
|
const domain = computeDomain(stateObj.entity_id);
|
||||||
|
return (
|
||||||
|
domain === "cover" &&
|
||||||
|
(supportsFeature(stateObj, CoverEntityFeature.OPEN_TILT) ||
|
||||||
|
supportsFeature(stateObj, CoverEntityFeature.CLOSE_TILT))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("hui-cover-tilt-tile-feature")
|
@customElement("hui-cover-tilt-tile-feature")
|
||||||
class HuiCoverTiltTileFeature
|
class HuiCoverTiltTileFeature
|
||||||
extends LitElement
|
extends LitElement
|
||||||
@ -60,9 +70,14 @@ class HuiCoverTiltTileFeature
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult | null {
|
||||||
if (!this._config || !this.hass || !this.stateObj) {
|
if (
|
||||||
return html``;
|
!this._config ||
|
||||||
|
!this.hass ||
|
||||||
|
!this.stateObj ||
|
||||||
|
!supportsCoverTiltTileFeature
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
|
@ -1,13 +1,20 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { stateActive } from "../../../common/entity/state_active";
|
import { stateActive } from "../../../common/entity/state_active";
|
||||||
import "../../../components/tile/ha-tile-slider";
|
import "../../../components/tile/ha-tile-slider";
|
||||||
import { UNAVAILABLE } from "../../../data/entity";
|
import { UNAVAILABLE } from "../../../data/entity";
|
||||||
|
import { lightSupportsBrightness } from "../../../data/light";
|
||||||
import { HomeAssistant } from "../../../types";
|
import { HomeAssistant } from "../../../types";
|
||||||
import { LovelaceTileFeature } from "../types";
|
import { LovelaceTileFeature } from "../types";
|
||||||
import { LightBrightnessTileFeatureConfig } from "./types";
|
import { LightBrightnessTileFeatureConfig } from "./types";
|
||||||
|
|
||||||
|
export const supportsLightBrightnessTileFeature = (stateObj: HassEntity) => {
|
||||||
|
const domain = computeDomain(stateObj.entity_id);
|
||||||
|
return domain === "light" && lightSupportsBrightness(stateObj);
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("hui-light-brightness-tile-feature")
|
@customElement("hui-light-brightness-tile-feature")
|
||||||
class HuiLightBrightnessTileFeature
|
class HuiLightBrightnessTileFeature
|
||||||
extends LitElement
|
extends LitElement
|
||||||
@ -32,9 +39,14 @@ class HuiLightBrightnessTileFeature
|
|||||||
this._config = config;
|
this._config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult | null {
|
||||||
if (!this._config || !this.hass || !this.stateObj) {
|
if (
|
||||||
return html``;
|
!this._config ||
|
||||||
|
!this.hass ||
|
||||||
|
!this.stateObj ||
|
||||||
|
!supportsLightBrightnessTileFeature(this.stateObj)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const position =
|
const position =
|
||||||
|
@ -10,6 +10,7 @@ import {
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
import { HassEntity } from "home-assistant-js-websocket";
|
||||||
import { css, html, LitElement, TemplateResult } from "lit";
|
import { css, html, LitElement, TemplateResult } from "lit";
|
||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
|
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||||
import "../../../components/ha-control-button";
|
import "../../../components/ha-control-button";
|
||||||
import "../../../components/ha-control-button-group";
|
import "../../../components/ha-control-button-group";
|
||||||
@ -113,6 +114,14 @@ export const VACUUM_COMMANDS_BUTTONS: Record<
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const supportsVacuumCommandTileFeature = (stateObj: HassEntity) => {
|
||||||
|
const domain = computeDomain(stateObj.entity_id);
|
||||||
|
return (
|
||||||
|
domain === "vacuum" &&
|
||||||
|
VACUUM_COMMANDS.some((c) => supportsVacuumCommand(stateObj, c))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
@customElement("hui-vacuum-commands-tile-feature")
|
@customElement("hui-vacuum-commands-tile-feature")
|
||||||
class HuiVacuumCommandTileFeature
|
class HuiVacuumCommandTileFeature
|
||||||
extends LitElement
|
extends LitElement
|
||||||
@ -160,9 +169,14 @@ class HuiVacuumCommandTileFeature
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render(): TemplateResult {
|
protected render(): TemplateResult | null {
|
||||||
if (!this._config || !this.hass || !this.stateObj) {
|
if (
|
||||||
return html``;
|
!this._config ||
|
||||||
|
!this.hass ||
|
||||||
|
!this.stateObj ||
|
||||||
|
!supportsVacuumCommandTileFeature(this.stateObj)
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateObj = this.stateObj as VacuumEntity;
|
const stateObj = this.stateObj as VacuumEntity;
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
import { HassEntity } from "home-assistant-js-websocket";
|
|
||||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
|
||||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
|
||||||
import { CoverEntityFeature } from "../../../data/cover";
|
|
||||||
import { lightSupportsBrightness } from "../../../data/light";
|
|
||||||
import { supportsVacuumCommand } from "./hui-vacuum-commands-tile-feature";
|
|
||||||
import { LovelaceTileFeatureConfig, VACUUM_COMMANDS } from "./types";
|
|
||||||
|
|
||||||
type TileFeatureType = LovelaceTileFeatureConfig["type"];
|
|
||||||
export type SupportsTileFeature = (stateObj: HassEntity) => boolean;
|
|
||||||
|
|
||||||
const TILE_FEATURES_SUPPORT: Record<TileFeatureType, SupportsTileFeature> = {
|
|
||||||
"cover-open-close": (stateObj) =>
|
|
||||||
computeDomain(stateObj.entity_id) === "cover" &&
|
|
||||||
(supportsFeature(stateObj, CoverEntityFeature.OPEN) ||
|
|
||||||
supportsFeature(stateObj, CoverEntityFeature.CLOSE)),
|
|
||||||
"cover-tilt": (stateObj) =>
|
|
||||||
computeDomain(stateObj.entity_id) === "cover" &&
|
|
||||||
(supportsFeature(stateObj, CoverEntityFeature.OPEN_TILT) ||
|
|
||||||
supportsFeature(stateObj, CoverEntityFeature.CLOSE_TILT)),
|
|
||||||
"light-brightness": (stateObj) =>
|
|
||||||
computeDomain(stateObj.entity_id) === "light" &&
|
|
||||||
lightSupportsBrightness(stateObj),
|
|
||||||
"vacuum-commands": (stateObj) =>
|
|
||||||
computeDomain(stateObj.entity_id) === "vacuum" &&
|
|
||||||
VACUUM_COMMANDS.some((c) => supportsVacuumCommand(stateObj, c)),
|
|
||||||
};
|
|
||||||
|
|
||||||
const TILE_FEATURE_EDITABLE: Set<TileFeatureType> = new Set([
|
|
||||||
"vacuum-commands",
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const supportsTileFeature = (
|
|
||||||
stateObj: HassEntity,
|
|
||||||
feature: TileFeatureType
|
|
||||||
): boolean => {
|
|
||||||
const supportFunction = TILE_FEATURES_SUPPORT[feature] as
|
|
||||||
| SupportsTileFeature
|
|
||||||
| undefined;
|
|
||||||
return !supportFunction || supportFunction(stateObj);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isTileFeatureEditable = (feature: TileFeatureType): boolean =>
|
|
||||||
TILE_FEATURE_EDITABLE.has(feature);
|
|
@ -109,6 +109,7 @@ export interface LovelaceTileFeatureConstructor
|
|||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
stateObj?: HassEntity
|
stateObj?: HassEntity
|
||||||
) => LovelaceTileFeatureConfig;
|
) => LovelaceTileFeatureConfig;
|
||||||
|
isSupported?: (stateObj?: HassEntity) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LovelaceTileFeatureEditor
|
export interface LovelaceTileFeatureEditor
|
||||||
|
Loading…
x
Reference in New Issue
Block a user