mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-01 13:37:47 +00:00
commit
7b057eaa77
127
.github/workflows/ci.yaml
vendored
Normal file
127
.github/workflows/ci.yaml
vendored
Normal file
@ -0,0 +1,127 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- dev
|
||||
- master
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build icons
|
||||
run: ./node_modules/.bin/gulp gen-icons-hassio gen-icons-mdi gen-icons-app
|
||||
- name: Build translations
|
||||
run: ./node_modules/.bin/gulp build-translations
|
||||
- name: Run eslint
|
||||
run: ./node_modules/.bin/eslint src hassio/src gallery/src
|
||||
- name: Run tslint
|
||||
run: ./node_modules/.bin/tslint 'src/**/*.ts' 'hassio/src/**/*.ts' 'gallery/src/**/*.ts' 'cast/src/**/*.ts' 'test-mocha/**/*.ts'
|
||||
- name: Run tsc
|
||||
run: ./node_modules/.bin/tsc
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Run Mocha
|
||||
run: npm run mocha
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint, test]
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build Application
|
||||
run: ./node_modules/.bin/gulp build-app
|
||||
env:
|
||||
TRAVIS: "true"
|
||||
supervisor:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [lint, test]
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build Application
|
||||
run: ./node_modules/.bin/gulp build-hassio
|
||||
env:
|
||||
TRAVIS: "true"
|
39
.github/workflows/demo.yaml
vendored
Normal file
39
.github/workflows/demo.yaml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: Demo
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v2
|
||||
- name: Setting up Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
- name: Get yarn cache path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- name: Fetching Yarn cache
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
env:
|
||||
CI: true
|
||||
- name: Build Demo
|
||||
run: ./node_modules/.bin/gulp build-demo
|
||||
- name: Deploy to Netlify
|
||||
uses: netlify/actions/cli@master
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
|
||||
with:
|
||||
args: deploy --dir=demo/dist --prod
|
18
.travis.yml
18
.travis.yml
@ -1,18 +0,0 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- bower_components
|
||||
install: yarn install
|
||||
script:
|
||||
- npm run build
|
||||
- hassio/script/build_hassio
|
||||
# Because else eslint fails because hassio has cleaned that build
|
||||
- ./node_modules/.bin/gulp gen-icons-app
|
||||
- npm run test
|
||||
# - xvfb-run wct --module-resolution=node --npm
|
||||
# - 'if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct --module-resolution=node --npm --plugin sauce; fi'
|
||||
dist: trusty
|
||||
addons:
|
||||
sauce_connect: true
|
@ -15,6 +15,7 @@ import {
|
||||
import {
|
||||
LovelaceConfig,
|
||||
getLovelaceCollection,
|
||||
fetchResources,
|
||||
} from "../../../../src/data/lovelace";
|
||||
import "./hc-launch-screen";
|
||||
import { castContext } from "../cast_context";
|
||||
@ -23,6 +24,8 @@ import { ReceiverStatusMessage } from "../../../../src/cast/sender_messages";
|
||||
import { loadLovelaceResources } from "../../../../src/panels/lovelace/common/load-resources";
|
||||
import { isNavigationClick } from "../../../../src/common/dom/is-navigation-click";
|
||||
|
||||
let resourcesLoaded = false;
|
||||
|
||||
@customElement("hc-main")
|
||||
export class HcMain extends HassElement {
|
||||
@property() private _showDemo = false;
|
||||
@ -34,6 +37,7 @@ export class HcMain extends HassElement {
|
||||
@property() private _error?: string;
|
||||
|
||||
private _unsubLovelace?: UnsubscribeFunc;
|
||||
private _urlPath?: string | null;
|
||||
|
||||
public processIncomingMessage(msg: HassMessage) {
|
||||
if (msg.type === "connect") {
|
||||
@ -108,6 +112,7 @@ export class HcMain extends HassElement {
|
||||
if (this.hass) {
|
||||
status.hassUrl = this.hass.auth.data.hassUrl;
|
||||
status.lovelacePath = this._lovelacePath!;
|
||||
status.urlPath = this._urlPath;
|
||||
}
|
||||
|
||||
if (senderId) {
|
||||
@ -163,8 +168,19 @@ export class HcMain extends HassElement {
|
||||
this._error = "Cannot show Lovelace because we're not connected.";
|
||||
return;
|
||||
}
|
||||
if (!this._unsubLovelace) {
|
||||
const llColl = getLovelaceCollection(this.hass!.connection);
|
||||
if (!resourcesLoaded) {
|
||||
resourcesLoaded = true;
|
||||
loadLovelaceResources(
|
||||
await fetchResources(this.hass!.connection),
|
||||
this.hass!.auth.data.hassUrl
|
||||
);
|
||||
}
|
||||
if (!this._unsubLovelace || this._urlPath !== msg.urlPath) {
|
||||
this._urlPath = msg.urlPath;
|
||||
if (this._unsubLovelace) {
|
||||
this._unsubLovelace();
|
||||
}
|
||||
const llColl = getLovelaceCollection(this.hass!.connection, msg.urlPath);
|
||||
// We first do a single refresh because we need to check if there is LL
|
||||
// configuration.
|
||||
try {
|
||||
@ -194,12 +210,6 @@ export class HcMain extends HassElement {
|
||||
private _handleNewLovelaceConfig(lovelaceConfig: LovelaceConfig) {
|
||||
castContext.setApplicationState(lovelaceConfig.title!);
|
||||
this._lovelaceConfig = lovelaceConfig;
|
||||
if (lovelaceConfig.resources) {
|
||||
loadLovelaceResources(
|
||||
lovelaceConfig.resources,
|
||||
this.hass!.auth.data.hassUrl
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private _handleShowDemo(_msg: ShowDemoMessage) {
|
||||
|
@ -128,22 +128,27 @@ class HassioAddonAudio extends LitElement {
|
||||
|
||||
private _setInputDevice(ev): void {
|
||||
const device = ev.detail.item.getAttribute("device");
|
||||
this._selectedInput = device || null;
|
||||
this._selectedInput = device;
|
||||
}
|
||||
|
||||
private _setOutputDevice(ev): void {
|
||||
const device = ev.detail.item.getAttribute("device");
|
||||
this._selectedOutput = device || null;
|
||||
this._selectedOutput = device;
|
||||
}
|
||||
|
||||
private async _addonChanged(): Promise<void> {
|
||||
this._selectedInput = this.addon.audio_input;
|
||||
this._selectedOutput = this.addon.audio_output;
|
||||
this._selectedInput =
|
||||
this.addon.audio_input === null ? "default" : this.addon.audio_input;
|
||||
this._selectedOutput =
|
||||
this.addon.audio_output === null ? "default" : this.addon.audio_output;
|
||||
if (this._outputDevices) {
|
||||
return;
|
||||
}
|
||||
|
||||
const noDevice: HassioHardwareAudioDevice = { device: null, name: "-" };
|
||||
const noDevice: HassioHardwareAudioDevice = {
|
||||
device: "default",
|
||||
name: "Default",
|
||||
};
|
||||
|
||||
try {
|
||||
const { audio } = await fetchHassioHardwareAudio(this.hass);
|
||||
@ -168,8 +173,10 @@ class HassioAddonAudio extends LitElement {
|
||||
private async _saveSettings(): Promise<void> {
|
||||
this._error = undefined;
|
||||
const data: HassioAddonSetOptionParams = {
|
||||
audio_input: this._selectedInput || null,
|
||||
audio_output: this._selectedOutput || null,
|
||||
audio_input:
|
||||
this._selectedInput === "default" ? null : this._selectedInput,
|
||||
audio_output:
|
||||
this._selectedOutput === "default" ? null : this._selectedOutput,
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass, this.addon.slug, data);
|
||||
|
@ -452,7 +452,7 @@ class HassioAddonInfo extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
<ha-progress-button
|
||||
.disabled=${!this.addon.available}
|
||||
.disabled=${!this.addon.available || this._installing}
|
||||
.progress=${this._installing}
|
||||
@click=${this._installClicked}
|
||||
>
|
||||
|
@ -42,7 +42,9 @@ export class HassioUpdate extends LitElement {
|
||||
!!value &&
|
||||
(value.last_version
|
||||
? value.version !== value.last_version
|
||||
: value.version !== value.version_latest)
|
||||
: value.version_latest
|
||||
? value.version !== value.version_latest
|
||||
: false)
|
||||
);
|
||||
}).length;
|
||||
|
||||
@ -102,7 +104,7 @@ export class HassioUpdate extends LitElement {
|
||||
releaseNotesUrl: string,
|
||||
icon?: string
|
||||
): TemplateResult {
|
||||
if (lastVersion === curVersion) {
|
||||
if (!lastVersion || lastVersion === curVersion) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
|
@ -148,7 +148,7 @@ class HassioSupervisorInfo extends LitElement {
|
||||
!confirm(`WARNING:
|
||||
Beta releases are for testers and early adopters and can contain unstable code changes. Make sure you have backups of your data before you activate this feature.
|
||||
|
||||
This inludes beta releases for:
|
||||
This includes beta releases for:
|
||||
- Home Assistant (Release Candidates)
|
||||
- Hass.io supervisor
|
||||
- Host system`)
|
||||
|
2
setup.py
2
setup.py
@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
||||
|
||||
setup(
|
||||
name="home-assistant-frontend",
|
||||
version="20200220.5",
|
||||
version="20200228.0",
|
||||
description="The Home Assistant frontend",
|
||||
url="https://github.com/home-assistant/home-assistant-polymer",
|
||||
author="The Home Assistant Authors",
|
||||
|
@ -21,6 +21,7 @@ export interface ConnectMessage extends BaseCastMessage {
|
||||
export interface ShowLovelaceViewMessage extends BaseCastMessage {
|
||||
type: "show_lovelace_view";
|
||||
viewPath: string | number | null;
|
||||
urlPath: string | null;
|
||||
}
|
||||
|
||||
export interface ShowDemoMessage extends BaseCastMessage {
|
||||
@ -43,11 +44,13 @@ export const castSendAuth = (cast: CastManager, auth: Auth) =>
|
||||
|
||||
export const castSendShowLovelaceView = (
|
||||
cast: CastManager,
|
||||
viewPath: ShowLovelaceViewMessage["viewPath"]
|
||||
viewPath: ShowLovelaceViewMessage["viewPath"],
|
||||
urlPath?: string | null
|
||||
) =>
|
||||
cast.sendMessage({
|
||||
type: "show_lovelace_view",
|
||||
viewPath,
|
||||
urlPath: urlPath || null,
|
||||
});
|
||||
|
||||
export const castSendShowDemo = (cast: CastManager) =>
|
||||
|
@ -8,6 +8,7 @@ export interface ReceiverStatusMessage extends BaseCastMessage {
|
||||
showDemo: boolean;
|
||||
hassUrl?: string;
|
||||
lovelacePath?: string | number | null;
|
||||
urlPath?: string | null;
|
||||
}
|
||||
|
||||
export type SenderMessage = ReceiverStatusMessage;
|
||||
|
@ -4,7 +4,7 @@ export const dynamicElement = directive(
|
||||
(tag: string, properties?: { [key: string]: any }) => (part: Part): void => {
|
||||
if (!(part instanceof NodePart)) {
|
||||
throw new Error(
|
||||
"dynamicContentDirective can only be used in content bindings"
|
||||
"dynamicElementDirective can only be used in content bindings"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ const fixedIcons = {
|
||||
homeassistant: "hass:home-assistant",
|
||||
homekit: "hass:home-automation",
|
||||
image_processing: "hass:image-filter-frames",
|
||||
input_boolean: "hass:drawing",
|
||||
input_boolean: "hass:toggle-switch-outline",
|
||||
input_datetime: "hass:calendar-clock",
|
||||
input_number: "hass:ray-vertex",
|
||||
input_select: "hass:format-list-bulleted",
|
||||
|
@ -29,6 +29,10 @@ export const iconColorCSS = css`
|
||||
color: var(--heat-color, #ff8100);
|
||||
}
|
||||
|
||||
ha-icon[data-domain="climate"][data-state="drying"] {
|
||||
color: var(--dry-color, #efbd07);
|
||||
}
|
||||
|
||||
ha-icon[data-domain="alarm_control_panel"] {
|
||||
color: var(--alarm-color-armed, var(--label-badge-red));
|
||||
}
|
||||
|
@ -571,6 +571,18 @@ export class HaDataTable extends BaseElement {
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell--icon {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--icon:first-child ha-icon {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.mdc-data-table__cell--icon:first-child state-badge {
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell {
|
||||
font-family: Roboto, sans-serif;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
@ -598,10 +610,6 @@ export class HaDataTable extends BaseElement {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell--icon {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* custom from here */
|
||||
|
||||
:host {
|
||||
@ -615,27 +623,39 @@ export class HaDataTable extends BaseElement {
|
||||
}
|
||||
.mdc-data-table__header-cell {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.mdc-data-table__header-cell span {
|
||||
position: relative;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.mdc-data-table__header-cell.sortable {
|
||||
cursor: pointer;
|
||||
}
|
||||
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__header-cell--numeric):not(.mdc-data-table__header-cell--icon)
|
||||
span {
|
||||
position: relative;
|
||||
left: -24px;
|
||||
}
|
||||
.mdc-data-table__header-cell.not-sorted > * {
|
||||
.mdc-data-table__header-cell > * {
|
||||
transition: left 0.2s ease 0s;
|
||||
}
|
||||
.mdc-data-table__header-cell ha-icon {
|
||||
top: 15px;
|
||||
position: absolute;
|
||||
}
|
||||
.mdc-data-table__header-cell.not-sorted ha-icon {
|
||||
left: -36px;
|
||||
left: -20px;
|
||||
}
|
||||
.mdc-data-table__header-cell.not-sorted:not(.mdc-data-table__header-cell--numeric):not(.mdc-data-table__header-cell--icon):hover
|
||||
.mdc-data-table__header-cell:not(.not-sorted) span,
|
||||
.mdc-data-table__header-cell.not-sorted:hover span {
|
||||
left: 24px;
|
||||
}
|
||||
.mdc-data-table__header-cell.mdc-data-table__header-cell--numeric:not(.not-sorted)
|
||||
span,
|
||||
.mdc-data-table__header-cell.mdc-data-table__header-cell--numeric.not-sorted:hover
|
||||
span {
|
||||
left: 0px;
|
||||
left: 12px;
|
||||
}
|
||||
.mdc-data-table__header-cell:not(.not-sorted) ha-icon,
|
||||
.mdc-data-table__header-cell:hover.not-sorted ha-icon {
|
||||
left: 0px;
|
||||
left: 12px;
|
||||
}
|
||||
.table-header {
|
||||
border-bottom: 1px solid rgba(var(--rgb-primary-text-color), 0.12);
|
||||
|
@ -1,12 +1,23 @@
|
||||
import { customElement, CSSResult, css } from "lit-element";
|
||||
import { customElement, CSSResult, css, html } from "lit-element";
|
||||
import "@polymer/paper-icon-button/paper-icon-button";
|
||||
import "@material/mwc-dialog";
|
||||
import { style } from "@material/mwc-dialog/mwc-dialog-css";
|
||||
// tslint:disable-next-line
|
||||
import { Dialog } from "@material/mwc-dialog";
|
||||
import { Constructor } from "../types";
|
||||
import { Constructor, HomeAssistant } from "../types";
|
||||
// tslint:disable-next-line
|
||||
const MwcDialog = customElements.get("mwc-dialog") as Constructor<Dialog>;
|
||||
|
||||
export const createCloseHeading = (hass: HomeAssistant, title: string) => html`
|
||||
${title}
|
||||
<paper-icon-button
|
||||
aria-label=${hass.localize("ui.dialogs.generic.close")}
|
||||
icon="hass:close"
|
||||
dialogAction="close"
|
||||
class="close_button"
|
||||
></paper-icon-button>
|
||||
`;
|
||||
|
||||
@customElement("ha-dialog")
|
||||
export class HaDialog extends MwcDialog {
|
||||
protected static get styles(): CSSResult[] {
|
||||
@ -19,6 +30,15 @@ export class HaDialog extends MwcDialog {
|
||||
.mdc-dialog__container {
|
||||
align-items: var(--vertial-align-dialog, center);
|
||||
}
|
||||
.mdc-dialog__title::before {
|
||||
display: block;
|
||||
height: 20px;
|
||||
}
|
||||
.close_button {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
top: 12px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
65
src/components/ha-icon-input.ts
Normal file
65
src/components/ha-icon-input.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import {
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "./ha-icon";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
@customElement("ha-icon-input")
|
||||
export class HaIconInput extends LitElement {
|
||||
@property() public value?: string;
|
||||
@property() public label?: string;
|
||||
@property() public placeholder?: string;
|
||||
@property({ attribute: "error-message" }) public errorMessage?: string;
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<paper-input
|
||||
.value=${this.value}
|
||||
.label=${this.label}
|
||||
.placeholder=${this.placeholder}
|
||||
@value-changed=${this._valueChanged}
|
||||
.disabled=${this.disabled}
|
||||
auto-validate
|
||||
.errorMessage=${this.errorMessage}
|
||||
pattern="^\\S+:\\S+$"
|
||||
>
|
||||
${this.value || this.placeholder
|
||||
? html`
|
||||
<ha-icon .icon=${this.value || this.placeholder} slot="suffix">
|
||||
</ha-icon>
|
||||
`
|
||||
: ""}
|
||||
</paper-input>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this.value = ev.detail.value;
|
||||
fireEvent(
|
||||
this,
|
||||
"value-changed",
|
||||
{ value: ev.detail.value },
|
||||
{
|
||||
bubbles: false,
|
||||
composed: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-icon {
|
||||
position: relative;
|
||||
bottom: 4px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
@ -70,9 +70,7 @@ export class HaRelatedItems extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
if (Object.keys(this._related).length === 0) {
|
||||
return html`
|
||||
<p>
|
||||
${this.hass.localize("ui.components.related-items.no_related_found")}
|
||||
</p>
|
||||
${this.hass.localize("ui.components.related-items.no_related_found")}
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
|
@ -46,7 +46,18 @@ const SORT_VALUE_URL_PATHS = {
|
||||
config: 11,
|
||||
};
|
||||
|
||||
const panelSorter = (a, b) => {
|
||||
const panelSorter = (a: PanelInfo, b: PanelInfo) => {
|
||||
// Put all the Lovelace at the top.
|
||||
const aLovelace = a.component_name === "lovelace";
|
||||
const bLovelace = b.component_name === "lovelace";
|
||||
|
||||
if (aLovelace && !bLovelace) {
|
||||
return -1;
|
||||
}
|
||||
if (bLovelace) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const aBuiltIn = a.url_path in SORT_VALUE_URL_PATHS;
|
||||
const bBuiltIn = b.url_path in SORT_VALUE_URL_PATHS;
|
||||
|
||||
|
@ -6,6 +6,13 @@ import { afterNextRender } from "../common/util/render-status";
|
||||
// tslint:disable-next-line
|
||||
import { HaCodeEditor } from "./ha-code-editor";
|
||||
|
||||
declare global {
|
||||
// for fire event
|
||||
interface HASSDomEvents {
|
||||
"editor-refreshed": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const isEmpty = (obj: object) => {
|
||||
if (typeof obj !== "object") {
|
||||
return false;
|
||||
@ -37,6 +44,7 @@ export class HaYamlEditor extends LitElement {
|
||||
if (this._editor?.codemirror) {
|
||||
this._editor.codemirror.refresh();
|
||||
}
|
||||
afterNextRender(() => fireEvent(this, "editor-refreshed"));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -6,14 +6,23 @@ import { debounce } from "../common/util/debounce";
|
||||
export interface EntityRegistryEntry {
|
||||
entity_id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
platform: string;
|
||||
config_entry_id?: string;
|
||||
device_id?: string;
|
||||
disabled_by: string | null;
|
||||
}
|
||||
|
||||
export interface ExtEntityRegistryEntry extends EntityRegistryEntry {
|
||||
unique_id: string;
|
||||
capabilities: object;
|
||||
original_name?: string;
|
||||
original_icon?: string;
|
||||
}
|
||||
|
||||
export interface EntityRegistryEntryUpdateParams {
|
||||
name?: string | null;
|
||||
icon?: string | null;
|
||||
disabled_by?: string | null;
|
||||
new_entity_id?: string;
|
||||
}
|
||||
@ -29,12 +38,21 @@ export const computeEntityRegistryName = (
|
||||
return state ? computeStateName(state) : null;
|
||||
};
|
||||
|
||||
export const getExtendedEntityRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string
|
||||
): Promise<ExtEntityRegistryEntry> =>
|
||||
hass.callWS({
|
||||
type: "config/entity_registry/get",
|
||||
entity_id: entityId,
|
||||
});
|
||||
|
||||
export const updateEntityRegistryEntry = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
updates: Partial<EntityRegistryEntryUpdateParams>
|
||||
): Promise<EntityRegistryEntry> =>
|
||||
hass.callWS<EntityRegistryEntry>({
|
||||
): Promise<ExtEntityRegistryEntry> =>
|
||||
hass.callWS({
|
||||
type: "config/entity_registry/update",
|
||||
entity_id: entityId,
|
||||
...updates,
|
||||
|
@ -59,3 +59,12 @@ export const getOptimisticFrontendUserDataCollection = <
|
||||
`_frontendUserData-${userDataKey}`,
|
||||
() => fetchFrontendUserData(conn, userDataKey)
|
||||
);
|
||||
|
||||
export const subscribeFrontendUserData = <UserDataKey extends ValidUserDataKey>(
|
||||
conn: Connection,
|
||||
userDataKey: UserDataKey,
|
||||
onChange: (state: FrontendUserData[UserDataKey] | null) => void
|
||||
) =>
|
||||
getOptimisticFrontendUserDataCollection(conn, userDataKey).subscribe(
|
||||
onChange
|
||||
);
|
||||
|
@ -1,11 +0,0 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export const setInputSelectOption = (
|
||||
hass: HomeAssistant,
|
||||
entity: string,
|
||||
option: string
|
||||
) =>
|
||||
hass.callService("input_select", "select_option", {
|
||||
option,
|
||||
entity_id: entity,
|
||||
});
|
43
src/data/input_boolean.ts
Normal file
43
src/data/input_boolean.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputBoolean {
|
||||
id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
initial?: boolean;
|
||||
}
|
||||
|
||||
export interface InputBooleanMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: boolean;
|
||||
}
|
||||
|
||||
export const fetchInputBoolean = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputBoolean[]>({ type: "input_boolean/list" });
|
||||
|
||||
export const createInputBoolean = (
|
||||
hass: HomeAssistant,
|
||||
values: InputBooleanMutableParams
|
||||
) =>
|
||||
hass.callWS<InputBoolean>({
|
||||
type: "input_boolean/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputBoolean = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputBooleanMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputBoolean>({
|
||||
type: "input_boolean/update",
|
||||
input_boolean_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputBoolean = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_boolean/delete",
|
||||
input_boolean_id: id,
|
||||
});
|
@ -1,5 +1,22 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputDateTime {
|
||||
id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
initial?: string;
|
||||
has_time: boolean;
|
||||
has_date: boolean;
|
||||
}
|
||||
|
||||
export interface InputDateTimeMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: string;
|
||||
has_time: boolean;
|
||||
has_date: boolean;
|
||||
}
|
||||
|
||||
export const setInputDateTimeValue = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string,
|
||||
@ -9,3 +26,32 @@ export const setInputDateTimeValue = (
|
||||
const param = { entity_id: entityId, time, date };
|
||||
hass.callService(entityId.split(".", 1)[0], "set_datetime", param);
|
||||
};
|
||||
|
||||
export const fetchInputDateTime = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputDateTime[]>({ type: "input_datetime/list" });
|
||||
|
||||
export const createInputDateTime = (
|
||||
hass: HomeAssistant,
|
||||
values: InputDateTimeMutableParams
|
||||
) =>
|
||||
hass.callWS<InputDateTime>({
|
||||
type: "input_datetime/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputDateTime = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputDateTimeMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputDateTime>({
|
||||
type: "input_datetime/update",
|
||||
input_datetime_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputDateTime = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_datetime/delete",
|
||||
input_datetime_id: id,
|
||||
});
|
||||
|
53
src/data/input_number.ts
Normal file
53
src/data/input_number.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputNumber {
|
||||
id: string;
|
||||
name: string;
|
||||
min: number;
|
||||
max: number;
|
||||
icon?: string;
|
||||
initial?: number;
|
||||
step?: number;
|
||||
mode?: "box" | "slider";
|
||||
unit_of_measurement?: string;
|
||||
}
|
||||
|
||||
export interface InputNumberMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
mode: "box" | "slider";
|
||||
unit_of_measurement?: string;
|
||||
}
|
||||
|
||||
export const fetchInputNumber = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputNumber[]>({ type: "input_number/list" });
|
||||
|
||||
export const createInputNumber = (
|
||||
hass: HomeAssistant,
|
||||
values: InputNumberMutableParams
|
||||
) =>
|
||||
hass.callWS<InputNumber>({
|
||||
type: "input_number/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputNumber = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputNumberMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputNumber>({
|
||||
type: "input_number/update",
|
||||
input_number_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputNumber = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_number/delete",
|
||||
input_number_id: id,
|
||||
});
|
55
src/data/input_select.ts
Normal file
55
src/data/input_select.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputSelect {
|
||||
id: string;
|
||||
name: string;
|
||||
options: string[];
|
||||
icon?: string;
|
||||
initial?: string;
|
||||
}
|
||||
|
||||
export interface InputSelectMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: string;
|
||||
options: string[];
|
||||
}
|
||||
|
||||
export const setInputSelectOption = (
|
||||
hass: HomeAssistant,
|
||||
entity: string,
|
||||
option: string
|
||||
) =>
|
||||
hass.callService("input_select", "select_option", {
|
||||
option,
|
||||
entity_id: entity,
|
||||
});
|
||||
|
||||
export const fetchInputSelect = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputSelect[]>({ type: "input_select/list" });
|
||||
|
||||
export const createInputSelect = (
|
||||
hass: HomeAssistant,
|
||||
values: InputSelectMutableParams
|
||||
) =>
|
||||
hass.callWS<InputSelect>({
|
||||
type: "input_select/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputSelect = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputSelectMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputSelect>({
|
||||
type: "input_select/update",
|
||||
input_select_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputSelect = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_select/delete",
|
||||
input_select_id: id,
|
||||
});
|
@ -1,7 +1,57 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface InputText {
|
||||
id: string;
|
||||
name: string;
|
||||
icon?: string;
|
||||
initial?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
pattern?: string;
|
||||
mode?: "text" | "password";
|
||||
}
|
||||
|
||||
export interface InputTextMutableParams {
|
||||
name: string;
|
||||
icon: string;
|
||||
initial: string;
|
||||
min: number;
|
||||
max: number;
|
||||
pattern: string;
|
||||
mode: "text" | "password";
|
||||
}
|
||||
|
||||
export const setValue = (hass: HomeAssistant, entity: string, value: string) =>
|
||||
hass.callService(entity.split(".", 1)[0], "set_value", {
|
||||
value,
|
||||
entity_id: entity,
|
||||
});
|
||||
|
||||
export const fetchInputText = (hass: HomeAssistant) =>
|
||||
hass.callWS<InputText[]>({ type: "input_text/list" });
|
||||
|
||||
export const createInputText = (
|
||||
hass: HomeAssistant,
|
||||
values: InputTextMutableParams
|
||||
) =>
|
||||
hass.callWS<InputText>({
|
||||
type: "input_text/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateInputText = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<InputTextMutableParams>
|
||||
) =>
|
||||
hass.callWS<InputText>({
|
||||
type: "input_text/update",
|
||||
input_text_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteInputText = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "input_text/delete",
|
||||
input_text_id: id,
|
||||
});
|
||||
|
@ -1,12 +1,57 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
import { Connection, getCollection } from "home-assistant-js-websocket";
|
||||
import {
|
||||
Connection,
|
||||
getCollection,
|
||||
HassEventBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { HASSDomEvent } from "../common/dom/fire_event";
|
||||
|
||||
export interface LovelaceConfig {
|
||||
title?: string;
|
||||
views: LovelaceViewConfig[];
|
||||
background?: string;
|
||||
resources?: Array<{ type: "css" | "js" | "module" | "html"; url: string }>;
|
||||
}
|
||||
|
||||
export interface LovelaceResource {
|
||||
id: string;
|
||||
type: "css" | "js" | "module" | "html";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface LovelaceResourcesMutableParams {
|
||||
res_type: "css" | "js" | "module" | "html";
|
||||
url: string;
|
||||
}
|
||||
|
||||
export type LovelaceDashboard =
|
||||
| LovelaceYamlDashboard
|
||||
| LovelaceStorageDashboard;
|
||||
|
||||
interface LovelaceGenericDashboard {
|
||||
id: string;
|
||||
url_path: string;
|
||||
require_admin: boolean;
|
||||
sidebar?: { icon: string; title: string };
|
||||
}
|
||||
|
||||
export interface LovelaceYamlDashboard extends LovelaceGenericDashboard {
|
||||
mode: "yaml";
|
||||
filename: string;
|
||||
}
|
||||
|
||||
export interface LovelaceStorageDashboard extends LovelaceGenericDashboard {
|
||||
mode: "storage";
|
||||
}
|
||||
|
||||
export interface LovelaceDashboardMutableParams {
|
||||
require_admin: boolean;
|
||||
sidebar: { icon: string; title: string } | null;
|
||||
}
|
||||
|
||||
export interface LovelaceDashboardCreateParams
|
||||
extends LovelaceDashboardMutableParams {
|
||||
url_path: string;
|
||||
mode: "storage";
|
||||
}
|
||||
|
||||
export interface LovelaceViewConfig {
|
||||
@ -95,47 +140,139 @@ export type ActionConfig =
|
||||
| NoActionConfig
|
||||
| CustomActionConfig;
|
||||
|
||||
type LovelaceUpdatedEvent = HassEventBase & {
|
||||
event_type: "lovelace_updated";
|
||||
data: {
|
||||
url_path: string | null;
|
||||
mode: "yaml" | "storage";
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchResources = (conn: Connection): Promise<LovelaceResource[]> =>
|
||||
conn.sendMessagePromise({
|
||||
type: "lovelace/resources",
|
||||
});
|
||||
|
||||
export const createResource = (
|
||||
hass: HomeAssistant,
|
||||
values: LovelaceResourcesMutableParams
|
||||
) =>
|
||||
hass.callWS<LovelaceResource>({
|
||||
type: "lovelace/resources/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateResource = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<LovelaceResourcesMutableParams>
|
||||
) =>
|
||||
hass.callWS<LovelaceResource>({
|
||||
type: "lovelace/resources/update",
|
||||
resource_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteResource = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "lovelace/resources/delete",
|
||||
resource_id: id,
|
||||
});
|
||||
|
||||
export const fetchDashboards = (
|
||||
hass: HomeAssistant
|
||||
): Promise<LovelaceDashboard[]> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/dashboards/list",
|
||||
});
|
||||
|
||||
export const createDashboard = (
|
||||
hass: HomeAssistant,
|
||||
values: LovelaceDashboardCreateParams
|
||||
) =>
|
||||
hass.callWS<LovelaceDashboard>({
|
||||
type: "lovelace/dashboards/create",
|
||||
...values,
|
||||
});
|
||||
|
||||
export const updateDashboard = (
|
||||
hass: HomeAssistant,
|
||||
id: string,
|
||||
updates: Partial<LovelaceDashboardMutableParams>
|
||||
) =>
|
||||
hass.callWS<LovelaceDashboard>({
|
||||
type: "lovelace/dashboards/update",
|
||||
dashboard_id: id,
|
||||
...updates,
|
||||
});
|
||||
|
||||
export const deleteDashboard = (hass: HomeAssistant, id: string) =>
|
||||
hass.callWS({
|
||||
type: "lovelace/dashboards/delete",
|
||||
dashboard_id: id,
|
||||
});
|
||||
|
||||
export const fetchConfig = (
|
||||
conn: Connection,
|
||||
urlPath: string | null,
|
||||
force: boolean
|
||||
): Promise<LovelaceConfig> =>
|
||||
conn.sendMessagePromise({
|
||||
type: "lovelace/config",
|
||||
url_path: urlPath,
|
||||
force,
|
||||
});
|
||||
|
||||
export const saveConfig = (
|
||||
hass: HomeAssistant,
|
||||
urlPath: string | null,
|
||||
config: LovelaceConfig
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/save",
|
||||
url_path: urlPath,
|
||||
config,
|
||||
});
|
||||
|
||||
export const deleteConfig = (hass: HomeAssistant): Promise<void> =>
|
||||
export const deleteConfig = (
|
||||
hass: HomeAssistant,
|
||||
urlPath: string | null
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "lovelace/config/delete",
|
||||
url_path: urlPath,
|
||||
});
|
||||
|
||||
export const subscribeLovelaceUpdates = (
|
||||
conn: Connection,
|
||||
urlPath: string | null,
|
||||
onChange: () => void
|
||||
) => conn.subscribeEvents(onChange, "lovelace_updated");
|
||||
) =>
|
||||
conn.subscribeEvents<LovelaceUpdatedEvent>((ev) => {
|
||||
if (ev.data.url_path === urlPath) {
|
||||
onChange();
|
||||
}
|
||||
}, "lovelace_updated");
|
||||
|
||||
export const getLovelaceCollection = (conn: Connection) =>
|
||||
export const getLovelaceCollection = (
|
||||
conn: Connection,
|
||||
urlPath: string | null = null
|
||||
) =>
|
||||
getCollection(
|
||||
conn,
|
||||
"_lovelace",
|
||||
(conn2) => fetchConfig(conn2, false),
|
||||
`_lovelace_${urlPath ?? ""}`,
|
||||
(conn2) => fetchConfig(conn2, urlPath, false),
|
||||
(_conn, store) =>
|
||||
subscribeLovelaceUpdates(conn, () =>
|
||||
fetchConfig(conn, false).then((config) => store.setState(config, true))
|
||||
subscribeLovelaceUpdates(conn, urlPath, () =>
|
||||
fetchConfig(conn, urlPath, false).then((config) =>
|
||||
store.setState(config, true)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
export interface WindowWithLovelaceProm extends Window {
|
||||
llConfProm?: Promise<LovelaceConfig>;
|
||||
llResProm?: Promise<LovelaceResource[]>;
|
||||
}
|
||||
|
||||
export interface ActionHandlerOptions {
|
||||
|
@ -1 +1,2 @@
|
||||
export const SENSOR_DEVICE_CLASS_BATTERY = "battery";
|
||||
export const SENSOR_DEVICE_CLASS_TIMESTAMP = "timestamp";
|
||||
|
@ -129,6 +129,10 @@ class DialogBox extends LitElement {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
:host([inert]) {
|
||||
pointer-events: initial !important;
|
||||
cursor: initial !important;
|
||||
}
|
||||
ha-paper-dialog {
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
|
@ -8,7 +8,6 @@ import "../resources/ha-style";
|
||||
import "./more-info/more-info-controls";
|
||||
|
||||
import { computeStateDomain } from "../common/entity/compute_state_domain";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
|
||||
import DialogMixin from "../mixins/dialog-mixin";
|
||||
|
||||
@ -81,7 +80,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
hass="[[hass]]"
|
||||
state-obj="[[stateObj]]"
|
||||
dialog-element="[[_dialogElement()]]"
|
||||
registry-entry="[[_registryInfo]]"
|
||||
large="{{large}}"
|
||||
></more-info-controls>
|
||||
`;
|
||||
@ -102,8 +100,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
observer: "_largeChanged",
|
||||
},
|
||||
|
||||
_registryInfo: Object,
|
||||
|
||||
dataDomain: {
|
||||
computed: "_computeDomain(stateObj)",
|
||||
reflectToAttribute: true,
|
||||
@ -127,11 +123,10 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
return hass.states[hass.moreInfoEntityId] || null;
|
||||
}
|
||||
|
||||
async _stateObjChanged(newVal, oldVal) {
|
||||
async _stateObjChanged(newVal) {
|
||||
if (!newVal) {
|
||||
this.setProperties({
|
||||
opened: false,
|
||||
_registryInfo: null,
|
||||
large: false,
|
||||
});
|
||||
return;
|
||||
@ -144,25 +139,6 @@ class HaMoreInfoDialog extends DialogMixin(PolymerElement) {
|
||||
this.opened = true;
|
||||
})
|
||||
);
|
||||
|
||||
if (
|
||||
!isComponentLoaded(this.hass, "config") ||
|
||||
(oldVal && oldVal.entity_id === newVal.entity_id)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.hass.user.is_admin) {
|
||||
try {
|
||||
const info = await this.hass.callWS({
|
||||
type: "config/entity_registry/get",
|
||||
entity_id: newVal.entity_id,
|
||||
});
|
||||
this._registryInfo = info;
|
||||
} catch (err) {
|
||||
this._registryInfo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_dialogOpenChanged(newVal) {
|
||||
|
@ -45,7 +45,7 @@ class MoreInfoCamera extends LitElement {
|
||||
|
||||
return html`
|
||||
<ha-camera-stream
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.stateObj="${this.stateObj}"
|
||||
showcontrols
|
||||
></ha-camera-stream>
|
||||
|
@ -39,7 +39,8 @@ class MoreInfoPerson extends LitElement {
|
||||
></ha-map>
|
||||
`
|
||||
: ""}
|
||||
${this.hass.user?.is_admin &&
|
||||
${!__DEMO__ &&
|
||||
this.hass.user?.is_admin &&
|
||||
this.stateObj.state === "not_home" &&
|
||||
this.stateObj.attributes.latitude &&
|
||||
this.stateObj.attributes.longitude
|
||||
|
@ -22,7 +22,7 @@ import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import { removeEntityRegistryEntry } from "../../data/entity_registry";
|
||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import { showEntityRegistryDetailDialog } from "../../panels/config/entities/show-dialog-entity-registry-detail";
|
||||
import { showEntityEditorDialog } from "../../panels/config/entities/show-dialog-entity-editor";
|
||||
|
||||
const DOMAINS_NO_INFO = ["camera", "configurator", "history_graph"];
|
||||
const EDITABLE_DOMAINS_WITH_ID = ["scene", "automation"];
|
||||
@ -88,7 +88,7 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
<div class="main-title" main-title="" on-click="enlarge">
|
||||
[[_computeStateName(stateObj)]]
|
||||
</div>
|
||||
<template is="dom-if" if="[[registryEntry]]">
|
||||
<template is="dom-if" if="[[_computeConfig(hass)]]">
|
||||
<paper-icon-button
|
||||
aria-label$="[[localize('ui.dialogs.more_info_control.settings')]]"
|
||||
icon="hass:settings"
|
||||
@ -221,6 +221,10 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
return stateObj ? computeStateName(stateObj) : "";
|
||||
}
|
||||
|
||||
_computeConfig(hass) {
|
||||
return hass.user.is_admin && isComponentLoaded(hass, "config");
|
||||
}
|
||||
|
||||
_computeEdit(hass, stateObj) {
|
||||
const domain = this._computeDomain(stateObj);
|
||||
return (
|
||||
@ -260,7 +264,9 @@ class MoreInfoControls extends LocalizeMixin(EventsMixin(PolymerElement)) {
|
||||
}
|
||||
|
||||
_gotoSettings() {
|
||||
showEntityRegistryDetailDialog(this, { entry: this.registryEntry });
|
||||
showEntityEditorDialog(this, {
|
||||
entity_id: this.stateObj.entity_id,
|
||||
});
|
||||
this.fire("hass-more-info", { entityId: null });
|
||||
}
|
||||
|
||||
|
@ -36,13 +36,13 @@ export class HuiNotificationItem extends LitElement {
|
||||
return "entity_id" in this.notification
|
||||
? html`
|
||||
<configurator-notification-item
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.notification="${this.notification}"
|
||||
></configurator-notification-item>
|
||||
`
|
||||
: html`
|
||||
<persistent-notification-item
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.notification="${this.notification}"
|
||||
></persistent-notification-item>
|
||||
`;
|
||||
|
@ -39,7 +39,7 @@ export class HuiPersistentNotificationItem extends LitElement {
|
||||
<div class="time">
|
||||
<span>
|
||||
<ha-relative-time
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.datetime="${this.notification.created_at}"
|
||||
></ha-relative-time>
|
||||
<paper-tooltip
|
||||
|
@ -16,7 +16,12 @@ import { subscribeThemes } from "../data/ws-themes";
|
||||
import { subscribeUser } from "../data/ws-user";
|
||||
import { HomeAssistant } from "../types";
|
||||
import { hassUrl } from "../data/auth";
|
||||
import { fetchConfig, WindowWithLovelaceProm } from "../data/lovelace";
|
||||
import { subscribeFrontendUserData } from "../data/frontend";
|
||||
import {
|
||||
fetchConfig,
|
||||
fetchResources,
|
||||
WindowWithLovelaceProm,
|
||||
} from "../data/lovelace";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -84,9 +89,15 @@ window.hassConnection.then(({ conn }) => {
|
||||
subscribePanels(conn, noop);
|
||||
subscribeThemes(conn, noop);
|
||||
subscribeUser(conn, noop);
|
||||
subscribeFrontendUserData(conn, "core", noop);
|
||||
|
||||
if (location.pathname === "/" || location.pathname.startsWith("/lovelace/")) {
|
||||
(window as WindowWithLovelaceProm).llConfProm = fetchConfig(conn, false);
|
||||
(window as WindowWithLovelaceProm).llConfProm = fetchConfig(
|
||||
conn,
|
||||
null,
|
||||
false
|
||||
);
|
||||
(window as WindowWithLovelaceProm).llResProm = fetchResources(conn);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -15,6 +15,7 @@ import { Route, HomeAssistant } from "../types";
|
||||
import { navigate } from "../common/navigate";
|
||||
import "@material/mwc-ripple";
|
||||
import { isComponentLoaded } from "../common/config/is_component_loaded";
|
||||
import memoizeOne from "memoize-one";
|
||||
|
||||
export interface PageNavigation {
|
||||
path: string;
|
||||
@ -22,7 +23,7 @@ export interface PageNavigation {
|
||||
component?: string;
|
||||
name?: string;
|
||||
core?: boolean;
|
||||
exportOnly?: boolean;
|
||||
advancedOnly?: boolean;
|
||||
icon?: string;
|
||||
info?: any;
|
||||
}
|
||||
@ -33,12 +34,57 @@ class HassTabsSubpage extends LitElement {
|
||||
@property({ type: String, attribute: "back-path" }) public backPath?: string;
|
||||
@property() public backCallback?: () => void;
|
||||
@property({ type: Boolean }) public hassio = false;
|
||||
@property({ type: Boolean }) public showAdvanced = false;
|
||||
@property() public route!: Route;
|
||||
@property() public tabs!: PageNavigation[];
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
@property() private _activeTab: number = -1;
|
||||
|
||||
private _getTabs = memoizeOne(
|
||||
(
|
||||
tabs: PageNavigation[],
|
||||
activeTab: number,
|
||||
showAdvanced: boolean | undefined,
|
||||
_components,
|
||||
_language
|
||||
) => {
|
||||
const shownTabs = tabs.filter(
|
||||
(page) =>
|
||||
(!page.component ||
|
||||
page.core ||
|
||||
isComponentLoaded(this.hass, page.component)) &&
|
||||
(!page.advancedOnly || showAdvanced)
|
||||
);
|
||||
|
||||
return shownTabs.map(
|
||||
(page, index) => html`
|
||||
<div
|
||||
class="tab ${classMap({
|
||||
active: index === activeTab,
|
||||
})}"
|
||||
@click=${this._tabTapped}
|
||||
.path=${page.path}
|
||||
>
|
||||
${this.narrow
|
||||
? html`
|
||||
<ha-icon .icon=${page.icon}></ha-icon>
|
||||
`
|
||||
: ""}
|
||||
${!this.narrow || index === activeTab
|
||||
? html`
|
||||
<span class="name"
|
||||
>${page.translationKey
|
||||
? this.hass.localize(page.translationKey)
|
||||
: name}</span
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
<mwc-ripple></mwc-ripple>
|
||||
</div>
|
||||
`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("route")) {
|
||||
@ -49,6 +95,14 @@ class HassTabsSubpage extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const tabs = this._getTabs(
|
||||
this.tabs,
|
||||
this._activeTab,
|
||||
this.hass.userData?.showAdvanced,
|
||||
this.hass.config.components,
|
||||
this.hass.language
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="toolbar">
|
||||
<ha-paper-icon-button-arrow-prev
|
||||
@ -61,41 +115,13 @@ class HassTabsSubpage extends LitElement {
|
||||
<div main-title><slot name="header"></slot></div>
|
||||
`
|
||||
: ""}
|
||||
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
|
||||
${this.tabs.map((page, index) =>
|
||||
(!page.component ||
|
||||
page.core ||
|
||||
isComponentLoaded(this.hass, page.component)) &&
|
||||
(!page.exportOnly || this.showAdvanced)
|
||||
? html`
|
||||
<div
|
||||
class="tab ${classMap({
|
||||
active: index === this._activeTab,
|
||||
})}"
|
||||
@click=${this._tabTapped}
|
||||
.path=${page.path}
|
||||
>
|
||||
${this.narrow
|
||||
? html`
|
||||
<ha-icon .icon=${page.icon}></ha-icon>
|
||||
`
|
||||
: ""}
|
||||
${!this.narrow || index === this._activeTab
|
||||
? html`
|
||||
<span class="name"
|
||||
>${page.translationKey
|
||||
? this.hass.localize(page.translationKey)
|
||||
: name}</span
|
||||
>
|
||||
`
|
||||
: ""}
|
||||
<mwc-ripple></mwc-ripple>
|
||||
</div>
|
||||
`
|
||||
: ""
|
||||
)}
|
||||
</div>
|
||||
|
||||
${tabs.length > 1 || !this.narrow
|
||||
? html`
|
||||
<div id="tabbar" class=${classMap({ "bottom-bar": this.narrow })}>
|
||||
${tabs}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div id="toolbar-icon">
|
||||
<slot name="toolbar-icon"></slot>
|
||||
</div>
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
} from "./hass-router-page";
|
||||
import { removeInitSkeleton } from "../util/init-skeleton";
|
||||
|
||||
const CACHE_COMPONENTS = ["lovelace", "states", "developer-tools"];
|
||||
const CACHE_URL_PATHS = ["lovelace", "states", "developer-tools"];
|
||||
const COMPONENTS = {
|
||||
calendar: () =>
|
||||
import(
|
||||
@ -69,11 +69,10 @@ const COMPONENTS = {
|
||||
|
||||
const getRoutes = (panels: Panels): RouterOptions => {
|
||||
const routes: RouterOptions["routes"] = {};
|
||||
|
||||
Object.values(panels).forEach((panel) => {
|
||||
const data: RouteOptions = {
|
||||
tag: `ha-panel-${panel.component_name}`,
|
||||
cache: CACHE_COMPONENTS.includes(panel.component_name),
|
||||
cache: CACHE_URL_PATHS.includes(panel.url_path),
|
||||
};
|
||||
if (panel.component_name in COMPONENTS) {
|
||||
data.load = COMPONENTS[panel.component_name];
|
||||
|
@ -82,6 +82,10 @@ class NotificationManager extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
mwc-button {
|
||||
color: var(--primary-color);
|
||||
font-weight: bold;
|
||||
|
@ -26,7 +26,7 @@ export default class HaNumericStateCondition extends LitElement {
|
||||
<ha-entity-picker
|
||||
.value="${entity_id}"
|
||||
@value-changed="${this._entityPicked}"
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<paper-input
|
||||
|
@ -42,7 +42,7 @@ export default class HaNumericStateTrigger extends LitElement {
|
||||
<ha-entity-picker
|
||||
.value="${entity_id}"
|
||||
@value-changed="${this._entityPicked}"
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
allow-custom-entity
|
||||
></ha-entity-picker>
|
||||
<paper-input
|
||||
|
@ -118,7 +118,7 @@ export class CloudGooglePref extends LitElement {
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-api-button
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.disabled="${!google_enabled}"
|
||||
@hass-api-called=${this._syncEntitiesCalled}
|
||||
path="cloud/google_actions/sync"
|
||||
|
@ -31,7 +31,7 @@ class HaConfigNavigation extends LitElement {
|
||||
(!page.component ||
|
||||
page.core ||
|
||||
isComponentLoaded(this.hass, page.component)) &&
|
||||
(!page.exportOnly || this.showAdvanced)
|
||||
(!page.advancedOnly || this.showAdvanced)
|
||||
? html`
|
||||
<a
|
||||
href=${`/config/${page.component}`}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
property,
|
||||
@ -22,6 +21,7 @@ import {
|
||||
fetchDeviceConditions,
|
||||
fetchDeviceActions,
|
||||
} from "../../../../data/device_automation";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
|
||||
@customElement("dialog-device-automation")
|
||||
export class DialogDeviceAutomation extends LitElement {
|
||||
@ -129,16 +129,7 @@ export class DialogDeviceAutomation extends LitElement {
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-dialog {
|
||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
||||
}
|
||||
@media only screen and (min-width: 600px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 600px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
return haStyleDialog;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ import "@polymer/paper-item/paper-item-body";
|
||||
|
||||
import "../../../../components/ha-card";
|
||||
import "../../../../components/ha-icon";
|
||||
import { showEntityRegistryDetailDialog } from "../../entities/show-dialog-entity-registry-detail";
|
||||
import { showEntityEditorDialog } from "../../entities/show-dialog-entity-editor";
|
||||
import { computeDomain } from "../../../../common/entity/compute_domain";
|
||||
import { domainIcon } from "../../../../common/entity/domain_icon";
|
||||
import { EntityRegistryStateEntry } from "../ha-config-device-page";
|
||||
@ -150,7 +150,7 @@ export class HaDeviceEntitiesCard extends LitElement {
|
||||
private _openEditEntry(ev: Event): void {
|
||||
ev.stopPropagation();
|
||||
const entry = (ev.currentTarget! as any).entry;
|
||||
showEntityRegistryDetailDialog(this, {
|
||||
showEntityEditorDialog(this, {
|
||||
entry,
|
||||
entity_id: entry.entity_id,
|
||||
});
|
||||
|
@ -6,8 +6,6 @@ import {
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import {
|
||||
@ -253,17 +251,6 @@ export class HaConfigDeviceDashboard extends LitElement {
|
||||
const deviceId = (ev.detail as RowClickedEvent).id;
|
||||
navigate(this, `/config/devices/device/${deviceId}`);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
.content {
|
||||
padding: 4px;
|
||||
}
|
||||
ha-devices-data-table {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@ -1,5 +1,3 @@
|
||||
import "@polymer/app-route/app-route";
|
||||
|
||||
import "./ha-config-devices-dashboard";
|
||||
import "./ha-config-device-page";
|
||||
import { compare } from "../../../common/string/compare";
|
||||
|
8
src/panels/config/entities/const.ts
Normal file
8
src/panels/config/entities/const.ts
Normal file
@ -0,0 +1,8 @@
|
||||
/** Platforms that have a settings tab. */
|
||||
export const PLATFORMS_WITH_SETTINGS_TAB = {
|
||||
input_number: "entity-settings-helper-tab",
|
||||
input_select: "entity-settings-helper-tab",
|
||||
input_text: "entity-settings-helper-tab",
|
||||
input_boolean: "entity-settings-helper-tab",
|
||||
input_datetime: "entity-settings-helper-tab",
|
||||
};
|
@ -13,25 +13,45 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { cache } from "lit-html/directives/cache";
|
||||
import { PLATFORMS_WITH_SETTINGS_TAB } from "./const";
|
||||
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/dialog/ha-paper-dialog";
|
||||
// tslint:disable-next-line: no-duplicate-imports
|
||||
import { HaPaperDialog } from "../../../components/dialog/ha-paper-dialog";
|
||||
import "../../../components/ha-related-items";
|
||||
import "../../../dialogs/more-info/controls/more-info-content";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
ExtEntityRegistryEntry,
|
||||
getExtendedEntityRegistryEntry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import "../../../state-summary/state-card-content";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import "./entity-registry-settings";
|
||||
import { EntityRegistryDetailDialogParams } from "./show-dialog-entity-registry-detail";
|
||||
import { EntityRegistryDetailDialogParams } from "./show-dialog-entity-editor";
|
||||
|
||||
@customElement("dialog-entity-registry-detail")
|
||||
export class DialogEntityRegistryDetail extends LitElement {
|
||||
interface Tabs {
|
||||
[key: string]: Tab;
|
||||
}
|
||||
|
||||
interface Tab {
|
||||
component: string;
|
||||
translationKey: string;
|
||||
}
|
||||
|
||||
@customElement("dialog-entity-editor")
|
||||
export class DialogEntityEditor extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() private _params?: EntityRegistryDetailDialogParams;
|
||||
@property() private _entry?:
|
||||
| EntityRegistryEntry
|
||||
| ExtEntityRegistryEntry
|
||||
| null;
|
||||
@property() private _curTab?: string;
|
||||
@property() private _extraTabs: Tabs = {};
|
||||
@property() private _settingsElementTag?: string;
|
||||
@query("ha-paper-dialog") private _dialog!: HaPaperDialog;
|
||||
private _curTabIndex = 0;
|
||||
|
||||
@ -39,6 +59,10 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
params: EntityRegistryDetailDialogParams
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
this._entry = undefined;
|
||||
this._settingsElementTag = undefined;
|
||||
this._extraTabs = {};
|
||||
this._getEntityReg();
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
@ -47,11 +71,11 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
if (!this._params || this._entry === undefined) {
|
||||
return html``;
|
||||
}
|
||||
const entry = this._params.entry;
|
||||
const entityId = this._params.entity_id;
|
||||
const entry = this._entry;
|
||||
const stateObj: HassEntity | undefined = this.hass.states[entityId];
|
||||
|
||||
return html`
|
||||
@ -59,6 +83,7 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
with-backdrop
|
||||
opened
|
||||
@opened-changed=${this._openedChanged}
|
||||
@close-dialog=${this.closeDialog}
|
||||
>
|
||||
<app-toolbar>
|
||||
<paper-icon-button
|
||||
@ -92,6 +117,13 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
<paper-tab id="tab-settings">
|
||||
${this.hass.localize("ui.dialogs.entity_registry.settings")}
|
||||
</paper-tab>
|
||||
${Object.entries(this._extraTabs).map(
|
||||
([key, tab]) => html`
|
||||
<paper-tab id=${key}>
|
||||
${this.hass.localize(tab.translationKey) || key}
|
||||
</paper-tab>
|
||||
`
|
||||
)}
|
||||
<paper-tab id="tab-related">
|
||||
${this.hass.localize("ui.dialogs.entity_registry.related")}
|
||||
</paper-tab>
|
||||
@ -99,14 +131,16 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
${cache(
|
||||
this._curTab === "tab-settings"
|
||||
? entry
|
||||
? html`
|
||||
<entity-registry-settings
|
||||
.hass=${this.hass}
|
||||
.entry=${entry}
|
||||
.dialogElement=${this._dialog}
|
||||
@close-dialog=${this._closeDialog}
|
||||
></entity-registry-settings>
|
||||
`
|
||||
? this._settingsElementTag
|
||||
? html`
|
||||
${dynamicElement(this._settingsElementTag, {
|
||||
hass: this.hass,
|
||||
entry,
|
||||
entityId,
|
||||
dialogElement: this._dialog,
|
||||
})}
|
||||
`
|
||||
: ""
|
||||
: html`
|
||||
<paper-dialog-scrollable>
|
||||
${this.hass.localize(
|
||||
@ -121,7 +155,6 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.itemId=${entityId}
|
||||
itemType="entity"
|
||||
@close-dialog=${this._closeDialog}
|
||||
></ha-related-items>
|
||||
</paper-dialog-scrollable>
|
||||
`
|
||||
@ -131,6 +164,18 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private async _getEntityReg() {
|
||||
try {
|
||||
this._entry = await getExtendedEntityRegistryEntry(
|
||||
this.hass,
|
||||
this._params!.entity_id
|
||||
);
|
||||
this._loadPlatformSettingTabs();
|
||||
} catch {
|
||||
this._entry = null;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleTabSelected(ev: CustomEvent): void {
|
||||
if (!ev.detail.value) {
|
||||
return;
|
||||
@ -144,15 +189,26 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
fireEvent(this._dialog as HTMLElement, "iron-resize");
|
||||
}
|
||||
|
||||
private async _loadPlatformSettingTabs(): Promise<void> {
|
||||
if (!this._entry) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!Object.keys(PLATFORMS_WITH_SETTINGS_TAB).includes(this._entry.platform)
|
||||
) {
|
||||
this._settingsElementTag = "entity-registry-settings";
|
||||
return;
|
||||
}
|
||||
const tag = PLATFORMS_WITH_SETTINGS_TAB[this._entry.platform];
|
||||
await import(`./editor-tabs/settings/${tag}`);
|
||||
this._settingsElementTag = tag;
|
||||
}
|
||||
|
||||
private _openMoreInfo(): void {
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: this._params!.entity_id,
|
||||
});
|
||||
this._params = undefined;
|
||||
}
|
||||
|
||||
private _closeDialog(): void {
|
||||
this._params = undefined;
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private _openedChanged(ev: PolymerChangedEvent<boolean>): void {
|
||||
@ -250,6 +306,6 @@ export class DialogEntityRegistryDetail extends LitElement {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-entity-registry-detail": DialogEntityRegistryDetail;
|
||||
"dialog-entity-editor": DialogEntityEditor;
|
||||
}
|
||||
}
|
@ -0,0 +1,260 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { isComponentLoaded } from "../../../../../common/config/is_component_loaded";
|
||||
import { dynamicElement } from "../../../../../common/dom/dynamic-element-directive";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { HaPaperDialog } from "../../../../../components/dialog/ha-paper-dialog";
|
||||
import { ExtEntityRegistryEntry } from "../../../../../data/entity_registry";
|
||||
import {
|
||||
deleteInputBoolean,
|
||||
fetchInputBoolean,
|
||||
updateInputBoolean,
|
||||
} from "../../../../../data/input_boolean";
|
||||
import {
|
||||
deleteInputDateTime,
|
||||
fetchInputDateTime,
|
||||
updateInputDateTime,
|
||||
} from "../../../../../data/input_datetime";
|
||||
import {
|
||||
deleteInputNumber,
|
||||
fetchInputNumber,
|
||||
updateInputNumber,
|
||||
} from "../../../../../data/input_number";
|
||||
import {
|
||||
deleteInputSelect,
|
||||
fetchInputSelect,
|
||||
updateInputSelect,
|
||||
} from "../../../../../data/input_select";
|
||||
import {
|
||||
deleteInputText,
|
||||
fetchInputText,
|
||||
updateInputText,
|
||||
} from "../../../../../data/input_text";
|
||||
import { showConfirmationDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import { HomeAssistant } from "../../../../../types";
|
||||
import "../../../helpers/forms/ha-input_boolean-form";
|
||||
import "../../../helpers/forms/ha-input_text-form";
|
||||
import "../../../helpers/forms/ha-input_datetime-form";
|
||||
import "../../../helpers/forms/ha-input_select-form";
|
||||
import "../../../helpers/forms/ha-input_number-form";
|
||||
import { Helper } from "../../../helpers/const";
|
||||
import "../../entity-registry-basic-editor";
|
||||
// tslint:disable-next-line: no-duplicate-imports
|
||||
import { HaEntityRegistryBasicEditor } from "../../entity-registry-basic-editor";
|
||||
|
||||
const HELPERS = {
|
||||
input_boolean: {
|
||||
fetch: fetchInputBoolean,
|
||||
update: updateInputBoolean,
|
||||
delete: deleteInputBoolean,
|
||||
},
|
||||
input_text: {
|
||||
fetch: fetchInputText,
|
||||
update: updateInputText,
|
||||
delete: deleteInputText,
|
||||
},
|
||||
input_number: {
|
||||
fetch: fetchInputNumber,
|
||||
update: updateInputNumber,
|
||||
delete: deleteInputNumber,
|
||||
},
|
||||
input_datetime: {
|
||||
fetch: fetchInputDateTime,
|
||||
update: updateInputDateTime,
|
||||
delete: deleteInputDateTime,
|
||||
},
|
||||
input_select: {
|
||||
fetch: fetchInputSelect,
|
||||
update: updateInputSelect,
|
||||
delete: deleteInputSelect,
|
||||
},
|
||||
};
|
||||
|
||||
@customElement("entity-settings-helper-tab")
|
||||
export class EntityRegistrySettingsHelper extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public entry!: ExtEntityRegistryEntry;
|
||||
@property() public dialogElement!: HaPaperDialog;
|
||||
@property() private _error?: string;
|
||||
@property() private _item?: Helper | null;
|
||||
@property() private _submitting?: boolean;
|
||||
@property() private _componentLoaded?: boolean;
|
||||
@query("ha-registry-basic-editor")
|
||||
private _registryEditor?: HaEntityRegistryBasicEditor;
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues) {
|
||||
super.firstUpdated(changedProperties);
|
||||
this._componentLoaded = isComponentLoaded(this.hass, this.entry.platform);
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has("entry")) {
|
||||
this._error = undefined;
|
||||
this._item = undefined;
|
||||
this._getItem();
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (this._item === undefined) {
|
||||
return html``;
|
||||
}
|
||||
if (!this._componentLoaded) {
|
||||
return html`
|
||||
<paper-dialog-scrollable .dialogElement=${this.dialogElement}>
|
||||
The ${this.entry.platform} component is not loaded, please add it your
|
||||
configuration. Either by adding 'default_config:' or
|
||||
'${this.entry.platform}:'.
|
||||
</paper-dialog-scrollable>
|
||||
`;
|
||||
}
|
||||
if (this._item === null) {
|
||||
return html`
|
||||
<paper-dialog-scrollable .dialogElement=${this.dialogElement}>
|
||||
This entity can not be edited from the UI. Only entities setup from
|
||||
the UI are editable.
|
||||
</paper-dialog-scrollable>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<paper-dialog-scrollable .dialogElement=${this.dialogElement}>
|
||||
${this._error
|
||||
? html`
|
||||
<div class="error">${this._error}</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="form">
|
||||
<div @value-changed=${this._valueChanged}>
|
||||
${dynamicElement(`ha-${this.entry.platform}-form`, {
|
||||
hass: this.hass,
|
||||
item: this._item,
|
||||
entry: this.entry,
|
||||
})}
|
||||
</div>
|
||||
<ha-registry-basic-editor
|
||||
.hass=${this.hass}
|
||||
.entry=${this.entry}
|
||||
></ha-registry-basic-editor>
|
||||
</div>
|
||||
</paper-dialog-scrollable>
|
||||
<div class="buttons">
|
||||
<mwc-button
|
||||
class="warning"
|
||||
@click=${this._confirmDeleteItem}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.dialogs.entity_registry.editor.delete")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
@click=${this._updateItem}
|
||||
.disabled=${this._submitting || !this._item.name}
|
||||
>
|
||||
${this.hass.localize("ui.dialogs.entity_registry.editor.update")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
this._error = undefined;
|
||||
this._item = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _getItem() {
|
||||
const items = await HELPERS[this.entry.platform].fetch(this.hass!);
|
||||
this._item = items.find((item) => item.id === this.entry.unique_id) || null;
|
||||
await this.updateComplete;
|
||||
fireEvent(this.dialogElement as HTMLElement, "iron-resize");
|
||||
}
|
||||
|
||||
private async _updateItem(): Promise<void> {
|
||||
if (!this._item) {
|
||||
return;
|
||||
}
|
||||
this._submitting = true;
|
||||
try {
|
||||
await HELPERS[this.entry.platform].update(
|
||||
this.hass!,
|
||||
this._item.id,
|
||||
this._item
|
||||
);
|
||||
await this._registryEditor?.updateEntry();
|
||||
fireEvent(this, "close-dialog");
|
||||
} catch (err) {
|
||||
this._error = err.message || "Unknown error";
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _confirmDeleteItem(): Promise<void> {
|
||||
if (!this._item) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.confirm_delete"
|
||||
),
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._submitting = true;
|
||||
|
||||
try {
|
||||
await HELPERS[this.entry.platform].delete(this.hass!, this._item.id);
|
||||
fireEvent(this, "close-dialog");
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
margin-bottom: -20px;
|
||||
}
|
||||
mwc-button.warning {
|
||||
--mdc-theme-primary: var(--google-red-500);
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.row {
|
||||
margin-top: 8px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"entity-platform-helper-tab": EntityRegistrySettingsHelper;
|
||||
}
|
||||
}
|
138
src/panels/config/entities/entity-registry-basic-editor.ts
Normal file
138
src/panels/config/entities/entity-registry-basic-editor.ts
Normal file
@ -0,0 +1,138 @@
|
||||
import {
|
||||
html,
|
||||
css,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import "../../../components/ha-switch";
|
||||
import {
|
||||
ExtEntityRegistryEntry,
|
||||
EntityRegistryEntryUpdateParams,
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
// tslint:disable-next-line: no-duplicate-imports
|
||||
import { HaSwitch } from "../../../components/ha-switch";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
|
||||
@customElement("ha-registry-basic-editor")
|
||||
export class HaEntityRegistryBasicEditor extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public entry!: ExtEntityRegistryEntry;
|
||||
@property() private _origEntityId!: string;
|
||||
@property() private _entityId!: string;
|
||||
@property() private _disabledBy!: string | null;
|
||||
@property() private _submitting?: boolean;
|
||||
|
||||
public async updateEntry(): Promise<void> {
|
||||
this._submitting = true;
|
||||
const params: Partial<EntityRegistryEntryUpdateParams> = {
|
||||
new_entity_id: this._entityId.trim(),
|
||||
};
|
||||
if (this._disabledBy === null || this._disabledBy === "user") {
|
||||
params.disabled_by = this._disabledBy;
|
||||
}
|
||||
try {
|
||||
await updateEntityRegistryEntry(this.hass!, this._origEntityId, params);
|
||||
} catch (err) {
|
||||
throw err;
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProperties: PropertyValues) {
|
||||
super.updated(changedProperties);
|
||||
if (!changedProperties.has("entry")) {
|
||||
return;
|
||||
}
|
||||
if (this.entry) {
|
||||
this._origEntityId = this.entry.entity_id;
|
||||
this._entityId = this.entry.entity_id;
|
||||
this._disabledBy = this.entry.disabled_by;
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (
|
||||
!this.hass ||
|
||||
!this.entry ||
|
||||
this.entry.entity_id !== this._origEntityId
|
||||
) {
|
||||
return html``;
|
||||
}
|
||||
const invalidDomainUpdate =
|
||||
computeDomain(this._entityId.trim()) !==
|
||||
computeDomain(this.entry.entity_id);
|
||||
|
||||
return html`
|
||||
<paper-input
|
||||
.value=${this._entityId}
|
||||
@value-changed=${this._entityIdChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.entity_id"
|
||||
)}
|
||||
error-message="Domain needs to stay the same"
|
||||
.invalid=${invalidDomainUpdate}
|
||||
.disabled=${this._submitting}
|
||||
></paper-input>
|
||||
<div class="row">
|
||||
<ha-switch
|
||||
.checked=${!this._disabledBy}
|
||||
@change=${this._disabledByChanged}
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.enabled_label"
|
||||
)}
|
||||
</div>
|
||||
<div class="secondary">
|
||||
${this._disabledBy && this._disabledBy !== "user"
|
||||
? this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.enabled_cause",
|
||||
"cause",
|
||||
this.hass.localize(
|
||||
`config_entry.disabled_by.${this._disabledBy}`
|
||||
)
|
||||
)
|
||||
: ""}
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.enabled_description"
|
||||
)}
|
||||
<br />${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.note"
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</ha-switch>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _entityIdChanged(ev: PolymerChangedEvent<string>): void {
|
||||
this._entityId = ev.detail.value;
|
||||
}
|
||||
|
||||
private _disabledByChanged(ev: Event): void {
|
||||
this._disabledBy = (ev.target as HaSwitch).checked ? null : "user";
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.row {
|
||||
margin-top: 8px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
@ -12,26 +12,28 @@ import {
|
||||
} from "lit-element";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-icon-input";
|
||||
// tslint:disable-next-line: no-duplicate-imports
|
||||
import { HaSwitch } from "../../../components/ha-switch";
|
||||
import {
|
||||
EntityRegistryEntry,
|
||||
removeEntityRegistryEntry,
|
||||
updateEntityRegistryEntry,
|
||||
EntityRegistryEntryUpdateParams,
|
||||
ExtEntityRegistryEntry,
|
||||
} from "../../../data/entity_registry";
|
||||
import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
|
||||
@customElement("entity-registry-settings")
|
||||
export class EntityRegistrySettings extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public entry!: EntityRegistryEntry;
|
||||
@property() public entry!: ExtEntityRegistryEntry;
|
||||
@property() public dialogElement!: HTMLElement;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _entityId!: string;
|
||||
@property() private _disabledBy!: string | null;
|
||||
@property() private _error?: string;
|
||||
@ -43,6 +45,7 @@ export class EntityRegistrySettings extends LitElement {
|
||||
if (changedProperties.has("entry")) {
|
||||
this._error = undefined;
|
||||
this._name = this.entry.name || "";
|
||||
this._icon = this.entry.icon || "";
|
||||
this._origEntityId = this.entry.entity_id;
|
||||
this._entityId = this.entry.entity_id;
|
||||
this._disabledBy = this.entry.disabled_by;
|
||||
@ -59,7 +62,6 @@ export class EntityRegistrySettings extends LitElement {
|
||||
const invalidDomainUpdate =
|
||||
computeDomain(this._entityId.trim()) !==
|
||||
computeDomain(this.entry.entity_id);
|
||||
|
||||
return html`
|
||||
<paper-dialog-scrollable .dialogElement=${this.dialogElement}>
|
||||
${!stateObj
|
||||
@ -83,9 +85,21 @@ export class EntityRegistrySettings extends LitElement {
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.name"
|
||||
)}
|
||||
.placeholder=${stateObj ? computeStateName(stateObj) : ""}
|
||||
.placeholder=${this.entry.original_name}
|
||||
.disabled=${this._submitting}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
@value-changed=${this._iconChanged}
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.icon"
|
||||
)}
|
||||
.placeholder=${this.entry.original_icon}
|
||||
.disabled=${this._submitting}
|
||||
.errorMessage=${this.hass.localize(
|
||||
"ui.dialogs.entity_registry.editor.icon_error"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
<paper-input
|
||||
.value=${this._entityId}
|
||||
@value-changed=${this._entityIdChanged}
|
||||
@ -153,6 +167,11 @@ export class EntityRegistrySettings extends LitElement {
|
||||
this._name = ev.detail.value;
|
||||
}
|
||||
|
||||
private _iconChanged(ev: PolymerChangedEvent<string>): void {
|
||||
this._error = undefined;
|
||||
this._icon = ev.detail.value;
|
||||
}
|
||||
|
||||
private _entityIdChanged(ev: PolymerChangedEvent<string>): void {
|
||||
this._error = undefined;
|
||||
this._entityId = ev.detail.value;
|
||||
@ -162,6 +181,7 @@ export class EntityRegistrySettings extends LitElement {
|
||||
this._submitting = true;
|
||||
const params: Partial<EntityRegistryEntryUpdateParams> = {
|
||||
name: this._name.trim() || null,
|
||||
icon: this._icon.trim() || null,
|
||||
new_entity_id: this._entityId.trim(),
|
||||
};
|
||||
if (this._disabledBy === null || this._disabledBy === "user") {
|
||||
@ -192,7 +212,7 @@ export class EntityRegistrySettings extends LitElement {
|
||||
|
||||
try {
|
||||
await removeEntityRegistryEntry(this.hass!, this._origEntityId);
|
||||
fireEvent(this as HTMLElement, "close-dialog");
|
||||
fireEvent(this, "close-dialog");
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
@ -202,36 +222,39 @@ export class EntityRegistrySettings extends LitElement {
|
||||
this._disabledBy = (ev.target as HaSwitch).checked ? null : "user";
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: block;
|
||||
margin-bottom: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 8px;
|
||||
}
|
||||
mwc-button.warning {
|
||||
margin-right: auto;
|
||||
--mdc-theme-primary: var(--google-red-500);
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.row {
|
||||
margin-top: 8px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`;
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
margin-bottom: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 8px;
|
||||
}
|
||||
mwc-button.warning {
|
||||
margin-right: auto;
|
||||
--mdc-theme-primary: var(--google-red-500);
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
.row {
|
||||
margin-top: 8px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,11 +39,11 @@ import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail";
|
||||
import { DialogEntityEditor } from "./dialog-entity-editor";
|
||||
import {
|
||||
loadEntityRegistryDetailDialog,
|
||||
showEntityRegistryDetailDialog,
|
||||
} from "./show-dialog-entity-registry-detail";
|
||||
loadEntityEditorDialog,
|
||||
showEntityEditorDialog,
|
||||
} from "./show-dialog-entity-editor";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { computeStateName } from "../../../common/entity/compute_state_name";
|
||||
@ -75,7 +75,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
@property() private _selectedEntities: string[] = [];
|
||||
@query("hass-tabs-subpage-data-table")
|
||||
private _dataTable!: HaTabsSubpageDataTable;
|
||||
private getDialog?: () => DialogEntityRegistryDetail | undefined;
|
||||
private getDialog?: () => DialogEntityEditor | undefined;
|
||||
|
||||
private _columns = memoize(
|
||||
(narrow, _language): DataTableColumnContainer => {
|
||||
@ -387,33 +387,35 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.integrations}
|
||||
.columns=${this._columns(this.narrow, this.hass.language)}
|
||||
.data=${this._filteredEntities(
|
||||
this._entities,
|
||||
this.hass.states,
|
||||
this._showDisabled,
|
||||
this._showUnavailable,
|
||||
this._showReadOnly
|
||||
)}
|
||||
.filter=${this._filter}
|
||||
selectable
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
@row-click=${this._openEditEntry}
|
||||
id="entity_id"
|
||||
.data=${this._filteredEntities(
|
||||
this._entities,
|
||||
this.hass.states,
|
||||
this._showDisabled,
|
||||
this._showUnavailable,
|
||||
this._showReadOnly
|
||||
)}
|
||||
.filter=${this._filter}
|
||||
selectable
|
||||
@selection-changed=${this._handleSelectionChanged}
|
||||
@row-click=${this._openEditEntry}
|
||||
id="entity_id"
|
||||
>
|
||||
<div class=${classMap({
|
||||
"search-toolbar": this.narrow,
|
||||
"table-header": !this.narrow,
|
||||
})} slot="header">
|
||||
${headerToolbar}
|
||||
</div>
|
||||
</ha-data-table>
|
||||
<div
|
||||
class=${classMap({
|
||||
"search-toolbar": this.narrow,
|
||||
"table-header": !this.narrow,
|
||||
})}
|
||||
slot="header"
|
||||
>
|
||||
${headerToolbar}
|
||||
</div>
|
||||
</hass-tabs-subpage-data-table>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps): void {
|
||||
super.firstUpdated(changedProps);
|
||||
loadEntityRegistryDetailDialog();
|
||||
loadEntityEditorDialog();
|
||||
}
|
||||
|
||||
private _showDisabledChanged() {
|
||||
@ -524,7 +526,7 @@ export class HaConfigEntities extends SubscribeMixin(LitElement) {
|
||||
const entry = this._entities!.find(
|
||||
(entity) => entity.entity_id === entityId
|
||||
);
|
||||
this.getDialog = showEntityRegistryDetailDialog(this, {
|
||||
this.getDialog = showEntityEditorDialog(this, {
|
||||
entry,
|
||||
entity_id: entityId,
|
||||
});
|
||||
|
@ -1,32 +1,33 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
import { DialogEntityRegistryDetail } from "./dialog-entity-registry-detail";
|
||||
import { DialogEntityEditor } from "./dialog-entity-editor";
|
||||
|
||||
export interface EntityRegistryDetailDialogParams {
|
||||
entry?: EntityRegistryEntry;
|
||||
entity_id: string;
|
||||
tab?: string;
|
||||
}
|
||||
|
||||
export const loadEntityRegistryDetailDialog = () =>
|
||||
export const loadEntityEditorDialog = () =>
|
||||
import(
|
||||
/* webpackChunkName: "entity-registry-detail-dialog" */ "./dialog-entity-registry-detail"
|
||||
/* webpackChunkName: "entity-editor-dialog" */ "./dialog-entity-editor"
|
||||
);
|
||||
|
||||
const getDialog = () => {
|
||||
return document
|
||||
.querySelector("home-assistant")!
|
||||
.shadowRoot!.querySelector("dialog-entity-registry-detail") as
|
||||
| DialogEntityRegistryDetail
|
||||
.shadowRoot!.querySelector("dialog-entity-editor") as
|
||||
| DialogEntityEditor
|
||||
| undefined;
|
||||
};
|
||||
|
||||
export const showEntityRegistryDetailDialog = (
|
||||
export const showEntityEditorDialog = (
|
||||
element: HTMLElement,
|
||||
entityDetailParams: EntityRegistryDetailDialogParams
|
||||
): (() => DialogEntityRegistryDetail | undefined) => {
|
||||
): (() => DialogEntityEditor | undefined) => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-entity-registry-detail",
|
||||
dialogImport: loadEntityRegistryDetailDialog,
|
||||
dialogTag: "dialog-entity-editor",
|
||||
dialogImport: loadEntityEditorDialog,
|
||||
dialogParams: entityDetailParams,
|
||||
});
|
||||
return getDialog;
|
@ -6,10 +6,6 @@ import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { HomeAssistant, Route } from "../../types";
|
||||
import { CloudStatus, fetchCloudStatus } from "../../data/cloud";
|
||||
import { listenMediaQuery } from "../../common/dom/media_query";
|
||||
import {
|
||||
getOptimisticFrontendUserDataCollection,
|
||||
CoreFrontendUserData,
|
||||
} from "../../data/frontend";
|
||||
import { HassRouterPage, RouterOptions } from "../../layouts/hass-router-page";
|
||||
import { PolymerElement } from "@polymer/polymer";
|
||||
import { PageNavigation } from "../../layouts/hass-tabs-subpage";
|
||||
@ -71,6 +67,21 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
translationKey: "ui.panel.config.script.caption",
|
||||
icon: "hass:script-text",
|
||||
},
|
||||
{
|
||||
component: "helpers",
|
||||
path: "/config/helpers",
|
||||
translationKey: "ui.panel.config.helpers.caption",
|
||||
icon: "hass:tools",
|
||||
core: true,
|
||||
},
|
||||
],
|
||||
lovelace: [
|
||||
{
|
||||
component: "lovelace",
|
||||
path: "/config/lovelace/dashboards",
|
||||
translationKey: "ui.panel.config.lovelace.caption",
|
||||
icon: "hass:view-dashboard",
|
||||
},
|
||||
],
|
||||
persons: [
|
||||
{
|
||||
@ -114,7 +125,7 @@ export const configSections: { [name: string]: PageNavigation[] } = {
|
||||
translationKey: "ui.panel.config.customize.caption",
|
||||
icon: "hass:pencil",
|
||||
core: true,
|
||||
exportOnly: true,
|
||||
advancedOnly: true,
|
||||
},
|
||||
],
|
||||
other: [
|
||||
@ -214,6 +225,13 @@ class HaPanelConfig extends HassRouterPage {
|
||||
/* webpackChunkName: "panel-config-integrations" */ "./integrations/ha-config-integrations"
|
||||
),
|
||||
},
|
||||
lovelace: {
|
||||
tag: "ha-config-lovelace",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-lovelace" */ "./lovelace/ha-config-lovelace"
|
||||
),
|
||||
},
|
||||
person: {
|
||||
tag: "ha-config-person",
|
||||
load: () =>
|
||||
@ -235,6 +253,13 @@ class HaPanelConfig extends HassRouterPage {
|
||||
/* webpackChunkName: "panel-config-scene" */ "./scene/ha-config-scene"
|
||||
),
|
||||
},
|
||||
helpers: {
|
||||
tag: "ha-config-helpers",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-helpers" */ "./helpers/ha-config-helpers"
|
||||
),
|
||||
},
|
||||
users: {
|
||||
tag: "ha-config-users",
|
||||
load: () =>
|
||||
@ -268,8 +293,6 @@ class HaPanelConfig extends HassRouterPage {
|
||||
|
||||
@property() private _wideSidebar: boolean = false;
|
||||
@property() private _wide: boolean = false;
|
||||
@property() private _coreUserData?: CoreFrontendUserData;
|
||||
@property() private _showAdvanced = false;
|
||||
@property() private _cloudStatus?: CloudStatus;
|
||||
|
||||
private _listeners: Array<() => void> = [];
|
||||
@ -286,17 +309,6 @@ class HaPanelConfig extends HassRouterPage {
|
||||
this._wideSidebar = matches;
|
||||
})
|
||||
);
|
||||
this._listeners.push(
|
||||
getOptimisticFrontendUserDataCollection(
|
||||
this.hass.connection,
|
||||
"core"
|
||||
).subscribe((coreUserData) => {
|
||||
this._coreUserData = coreUserData || {};
|
||||
this._showAdvanced = !!(
|
||||
this._coreUserData && this._coreUserData.showAdvanced
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
@ -337,7 +349,7 @@ class HaPanelConfig extends HassRouterPage {
|
||||
(el as PolymerElement).setProperties({
|
||||
route: this.routeTail,
|
||||
hass: this.hass,
|
||||
showAdvanced: this._showAdvanced,
|
||||
showAdvanced: Boolean(this.hass.userData?.showAdvanced),
|
||||
isWide,
|
||||
narrow: this.narrow,
|
||||
cloudStatus: this._cloudStatus,
|
||||
@ -345,7 +357,7 @@ class HaPanelConfig extends HassRouterPage {
|
||||
} else {
|
||||
el.route = this.routeTail;
|
||||
el.hass = this.hass;
|
||||
el.showAdvanced = this._showAdvanced;
|
||||
el.showAdvanced = Boolean(this.hass.userData?.showAdvanced);
|
||||
el.isWide = isWide;
|
||||
el.narrow = this.narrow;
|
||||
el.cloudStatus = this._cloudStatus;
|
||||
|
24
src/panels/config/helpers/const.ts
Normal file
24
src/panels/config/helpers/const.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { InputBoolean } from "../../../data/input_boolean";
|
||||
|
||||
import { InputText } from "../../../data/input_text";
|
||||
|
||||
import { InputNumber } from "../../../data/input_number";
|
||||
|
||||
import { InputSelect } from "../../../data/input_select";
|
||||
|
||||
import { InputDateTime } from "../../../data/input_datetime";
|
||||
|
||||
export const HELPER_DOMAINS = [
|
||||
"input_boolean",
|
||||
"input_text",
|
||||
"input_number",
|
||||
"input_datetime",
|
||||
"input_select",
|
||||
];
|
||||
|
||||
export type Helper =
|
||||
| InputBoolean
|
||||
| InputText
|
||||
| InputNumber
|
||||
| InputSelect
|
||||
| InputDateTime;
|
179
src/panels/config/helpers/dialog-helper-detail.ts
Normal file
179
src/panels/config/helpers/dialog-helper-detail.ts
Normal file
@ -0,0 +1,179 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../components/ha-dialog";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { dynamicElement } from "../../../common/dom/dynamic-element-directive";
|
||||
import { createInputBoolean } from "../../../data/input_boolean";
|
||||
import { createInputText } from "../../../data/input_text";
|
||||
import { createInputNumber } from "../../../data/input_number";
|
||||
import { createInputDateTime } from "../../../data/input_datetime";
|
||||
import { createInputSelect } from "../../../data/input_select";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { Helper } from "./const";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "./forms/ha-input_boolean-form";
|
||||
import "./forms/ha-input_text-form";
|
||||
import "./forms/ha-input_datetime-form";
|
||||
import "./forms/ha-input_select-form";
|
||||
import "./forms/ha-input_number-form";
|
||||
import { domainIcon } from "../../../common/entity/domain_icon";
|
||||
import { classMap } from "lit-html/directives/class-map";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
|
||||
const HELPERS = {
|
||||
input_boolean: createInputBoolean,
|
||||
input_text: createInputText,
|
||||
input_number: createInputNumber,
|
||||
input_datetime: createInputDateTime,
|
||||
input_select: createInputSelect,
|
||||
};
|
||||
|
||||
@customElement("dialog-helper-detail")
|
||||
export class DialogHelperDetail extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() private _item?: Helper;
|
||||
@property() private _opened = false;
|
||||
@property() private _platform?: string;
|
||||
@property() private _error?: string;
|
||||
@property() private _submitting = false;
|
||||
|
||||
public async showDialog(): Promise<void> {
|
||||
this._platform = undefined;
|
||||
this._item = undefined;
|
||||
this._opened = true;
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._opened = false;
|
||||
this._error = "";
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._opened}
|
||||
@closing=${this.closeDialog}
|
||||
class=${classMap({ "button-left": !this._platform })}
|
||||
.heading=${this._platform
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.helpers.dialog.add_platform",
|
||||
"platform",
|
||||
this.hass.localize(
|
||||
`ui.panel.config.helpers.types.${this._platform}`
|
||||
) || this._platform
|
||||
)
|
||||
: this.hass.localize("ui.panel.config.helpers.dialog.add_helper")}
|
||||
>
|
||||
${this._platform
|
||||
? html`
|
||||
<div class="form" @value-changed=${this._valueChanged}>
|
||||
${this._error
|
||||
? html`
|
||||
<div class="error">${this._error}</div>
|
||||
`
|
||||
: ""}
|
||||
${dynamicElement(`ha-${this._platform}-form`, {
|
||||
hass: this.hass,
|
||||
item: this._item,
|
||||
new: true,
|
||||
})}
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click="${this._createItem}"
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass!.localize("ui.panel.config.helpers.dialog.create")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
@click="${this._goBack}"
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
Back
|
||||
</mwc-button>
|
||||
`
|
||||
: html`
|
||||
${Object.keys(HELPERS).map((platform: string) => {
|
||||
return html`
|
||||
<paper-icon-item
|
||||
.disabled=${!isComponentLoaded(this.hass, platform)}
|
||||
@click="${this._platformPicked}"
|
||||
.platform="${platform}"
|
||||
>
|
||||
<ha-icon
|
||||
slot="item-icon"
|
||||
.icon=${domainIcon(platform)}
|
||||
></ha-icon>
|
||||
<span class="item-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.helpers.types.${platform}`
|
||||
) || platform}
|
||||
</span>
|
||||
</paper-icon-item>
|
||||
`;
|
||||
})}
|
||||
<mwc-button slot="primaryAction" @click="${this.closeDialog}">
|
||||
${this.hass!.localize("ui.common.cancel")}
|
||||
</mwc-button>
|
||||
`}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
this._item = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _createItem(): Promise<void> {
|
||||
if (!this._platform || !this._item) {
|
||||
return;
|
||||
}
|
||||
this._submitting = true;
|
||||
this._error = "";
|
||||
try {
|
||||
await HELPERS[this._platform](this.hass, this._item);
|
||||
this.closeDialog();
|
||||
} catch (err) {
|
||||
this._error = err.message || "Unknown error";
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _platformPicked(ev: Event): void {
|
||||
this._platform = (ev.currentTarget! as any).platform;
|
||||
}
|
||||
|
||||
private _goBack() {
|
||||
this._platform = undefined;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog.button-left {
|
||||
--justify-action-buttons: flex-start;
|
||||
}
|
||||
paper-icon-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-helper-detail": DialogHelperDetail;
|
||||
}
|
||||
}
|
137
src/panels/config/helpers/forms/ha-input_boolean-form.ts
Normal file
137
src/panels/config/helpers/forms/ha-input_boolean-form.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { InputBoolean } from "../../../../data/input_boolean";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
|
||||
@customElement("ha-input_boolean-form")
|
||||
class HaInputBooleanForm extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public new?: boolean;
|
||||
private _item?: InputBoolean;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _initial?: boolean;
|
||||
|
||||
set item(item: InputBoolean) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
this._name = item.name || "";
|
||||
this._icon = item.icon || "";
|
||||
this._initial = item.initial;
|
||||
} else {
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = !this._name || this._name.trim() === "";
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
<br />
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value_explain"
|
||||
)}
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<div class="row layout horizontal justified">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value"
|
||||
)}:
|
||||
<ha-switch
|
||||
.checked=${this._initial}
|
||||
@change=${this._initialChanged}
|
||||
></ha-switch>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _initialChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, initial: ev.target.checked },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!this.new && !this._item) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
const newValue = { ...this._item };
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.row {
|
||||
padding: 16px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-input_boolean-form": HaInputBooleanForm;
|
||||
}
|
||||
}
|
165
src/panels/config/helpers/forms/ha-input_datetime-form.ts
Normal file
165
src/panels/config/helpers/forms/ha-input_datetime-form.ts
Normal file
@ -0,0 +1,165 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { InputDateTime } from "../../../../data/input_datetime";
|
||||
|
||||
@customElement("ha-input_datetime-form")
|
||||
class HaInputDateTimeForm extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public new?: boolean;
|
||||
private _item?: InputDateTime;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _initial?: string;
|
||||
@property() private _hasTime?: boolean;
|
||||
@property() private _hasDate?: boolean;
|
||||
|
||||
set item(item: InputDateTime) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
this._name = item.name || "";
|
||||
this._icon = item.icon || "";
|
||||
this._initial = item.initial;
|
||||
this._hasTime = item.has_time;
|
||||
this._hasDate = item.has_date;
|
||||
} else {
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = !this._name || this._name.trim() === "";
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
<div class="row layout horizontal justified">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_datetime.has_time"
|
||||
)}:
|
||||
<ha-switch
|
||||
.checked=${this._hasTime}
|
||||
@change=${this._hasTimeChanged}
|
||||
></ha-switch>
|
||||
</div>
|
||||
<div class="row layout horizontal justified">
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_datetime.has_date"
|
||||
)}:
|
||||
<ha-switch
|
||||
.checked=${this._hasDate}
|
||||
@change=${this._hasDateChanged}
|
||||
></ha-switch>
|
||||
</div>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<br />
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value_explain"
|
||||
)}
|
||||
<paper-input
|
||||
.value=${this._initial}
|
||||
.configValue=${"initial"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value"
|
||||
)}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _hasTimeChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, has_time: ev.target.checked },
|
||||
});
|
||||
}
|
||||
|
||||
private _hasDateChanged(ev) {
|
||||
ev.stopPropagation();
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, has_date: ev.target.checked },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!this.new && !this._item) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
const newValue = { ...this._item };
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.row {
|
||||
padding: 16px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-input_datetime-form": HaInputDateTimeForm;
|
||||
}
|
||||
}
|
214
src/panels/config/helpers/forms/ha-input_number-form.ts
Normal file
214
src/panels/config/helpers/forms/ha-input_number-form.ts
Normal file
@ -0,0 +1,214 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { InputNumber } from "../../../../data/input_number";
|
||||
|
||||
@customElement("ha-input_number-form")
|
||||
class HaInputNumberForm extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public new?: boolean;
|
||||
private _item?: Partial<InputNumber>;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _initial?: number;
|
||||
@property() private _max?: number;
|
||||
@property() private _min?: number;
|
||||
@property() private _mode?: string;
|
||||
@property() private _step?: number;
|
||||
// tslint:disable-next-line: variable-name
|
||||
@property() private _unit_of_measurement?: string;
|
||||
|
||||
set item(item: InputNumber) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
this._name = item.name || "";
|
||||
this._icon = item.icon || "";
|
||||
this._max = item.max ?? 100;
|
||||
this._min = item.min ?? 0;
|
||||
this._initial = item.initial;
|
||||
this._mode = item.mode || "slider";
|
||||
this._step = item.step || 1;
|
||||
this._unit_of_measurement = item.unit_of_measurement;
|
||||
} else {
|
||||
this._item = {
|
||||
min: 0,
|
||||
max: 0,
|
||||
};
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
this._max = 100;
|
||||
this._min = 0;
|
||||
this._mode = "slider";
|
||||
this._step = 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = !this._name || this._name.trim() === "";
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
<paper-input
|
||||
.value=${this._min}
|
||||
.configValue=${"min"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.min"
|
||||
)}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.value=${this._max}
|
||||
.configValue=${"max"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.max"
|
||||
)}
|
||||
></paper-input>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<div class="layout horizontal center justified">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_number.mode"
|
||||
)}
|
||||
<paper-radio-group
|
||||
.selected=${this._mode}
|
||||
@selected-changed=${this._modeChanged}
|
||||
>
|
||||
<paper-radio-button name="slider">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_number.slider"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="box">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_number.box"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
</div>
|
||||
${this._mode === "slider"
|
||||
? html`
|
||||
<paper-input
|
||||
.value=${this._step}
|
||||
.configValue=${"step"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.step"
|
||||
)}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
<paper-input
|
||||
.value=${this._unit_of_measurement}
|
||||
.configValue=${"unit_of_measurement"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_number.unit_of_measurement"
|
||||
)}
|
||||
></paper-input>
|
||||
<br />
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value_explain"
|
||||
)}
|
||||
<paper-input
|
||||
.value=${this._initial}
|
||||
.configValue=${"initial"}
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value"
|
||||
)}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _modeChanged(ev: CustomEvent) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, mode: ev.detail.value },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!this.new && !this._item) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
const newValue = { ...this._item };
|
||||
if (value === undefined || value === "") {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-input_number-form": HaInputNumberForm;
|
||||
}
|
||||
}
|
240
src/panels/config/helpers/forms/ha-input_select-form.ts
Normal file
240
src/panels/config/helpers/forms/ha-input_select-form.ts
Normal file
@ -0,0 +1,240 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
query,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import { InputSelect } from "../../../../data/input_select";
|
||||
// tslint:disable-next-line: no-duplicate-imports
|
||||
import { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import { showConfirmationDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
|
||||
@customElement("ha-input_select-form")
|
||||
class HaInputSelectForm extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public new?: boolean;
|
||||
private _item?: InputSelect;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _options: string[] = [];
|
||||
@property() private _initial?: string;
|
||||
@query("#option_input") private _optionInput?: PaperInputElement;
|
||||
|
||||
set item(item: InputSelect) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
this._name = item.name || "";
|
||||
this._icon = item.icon || "";
|
||||
this._initial = item.initial;
|
||||
this._options = item.options || [];
|
||||
} else {
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
this._options = [];
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = !this._name || this._name.trim() === "";
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.options"
|
||||
)}:
|
||||
${this._options.length
|
||||
? this._options.map((option, index) => {
|
||||
return html`
|
||||
<paper-item class="option">
|
||||
<paper-item-body> ${option} </paper-item-body>
|
||||
<paper-icon-button
|
||||
.index=${index}
|
||||
.title=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_select.remove_option"
|
||||
)}
|
||||
@click=${this._removeOption}
|
||||
icon="hass:delete"
|
||||
></paper-icon-button>
|
||||
</paper-item>
|
||||
`;
|
||||
})
|
||||
: html`
|
||||
<paper-item>
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.no_options"
|
||||
)}
|
||||
</paper-item>
|
||||
`}
|
||||
<div class="layout horizontal bottom">
|
||||
<paper-input
|
||||
class="flex-auto"
|
||||
id="option_input"
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.add_option"
|
||||
)}
|
||||
@keydown=${this._handleKeyAdd}
|
||||
></paper-input>
|
||||
<mwc-button @click=${this._addOption}
|
||||
>${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_select.add"
|
||||
)}</mwc-button
|
||||
>
|
||||
</div>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<br />
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value_explain"
|
||||
)}
|
||||
<ha-paper-dropdown-menu
|
||||
label-float
|
||||
dynamic-align
|
||||
.label=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value"
|
||||
)}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
attr-for-selected="item-initial"
|
||||
.selected=${this._initial}
|
||||
@selected-changed=${this._initialChanged}
|
||||
>
|
||||
${this._options.map(
|
||||
(option) => html`
|
||||
<paper-item item-initial=${option}>${option}</paper-item>
|
||||
`
|
||||
)}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _initialChanged(ev: CustomEvent) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, initial: ev.detail.value },
|
||||
});
|
||||
}
|
||||
|
||||
private _handleKeyAdd(ev: KeyboardEvent) {
|
||||
ev.stopPropagation();
|
||||
if (ev.keyCode !== 13) {
|
||||
return;
|
||||
}
|
||||
this._addOption();
|
||||
}
|
||||
|
||||
private _addOption() {
|
||||
const input = this._optionInput;
|
||||
if (!input || !input.value) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, options: [...this._options, input.value] },
|
||||
});
|
||||
input.value = "";
|
||||
}
|
||||
|
||||
private async _removeOption(ev: Event) {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: "Delete this item?",
|
||||
text: "Are you sure you want to delete this item?",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const index = (ev.target as any).index;
|
||||
const options = [...this._options];
|
||||
options.splice(index, 1);
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, options },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!this.new && !this._item) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
const newValue = { ...this._item };
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.option {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
mwc-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-input_select-form": HaInputSelectForm;
|
||||
}
|
||||
}
|
199
src/panels/config/helpers/forms/ha-input_text-form.ts
Normal file
199
src/panels/config/helpers/forms/ha-input_text-form.ts
Normal file
@ -0,0 +1,199 @@
|
||||
import {
|
||||
LitElement,
|
||||
html,
|
||||
css,
|
||||
CSSResult,
|
||||
TemplateResult,
|
||||
property,
|
||||
customElement,
|
||||
} from "lit-element";
|
||||
|
||||
import "@polymer/paper-input/paper-input";
|
||||
|
||||
import "../../../../components/ha-switch";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { InputText } from "../../../../data/input_text";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
|
||||
@customElement("ha-input_text-form")
|
||||
class HaInputTextForm extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public new?: boolean;
|
||||
private _item?: InputText;
|
||||
@property() private _name!: string;
|
||||
@property() private _icon!: string;
|
||||
@property() private _initial?: string;
|
||||
@property() private _max?: number;
|
||||
@property() private _min?: number;
|
||||
@property() private _mode?: string;
|
||||
@property() private _pattern?: string;
|
||||
|
||||
set item(item: InputText) {
|
||||
this._item = item;
|
||||
if (item) {
|
||||
this._name = item.name || "";
|
||||
this._icon = item.icon || "";
|
||||
this._max = item.max || 100;
|
||||
this._min = item.min || 0;
|
||||
this._initial = item.initial;
|
||||
this._mode = item.mode || "text";
|
||||
this._pattern = item.pattern;
|
||||
} else {
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
this._max = 100;
|
||||
this._min = 0;
|
||||
this._mode = "text";
|
||||
}
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass) {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = !this._name || this._name.trim() === "";
|
||||
|
||||
return html`
|
||||
<div class="form">
|
||||
<paper-input
|
||||
.value=${this._name}
|
||||
.configValue=${"name"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.name"
|
||||
)}
|
||||
.errorMessage="${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.required_error_msg"
|
||||
)}"
|
||||
.invalid=${nameInvalid}
|
||||
></paper-input>
|
||||
<ha-icon-input
|
||||
.value=${this._icon}
|
||||
.configValue=${"icon"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.icon"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
${this.hass.userData?.showAdvanced
|
||||
? html`
|
||||
<paper-input
|
||||
.value=${this._min}
|
||||
.configValue=${"min"}
|
||||
type="number"
|
||||
min="0"
|
||||
max="255"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.min"
|
||||
)}
|
||||
></paper-input>
|
||||
<paper-input
|
||||
.value=${this._max}
|
||||
.configValue=${"max"}
|
||||
min="0"
|
||||
max="255"
|
||||
type="number"
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.max"
|
||||
)}
|
||||
></paper-input>
|
||||
<div class="layout horizontal center justified">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_text.mode"
|
||||
)}
|
||||
<paper-radio-group
|
||||
.selected=${this._mode}
|
||||
@selected-changed=${this._modeChanged}
|
||||
>
|
||||
<paper-radio-button name="text">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_text.text"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
<paper-radio-button name="password">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_text.password"
|
||||
)}
|
||||
</paper-radio-button>
|
||||
</paper-radio-group>
|
||||
</div>
|
||||
<paper-input
|
||||
.value=${this._pattern}
|
||||
.configValue=${"pattern"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.input_text.pattern"
|
||||
)}
|
||||
></paper-input>
|
||||
<br />
|
||||
${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value_explain"
|
||||
)}
|
||||
<paper-input
|
||||
.value=${this._initial}
|
||||
.configValue=${"initial"}
|
||||
@value-changed=${this._valueChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.dialogs.helper_settings.generic.initial_value"
|
||||
)}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _modeChanged(ev: CustomEvent) {
|
||||
fireEvent(this, "value-changed", {
|
||||
value: { ...this._item, mode: ev.detail.value },
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!this.new && !this._item) {
|
||||
return;
|
||||
}
|
||||
ev.stopPropagation();
|
||||
const configValue = (ev.target as any).configValue;
|
||||
const value = ev.detail.value;
|
||||
if (this[`_${configValue}`] === value) {
|
||||
return;
|
||||
}
|
||||
const newValue = { ...this._item };
|
||||
if (!value) {
|
||||
delete newValue[configValue];
|
||||
} else {
|
||||
newValue[configValue] = ev.detail.value;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.form {
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
.row {
|
||||
padding: 16px 0;
|
||||
}
|
||||
ha-paper-dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-input_text-form": HaInputTextForm;
|
||||
}
|
||||
}
|
184
src/panels/config/helpers/ha-config-helpers.ts
Normal file
184
src/panels/config/helpers/ha-config-helpers.ts
Normal file
@ -0,0 +1,184 @@
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import memoize from "memoize-one";
|
||||
import { computeStateDomain } from "../../../common/entity/compute_state_domain";
|
||||
import "../../../common/search/search-input";
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../components/data-table/ha-data-table";
|
||||
import "../../../components/ha-icon";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { showEntityEditorDialog } from "../entities/show-dialog-entity-editor";
|
||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||
import { HELPER_DOMAINS } from "./const";
|
||||
|
||||
@customElement("ha-config-helpers")
|
||||
export class HaConfigHelpers extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public isWide!: boolean;
|
||||
@property() public narrow!: boolean;
|
||||
@property() public route!: Route;
|
||||
@property() private _stateItems: HassEntity[] = [];
|
||||
|
||||
private _columns = memoize(
|
||||
(_language): DataTableColumnContainer => {
|
||||
return {
|
||||
icon: {
|
||||
title: "",
|
||||
type: "icon",
|
||||
template: (icon) => html`
|
||||
<ha-icon slot="item-icon" .icon=${icon}></ha-icon>
|
||||
`,
|
||||
},
|
||||
name: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.headers.name"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
template: (name, item: any) =>
|
||||
html`
|
||||
${name}
|
||||
<div style="color: var(--secondary-text-color)">
|
||||
${item.entity_id}
|
||||
</div>
|
||||
`,
|
||||
},
|
||||
type: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.headers.type"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
template: (type) =>
|
||||
html`
|
||||
${this.hass.localize(`ui.panel.config.helpers.types.${type}`) ||
|
||||
type}
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
private _getItems = memoize((stateItems: HassEntity[]) => {
|
||||
return stateItems.map((state) => {
|
||||
return {
|
||||
id: state.entity_id,
|
||||
icon: state.attributes.icon,
|
||||
name: state.attributes.friendly_name || "",
|
||||
entity_id: state.entity_id,
|
||||
editable: state.attributes.editable,
|
||||
type: computeStateDomain(state),
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || this._stateItems === undefined) {
|
||||
return html`
|
||||
<hass-loading-screen></hass-loading-screen>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.tabs=${configSections.automation}
|
||||
.columns=${this._columns(this.hass.language)}
|
||||
.data=${this._getItems(this._stateItems)}
|
||||
@row-click=${this._openEditDialog}
|
||||
>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.add_helper"
|
||||
)}"
|
||||
@click=${this._createHelpler}
|
||||
></ha-fab>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._getStates();
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (oldHass && this._stateItems) {
|
||||
this._getStates(oldHass);
|
||||
}
|
||||
}
|
||||
|
||||
private _getStates(oldHass?: HomeAssistant) {
|
||||
let changed = false;
|
||||
const tempStates = Object.values(this.hass!.states).filter((entity) => {
|
||||
if (!HELPER_DOMAINS.includes(computeStateDomain(entity))) {
|
||||
return false;
|
||||
}
|
||||
if (oldHass?.states[entity.entity_id] !== entity) {
|
||||
changed = true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
if (changed || this._stateItems.length !== tempStates.length) {
|
||||
this._stateItems = tempStates;
|
||||
}
|
||||
}
|
||||
|
||||
private async _openEditDialog(ev: CustomEvent): Promise<void> {
|
||||
const entityId = (ev.detail as RowClickedEvent).id;
|
||||
showEntityEditorDialog(this, {
|
||||
entity_id: entityId,
|
||||
});
|
||||
}
|
||||
|
||||
private _createHelpler() {
|
||||
showHelperDetailDialog(this);
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
14
src/panels/config/helpers/show-dialog-helper-detail.ts
Normal file
14
src/panels/config/helpers/show-dialog-helper-detail.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
|
||||
export const loadHelperDetailDialog = () =>
|
||||
import(
|
||||
/* webpackChunkName: "helper-detail-dialog" */ "./dialog-helper-detail"
|
||||
);
|
||||
|
||||
export const showHelperDetailDialog = (element: HTMLElement) => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-helper-detail",
|
||||
dialogImport: loadHelperDetailDialog,
|
||||
dialogParams: {},
|
||||
});
|
||||
};
|
@ -0,0 +1,262 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import "../../../../components/ha-icon-input";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
LovelaceDashboard,
|
||||
LovelaceDashboardMutableParams,
|
||||
LovelaceDashboardCreateParams,
|
||||
} from "../../../../data/lovelace";
|
||||
import { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
|
||||
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||
import { HaSwitch } from "../../../../components/ha-switch";
|
||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
|
||||
@customElement("dialog-lovelace-dashboard-detail")
|
||||
export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() private _params?: LovelaceDashboardDetailsDialogParams;
|
||||
@property() private _urlPath!: LovelaceDashboard["url_path"];
|
||||
@property() private _showSidebar!: boolean;
|
||||
@property() private _sidebarIcon!: string;
|
||||
@property() private _sidebarTitle!: string;
|
||||
@property() private _requireAdmin!: LovelaceDashboard["require_admin"];
|
||||
|
||||
@property() private _error?: string;
|
||||
@property() private _submitting = false;
|
||||
|
||||
public async showDialog(
|
||||
params: LovelaceDashboardDetailsDialogParams
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
if (this._params.dashboard) {
|
||||
this._urlPath = this._params.dashboard.url_path || "";
|
||||
this._showSidebar = !!this._params.dashboard.sidebar;
|
||||
this._sidebarIcon = this._params.dashboard.sidebar?.icon || "";
|
||||
this._sidebarTitle = this._params.dashboard.sidebar?.title || "";
|
||||
this._requireAdmin = this._params.dashboard.require_admin || false;
|
||||
} else {
|
||||
this._urlPath = "";
|
||||
this._showSidebar = true;
|
||||
this._sidebarIcon = "";
|
||||
this._sidebarTitle = "";
|
||||
this._requireAdmin = false;
|
||||
}
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
const urlInvalid = !/^[a-zA-Z0-9_-]+$/.test(this._urlPath);
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closing="${this._close}"
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._params.dashboard
|
||||
? this._sidebarTitle ||
|
||||
this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.edit_dashboard"
|
||||
)
|
||||
: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.new_dashboard"
|
||||
)
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
${this._error
|
||||
? html`
|
||||
<div class="error">${this._error}</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="form">
|
||||
<ha-switch
|
||||
.checked=${this._showSidebar}
|
||||
@change=${this._showSidebarChanged}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.show_sidebar"
|
||||
)}</ha-switch
|
||||
>
|
||||
${this._showSidebar
|
||||
? html`
|
||||
<ha-icon-input
|
||||
.value=${this._sidebarIcon}
|
||||
@value-changed=${this._sidebarIconChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.icon"
|
||||
)}
|
||||
></ha-icon-input>
|
||||
<paper-input
|
||||
.value=${this._sidebarTitle}
|
||||
@value-changed=${this._sidebarTitleChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.title"
|
||||
)}
|
||||
@blur=${this._fillUrlPath}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
${!this._params.dashboard
|
||||
? html`
|
||||
<paper-input
|
||||
.value=${this._urlPath}
|
||||
@value-changed=${this._urlChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.url"
|
||||
)}
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.url_error_msg"
|
||||
)}
|
||||
.invalid=${urlInvalid}
|
||||
></paper-input>
|
||||
`
|
||||
: ""}
|
||||
<ha-switch
|
||||
.checked=${this._requireAdmin}
|
||||
@change=${this._requireAdminChanged}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.require_admin"
|
||||
)}</ha-switch
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
${this._params.dashboard
|
||||
? html`
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
class="warning"
|
||||
@click="${this._deleteDashboard}"
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.delete"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: html``}
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click="${this._updateDashboard}"
|
||||
.disabled=${urlInvalid || this._submitting}
|
||||
>
|
||||
${this._params.dashboard
|
||||
? this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.update"
|
||||
)
|
||||
: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.create"
|
||||
)}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _urlChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._error = undefined;
|
||||
this._urlPath = ev.detail.value;
|
||||
}
|
||||
|
||||
private _sidebarIconChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._error = undefined;
|
||||
this._sidebarIcon = ev.detail.value;
|
||||
}
|
||||
|
||||
private _sidebarTitleChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._error = undefined;
|
||||
this._sidebarTitle = ev.detail.value;
|
||||
}
|
||||
|
||||
private _fillUrlPath() {
|
||||
if (this._urlPath) {
|
||||
return;
|
||||
}
|
||||
const parts = this._sidebarTitle.split(" ");
|
||||
|
||||
if (parts.length) {
|
||||
this._urlPath = parts[0].toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
private _showSidebarChanged(ev: Event) {
|
||||
this._showSidebar = (ev.target as HaSwitch).checked;
|
||||
}
|
||||
|
||||
private _requireAdminChanged(ev: Event) {
|
||||
this._requireAdmin = (ev.target as HaSwitch).checked;
|
||||
}
|
||||
|
||||
private async _updateDashboard() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
const values: Partial<LovelaceDashboardMutableParams> = {
|
||||
require_admin: this._requireAdmin,
|
||||
sidebar: this._showSidebar
|
||||
? { icon: this._sidebarIcon, title: this._sidebarTitle }
|
||||
: null,
|
||||
};
|
||||
if (this._params!.dashboard) {
|
||||
await this._params!.updateDashboard(values);
|
||||
} else {
|
||||
(values as LovelaceDashboardCreateParams).url_path = this._urlPath.trim();
|
||||
(values as LovelaceDashboardCreateParams).mode = "storage";
|
||||
await this._params!.createDashboard(
|
||||
values as LovelaceDashboardCreateParams
|
||||
);
|
||||
}
|
||||
this._params = undefined;
|
||||
} catch (err) {
|
||||
this._error = err?.message || "Unknown error";
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteDashboard() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
if (await this._params!.removeDashboard()) {
|
||||
this._close();
|
||||
}
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
this._params = undefined;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
ha-switch {
|
||||
padding: 16px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-lovelace-dashboard-detail": DialogLovelaceDashboardDetail;
|
||||
}
|
||||
}
|
@ -0,0 +1,276 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import memoize from "memoize-one";
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../../components/data-table/ha-data-table";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../layouts/hass-loading-screen";
|
||||
import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../../types";
|
||||
import {
|
||||
LovelaceDashboard,
|
||||
fetchDashboards,
|
||||
createDashboard,
|
||||
updateDashboard,
|
||||
deleteDashboard,
|
||||
LovelaceDashboardCreateParams,
|
||||
} from "../../../../data/lovelace";
|
||||
import { showDashboardDetailDialog } from "./show-dialog-lovelace-dashboard-detail";
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showAlertDialog,
|
||||
} from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||
import { navigate } from "../../../../common/navigate";
|
||||
|
||||
@customElement("ha-config-lovelace-dashboards")
|
||||
export class HaConfigLovelaceDashboards extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public isWide!: boolean;
|
||||
@property() public narrow!: boolean;
|
||||
@property() public route!: Route;
|
||||
@property() private _dashboards: LovelaceDashboard[] = [];
|
||||
|
||||
private _columns = memoize(
|
||||
(_language, dashboards): DataTableColumnContainer => {
|
||||
const columns: DataTableColumnContainer = {
|
||||
icon: {
|
||||
title: "",
|
||||
type: "icon",
|
||||
template: (icon) =>
|
||||
icon
|
||||
? html`
|
||||
<ha-icon slot="item-icon" .icon=${icon}></ha-icon>
|
||||
`
|
||||
: html``,
|
||||
},
|
||||
title: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.headers.title"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
},
|
||||
mode: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.headers.conf_mode"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
template: (mode) =>
|
||||
html`
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.lovelace.dashboards.conf_mode.${mode}`
|
||||
) || mode}
|
||||
`,
|
||||
},
|
||||
};
|
||||
|
||||
if (dashboards.some((dashboard) => dashboard.mode === "yaml")) {
|
||||
columns.filename = {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.headers.filename"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
};
|
||||
}
|
||||
|
||||
const columns2: DataTableColumnContainer = {
|
||||
require_admin: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.headers.require_admin"
|
||||
),
|
||||
sortable: true,
|
||||
type: "icon",
|
||||
template: (requireAdmin: boolean) =>
|
||||
requireAdmin
|
||||
? html`
|
||||
<ha-icon icon="hass:check-circle-outline"></ha-icon>
|
||||
`
|
||||
: html`
|
||||
-
|
||||
`,
|
||||
},
|
||||
sidebar: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.headers.sidebar"
|
||||
),
|
||||
type: "icon",
|
||||
template: (sidebar) =>
|
||||
sidebar
|
||||
? html`
|
||||
<ha-icon icon="hass:check-circle-outline"></ha-icon>
|
||||
`
|
||||
: html`
|
||||
-
|
||||
`,
|
||||
},
|
||||
url_path: {
|
||||
title: "",
|
||||
type: "icon",
|
||||
filterable: true,
|
||||
template: (urlPath) =>
|
||||
html`
|
||||
<mwc-button .urlPath=${urlPath} @click=${this._navigate}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.open"
|
||||
)}</mwc-button
|
||||
>
|
||||
`,
|
||||
},
|
||||
};
|
||||
return { ...columns, ...columns2 };
|
||||
}
|
||||
);
|
||||
|
||||
private _getItems = memoize((dashboards: LovelaceDashboard[]) => {
|
||||
return dashboards.map((dashboard) => {
|
||||
return {
|
||||
filename: "",
|
||||
...dashboard,
|
||||
icon: dashboard.sidebar?.icon,
|
||||
title: dashboard.sidebar?.title || dashboard.url_path,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || this._dashboards === undefined) {
|
||||
return html`
|
||||
<hass-loading-screen></hass-loading-screen>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.tabs=${lovelaceTabs}
|
||||
.columns=${this._columns(this.hass.language, this._dashboards)}
|
||||
.data=${this._getItems(this._dashboards)}
|
||||
@row-click=${this._editDashboard}
|
||||
>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.picker.add_dashboard"
|
||||
)}"
|
||||
@click=${this._addDashboard}
|
||||
></ha-fab>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._getDashboards();
|
||||
}
|
||||
|
||||
private async _getDashboards() {
|
||||
this._dashboards = await fetchDashboards(this.hass);
|
||||
}
|
||||
|
||||
private _navigate(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
const url = `/${(ev.target as any).urlPath}`;
|
||||
navigate(this, url);
|
||||
}
|
||||
|
||||
private _editDashboard(ev: CustomEvent) {
|
||||
const id = (ev.detail as RowClickedEvent).id;
|
||||
const dashboard = id
|
||||
? this._dashboards.find((res) => res.id === id)
|
||||
: undefined;
|
||||
if (!dashboard) {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.cant_edit_yaml"
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._openDialog(dashboard);
|
||||
}
|
||||
|
||||
private _addDashboard() {
|
||||
this._openDialog();
|
||||
}
|
||||
|
||||
private async _openDialog(dashboard?: LovelaceDashboard): Promise<void> {
|
||||
showDashboardDetailDialog(this, {
|
||||
dashboard,
|
||||
createDashboard: async (values: LovelaceDashboardCreateParams) => {
|
||||
const created = await createDashboard(this.hass!, values);
|
||||
this._dashboards = this._dashboards!.concat(
|
||||
created
|
||||
).sort((res1, res2) => compare(res1.url_path, res2.url_path));
|
||||
},
|
||||
updateDashboard: async (values) => {
|
||||
const updated = await updateDashboard(
|
||||
this.hass!,
|
||||
dashboard!.id,
|
||||
values
|
||||
);
|
||||
this._dashboards = this._dashboards!.map((res) =>
|
||||
res === dashboard ? updated : res
|
||||
);
|
||||
},
|
||||
removeDashboard: async () => {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.dashboards.confirm_delete"
|
||||
),
|
||||
}))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteDashboard(this.hass!, dashboard!.id);
|
||||
this._dashboards = this._dashboards!.filter(
|
||||
(res) => res !== dashboard
|
||||
);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import {
|
||||
LovelaceDashboard,
|
||||
LovelaceDashboardMutableParams,
|
||||
LovelaceDashboardCreateParams,
|
||||
} from "../../../../data/lovelace";
|
||||
|
||||
export interface LovelaceDashboardDetailsDialogParams {
|
||||
dashboard?: LovelaceDashboard;
|
||||
createDashboard: (values: LovelaceDashboardCreateParams) => Promise<unknown>;
|
||||
updateDashboard: (
|
||||
updates: Partial<LovelaceDashboardMutableParams>
|
||||
) => Promise<unknown>;
|
||||
removeDashboard: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export const loadDashboardDetailDialog = () =>
|
||||
import(
|
||||
/* webpackChunkName: "lovelace-dashboard-detail-dialog" */ "./dialog-lovelace-dashboard-detail"
|
||||
);
|
||||
|
||||
export const showDashboardDetailDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: LovelaceDashboardDetailsDialogParams
|
||||
) => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-lovelace-dashboard-detail",
|
||||
dialogImport: loadDashboardDetailDialog,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
63
src/panels/config/lovelace/ha-config-lovelace.ts
Normal file
63
src/panels/config/lovelace/ha-config-lovelace.ts
Normal file
@ -0,0 +1,63 @@
|
||||
import {
|
||||
HassRouterPage,
|
||||
RouterOptions,
|
||||
} from "../../../layouts/hass-router-page";
|
||||
import { property, customElement } from "lit-element";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
|
||||
export const lovelaceTabs = [
|
||||
{
|
||||
component: "lovelace",
|
||||
path: "/config/lovelace/dashboards",
|
||||
translationKey: "ui.panel.config.lovelace.dashboards.caption",
|
||||
icon: "hass:view-dashboard",
|
||||
},
|
||||
{
|
||||
component: "lovelace",
|
||||
path: "/config/lovelace/resources",
|
||||
translationKey: "ui.panel.config.lovelace.resources.caption",
|
||||
icon: "hass:file-multiple",
|
||||
advancedOnly: true,
|
||||
},
|
||||
];
|
||||
|
||||
@customElement("ha-config-lovelace")
|
||||
class HaConfigLovelace extends HassRouterPage {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public narrow!: boolean;
|
||||
@property() public isWide!: boolean;
|
||||
|
||||
protected routerOptions: RouterOptions = {
|
||||
defaultPage: "dashboards",
|
||||
routes: {
|
||||
dashboards: {
|
||||
tag: "ha-config-lovelace-dashboards",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-lovelace-dashboards" */ "./dashboards/ha-config-lovelace-dashboards"
|
||||
),
|
||||
cache: true,
|
||||
},
|
||||
resources: {
|
||||
tag: "ha-config-lovelace-resources",
|
||||
load: () =>
|
||||
import(
|
||||
/* webpackChunkName: "panel-config-lovelace-resources" */ "./resources/ha-config-lovelace-resources"
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
protected updatePageEl(pageEl) {
|
||||
pageEl.hass = this.hass;
|
||||
pageEl.narrow = this.narrow;
|
||||
pageEl.isWide = this.isWide;
|
||||
pageEl.route = this.routeTail;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-config-lovelace": HaConfigLovelace;
|
||||
}
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
import {
|
||||
css,
|
||||
CSSResult,
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import {
|
||||
LovelaceResource,
|
||||
LovelaceResourcesMutableParams,
|
||||
} from "../../../../data/lovelace";
|
||||
import { LovelaceResourceDetailsDialogParams } from "./show-dialog-lovelace-resource-detail";
|
||||
import { PolymerChangedEvent } from "../../../../polymer-types";
|
||||
import { createCloseHeading } from "../../../../components/ha-dialog";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
|
||||
@customElement("dialog-lovelace-resource-detail")
|
||||
export class DialogLovelaceResourceDetail extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() private _params?: LovelaceResourceDetailsDialogParams;
|
||||
@property() private _url!: LovelaceResource["url"];
|
||||
@property() private _type!: LovelaceResource["type"];
|
||||
@property() private _error?: string;
|
||||
@property() private _submitting = false;
|
||||
|
||||
public async showDialog(
|
||||
params: LovelaceResourceDetailsDialogParams
|
||||
): Promise<void> {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
if (this._params.resource) {
|
||||
this._url = this._params.resource.url || "";
|
||||
this._type = this._params.resource.type || "module";
|
||||
} else {
|
||||
this._url = "";
|
||||
this._type = "module";
|
||||
}
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
const urlInvalid = this._url.trim() === "";
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closing=${this._close}
|
||||
scrimClickAction
|
||||
escapeKeyAction
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._params.resource
|
||||
? this._params.resource.url
|
||||
: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.new_resource"
|
||||
)
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
${this._error
|
||||
? html`
|
||||
<div class="error">${this._error}</div>
|
||||
`
|
||||
: ""}
|
||||
<div class="form">
|
||||
<h3 class="warning">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.warning_header"
|
||||
)}
|
||||
</h3>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.warning_text"
|
||||
)}
|
||||
<paper-input
|
||||
.value=${this._url}
|
||||
@value-changed=${this._urlChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.url"
|
||||
)}
|
||||
.errorMessage=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.url_error_msg"
|
||||
)}
|
||||
.invalid=${urlInvalid}
|
||||
></paper-input>
|
||||
<br />
|
||||
<ha-paper-dropdown-menu
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.type"
|
||||
)}
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
.selected=${this._type}
|
||||
@iron-select=${this._typeChanged}
|
||||
attr-for-selected="type"
|
||||
>
|
||||
<paper-item type="module">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.module"
|
||||
)}
|
||||
</paper-item>
|
||||
${this._type === "js"
|
||||
? html`
|
||||
<paper-item type="js">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.js"
|
||||
)}
|
||||
</paper-item>
|
||||
`
|
||||
: ""}
|
||||
<paper-item type="css">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.css"
|
||||
)}
|
||||
</paper-item>
|
||||
${this._type === "html"
|
||||
? html`
|
||||
<paper-item type="html">
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.types.html"
|
||||
)}
|
||||
</paper-item>
|
||||
`
|
||||
: ""}
|
||||
</paper-listbox>
|
||||
</ha-paper-dropdown-menu>
|
||||
</div>
|
||||
</div>
|
||||
${this._params.resource
|
||||
? html`
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
class="warning"
|
||||
@click="${this._deleteResource}"
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.delete"
|
||||
)}
|
||||
</mwc-button>
|
||||
`
|
||||
: html``}
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click="${this._updateResource}"
|
||||
.disabled=${urlInvalid || this._submitting}
|
||||
>
|
||||
${this._params.resource
|
||||
? this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.update"
|
||||
)
|
||||
: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.detail.create"
|
||||
)}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _urlChanged(ev: PolymerChangedEvent<string>) {
|
||||
this._error = undefined;
|
||||
this._url = ev.detail.value;
|
||||
}
|
||||
|
||||
private _typeChanged(ev: CustomEvent) {
|
||||
this._type = ev.detail.item.getAttribute("type");
|
||||
}
|
||||
|
||||
private async _updateResource() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
const values: LovelaceResourcesMutableParams = {
|
||||
url: this._url.trim(),
|
||||
res_type: this._type,
|
||||
};
|
||||
if (this._params!.resource) {
|
||||
await this._params!.updateResource(values);
|
||||
} else {
|
||||
await this._params!.createResource(values);
|
||||
}
|
||||
this._params = undefined;
|
||||
} catch (err) {
|
||||
this._error = err?.message || "Unknown error";
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteResource() {
|
||||
this._submitting = true;
|
||||
try {
|
||||
if (await this._params!.removeResource()) {
|
||||
this._close();
|
||||
}
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _close(): void {
|
||||
this._params = undefined;
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
.warning {
|
||||
color: var(--error-color);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-lovelace-resource-detail": DialogLovelaceResourceDetail;
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-dropdown-menu/paper-dropdown-menu";
|
||||
import "@polymer/paper-item/paper-icon-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import "@polymer/paper-tooltip/paper-tooltip";
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
LitElement,
|
||||
property,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
import memoize from "memoize-one";
|
||||
import "../../../../common/search/search-input";
|
||||
import {
|
||||
DataTableColumnContainer,
|
||||
RowClickedEvent,
|
||||
} from "../../../../components/data-table/ha-data-table";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../layouts/hass-loading-screen";
|
||||
import "../../../../layouts/hass-tabs-subpage-data-table";
|
||||
import { HomeAssistant, Route } from "../../../../types";
|
||||
import {
|
||||
LovelaceResource,
|
||||
fetchResources,
|
||||
createResource,
|
||||
updateResource,
|
||||
deleteResource,
|
||||
} from "../../../../data/lovelace";
|
||||
import { showResourceDetailDialog } from "./show-dialog-lovelace-resource-detail";
|
||||
import { compare } from "../../../../common/string/compare";
|
||||
import {
|
||||
showConfirmationDialog,
|
||||
showAlertDialog,
|
||||
} from "../../../../dialogs/generic/show-dialog-box";
|
||||
import { lovelaceTabs } from "../ha-config-lovelace";
|
||||
import { loadLovelaceResources } from "../../../lovelace/common/load-resources";
|
||||
|
||||
@customElement("ha-config-lovelace-resources")
|
||||
export class HaConfigLovelaceRescources extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@property() public isWide!: boolean;
|
||||
@property() public narrow!: boolean;
|
||||
@property() public route!: Route;
|
||||
@property() private _resources: LovelaceResource[] = [];
|
||||
|
||||
private _columns = memoize(
|
||||
(_language): DataTableColumnContainer => {
|
||||
return {
|
||||
url: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.lovelace.resources.picker.headers.url"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
direction: "asc",
|
||||
},
|
||||
type: {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.lovelace.resources.picker.headers.type"
|
||||
),
|
||||
sortable: true,
|
||||
filterable: true,
|
||||
template: (type) =>
|
||||
html`
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.lovelace.resources.types.${type}`
|
||||
) || type}
|
||||
`,
|
||||
},
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || this._resources === undefined) {
|
||||
return html`
|
||||
<hass-loading-screen></hass-loading-screen>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<hass-tabs-subpage-data-table
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
back-path="/config"
|
||||
.route=${this.route}
|
||||
.tabs=${lovelaceTabs}
|
||||
.columns=${this._columns(this.hass.language)}
|
||||
.data=${this._resources}
|
||||
@row-click=${this._editResource}
|
||||
>
|
||||
</hass-tabs-subpage-data-table>
|
||||
<ha-fab
|
||||
?is-wide=${this.isWide}
|
||||
?narrow=${this.narrow}
|
||||
icon="hass:plus"
|
||||
title="${this.hass.localize(
|
||||
"ui.panel.config.lovelace.resources.picker.add_resource"
|
||||
)}"
|
||||
@click=${this._addResource}
|
||||
></ha-fab>
|
||||
`;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._getResources();
|
||||
}
|
||||
|
||||
private async _getResources() {
|
||||
this._resources = await fetchResources(this.hass.connection);
|
||||
}
|
||||
|
||||
private _editResource(ev: CustomEvent) {
|
||||
if ((this.hass.panels.lovelace?.config as any)?.mode !== "storage") {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.cant_edit_yaml"
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
const id = (ev.detail as RowClickedEvent).id;
|
||||
const resource = this._resources.find((res) => res.id === id);
|
||||
this._openDialog(resource);
|
||||
}
|
||||
|
||||
private _addResource() {
|
||||
if ((this.hass.panels.lovelace?.config as any)?.mode !== "storage") {
|
||||
showAlertDialog(this, {
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.cant_edit_yaml"
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._openDialog();
|
||||
}
|
||||
|
||||
private async _openDialog(resource?: LovelaceResource): Promise<void> {
|
||||
showResourceDetailDialog(this, {
|
||||
resource,
|
||||
createResource: async (values) => {
|
||||
const created = await createResource(this.hass!, values);
|
||||
this._resources = this._resources!.concat(created).sort((res1, res2) =>
|
||||
compare(res1.url, res2.url)
|
||||
);
|
||||
loadLovelaceResources(this._resources, this.hass!.auth.data.hassUrl);
|
||||
},
|
||||
updateResource: async (values) => {
|
||||
const updated = await updateResource(this.hass!, resource!.id, values);
|
||||
this._resources = this._resources!.map((res) =>
|
||||
res === resource ? updated : res
|
||||
);
|
||||
loadLovelaceResources(this._resources, this.hass!.auth.data.hassUrl);
|
||||
},
|
||||
removeResource: async () => {
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.confirm_delete"
|
||||
),
|
||||
}))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await deleteResource(this.hass!, resource!.id);
|
||||
this._resources = this._resources!.filter((res) => res !== resource);
|
||||
showConfirmationDialog(this, {
|
||||
title: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.refresh_header"
|
||||
),
|
||||
text: this.hass!.localize(
|
||||
"ui.panel.config.lovelace.resources.refresh_body"
|
||||
),
|
||||
confirm: () => location.reload(),
|
||||
});
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
ha-fab {
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
z-index: 1;
|
||||
}
|
||||
ha-fab[is-wide] {
|
||||
bottom: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
ha-fab[narrow] {
|
||||
bottom: 84px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import {
|
||||
LovelaceResource,
|
||||
LovelaceResourcesMutableParams,
|
||||
} from "../../../../data/lovelace";
|
||||
|
||||
export interface LovelaceResourceDetailsDialogParams {
|
||||
resource?: LovelaceResource;
|
||||
createResource: (values: LovelaceResourcesMutableParams) => Promise<unknown>;
|
||||
updateResource: (
|
||||
updates: Partial<LovelaceResourcesMutableParams>
|
||||
) => Promise<unknown>;
|
||||
removeResource: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
export const loadResourceDetailDialog = () =>
|
||||
import(
|
||||
/* webpackChunkName: "lovelace-resource-detail-dialog" */ "./dialog-lovelace-resource-detail"
|
||||
);
|
||||
|
||||
export const showResourceDetailDialog = (
|
||||
element: HTMLElement,
|
||||
dialogParams: LovelaceResourceDetailsDialogParams
|
||||
) => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-lovelace-resource-detail",
|
||||
dialogImport: loadResourceDetailDialog,
|
||||
dialogParams,
|
||||
});
|
||||
};
|
@ -13,11 +13,12 @@ import "@material/mwc-button";
|
||||
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import "../../../components/user/ha-user-picker";
|
||||
import "../../../components/ha-dialog";
|
||||
import { PersonDetailDialogParams } from "./show-dialog-person-detail";
|
||||
import { PolymerChangedEvent } from "../../../polymer-types";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { PersonMutableParams } from "../../../data/person";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
|
||||
class DialogPersonDetail extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@ -55,26 +56,18 @@ class DialogPersonDetail extends LitElement {
|
||||
return html``;
|
||||
}
|
||||
const nameInvalid = this._name.trim() === "";
|
||||
const title = html`
|
||||
${this._params.entry
|
||||
? this._params.entry.name
|
||||
: this.hass!.localize("ui.panel.config.person.detail.new_person")}
|
||||
<paper-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.dismiss"
|
||||
)}
|
||||
icon="hass:close"
|
||||
dialogAction="close"
|
||||
style="position: absolute; right: 16px; top: 12px;"
|
||||
></paper-icon-button>
|
||||
`;
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closing="${this._close}"
|
||||
scrimClickAction=""
|
||||
escapeKeyAction=""
|
||||
.heading=${title}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._params.entry
|
||||
? this._params.entry.name
|
||||
: this.hass!.localize("ui.panel.config.person.detail.new_person")
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
${this._error
|
||||
@ -236,34 +229,14 @@ class DialogPersonDetail extends LitElement {
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 400px;
|
||||
--mdc-dialog-max-width: 600px;
|
||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
||||
--justify-action-buttons: space-between;
|
||||
}
|
||||
/* make dialog fullscreen on small screens */
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 100vw;
|
||||
--mdc-dialog-max-height: 100vh;
|
||||
--mdc-dialog-shape-radius: 0px;
|
||||
--vertial-align-dialog: flex-end;
|
||||
}
|
||||
}
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
ha-user-picker {
|
||||
margin-top: 16px;
|
||||
}
|
||||
mwc-button.warning {
|
||||
--mdc-theme-primary: var(--google-red-500);
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
${this._showHelp
|
||||
? html`
|
||||
<ha-service-description
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
domain="zha"
|
||||
service="permit"
|
||||
class="help-text"
|
||||
|
@ -165,7 +165,7 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
<ha-call-service-button
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
domain="zha"
|
||||
service="set_zigbee_cluster_attribute"
|
||||
.serviceData="${this._setAttributeServiceData}"
|
||||
@ -177,7 +177,7 @@ export class ZHAClusterAttributes extends LitElement {
|
||||
${this.showHelp
|
||||
? html`
|
||||
<ha-service-description
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
domain="zha"
|
||||
service="set_zigbee_cluster_attribute"
|
||||
class="help-text2"
|
||||
|
@ -127,7 +127,7 @@ export class ZHAClusterCommands extends LitElement {
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-call-service-button
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
domain="zha"
|
||||
service="issue_zigbee_cluster_command"
|
||||
.serviceData="${this._issueClusterCommandServiceData}"
|
||||
@ -139,7 +139,7 @@ export class ZHAClusterCommands extends LitElement {
|
||||
${this._showHelp
|
||||
? html`
|
||||
<ha-service-description
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
domain="zha"
|
||||
service="issue_zigbee_cluster_command"
|
||||
class="help-text2"
|
||||
|
@ -299,7 +299,7 @@ class ZHADeviceCard extends LitElement {
|
||||
: ""}
|
||||
|
||||
<ha-call-service-button
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
domain="zha"
|
||||
service="remove"
|
||||
.confirmation=${this.hass!.localize(
|
||||
@ -331,7 +331,7 @@ class ZHADeviceCard extends LitElement {
|
||||
${this.showHelp
|
||||
? html`
|
||||
<ha-service-description
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
domain="zha"
|
||||
service="permit"
|
||||
class="help-text2"
|
||||
|
@ -59,14 +59,14 @@ export class ZHADevicePage extends LitElement {
|
||||
>
|
||||
<zha-node
|
||||
.isWide="${this.isWide}"
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.device=${this.device}
|
||||
></zha-node>
|
||||
|
||||
${this.device && this.device.device_type !== "Coordinator"
|
||||
? html`
|
||||
<zha-clusters
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.isWide="${this.isWide}"
|
||||
.selectedDevice="${this.device}"
|
||||
@zha-cluster-selected="${this._onClusterSelected}"
|
||||
@ -75,14 +75,14 @@ export class ZHADevicePage extends LitElement {
|
||||
? html`
|
||||
<zha-cluster-attributes
|
||||
.isWide="${this.isWide}"
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.selectedNode="${this.device}"
|
||||
.selectedCluster="${this._selectedCluster}"
|
||||
></zha-cluster-attributes>
|
||||
|
||||
<zha-cluster-commands
|
||||
.isWide="${this.isWide}"
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.selectedNode="${this.device}"
|
||||
.selectedCluster="${this._selectedCluster}"
|
||||
></zha-cluster-commands>
|
||||
@ -92,7 +92,7 @@ export class ZHADevicePage extends LitElement {
|
||||
? html`
|
||||
<zha-device-binding-control
|
||||
.isWide="${this.isWide}"
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.selectedDevice="${this.device}"
|
||||
.bindableDevices="${this._bindableDevices}"
|
||||
></zha-device-binding-control>
|
||||
@ -103,7 +103,7 @@ export class ZHADevicePage extends LitElement {
|
||||
<zha-group-binding-control
|
||||
.isWide="${this.isWide}"
|
||||
.narrow="${this.narrow}"
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.selectedDevice="${this.device}"
|
||||
.groups="${this._groups}"
|
||||
></zha-group-binding-control>
|
||||
|
@ -12,7 +12,6 @@ import "@material/mwc-button";
|
||||
|
||||
import "../../../components/map/ha-location-editor";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/ha-dialog";
|
||||
|
||||
import { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
@ -23,6 +22,8 @@ import {
|
||||
getZoneEditorInitData,
|
||||
} from "../../../data/zone";
|
||||
import { addDistanceToCoord } from "../../../common/location/add_distance_to_coord";
|
||||
import { createCloseHeading } from "../../../components/ha-dialog";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
|
||||
class DialogZoneDetail extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
@ -72,19 +73,6 @@ class DialogZoneDetail extends LitElement {
|
||||
if (!this._params) {
|
||||
return html``;
|
||||
}
|
||||
const title = html`
|
||||
${this._params.entry
|
||||
? this._params.entry.name
|
||||
: this.hass!.localize("ui.panel.config.zone.detail.new_zone")}
|
||||
<paper-icon-button
|
||||
aria-label=${this.hass.localize(
|
||||
"ui.panel.config.integrations.config_flow.dismiss"
|
||||
)}
|
||||
icon="hass:close"
|
||||
dialogAction="close"
|
||||
style="position: absolute; right: 16px; top: 12px;"
|
||||
></paper-icon-button>
|
||||
`;
|
||||
const nameValid = this._name.trim() === "";
|
||||
const iconValid = !this._icon.trim().includes(":");
|
||||
const latValid = String(this._latitude) === "";
|
||||
@ -100,7 +88,12 @@ class DialogZoneDetail extends LitElement {
|
||||
@closing="${this._close}"
|
||||
scrimClickAction=""
|
||||
escapeKeyAction=""
|
||||
.heading=${title}
|
||||
.heading=${createCloseHeading(
|
||||
this.hass,
|
||||
this._params.entry
|
||||
? this._params.entry.name
|
||||
: this.hass!.localize("ui.panel.config.zone.detail.new_zone")
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
${this._error
|
||||
@ -277,26 +270,8 @@ class DialogZoneDetail extends LitElement {
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--mdc-dialog-title-ink-color: var(--primary-text-color);
|
||||
--justify-action-buttons: space-between;
|
||||
}
|
||||
@media only screen and (min-width: 600px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
/* make dialog fullscreen on small screens */
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 100vw;
|
||||
--mdc-dialog-max-height: 100vh;
|
||||
--mdc-dialog-shape-radius: 0px;
|
||||
--vertial-align-dialog: flex-end;
|
||||
}
|
||||
}
|
||||
.form {
|
||||
padding-bottom: 24px;
|
||||
color: var(--primary-text-color);
|
||||
@ -320,12 +295,6 @@ class DialogZoneDetail extends LitElement {
|
||||
ha-user-picker {
|
||||
margin-top: 16px;
|
||||
}
|
||||
mwc-button.warning {
|
||||
--mdc-theme-primary: var(--google-red-500);
|
||||
}
|
||||
.error {
|
||||
color: var(--google-red-500);
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
@ -41,7 +41,10 @@ class HaPanelHistory extends LocalizeMixin(PolymerElement) {
|
||||
margin-right: 16px;
|
||||
margin-top: 5px;
|
||||
--paper-input-container-label-floating: {
|
||||
padding-bottom: 10px;
|
||||
padding-bottom: 11px;
|
||||
}
|
||||
--paper-input-suffix: {
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ import {
|
||||
} from "lit-element";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "lit-virtualizer";
|
||||
import { scroll } from "lit-virtualizer";
|
||||
import { LogbookEntry } from "../../data/logbook";
|
||||
|
||||
class HaLogbook extends LitElement {
|
||||
@ -44,19 +44,23 @@ class HaLogbook extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<lit-virtualizer
|
||||
.items=${this.entries}
|
||||
.renderItem=${(item: LogbookEntry, index: number) =>
|
||||
this._renderLogbookItem(item, index)}
|
||||
style="height: 100%;"
|
||||
></lit-virtualizer>
|
||||
<div>
|
||||
${scroll({
|
||||
items: this.entries,
|
||||
renderItem: (item: LogbookEntry, index?: number) =>
|
||||
this._renderLogbookItem(item, index),
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderLogbookItem(
|
||||
item: LogbookEntry,
|
||||
index: number
|
||||
index?: number
|
||||
): TemplateResult {
|
||||
if (!index) {
|
||||
return html``;
|
||||
}
|
||||
const previous = this.entries[index - 1];
|
||||
const state = item.entity_id ? this.hass.states[item.entity_id] : undefined;
|
||||
return html`
|
||||
@ -149,6 +153,19 @@ class HaLogbook extends LitElement {
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.uni-virtualizer-host {
|
||||
display: block;
|
||||
position: relative;
|
||||
contain: strict;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.uni-virtualizer-host > * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -27,10 +27,6 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="ha-style">
|
||||
.content {
|
||||
padding: 0 16px 0 16px;
|
||||
}
|
||||
|
||||
ha-logbook {
|
||||
height: calc(100vh - 136px);
|
||||
}
|
||||
@ -52,7 +48,8 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-end;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
:host([narrow]) .filters {
|
||||
@ -73,7 +70,10 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
|
||||
max-width: 100px;
|
||||
margin-right: 16px;
|
||||
--paper-input-container-label-floating: {
|
||||
padding-bottom: 10px;
|
||||
padding-bottom: 11px;
|
||||
}
|
||||
--paper-input-suffix: {
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,6 +92,9 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
|
||||
display: inline-block;
|
||||
flex-grow: 1;
|
||||
max-width: 400px;
|
||||
--paper-input-suffix: {
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
:host([narrow]) ha-entity-picker {
|
||||
@ -129,58 +132,53 @@ class HaPanelLogbook extends LocalizeMixin(PolymerElement) {
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
|
||||
<div class="content">
|
||||
<paper-spinner
|
||||
active="[[isLoading]]"
|
||||
hidden$="[[!isLoading]]"
|
||||
alt="[[localize('ui.common.loading')]]"
|
||||
></paper-spinner>
|
||||
<paper-spinner
|
||||
active="[[isLoading]]"
|
||||
hidden$="[[!isLoading]]"
|
||||
alt="[[localize('ui.common.loading')]]"
|
||||
></paper-spinner>
|
||||
|
||||
<div class="filters">
|
||||
<vaadin-date-picker
|
||||
id="picker"
|
||||
value="{{_currentDate}}"
|
||||
label="[[localize('ui.panel.logbook.showing_entries')]]"
|
||||
disabled="[[isLoading]]"
|
||||
required
|
||||
></vaadin-date-picker>
|
||||
<div class="filters">
|
||||
<vaadin-date-picker
|
||||
id="picker"
|
||||
value="{{_currentDate}}"
|
||||
label="[[localize('ui.panel.logbook.showing_entries')]]"
|
||||
disabled="[[isLoading]]"
|
||||
required
|
||||
></vaadin-date-picker>
|
||||
|
||||
<paper-dropdown-menu
|
||||
label-float
|
||||
label="[[localize('ui.panel.logbook.period')]]"
|
||||
disabled="[[isLoading]]"
|
||||
>
|
||||
<paper-listbox
|
||||
slot="dropdown-content"
|
||||
selected="{{_periodIndex}}"
|
||||
<paper-dropdown-menu
|
||||
label-float
|
||||
label="[[localize('ui.panel.logbook.period')]]"
|
||||
disabled="[[isLoading]]"
|
||||
>
|
||||
<paper-listbox slot="dropdown-content" selected="{{_periodIndex}}">
|
||||
<paper-item
|
||||
>[[localize('ui.duration.day', 'count', 1)]]</paper-item
|
||||
>
|
||||
<paper-item
|
||||
>[[localize('ui.duration.day', 'count', 1)]]</paper-item
|
||||
>
|
||||
<paper-item
|
||||
>[[localize('ui.duration.day', 'count', 3)]]</paper-item
|
||||
>
|
||||
<paper-item
|
||||
>[[localize('ui.duration.week', 'count', 1)]]</paper-item
|
||||
>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
<paper-item
|
||||
>[[localize('ui.duration.day', 'count', 3)]]</paper-item
|
||||
>
|
||||
<paper-item
|
||||
>[[localize('ui.duration.week', 'count', 1)]]</paper-item
|
||||
>
|
||||
</paper-listbox>
|
||||
</paper-dropdown-menu>
|
||||
|
||||
<ha-entity-picker
|
||||
hass="[[hass]]"
|
||||
value="{{_entityId}}"
|
||||
label="[[localize('ui.components.entity.entity-picker.entity')]]"
|
||||
disabled="[[isLoading]]"
|
||||
on-change="_entityPicked"
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
|
||||
<ha-logbook
|
||||
<ha-entity-picker
|
||||
hass="[[hass]]"
|
||||
entries="[[entries]]"
|
||||
hidden$="[[isLoading]]"
|
||||
></ha-logbook>
|
||||
value="{{_entityId}}"
|
||||
label="[[localize('ui.components.entity.entity-picker.entity')]]"
|
||||
disabled="[[isLoading]]"
|
||||
on-change="_entityPicked"
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
|
||||
<ha-logbook
|
||||
hass="[[hass]]"
|
||||
entries="[[entries]]"
|
||||
hidden$="[[isLoading]]"
|
||||
></ha-logbook>
|
||||
</app-header-layout>
|
||||
`;
|
||||
}
|
||||
|
@ -149,6 +149,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
${this._config.show_icon
|
||||
? html`
|
||||
<ha-icon
|
||||
tabindex="-1"
|
||||
data-domain=${ifDefined(
|
||||
this._config.state_color && stateObj
|
||||
? computeStateDomain(stateObj)
|
||||
@ -171,7 +172,7 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
: ""}
|
||||
${this._config.show_name
|
||||
? html`
|
||||
<span>
|
||||
<span tabindex="-1">
|
||||
${this._config.name ||
|
||||
(stateObj ? computeStateName(stateObj) : "")}
|
||||
</span>
|
||||
@ -225,6 +226,11 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
|
||||
color: var(--paper-item-icon-color, #44739e);
|
||||
}
|
||||
|
||||
ha-icon,
|
||||
span {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
${iconColorCSS}
|
||||
`;
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ import { EntitiesCardConfig, EntitiesCardEntityConfig } from "./types";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import { createHeaderFooterElement } from "../create-element/create-header-footer-element";
|
||||
import { LovelaceHeaderFooterConfig } from "../header-footer/types";
|
||||
import { DOMAINS_TOGGLE } from "../../../common/const";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
|
||||
@customElement("hui-entities-card")
|
||||
class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
@ -44,6 +46,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
private _hass?: HomeAssistant;
|
||||
|
||||
private _configEntities?: EntitiesCardEntityConfig[];
|
||||
private _showHeaderToggle?: boolean;
|
||||
|
||||
set hass(hass: HomeAssistant) {
|
||||
this._hass = hass;
|
||||
@ -78,6 +81,22 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
|
||||
this._config = { theme: "default", ...config };
|
||||
this._configEntities = entities;
|
||||
if (config.show_header_toggle === undefined) {
|
||||
// Default value is show toggle if we can at least toggle 2 entities.
|
||||
let toggleable = 0;
|
||||
for (const rowConf of entities) {
|
||||
if (!rowConf.entity) {
|
||||
continue;
|
||||
}
|
||||
toggleable += Number(DOMAINS_TOGGLE.has(computeDomain(rowConf.entity)));
|
||||
if (toggleable === 2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._showHeaderToggle = toggleable === 2;
|
||||
} else {
|
||||
this._showHeaderToggle = config.show_header_toggle;
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
@ -110,9 +129,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
${this._config.header
|
||||
? this.renderHeaderFooter(this._config.header, "header")
|
||||
: ""}
|
||||
${!this._config.title &&
|
||||
!this._config.show_header_toggle &&
|
||||
!this._config.icon
|
||||
${!this._config.title && !this._showHeaderToggle && !this._config.icon
|
||||
? ""
|
||||
: html`
|
||||
<div class="card-header">
|
||||
@ -127,7 +144,7 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
: ""}
|
||||
${this._config.title}
|
||||
</div>
|
||||
${this._config.show_header_toggle === false
|
||||
${!this._showHeaderToggle
|
||||
? html``
|
||||
: html`
|
||||
<hui-entities-toggle
|
||||
@ -173,6 +190,10 @@ class HuiEntitiesCard extends LitElement implements LovelaceCard {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#states > div {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 0px 18px 0px 8px;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { html, TemplateResult } from "lit-element";
|
||||
import { CSSResult, css } from "lit-element";
|
||||
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
import { HuiStackCard } from "./hui-stack-card";
|
||||
@ -17,9 +17,10 @@ class HuiHorizontalStackCard extends HuiStackCard {
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
protected renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
super.sharedStyles,
|
||||
css`
|
||||
#root {
|
||||
display: flex;
|
||||
}
|
||||
@ -34,8 +35,8 @@ class HuiHorizontalStackCard extends HuiStackCard {
|
||||
#root > *:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ import { toggleEntity } from "../common/entity/toggle-entity";
|
||||
import { LightCardConfig } from "./types";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { SUPPORT_BRIGHTNESS } from "../../../data/light";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
|
||||
@customElement("hui-light-card")
|
||||
export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
@ -84,10 +85,11 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
${stateObj.state === "unavailable"
|
||||
${stateObj.state === UNAVAILABLE
|
||||
? html`
|
||||
<hui-unavailable
|
||||
.text="${this.hass.localize("state.default.unavailable")}"
|
||||
.text=${this.hass.localize("state.default.unavailable")}
|
||||
@click=${this._handleMoreInfo}
|
||||
></hui-unavailable>
|
||||
`
|
||||
: ""}
|
||||
@ -233,6 +235,10 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
|
||||
display: block;
|
||||
}
|
||||
|
||||
hui-unavailable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
@ -1,18 +1,34 @@
|
||||
import { html, LitElement, TemplateResult, CSSResult, css } from "lit-element";
|
||||
import {
|
||||
html,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
CSSResult,
|
||||
css,
|
||||
property,
|
||||
} from "lit-element";
|
||||
|
||||
import { createCardElement } from "../create-element/create-card-element";
|
||||
import { LovelaceCard } from "../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { LovelaceCardConfig } from "../../../data/lovelace";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { StackCardConfig } from "./types";
|
||||
|
||||
export abstract class HuiStackCard extends LitElement implements LovelaceCard {
|
||||
static get properties() {
|
||||
return {
|
||||
_config: {},
|
||||
};
|
||||
public static async getConfigElement(): Promise<LovelaceCardEditor> {
|
||||
await import(
|
||||
/* webpackChunkName: "hui-stack-card-editor" */ "../editor/config-elements/hui-stack-card-editor"
|
||||
);
|
||||
return document.createElement("hui-stack-card-editor");
|
||||
}
|
||||
|
||||
public static getStubConfig(): object {
|
||||
return { cards: [] };
|
||||
}
|
||||
|
||||
@property() protected _cards?: LovelaceCard[];
|
||||
@property() private _config?: StackCardConfig;
|
||||
private _hass?: HomeAssistant;
|
||||
|
||||
set hass(hass: HomeAssistant) {
|
||||
this._hass = hass;
|
||||
|
||||
@ -24,9 +40,6 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
|
||||
element.hass = this._hass;
|
||||
}
|
||||
}
|
||||
protected _cards?: LovelaceCard[];
|
||||
private _config?: StackCardConfig;
|
||||
private _hass?: HomeAssistant;
|
||||
|
||||
public abstract getCardSize(): number;
|
||||
|
||||
@ -42,12 +55,11 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this._config) {
|
||||
if (!this._config || !this._cards) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this.renderStyle()}
|
||||
${this._config.title
|
||||
? html`
|
||||
<div class="card-header">${this._config.title}</div>
|
||||
@ -57,9 +69,7 @@ export abstract class HuiStackCard extends LitElement implements LovelaceCard {
|
||||
`;
|
||||
}
|
||||
|
||||
protected abstract renderStyle(): TemplateResult;
|
||||
|
||||
static get styles(): CSSResult {
|
||||
static get sharedStyles(): CSSResult {
|
||||
return css`
|
||||
.card-header {
|
||||
color: var(--ha-card-header-color, --primary-text-color);
|
||||
|
@ -34,6 +34,7 @@ import {
|
||||
} from "../../../data/climate";
|
||||
import { HassEntity } from "home-assistant-js-websocket";
|
||||
import { actionHandler } from "../common/directives/action-handler-directive";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
|
||||
const modeIcons: { [mode in HvacMode]: string } = {
|
||||
auto: "hass:calendar-repeat",
|
||||
@ -208,10 +209,11 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||
[mode]: true,
|
||||
})}
|
||||
>
|
||||
${stateObj.state === "unavailable"
|
||||
${stateObj.state === UNAVAILABLE
|
||||
? html`
|
||||
<hui-unavailable
|
||||
.text="${this.hass.localize("state.default.unavailable")}"
|
||||
@click=${this._handleMoreInfo}
|
||||
></hui-unavailable>
|
||||
`
|
||||
: ""}
|
||||
@ -396,6 +398,10 @@ export class HuiThermostatCard extends LitElement implements LovelaceCard {
|
||||
display: block;
|
||||
}
|
||||
|
||||
hui-unavailable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
ha-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { html, TemplateResult } from "lit-element";
|
||||
import { CSSResult, css } from "lit-element";
|
||||
|
||||
import { computeCardSize } from "../common/compute-card-size";
|
||||
import { HuiStackCard } from "./hui-stack-card";
|
||||
@ -18,9 +18,10 @@ class HuiVerticalStackCard extends HuiStackCard {
|
||||
return totalSize;
|
||||
}
|
||||
|
||||
protected renderStyle(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
super.sharedStyles,
|
||||
css`
|
||||
#root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -34,8 +35,8 @@ class HuiVerticalStackCard extends HuiStackCard {
|
||||
#root > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,6 +243,7 @@ export interface ShoppingListCardConfig extends LovelaceCardConfig {
|
||||
|
||||
export interface StackCardConfig extends LovelaceCardConfig {
|
||||
cards: LovelaceCardConfig[];
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface ThermostatCardConfig extends LovelaceCardConfig {
|
||||
|
@ -273,7 +273,6 @@ export const generateDefaultViewConfig = (
|
||||
areaEntities.map((entity) => [entity.entity_id, entity]),
|
||||
{
|
||||
title: area.name,
|
||||
show_header_toggle: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { loadModule, loadCSS, loadJS } from "../../../common/dom/load_resource";
|
||||
|
||||
import { LovelaceConfig } from "../../../data/lovelace";
|
||||
import { LovelaceResource } from "../../../data/lovelace";
|
||||
|
||||
// CSS and JS should only be imported once. Modules and HTML are safe.
|
||||
const CSS_CACHE = {};
|
||||
const JS_CACHE = {};
|
||||
|
||||
export const loadLovelaceResources = (
|
||||
resources: NonNullable<LovelaceConfig["resources"]>,
|
||||
resources: NonNullable<LovelaceResource[]>,
|
||||
hassUrl: string
|
||||
) =>
|
||||
resources.forEach((resource) => {
|
||||
|
@ -106,7 +106,7 @@ export class HuiActionEditor extends LitElement {
|
||||
${this.config && this.config.action === "call-service"
|
||||
? html`
|
||||
<ha-service-picker
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.value="${this._service}"
|
||||
.configValue="${"service"}"
|
||||
@value-changed="${this._valueChanged}"
|
||||
|
@ -93,6 +93,10 @@ export class HuiCardOptions extends LitElement {
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host(:hover) {
|
||||
outline: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
ha-card {
|
||||
border-top-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
|
@ -46,7 +46,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
return html`
|
||||
<div class="entity">
|
||||
<ha-entity-picker
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.value="${entityConf.entity}"
|
||||
.index="${index}"
|
||||
@change="${this._valueChanged}"
|
||||
@ -70,7 +70,7 @@ export class HuiEntityEditor extends LitElement {
|
||||
`;
|
||||
})}
|
||||
<ha-entity-picker
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
@change="${this._addEntity}"
|
||||
></ha-entity-picker>
|
||||
</div>
|
||||
|
@ -32,7 +32,7 @@ class HuiGenericEntityRow extends LitElement {
|
||||
|
||||
@property() public config?: EntitiesCardEntityConfig;
|
||||
|
||||
@property() public showSecondary: boolean = true;
|
||||
@property() public secondaryText?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.hass || !this.config) {
|
||||
@ -59,6 +59,8 @@ class HuiGenericEntityRow extends LitElement {
|
||||
(this.config.entity &&
|
||||
!DOMAINS_HIDE_MORE_INFO.includes(computeDomain(this.config.entity)));
|
||||
|
||||
const hasSecondary = this.secondaryText || this.config.secondary_info;
|
||||
|
||||
return html`
|
||||
<state-badge
|
||||
class=${classMap({
|
||||
@ -76,59 +78,59 @@ class HuiGenericEntityRow extends LitElement {
|
||||
})}
|
||||
tabindex=${ifDefined(pointer ? "0" : undefined)}
|
||||
></state-badge>
|
||||
<div class="flex">
|
||||
<div
|
||||
class=${classMap({
|
||||
info: true,
|
||||
pointer,
|
||||
padName: this.showSecondary && !this.config.secondary_info,
|
||||
padSecondary: Boolean(
|
||||
!this.showSecondary || this.config.secondary_info
|
||||
),
|
||||
})}
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: hasAction(this.config!.hold_action),
|
||||
hasDoubleClick: hasAction(this.config!.double_tap_action),
|
||||
})}
|
||||
>
|
||||
${this.config.name || computeStateName(stateObj)}
|
||||
<div class="secondary">
|
||||
${!this.showSecondary
|
||||
? html`
|
||||
<slot name="secondary"></slot>
|
||||
`
|
||||
: this.config.secondary_info === "entity-id"
|
||||
? stateObj.entity_id
|
||||
: this.config.secondary_info === "last-changed"
|
||||
? html`
|
||||
<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetime=${stateObj.last_changed}
|
||||
></ha-relative-time>
|
||||
`
|
||||
: this.config.secondary_info === "last-triggered"
|
||||
? stateObj.attributes.last_triggered
|
||||
? html`
|
||||
<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetime=${stateObj.attributes.last_triggered}
|
||||
></ha-relative-time>
|
||||
`
|
||||
: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.entities.never_triggered"
|
||||
)
|
||||
: ""}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
<div
|
||||
class="info ${classMap({
|
||||
pointer,
|
||||
"text-content": !hasSecondary,
|
||||
})}"
|
||||
@action=${this._handleAction}
|
||||
.actionHandler=${actionHandler({
|
||||
hasHold: hasAction(this.config!.hold_action),
|
||||
hasDoubleClick: hasAction(this.config!.double_tap_action),
|
||||
})}
|
||||
>
|
||||
${this.config.name || computeStateName(stateObj)}
|
||||
${hasSecondary
|
||||
? html`
|
||||
<div class="secondary">
|
||||
${this.secondaryText ||
|
||||
this.config.secondary_info === "entity-id"
|
||||
? stateObj.entity_id
|
||||
: this.config.secondary_info === "last-changed"
|
||||
? html`
|
||||
<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetime=${stateObj.last_changed}
|
||||
></ha-relative-time>
|
||||
`
|
||||
: this.config.secondary_info === "last-triggered"
|
||||
? stateObj.attributes.last_triggered
|
||||
? html`
|
||||
<ha-relative-time
|
||||
.hass=${this.hass}
|
||||
.datetime=${stateObj.attributes.last_triggered}
|
||||
></ha-relative-time>
|
||||
`
|
||||
: this.hass.localize(
|
||||
"ui.panel.lovelace.cards.entities.never_triggered"
|
||||
)
|
||||
: ""}
|
||||
}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
</div>
|
||||
<slot></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
toggleAttribute(
|
||||
this,
|
||||
"no-secondary",
|
||||
!this.secondaryText && !this.config?.secondary_info
|
||||
);
|
||||
if (changedProps.has("hass")) {
|
||||
toggleAttribute(this, "rtl", computeRTL(this.hass!));
|
||||
}
|
||||
@ -143,16 +145,10 @@ class HuiGenericEntityRow extends LitElement {
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.flex {
|
||||
flex: 1;
|
||||
margin-left: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
flex-direction: row;
|
||||
}
|
||||
.info {
|
||||
margin-left: 16px;
|
||||
flex: 1 0 60px;
|
||||
}
|
||||
.info,
|
||||
@ -161,6 +157,11 @@ class HuiGenericEntityRow extends LitElement {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
:host([no-secondary]) .text-content,
|
||||
:host([no-secondary]) ::slotted(.text-content) {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
.flex ::slotted(*) {
|
||||
margin-left: 8px;
|
||||
min-width: 0;
|
||||
@ -192,12 +193,6 @@ class HuiGenericEntityRow extends LitElement {
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.padName {
|
||||
padding: 12px 0px;
|
||||
}
|
||||
.padSecondary {
|
||||
padding: 4px 0px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ export class HuiImage extends LitElement {
|
||||
${this.cameraImage && this.cameraView === "live"
|
||||
? html`
|
||||
<ha-camera-stream
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.stateObj="${cameraObj}"
|
||||
></ha-camera-stream>
|
||||
`
|
||||
|
@ -22,7 +22,7 @@ export const addEntitiesToLovelaceView = async (
|
||||
}
|
||||
if (!lovelaceConfig) {
|
||||
try {
|
||||
lovelaceConfig = await fetchConfig(hass.connection, false);
|
||||
lovelaceConfig = await fetchConfig(hass.connection, null, false);
|
||||
} catch {
|
||||
alert(
|
||||
hass.localize(
|
||||
@ -41,7 +41,7 @@ export const addEntitiesToLovelaceView = async (
|
||||
if (!saveConfigFunc) {
|
||||
saveConfigFunc = async (newConfig: LovelaceConfig): Promise<void> => {
|
||||
try {
|
||||
await saveConfig(hass!, newConfig);
|
||||
await saveConfig(hass!, null, newConfig);
|
||||
} catch {
|
||||
alert(
|
||||
hass.localize("ui.panel.config.devices.add_entities.saving_failed")
|
||||
|
@ -99,7 +99,7 @@ export class HuiDialogEditCard extends LitElement {
|
||||
${this._cardConfig === undefined
|
||||
? html`
|
||||
<hui-card-picker
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
@config-changed="${this._handleCardPicked}"
|
||||
></hui-card-picker>
|
||||
`
|
||||
@ -107,14 +107,14 @@ export class HuiDialogEditCard extends LitElement {
|
||||
<div class="content">
|
||||
<div class="element-editor">
|
||||
<hui-card-editor
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.value="${this._cardConfig}"
|
||||
@config-changed="${this._handleConfigChanged}"
|
||||
></hui-card-editor>
|
||||
</div>
|
||||
<div class="element-preview">
|
||||
<hui-card-preview
|
||||
.hass="${this.hass}"
|
||||
.hass=${this.hass}
|
||||
.config="${this._cardConfig}"
|
||||
class=${this._error ? "blur" : ""}
|
||||
></hui-card-preview>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user