mirror of
https://github.com/home-assistant/frontend.git
synced 2025-08-11 18:29:27 +00:00
Compare commits
88 Commits
20231129.1
...
initial-fo
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e98eb8de7f | ||
![]() |
12e6701ffa | ||
![]() |
7167b66719 | ||
![]() |
a52ba5fbd1 | ||
![]() |
22cf903656 | ||
![]() |
d77b657036 | ||
![]() |
4a3038c12c | ||
![]() |
3ac7cd5d4a | ||
![]() |
58eddd2b42 | ||
![]() |
d808da68bd | ||
![]() |
2ed4d1efa0 | ||
![]() |
fcb9e13a84 | ||
![]() |
3ada2f3279 | ||
![]() |
8d2d45ae4e | ||
![]() |
c9e6963387 | ||
![]() |
6d36b0e28c | ||
![]() |
61117bb34f | ||
![]() |
86a3c32844 | ||
![]() |
b855b3e103 | ||
![]() |
80edeebab9 | ||
![]() |
f366e287b1 | ||
![]() |
9ce8684aba | ||
![]() |
caa6ea531c | ||
![]() |
cca1183ee3 | ||
![]() |
0f9c97aea0 | ||
![]() |
8d08aa8c79 | ||
![]() |
eebcab435d | ||
![]() |
76d3c6e237 | ||
![]() |
a820ca1e90 | ||
![]() |
39260d172f | ||
![]() |
e646528b86 | ||
![]() |
86726102fb | ||
![]() |
1330558819 | ||
![]() |
b4ab0fc10b | ||
![]() |
15becf9ef6 | ||
![]() |
ef3785ce9f | ||
![]() |
134e13005d | ||
![]() |
dccd9b2541 | ||
![]() |
de83ad7a7a | ||
![]() |
7e55b9e6b8 | ||
![]() |
1b74ca47bf | ||
![]() |
c3d4be9ceb | ||
![]() |
b8d0c7f7c7 | ||
![]() |
92bce4078f | ||
![]() |
ff8f0697c2 | ||
![]() |
dddba7af00 | ||
![]() |
14a49d6664 | ||
![]() |
22da402d56 | ||
![]() |
aa4acc6572 | ||
![]() |
4d432fba57 | ||
![]() |
97b71c785b | ||
![]() |
8a93284bb3 | ||
![]() |
bb2abe4efc | ||
![]() |
ccada33caf | ||
![]() |
c5f15ee6ba | ||
![]() |
6f240ec681 | ||
![]() |
fc6aef138d | ||
![]() |
6d0eb05954 | ||
![]() |
96fbbc55e1 | ||
![]() |
7e74502ba3 | ||
![]() |
b273b31b03 | ||
![]() |
cf00ac74c6 | ||
![]() |
cc702f0fb3 | ||
![]() |
cc6eb5789d | ||
![]() |
669b80a9e1 | ||
![]() |
4c4fddee94 | ||
![]() |
b28a4e6b50 | ||
![]() |
bdd6a55a84 | ||
![]() |
d1ebc06994 | ||
![]() |
2a150788b4 | ||
![]() |
72caf72e80 | ||
![]() |
a6ff1899df | ||
![]() |
6a999597df | ||
![]() |
219fc9e53a | ||
![]() |
861959ed2d | ||
![]() |
f7f50294e7 | ||
![]() |
a226333c1e | ||
![]() |
75f18aa69b | ||
![]() |
60ff4fdc1f | ||
![]() |
8d14cb0ab7 | ||
![]() |
606c809880 | ||
![]() |
f1692038f8 | ||
![]() |
79f5ec0cd5 | ||
![]() |
416a2a080d | ||
![]() |
92dc8b1561 | ||
![]() |
07b807adfc | ||
![]() |
843430ef41 | ||
![]() |
b5b2392dd2 |
2
.github/workflows/labeler.yaml
vendored
2
.github/workflows/labeler.yaml
vendored
@@ -10,6 +10,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Apply labels
|
||||
uses: actions/labeler@v4.3.0
|
||||
uses: actions/labeler@v5.0.0
|
||||
with:
|
||||
sync-labels: true
|
||||
|
2
.github/workflows/nightly.yaml
vendored
2
.github/workflows/nightly.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
uses: actions/checkout@v4.1.1
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
|
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v4
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 90 days stale policy
|
||||
uses: actions/stale@v8.0.0
|
||||
uses: actions/stale@v9.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 90
|
||||
|
@@ -39,7 +39,9 @@ export class HADemoCard extends LitElement implements LovelaceCard {
|
||||
<div class="picker">
|
||||
<div class="label">
|
||||
${this._switching
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
? html`<ha-circular-progress
|
||||
indeterminate
|
||||
></ha-circular-progress>`
|
||||
: until(
|
||||
selectedDemoConfig.then(
|
||||
(conf) => html`
|
||||
|
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: Circular Progress
|
||||
subtitle: Can be used to indicate an ongoing task.
|
||||
---
|
64
gallery/src/pages/components/ha-circular-progress.ts
Normal file
64
gallery/src/pages/components/ha-circular-progress.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { html, css, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../src/components/ha-bar";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-circular-progress";
|
||||
import "@material/web/progress/circular-progress";
|
||||
import { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
@customElement("demo-components-ha-circular-progress")
|
||||
export class DemoHaCircularProgress extends LitElement {
|
||||
@property({ attribute: false }) hass!: HomeAssistant;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<ha-card header="Basic circular progress">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress></div
|
||||
></ha-card>
|
||||
<ha-card header="Different circular progress sizes">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="tiny"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="medium"
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="large"
|
||||
></ha-circular-progress></div
|
||||
></ha-card>
|
||||
<ha-card header="Circular progress with an aria-label">
|
||||
<div class="card-content">
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
aria-label="Doing something..."
|
||||
></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
.ariaLabel=${"Doing something..."}
|
||||
></ha-circular-progress></div
|
||||
></ha-card>`;
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ha-card {
|
||||
max-width: 600px;
|
||||
margin: 24px auto;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-components-ha-circular-progress": DemoHaCircularProgress;
|
||||
}
|
||||
}
|
@@ -20,7 +20,7 @@ class HassioAddonConfigDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
}
|
||||
const hasConfiguration =
|
||||
(this.addon.options && Object.keys(this.addon.options).length) ||
|
||||
|
@@ -34,7 +34,7 @@ class HassioAddonDocumentationDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
|
@@ -22,7 +22,7 @@ class HassioAddonInfoDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
|
@@ -18,7 +18,9 @@ class HassioAddonLogDashboard extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
if (!this.addon) {
|
||||
return html` <ha-circular-progress active></ha-circular-progress> `;
|
||||
return html`
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<div class="content">
|
||||
|
@@ -95,7 +95,7 @@ class HassioBackupDialog
|
||||
</ha-header-bar>
|
||||
</div>
|
||||
${this._restoringBackup
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
|
||||
: html`
|
||||
<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
|
@@ -57,7 +57,7 @@ class HassioCreateBackupDialog extends LitElement {
|
||||
)}
|
||||
>
|
||||
${this._creatingBackup
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
? html`<ha-circular-progress indeterminate></ha-circular-progress>`
|
||||
: html`<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
|
@@ -71,7 +71,11 @@ class HassioDatadiskDialog extends LitElement {
|
||||
?hideActions=${this.moving}
|
||||
>
|
||||
${this.moving
|
||||
? html` <ha-circular-progress alt="Moving" size="large" active>
|
||||
? html` <ha-circular-progress
|
||||
aria-label="Moving"
|
||||
size="large"
|
||||
indeterminate
|
||||
>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this.dialogParams.supervisor.localize(
|
||||
|
@@ -155,7 +155,11 @@ export class DialogHassioNetwork
|
||||
.disabled=${this._scanning}
|
||||
>
|
||||
${this._scanning
|
||||
? html`<ha-circular-progress active size="small">
|
||||
? html`<ha-circular-progress
|
||||
aria-label="Scanning"
|
||||
indeterminate
|
||||
size="small"
|
||||
>
|
||||
</ha-circular-progress>`
|
||||
: this.supervisor.localize("dialog.network.scan_ap")}
|
||||
</mwc-button>
|
||||
@@ -274,7 +278,7 @@ export class DialogHassioNetwork
|
||||
</mwc-button>
|
||||
<mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
|
||||
${this._processing
|
||||
? html`<ha-circular-progress active size="small">
|
||||
? html`<ha-circular-progress indeterminate size="small">
|
||||
</ha-circular-progress>`
|
||||
: this.supervisor.localize("common.save")}
|
||||
</mwc-button>
|
||||
|
@@ -158,7 +158,7 @@ class HassioRepositoriesDialog extends LitElement {
|
||||
<mwc-button @click=${this._addRepository}>
|
||||
${this._processing
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>`
|
||||
: this._dialogParams!.supervisor.localize(
|
||||
|
@@ -174,7 +174,11 @@ class UpdateAvailableCard extends LitElement {
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: html`<ha-circular-progress alt="Updating" size="large" active>
|
||||
: html`<ha-circular-progress
|
||||
aria-label="Updating"
|
||||
size="large"
|
||||
indeterminate
|
||||
>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this.supervisor.localize("update_available.updating", {
|
||||
|
70
package.json
70
package.json
@@ -25,15 +25,15 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.23.4",
|
||||
"@braintree/sanitize-url": "6.0.4",
|
||||
"@codemirror/autocomplete": "6.11.0",
|
||||
"@codemirror/commands": "6.3.0",
|
||||
"@codemirror/language": "6.9.2",
|
||||
"@babel/runtime": "7.23.5",
|
||||
"@braintree/sanitize-url": "7.0.0",
|
||||
"@codemirror/autocomplete": "6.11.1",
|
||||
"@codemirror/commands": "6.3.2",
|
||||
"@codemirror/language": "6.9.3",
|
||||
"@codemirror/legacy-modes": "6.3.3",
|
||||
"@codemirror/search": "6.5.4",
|
||||
"@codemirror/state": "6.3.1",
|
||||
"@codemirror/view": "6.22.0",
|
||||
"@codemirror/search": "6.5.5",
|
||||
"@codemirror/state": "6.3.3",
|
||||
"@codemirror/view": "6.22.1",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.12.0",
|
||||
"@formatjs/intl-displaynames": "6.6.4",
|
||||
@@ -43,12 +43,12 @@
|
||||
"@formatjs/intl-numberformat": "8.9.0",
|
||||
"@formatjs/intl-pluralrules": "5.2.10",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.10",
|
||||
"@fullcalendar/core": "6.1.9",
|
||||
"@fullcalendar/daygrid": "6.1.9",
|
||||
"@fullcalendar/interaction": "6.1.9",
|
||||
"@fullcalendar/list": "6.1.9",
|
||||
"@fullcalendar/luxon3": "6.1.9",
|
||||
"@fullcalendar/timegrid": "6.1.9",
|
||||
"@fullcalendar/core": "6.1.10",
|
||||
"@fullcalendar/daygrid": "6.1.10",
|
||||
"@fullcalendar/interaction": "6.1.10",
|
||||
"@fullcalendar/list": "6.1.10",
|
||||
"@fullcalendar/luxon3": "6.1.10",
|
||||
"@fullcalendar/timegrid": "6.1.10",
|
||||
"@lezer/highlight": "1.2.0",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.6",
|
||||
@@ -60,7 +60,6 @@
|
||||
"@material/mwc-base": "0.27.0",
|
||||
"@material/mwc-button": "0.27.0",
|
||||
"@material/mwc-checkbox": "0.27.0",
|
||||
"@material/mwc-circular-progress": "0.27.0",
|
||||
"@material/mwc-dialog": "0.27.0",
|
||||
"@material/mwc-drawer": "0.27.0",
|
||||
"@material/mwc-fab": "0.27.0",
|
||||
@@ -91,8 +90,8 @@
|
||||
"@polymer/paper-toast": "3.0.1",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.2.3",
|
||||
"@vaadin/vaadin-themable-mixin": "24.2.3",
|
||||
"@vaadin/combo-box": "24.2.5",
|
||||
"@vaadin/vaadin-themable-mixin": "24.2.5",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@@ -100,7 +99,7 @@
|
||||
"@webcomponents/scoped-custom-element-registry": "0.0.9",
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"app-datepicker": "5.1.1",
|
||||
"chart.js": "4.4.0",
|
||||
"chart.js": "4.4.1",
|
||||
"comlink": "4.4.1",
|
||||
"core-js": "3.33.3",
|
||||
"cropperjs": "1.6.1",
|
||||
@@ -111,7 +110,7 @@
|
||||
"element-internals-polyfill": "1.3.9",
|
||||
"fuse.js": "7.0.0",
|
||||
"google-timezones-json": "1.2.0",
|
||||
"hls.js": "1.4.12",
|
||||
"hls.js": "1.4.13",
|
||||
"home-assistant-js-websocket": "9.1.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.5.8",
|
||||
@@ -120,17 +119,16 @@
|
||||
"leaflet-draw": "1.0.4",
|
||||
"lit": "2.8.0",
|
||||
"luxon": "3.4.4",
|
||||
"marked": "10.0.0",
|
||||
"marked": "11.0.1",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "0.3.2",
|
||||
"punycode": "2.3.1",
|
||||
"qr-scanner": "1.4.2",
|
||||
"qrcode": "1.5.3",
|
||||
"resize-observer-polyfill": "1.5.1",
|
||||
"roboto-fontface": "0.10.0",
|
||||
"rrule": "2.8.1",
|
||||
"sortablejs": "1.15.0",
|
||||
"sortablejs": "1.15.1",
|
||||
"stacktrace-js": "2.0.2",
|
||||
"superstruct": "1.0.3",
|
||||
"tinykeys": "2.1.0",
|
||||
@@ -152,15 +150,15 @@
|
||||
"xss": "1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.23.3",
|
||||
"@babel/core": "7.23.5",
|
||||
"@babel/helper-define-polyfill-provider": "0.4.3",
|
||||
"@babel/plugin-proposal-decorators": "7.23.3",
|
||||
"@babel/plugin-proposal-decorators": "7.23.5",
|
||||
"@babel/plugin-transform-runtime": "7.23.4",
|
||||
"@babel/preset-env": "7.23.3",
|
||||
"@babel/preset-env": "7.23.5",
|
||||
"@babel/preset-typescript": "7.23.3",
|
||||
"@bundle-stats/plugin-webpack-filter": "4.8.3",
|
||||
"@koa/cors": "4.0.0",
|
||||
"@lokalise/node-api": "12.0.0",
|
||||
"@lokalise/node-api": "12.1.0",
|
||||
"@octokit/auth-oauth-device": "6.0.1",
|
||||
"@octokit/plugin-retry": "6.0.1",
|
||||
"@octokit/rest": "20.0.2",
|
||||
@@ -178,7 +176,7 @@
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/leaflet": "1.9.8",
|
||||
"@types/leaflet-draw": "1.0.11",
|
||||
"@types/luxon": "3.3.5",
|
||||
"@types/luxon": "3.3.7",
|
||||
"@types/mocha": "10.0.6",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/serve-handler": "6.1.4",
|
||||
@@ -186,18 +184,18 @@
|
||||
"@types/tar": "6.1.10",
|
||||
"@types/ua-parser-js": "0.7.39",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "6.12.0",
|
||||
"@typescript-eslint/parser": "6.12.0",
|
||||
"@typescript-eslint/eslint-plugin": "6.13.2",
|
||||
"@typescript-eslint/parser": "6.13.2",
|
||||
"@web/dev-server": "0.1.38",
|
||||
"@web/dev-server-rollup": "0.4.1",
|
||||
"babel-loader": "9.1.3",
|
||||
"babel-plugin-template-html-minifier": "4.1.0",
|
||||
"chai": "4.3.10",
|
||||
"del": "7.1.0",
|
||||
"eslint": "8.54.0",
|
||||
"eslint": "8.55.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "17.1.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-import-resolver-webpack": "0.13.8",
|
||||
"eslint-plugin-disable": "2.0.3",
|
||||
"eslint-plugin-import": "2.29.0",
|
||||
@@ -206,7 +204,7 @@
|
||||
"eslint-plugin-unused-imports": "3.0.0",
|
||||
"eslint-plugin-wc": "2.0.4",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.1.1",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "10.3.10",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-flatmap": "1.0.2",
|
||||
@@ -218,7 +216,7 @@
|
||||
"husky": "8.0.3",
|
||||
"instant-mocha": "1.5.2",
|
||||
"jszip": "3.10.1",
|
||||
"lint-staged": "15.1.0",
|
||||
"lint-staged": "15.2.0",
|
||||
"lit-analyzer": "2.0.1",
|
||||
"lodash.template": "4.5.0",
|
||||
"magic-string": "0.30.5",
|
||||
@@ -231,7 +229,7 @@
|
||||
"rollup": "2.79.1",
|
||||
"rollup-plugin-string": "3.0.0",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"rollup-plugin-visualizer": "5.9.3",
|
||||
"rollup-plugin-visualizer": "5.10.0",
|
||||
"serve-handler": "6.1.5",
|
||||
"sinon": "17.0.1",
|
||||
"source-map-url": "0.4.1",
|
||||
@@ -239,7 +237,7 @@
|
||||
"tar": "6.2.0",
|
||||
"terser-webpack-plugin": "5.3.9",
|
||||
"ts-lit-plugin": "2.0.1",
|
||||
"typescript": "5.3.2",
|
||||
"typescript": "5.3.3",
|
||||
"vinyl-buffer": "1.0.1",
|
||||
"vinyl-source-stream": "2.0.0",
|
||||
"webpack": "5.89.0",
|
||||
@@ -255,7 +253,7 @@
|
||||
"@polymer/polymer": "patch:@polymer/polymer@3.5.1#./.yarn/patches/@polymer/polymer/pr-5569.patch",
|
||||
"@material/mwc-button@^0.25.3": "^0.27.0",
|
||||
"lit": "2.8.0",
|
||||
"clean-css": "5.3.2",
|
||||
"clean-css": "5.3.3",
|
||||
"@lit/reactive-element": "1.6.3",
|
||||
"sortablejs@1.15.0": "patch:sortablejs@npm%3A1.15.0#./.yarn/patches/sortablejs-npm-1.15.0-f3a393abcc.patch",
|
||||
"leaflet-draw@1.0.4": "patch:leaflet-draw@npm%3A1.0.4#./.yarn/patches/leaflet-draw-npm-1.0.4-0ca0ebcf65.patch"
|
||||
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20231129.1"
|
||||
version = "20231208.2"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
@@ -21,6 +21,7 @@ import {
|
||||
DataEntryFlowStepForm,
|
||||
} from "../data/data_entry_flow";
|
||||
import "./ha-auth-form";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
|
||||
type State = "loading" | "error" | "step";
|
||||
|
||||
@@ -85,10 +86,6 @@ export class HaAuthFlow extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<style>
|
||||
ha-auth-flow .action {
|
||||
margin: 24px 0 8px;
|
||||
text-align: center;
|
||||
}
|
||||
ha-auth-flow .store-token {
|
||||
margin-left: -16px;
|
||||
}
|
||||
@@ -158,14 +155,25 @@ export class HaAuthFlow extends LitElement {
|
||||
}
|
||||
|
||||
private _renderForm() {
|
||||
const showBack =
|
||||
this.step?.type === "form" &&
|
||||
this.authProvider?.users &&
|
||||
!["select_mfa_module", "mfa"].includes(this.step.step_id);
|
||||
|
||||
switch (this._state) {
|
||||
case "step":
|
||||
if (this.step == null) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
${this._renderStep(this.step)}
|
||||
<div class="action">
|
||||
<div class="action ${showBack ? "space-between" : ""}">
|
||||
${showBack
|
||||
? html`<mwc-button @click=${this._localFlow}>
|
||||
${this.localize("ui.panel.page-authorize.form.previous")}
|
||||
</mwc-button>`
|
||||
: nothing}
|
||||
<mwc-button
|
||||
raised
|
||||
@click=${this._handleSubmit}
|
||||
@@ -294,7 +302,8 @@ export class HaAuthFlow extends LitElement {
|
||||
redirectWithAuthCode(
|
||||
this.redirectUri!,
|
||||
data.result,
|
||||
this.oauth2State
|
||||
this.oauth2State,
|
||||
this.storeToken
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -375,7 +384,8 @@ export class HaAuthFlow extends LitElement {
|
||||
redirectWithAuthCode(
|
||||
this.redirectUri!,
|
||||
newStep.result,
|
||||
this.oauth2State
|
||||
this.oauth2State,
|
||||
this.storeToken
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -390,6 +400,10 @@ export class HaAuthFlow extends LitElement {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _localFlow() {
|
||||
fireEvent(this, "default-login-flow", { value: false });
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -63,6 +63,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
ha-authorize ha-alert {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
background-color: var(--primary-background-color, #fafafa);
|
||||
}
|
||||
</style>
|
||||
<ha-alert alert-type="error"
|
||||
@@ -93,6 +94,7 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
background-color: var(--primary-background-color, #fafafa);
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
@@ -116,6 +118,16 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
position: relative;
|
||||
padding: 16px;
|
||||
}
|
||||
.action {
|
||||
margin: 16px 0 8px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 336px;
|
||||
justify-content: center;
|
||||
}
|
||||
.space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
.footer {
|
||||
padding-top: 8px;
|
||||
display: flex;
|
||||
@@ -164,7 +176,10 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
|
||||
<div class="card-content">
|
||||
<div
|
||||
class="card-content"
|
||||
@default-login-flow=${this._handleDefaultLoginFlow}
|
||||
>
|
||||
${!this._authProvider
|
||||
? html`<p>
|
||||
${this.localize("ui.panel.page-authorize.initializing")}
|
||||
@@ -181,7 +196,6 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
.authProviders=${this._authProviders}
|
||||
.localize=${this.localize}
|
||||
.ownInstance=${this._ownInstance}
|
||||
@default-login-flow=${this._handleDefaultLoginFlow}
|
||||
></ha-local-auth-flow>`
|
||||
: html`<ha-auth-flow
|
||||
.clientId=${this.clientId}
|
||||
@@ -267,6 +281,10 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
);
|
||||
}
|
||||
|
||||
if (window.innerWidth > 450) {
|
||||
import("../resources/particles");
|
||||
}
|
||||
|
||||
// If we are logging into the instance that is hosting this auth form
|
||||
// we will register the service worker to start preloading.
|
||||
if (url.host === location.host) {
|
||||
@@ -315,8 +333,8 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
private _handleDefaultLoginFlow() {
|
||||
this._forceDefaultLogin = true;
|
||||
private _handleDefaultLoginFlow(ev) {
|
||||
this._forceDefaultLogin = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _handleAuthProviderPick(ev) {
|
||||
|
@@ -17,7 +17,7 @@ import {
|
||||
submitLoginFlow,
|
||||
} from "../data/auth";
|
||||
import { DataEntryFlowStep } from "../data/data_entry_flow";
|
||||
import { listPersons } from "../data/person";
|
||||
import { BasePerson, listUserPersons } from "../data/person";
|
||||
import "./ha-auth-textfield";
|
||||
import type { HaAuthTextField } from "./ha-auth-textfield";
|
||||
|
||||
@@ -43,7 +43,7 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
@state() private _persons?: Promise<Record<string, string>>;
|
||||
@state() private _persons?: Record<string, BasePerson>;
|
||||
|
||||
@state() private _selectedUser?: string;
|
||||
|
||||
@@ -65,7 +65,9 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
if (!this.authProvider?.users || !this._persons) {
|
||||
return nothing;
|
||||
}
|
||||
const userIds = Object.keys(this.authProvider.users);
|
||||
const userIds = Object.keys(this.authProvider.users).filter(
|
||||
(userId) => userId in this._persons!
|
||||
);
|
||||
return html`
|
||||
<style>
|
||||
.content {
|
||||
@@ -146,22 +148,16 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
height: 120px;
|
||||
--person-badge-font-size: 3em;
|
||||
}
|
||||
.action {
|
||||
margin: 16px 0 8px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 336px;
|
||||
justify-content: center;
|
||||
}
|
||||
.space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
ha-list-item {
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-button {
|
||||
--mdc-typography-button-text-transform: none;
|
||||
}
|
||||
.forgot-password-container {
|
||||
text-align: right;
|
||||
padding: 8px 0 16px 0;
|
||||
}
|
||||
a.forgot-password {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
@@ -185,7 +181,7 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
</style>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
: nothing}
|
||||
${this._step
|
||||
? html`<ha-auth-flow
|
||||
.clientId=${this.clientId}
|
||||
@@ -196,72 +192,76 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
.localize=${this.localize}
|
||||
></ha-auth-flow>`
|
||||
: this._selectedUser
|
||||
? html`<div class="login-form"><div class="person">
|
||||
<ha-person-badge
|
||||
.person=${this._persons![this._selectedUser]}
|
||||
></ha-person-badge>
|
||||
<p>${this._persons![this._selectedUser].name}</p>
|
||||
</div>
|
||||
<form>
|
||||
<input
|
||||
type="hidden"
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
.value=${this.authProvider.users[this._selectedUser]}
|
||||
/>
|
||||
<ha-auth-textfield
|
||||
.type=${this._unmaskedPassword ? "text" : "password"}
|
||||
id="password"
|
||||
name="password"
|
||||
.label=${this.localize(
|
||||
"ui.panel.page-authorize.form.providers.homeassistant.step.init.data.password"
|
||||
)}
|
||||
required
|
||||
autoValidate
|
||||
autocomplete
|
||||
iconTrailing
|
||||
validationMessage="Required"
|
||||
>
|
||||
<ha-icon-button
|
||||
toggles
|
||||
.label=${
|
||||
this.localize(
|
||||
this._unmaskedPassword
|
||||
? "ui.panel.page-authorize.form.hide_password"
|
||||
: "ui.panel.page-authorize.form.show_password"
|
||||
) || (this._unmaskedPassword ? "Hide password" : "Show password")
|
||||
}
|
||||
@click=${this._toggleUnmaskedPassword}
|
||||
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
||||
></ha-icon-button>
|
||||
</ha-auth-textfield>
|
||||
</div>
|
||||
<div class="action space-between">
|
||||
<mwc-button
|
||||
@click=${this._restart}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.localize("ui.panel.page-authorize.form.previous")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
raised
|
||||
@click=${this._handleSubmit}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.localize("ui.panel.page-authorize.form.next")}
|
||||
</mwc-button>
|
||||
? html`<div class="login-form">
|
||||
<div class="person">
|
||||
<ha-person-badge
|
||||
.person=${this._persons[this._selectedUser]}
|
||||
></ha-person-badge>
|
||||
<p>${this._persons[this._selectedUser].name}</p>
|
||||
</div>
|
||||
<div class="action">
|
||||
<a class="forgot-password"
|
||||
href="https://www.home-assistant.io/docs/locked_out/#forgot-password"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>${this.localize(
|
||||
"ui.panel.page-authorize.forgot_password"
|
||||
)}</a
|
||||
<form>
|
||||
<input
|
||||
type="hidden"
|
||||
name="username"
|
||||
autocomplete="username"
|
||||
readonly
|
||||
.value=${this.authProvider.users[this._selectedUser]}
|
||||
/>
|
||||
<ha-auth-textfield
|
||||
.type=${this._unmaskedPassword ? "text" : "password"}
|
||||
autocomplete="current-password"
|
||||
id="password"
|
||||
name="password"
|
||||
.label=${this.localize(
|
||||
"ui.panel.page-authorize.form.providers.homeassistant.step.init.data.password"
|
||||
)}
|
||||
required
|
||||
autoValidate
|
||||
iconTrailing
|
||||
validationMessage="Required"
|
||||
>
|
||||
<ha-icon-button
|
||||
toggles
|
||||
.label=${this.localize(
|
||||
this._unmaskedPassword
|
||||
? "ui.panel.page-authorize.form.hide_password"
|
||||
: "ui.panel.page-authorize.form.show_password"
|
||||
) ||
|
||||
(this._unmaskedPassword
|
||||
? "Hide password"
|
||||
: "Show password")}
|
||||
@click=${this._toggleUnmaskedPassword}
|
||||
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
||||
></ha-icon-button>
|
||||
</ha-auth-textfield>
|
||||
<div class="forgot-password-container">
|
||||
<a
|
||||
class="forgot-password"
|
||||
href="https://www.home-assistant.io/docs/locked_out/#forgot-password"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
>${this.localize(
|
||||
"ui.panel.page-authorize.forgot_password"
|
||||
)}</a
|
||||
>
|
||||
</div>
|
||||
</form>`
|
||||
<div class="action space-between">
|
||||
<mwc-button
|
||||
@click=${this._restart}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.localize("ui.panel.page-authorize.form.previous")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
raised
|
||||
@click=${this._handleSubmit}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.localize("ui.panel.page-authorize.form.next")}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>`
|
||||
: html`<h1>
|
||||
${this.localize("ui.panel.page-authorize.welcome_home")}
|
||||
</h1>
|
||||
@@ -273,6 +273,7 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
>
|
||||
${userIds.map((userId) => {
|
||||
const person = this._persons![userId];
|
||||
|
||||
return html`<div
|
||||
class="person"
|
||||
.userId=${userId}
|
||||
@@ -316,7 +317,12 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
}
|
||||
|
||||
private async _load() {
|
||||
this._persons = await (await listPersons()).json();
|
||||
try {
|
||||
this._persons = await listUserPersons();
|
||||
} catch {
|
||||
this._persons = {};
|
||||
this._error = "Failed to fetch persons";
|
||||
}
|
||||
}
|
||||
|
||||
private _restart() {
|
||||
@@ -353,7 +359,8 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
redirectWithAuthCode(
|
||||
this.redirectUri!,
|
||||
data.result,
|
||||
this.oauth2State
|
||||
this.oauth2State,
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -374,7 +381,8 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
redirectWithAuthCode(
|
||||
this.redirectUri!,
|
||||
result.result,
|
||||
this.oauth2State
|
||||
this.oauth2State,
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -433,7 +441,8 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
redirectWithAuthCode(
|
||||
this.redirectUri!,
|
||||
newStep.result,
|
||||
this.oauth2State
|
||||
this.oauth2State,
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -462,7 +471,7 @@ export class HaLocalAuthFlow extends LitElement {
|
||||
}
|
||||
|
||||
private _otherLogin() {
|
||||
fireEvent(this, "default-login-flow");
|
||||
fireEvent(this, "default-login-flow", { value: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,6 +480,6 @@ declare global {
|
||||
"ha-local-auth-flow": HaLocalAuthFlow;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"default-login-flow": undefined;
|
||||
"default-login-flow": { value: boolean };
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { HassConfig, HassEntity } from "home-assistant-js-websocket";
|
||||
import {
|
||||
DOMAIN_ATTRIBUTES_FORMATERS,
|
||||
DOMAIN_ATTRIBUTES_UNITS,
|
||||
TEMPERATURE_ATTRIBUTES,
|
||||
} from "../../data/entity_attributes";
|
||||
@@ -14,11 +15,10 @@ import { formatNumber } from "../number/format_number";
|
||||
import { capitalizeFirstLetter } from "../string/capitalize-first-letter";
|
||||
import { isDate } from "../string/is_date";
|
||||
import { isTimestamp } from "../string/is_timestamp";
|
||||
import { blankBeforePercent } from "../translations/blank_before_percent";
|
||||
import { blankBeforeUnit } from "../translations/blank_before_unit";
|
||||
import { LocalizeFunc } from "../translations/localize";
|
||||
import { computeDomain } from "./compute_domain";
|
||||
import { computeStateDomain } from "./compute_state_domain";
|
||||
import { blankBeforeUnit } from "../translations/blank_before_unit";
|
||||
|
||||
export const computeAttributeValueDisplay = (
|
||||
localize: LocalizeFunc,
|
||||
@@ -39,19 +39,18 @@ export const computeAttributeValueDisplay = (
|
||||
|
||||
// Number value, return formatted number
|
||||
if (typeof attributeValue === "number") {
|
||||
const formattedValue = formatNumber(attributeValue, locale);
|
||||
|
||||
const domain = computeStateDomain(stateObj);
|
||||
|
||||
const formatter = DOMAIN_ATTRIBUTES_FORMATERS[domain]?.[attribute];
|
||||
|
||||
const formattedValue = formatter
|
||||
? formatter(attributeValue, locale)
|
||||
: formatNumber(attributeValue, locale);
|
||||
|
||||
let unit = DOMAIN_ATTRIBUTES_UNITS[domain]?.[attribute] as
|
||||
| string
|
||||
| undefined;
|
||||
|
||||
if (domain === "light" && attribute === "brightness") {
|
||||
const percentage = Math.round((attributeValue / 255) * 100);
|
||||
return `${percentage}${blankBeforePercent(locale)}%`;
|
||||
}
|
||||
|
||||
if (domain === "weather") {
|
||||
unit = getWeatherUnit(config, stateObj as WeatherEntity, attribute);
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ export class HaProgressButton extends LitElement {
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
size="small"
|
||||
active
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
`
|
||||
: ""}
|
||||
|
@@ -469,6 +469,7 @@ export class HaChartBase extends LitElement {
|
||||
.chartTooltip li {
|
||||
display: flex;
|
||||
white-space: pre-line;
|
||||
word-break: break-word;
|
||||
align-items: center;
|
||||
line-height: 16px;
|
||||
padding: 4px 0;
|
||||
@@ -476,6 +477,7 @@ export class HaChartBase extends LitElement {
|
||||
.chartTooltip .title {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
word-break: break-word;
|
||||
direction: ltr;
|
||||
}
|
||||
.chartTooltip .footer {
|
||||
|
@@ -303,6 +303,11 @@ export class StateHistoryCharts extends LitElement {
|
||||
padding-right: 1px;
|
||||
}
|
||||
|
||||
.entry-container:not(:first-child) {
|
||||
border-top: 2px solid var(--divider-color);
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.container,
|
||||
lit-virtualizer {
|
||||
height: 100%;
|
||||
|
@@ -10,7 +10,6 @@ import {
|
||||
ScorableTextItem,
|
||||
fuzzyFilterSort,
|
||||
} from "../../common/string/filter/sequence-matching";
|
||||
import { AreaRegistryEntry } from "../../data/area_registry";
|
||||
import {
|
||||
DeviceEntityDisplayLookup,
|
||||
DeviceRegistryEntry,
|
||||
@@ -102,7 +101,7 @@ export class HaDevicePicker extends LitElement {
|
||||
private _getDevices = memoizeOne(
|
||||
(
|
||||
devices: DeviceRegistryEntry[],
|
||||
areas: AreaRegistryEntry[],
|
||||
areas: HomeAssistant["areas"],
|
||||
entities: EntityRegistryDisplayEntry[],
|
||||
includeDomains: this["includeDomains"],
|
||||
excludeDomains: this["excludeDomains"],
|
||||
@@ -133,8 +132,6 @@ export class HaDevicePicker extends LitElement {
|
||||
deviceEntityLookup = getDeviceEntityDisplayLookup(entities);
|
||||
}
|
||||
|
||||
const areaLookup = areas;
|
||||
|
||||
let inputDevices = devices.filter(
|
||||
(device) => device.id === this.value || !device.disabled_by
|
||||
);
|
||||
@@ -224,8 +221,8 @@ export class HaDevicePicker extends LitElement {
|
||||
id: device.id,
|
||||
name: name,
|
||||
area:
|
||||
device.area_id && areaLookup[device.area_id]
|
||||
? areaLookup[device.area_id].name
|
||||
device.area_id && areas[device.area_id]
|
||||
? areas[device.area_id].name
|
||||
: this.hass.localize("ui.components.device-picker.no_area"),
|
||||
strings: [name || ""],
|
||||
};
|
||||
@@ -267,7 +264,7 @@ export class HaDevicePicker extends LitElement {
|
||||
this._init = true;
|
||||
const devices = this._getDevices(
|
||||
Object.values(this.hass.devices),
|
||||
Object.values(this.hass.areas),
|
||||
this.hass.areas,
|
||||
Object.values(this.hass.entities),
|
||||
this.includeDomains,
|
||||
this.excludeDomains,
|
||||
|
@@ -1,54 +1,44 @@
|
||||
import { CircularProgress } from "@material/mwc-circular-progress";
|
||||
import { CSSResultGroup, css } from "lit";
|
||||
import "element-internals-polyfill";
|
||||
import { MdCircularProgress } from "@material/web/progress/circular-progress";
|
||||
import { CSSResult, PropertyValues, css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
@customElement("ha-circular-progress")
|
||||
// @ts-ignore
|
||||
export class HaCircularProgress extends CircularProgress {
|
||||
@property({ type: Boolean })
|
||||
public active = false;
|
||||
export class HaCircularProgress extends MdCircularProgress {
|
||||
@property({ attribute: "aria-label", type: String }) public ariaLabel =
|
||||
"Loading";
|
||||
|
||||
@property()
|
||||
public alt = "Loading";
|
||||
@property() public size: "tiny" | "small" | "medium" | "large" = "medium";
|
||||
|
||||
@property()
|
||||
public size: "tiny" | "small" | "medium" | "large" = "medium";
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
|
||||
// @ts-ignore
|
||||
public set density(_) {
|
||||
// just a dummy
|
||||
}
|
||||
|
||||
public get density() {
|
||||
switch (this.size) {
|
||||
case "tiny":
|
||||
return -8;
|
||||
case "small":
|
||||
return -5;
|
||||
case "medium":
|
||||
return 0;
|
||||
case "large":
|
||||
return 5;
|
||||
default:
|
||||
return 0;
|
||||
if (changedProps.has("size")) {
|
||||
switch (this.size) {
|
||||
case "tiny":
|
||||
this.style.setProperty("--md-circular-progress-size", "16px");
|
||||
break;
|
||||
case "small":
|
||||
this.style.setProperty("--md-circular-progress-size", "28px");
|
||||
break;
|
||||
// medium is default size
|
||||
case "medium":
|
||||
this.style.setProperty("--md-circular-progress-size", "48px");
|
||||
break;
|
||||
case "large":
|
||||
this.style.setProperty("--md-circular-progress-size", "68px");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
public set indeterminate(_) {
|
||||
// just a dummy
|
||||
}
|
||||
|
||||
public get indeterminate() {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
super.styles,
|
||||
...super.styles,
|
||||
css`
|
||||
:host {
|
||||
overflow: hidden;
|
||||
--md-sys-color-primary: var(--primary-color);
|
||||
--md-circular-progress-size: 48px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
@@ -2,6 +2,7 @@ import {
|
||||
DIRECTION_ALL,
|
||||
Manager,
|
||||
Pan,
|
||||
Press,
|
||||
Tap,
|
||||
TouchMouseInput,
|
||||
} from "@egjs/hammerjs";
|
||||
@@ -108,6 +109,9 @@ export class HaControlCircularSlider extends LitElement {
|
||||
@property({ type: Number })
|
||||
public max = 100;
|
||||
|
||||
@property({ type: Boolean, attribute: "prevent-interaction-on-scroll" })
|
||||
public preventInteractionOnScroll?: boolean;
|
||||
|
||||
@state()
|
||||
public _localValue?: number = this.value;
|
||||
|
||||
@@ -246,16 +250,62 @@ export class HaControlCircularSlider extends LitElement {
|
||||
this._mc = new Manager(this._interaction, {
|
||||
inputClass: TouchMouseInput,
|
||||
});
|
||||
|
||||
const pressToActivate =
|
||||
this.preventInteractionOnScroll && "ontouchstart" in window;
|
||||
|
||||
// If press to activate is true, a 60ms press is required to activate the slider
|
||||
this._mc.add(
|
||||
new Pan({
|
||||
direction: DIRECTION_ALL,
|
||||
enable: true,
|
||||
threshold: 0,
|
||||
new Press({
|
||||
enable: pressToActivate,
|
||||
pointers: 1,
|
||||
time: 60,
|
||||
})
|
||||
);
|
||||
|
||||
const panRecognizer = new Pan({
|
||||
direction: DIRECTION_ALL,
|
||||
enable: !pressToActivate,
|
||||
threshold: 0,
|
||||
});
|
||||
|
||||
this._mc.add(panRecognizer);
|
||||
|
||||
this._mc.add(new Tap({ event: "singletap" }));
|
||||
|
||||
this._mc.on("press", (e) => {
|
||||
e.srcEvent.stopPropagation();
|
||||
e.srcEvent.preventDefault();
|
||||
if (this.disabled || this.readonly) return;
|
||||
const percentage = this._getPercentageFromEvent(e);
|
||||
const raw = this._percentageToValue(percentage);
|
||||
this._activeSlider = this._findActiveSlider(raw);
|
||||
const bounded = this._boundedValue(raw);
|
||||
this._setActiveValue(bounded);
|
||||
const stepped = this._steppedValue(bounded);
|
||||
if (this._activeSlider) {
|
||||
fireEvent(this, `${this._activeSlider}-changing`, { value: stepped });
|
||||
}
|
||||
panRecognizer.set({ enable: true });
|
||||
});
|
||||
|
||||
this._mc.on("pressup", (e) => {
|
||||
e.srcEvent.stopPropagation();
|
||||
e.srcEvent.preventDefault();
|
||||
const percentage = this._getPercentageFromEvent(e);
|
||||
const raw = this._percentageToValue(percentage);
|
||||
const bounded = this._boundedValue(raw);
|
||||
const stepped = this._steppedValue(bounded);
|
||||
this._setActiveValue(stepped);
|
||||
if (this._activeSlider) {
|
||||
fireEvent(this, `${this._activeSlider}-changing`, {
|
||||
value: undefined,
|
||||
});
|
||||
fireEvent(this, `${this._activeSlider}-changed`, { value: stepped });
|
||||
}
|
||||
this._activeSlider = undefined;
|
||||
});
|
||||
|
||||
this._mc.on("pan", (e) => {
|
||||
e.srcEvent.stopPropagation();
|
||||
e.srcEvent.preventDefault();
|
||||
@@ -271,6 +321,9 @@ export class HaControlCircularSlider extends LitElement {
|
||||
this._mc.on("pancancel", () => {
|
||||
if (this.disabled || this.readonly) return;
|
||||
this._activeSlider = undefined;
|
||||
if (pressToActivate) {
|
||||
panRecognizer.set({ enable: false });
|
||||
}
|
||||
});
|
||||
this._mc.on("panmove", (e) => {
|
||||
if (this.disabled || this.readonly) return;
|
||||
@@ -297,6 +350,9 @@ export class HaControlCircularSlider extends LitElement {
|
||||
fireEvent(this, `${this._activeSlider}-changed`, { value: stepped });
|
||||
}
|
||||
this._activeSlider = undefined;
|
||||
if (pressToActivate) {
|
||||
panRecognizer.set({ enable: false });
|
||||
}
|
||||
});
|
||||
this._mc.on("singletap", (e) => {
|
||||
if (this.disabled || this.readonly) return;
|
||||
@@ -315,6 +371,9 @@ export class HaControlCircularSlider extends LitElement {
|
||||
this._lastSlider = this._activeSlider;
|
||||
this.shadowRoot?.getElementById("#slider")?.focus();
|
||||
this._activeSlider = undefined;
|
||||
if (pressToActivate) {
|
||||
panRecognizer.set({ enable: false });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -618,6 +677,7 @@ export class HaControlCircularSlider extends LitElement {
|
||||
--control-circular-slider-high-color: var(
|
||||
--control-circular-slider-color
|
||||
);
|
||||
--control-circular-slider-interaction-margin: 12px;
|
||||
width: 320px;
|
||||
display: block;
|
||||
}
|
||||
@@ -633,7 +693,9 @@ export class HaControlCircularSlider extends LitElement {
|
||||
fill: none;
|
||||
stroke: transparent;
|
||||
stroke-linecap: round;
|
||||
stroke-width: 48px;
|
||||
stroke-width: calc(
|
||||
24px + 2 * var(--control-circular-slider-interaction-margin)
|
||||
);
|
||||
cursor: pointer;
|
||||
}
|
||||
#display {
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||
import { mdiMinus, mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
@@ -49,6 +50,13 @@ export class HaControlNumberButton extends LitElement {
|
||||
|
||||
@query("#input") _input!: HTMLDivElement;
|
||||
|
||||
private _hideUnit = new ResizeController(this, {
|
||||
callback: (entries) => {
|
||||
const width = entries[0]?.contentRect.width;
|
||||
return width < 100;
|
||||
},
|
||||
});
|
||||
|
||||
private boundedValue(value: number) {
|
||||
const clamped = conditionalClamp(value, this.min, this.max);
|
||||
return Math.round(clamped / this._step) * this._step;
|
||||
@@ -145,7 +153,10 @@ export class HaControlNumberButton extends LitElement {
|
||||
?disabled=${this.disabled}
|
||||
@keydown=${this._handleKeyDown}
|
||||
>
|
||||
${value} ${unit ? html`<span class="unit">${unit}</span>` : nothing}
|
||||
${value}
|
||||
${unit && !this._hideUnit.value
|
||||
? html`<span class="unit">${unit}</span>`
|
||||
: nothing}
|
||||
</div>
|
||||
<button
|
||||
class="button minus"
|
||||
|
@@ -17,7 +17,7 @@ class HaFaded extends LitElement {
|
||||
@click=${this._showContent}
|
||||
>
|
||||
<slot
|
||||
@iron-resize=${
|
||||
@content-resize=${
|
||||
// ha-markdown-element fire this when render is complete
|
||||
this._setShowContent
|
||||
}
|
||||
@@ -79,4 +79,7 @@ declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-faded": HaFaded;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"content-resize": undefined;
|
||||
}
|
||||
}
|
||||
|
@@ -99,7 +99,7 @@ class HaMarkdownElement extends ReactiveElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _resize = () => fireEvent(this, "iron-resize");
|
||||
private _resize = () => fireEvent(this, "content-resize");
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -6,6 +6,11 @@ import { MdOutlinedIconButton } from "@material/web/iconbutton/outlined-icon-but
|
||||
@customElement("ha-outlined-icon-button")
|
||||
export class HaOutlinedIconButton extends MdOutlinedIconButton {
|
||||
static override styles = [
|
||||
css`
|
||||
.icon-button {
|
||||
border-radius: var(--_container-shape);
|
||||
}
|
||||
`,
|
||||
...super.styles,
|
||||
css`
|
||||
:host {
|
||||
|
@@ -30,9 +30,9 @@ export class HaNumberSelector extends LitElement {
|
||||
|
||||
protected willUpdate(changedProps: PropertyValues) {
|
||||
if (changedProps.has("value")) {
|
||||
if (this.value !== Number(this._valueStr)) {
|
||||
if (this._valueStr === "" || this.value !== Number(this._valueStr)) {
|
||||
this._valueStr =
|
||||
!this.value || isNaN(this.value) ? "" : this.value.toString();
|
||||
this.value == null || isNaN(this.value) ? "" : this.value.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -597,9 +597,9 @@ export class HaServiceControl extends LitElement {
|
||||
);
|
||||
}
|
||||
target = {
|
||||
entity_id: targetEntities,
|
||||
device_id: targetDevices,
|
||||
area_id: targetAreas,
|
||||
...(targetEntities.length ? { entity_id: targetEntities } : {}),
|
||||
...(targetDevices.length ? { device_id: targetDevices } : {}),
|
||||
...(targetAreas.length ? { area_id: targetAreas } : {}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,19 @@
|
||||
import { DEFAULT_SCHEMA, dump, load, Schema } from "js-yaml";
|
||||
import { html, LitElement, nothing, PropertyValues } from "lit";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
css,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import "./ha-code-editor";
|
||||
import { showToast } from "../util/toast";
|
||||
import { copyToClipboard } from "../common/util/copy-clipboard";
|
||||
|
||||
const isEmpty = (obj: Record<string, unknown>): boolean => {
|
||||
if (typeof obj !== "object") {
|
||||
@@ -37,6 +47,8 @@ export class HaYamlEditor extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public required = false;
|
||||
|
||||
@property({ type: Boolean }) public copyClipboard = false;
|
||||
|
||||
@state() private _yaml = "";
|
||||
|
||||
public setValue(value): void {
|
||||
@@ -88,6 +100,15 @@ export class HaYamlEditor extends LitElement {
|
||||
@value-changed=${this._onChange}
|
||||
dir="ltr"
|
||||
></ha-code-editor>
|
||||
${this.copyClipboard
|
||||
? html`<div class="card-actions">
|
||||
<mwc-button @click=${this._copyYaml}>
|
||||
${this.hass.localize(
|
||||
"ui.components.yaml-editor.copy_to_clipboard"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -117,6 +138,35 @@ export class HaYamlEditor extends LitElement {
|
||||
get yaml() {
|
||||
return this._yaml;
|
||||
}
|
||||
|
||||
private async _copyYaml(): Promise<void> {
|
||||
if (this.yaml) {
|
||||
await copyToClipboard(this.yaml);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.card-actions {
|
||||
border-radius: var(
|
||||
--actions-border-radius,
|
||||
0px 0px var(--ha-card-border-radius, 12px)
|
||||
var(--ha-card-border-radius, 12px)
|
||||
);
|
||||
border: 1px solid var(--divider-color);
|
||||
padding: 5px 16px;
|
||||
}
|
||||
ha-code-editor {
|
||||
flex-grow: 1;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -136,7 +136,7 @@ export class HaMap extends ReactiveElement {
|
||||
autoFitRequired = true;
|
||||
}
|
||||
|
||||
if (this.autoFit && autoFitRequired) {
|
||||
if (changedProps.has("_loaded") || (this.autoFit && autoFitRequired)) {
|
||||
this.fitMap();
|
||||
}
|
||||
|
||||
@@ -155,10 +155,11 @@ export class HaMap extends ReactiveElement {
|
||||
}
|
||||
|
||||
private _updateMapStyle(): void {
|
||||
const darkMode = this.darkMode ?? this.hass.themes.darkMode;
|
||||
const darkMode = this.darkMode ?? this.hass.themes.darkMode ?? false;
|
||||
const forcedDark = this.darkMode ?? false;
|
||||
const map = this.shadowRoot!.getElementById("map");
|
||||
map!.classList.toggle("dark", darkMode);
|
||||
map!.classList.toggle("forced-dark", this.darkMode);
|
||||
map!.classList.toggle("forced-dark", forcedDark);
|
||||
}
|
||||
|
||||
private async _loadMap(): Promise<void> {
|
||||
|
@@ -147,7 +147,7 @@ class DialogMediaManage extends LitElement {
|
||||
${!this._currentItem
|
||||
? html`
|
||||
<div class="refresh">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>
|
||||
`
|
||||
: !children.length
|
||||
|
@@ -58,6 +58,7 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
this._navigateIds = undefined;
|
||||
this._currentItem = undefined;
|
||||
this._preferredLayout = "auto";
|
||||
this.classList.remove("opened");
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@@ -79,6 +80,7 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
)
|
||||
: this._currentItem.title}
|
||||
@closed=${this.closeDialog}
|
||||
@opened=${this._dialogOpened}
|
||||
>
|
||||
<ha-dialog-header show-border slot="heading">
|
||||
${this._navigateIds.length > 1
|
||||
@@ -167,6 +169,10 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _dialogOpened() {
|
||||
this.classList.add("opened");
|
||||
}
|
||||
|
||||
private async _handleMenuAction(ev: CustomEvent<ActionDetail>) {
|
||||
switch (ev.detail.index) {
|
||||
case 0:
|
||||
@@ -217,10 +223,13 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
|
||||
ha-media-player-browse {
|
||||
--media-browser-max-height: calc(100vh - 65px);
|
||||
height: calc(100vh - 65px);
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
:host(.opened) ha-media-player-browse {
|
||||
height: calc(100vh - 65px);
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 800px;
|
||||
@@ -231,7 +240,6 @@ class DialogMediaPlayerBrowse extends LitElement {
|
||||
ha-media-player-browse {
|
||||
position: initial;
|
||||
--media-browser-max-height: 100vh - 137px;
|
||||
height: 100vh - 137px;
|
||||
width: 700px;
|
||||
}
|
||||
}
|
||||
|
@@ -332,7 +332,7 @@ export class HaMediaPlayerBrowse extends LitElement {
|
||||
}
|
||||
|
||||
if (!this._currentItem) {
|
||||
return html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
return html`<ha-circular-progress indeterminate></ha-circular-progress>`;
|
||||
}
|
||||
|
||||
const currentItem = this._currentItem;
|
||||
|
@@ -54,8 +54,8 @@ class MediaUploadButton extends LitElement {
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
size="tiny"
|
||||
active
|
||||
alt=""
|
||||
indeterminate
|
||||
area-label="Uploading"
|
||||
slot="icon"
|
||||
></ha-circular-progress>
|
||||
`
|
||||
|
@@ -2,12 +2,12 @@ import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { Person } from "../../data/person";
|
||||
import { BasePerson } from "../../data/person";
|
||||
import { computeUserInitials } from "../../data/user";
|
||||
|
||||
@customElement("ha-person-badge")
|
||||
class PersonBadge extends LitElement {
|
||||
@property({ attribute: false }) public person?: Person;
|
||||
@property({ attribute: false }) public person?: BasePerson;
|
||||
|
||||
protected render() {
|
||||
if (!this.person) {
|
||||
|
@@ -130,8 +130,8 @@ export const areaCompare =
|
||||
const indexA = order ? order.indexOf(a) : -1;
|
||||
const indexB = order ? order.indexOf(b) : 1;
|
||||
if (indexA === -1 && indexB === -1) {
|
||||
const nameA = entries?.[a].name ?? a;
|
||||
const nameB = entries?.[b].name ?? b;
|
||||
const nameA = entries?.[a]?.name ?? a;
|
||||
const nameB = entries?.[b]?.name ?? b;
|
||||
return stringCompare(nameA, nameB);
|
||||
}
|
||||
if (indexA === -1) {
|
||||
|
@@ -80,7 +80,8 @@ export const deleteLoginFlow = (flow_id) =>
|
||||
export const redirectWithAuthCode = (
|
||||
url: string,
|
||||
authCode: string,
|
||||
oauth2State: string | undefined
|
||||
oauth2State: string | undefined,
|
||||
storeToken: boolean
|
||||
) => {
|
||||
// OAuth 2: 3.1.2 we need to retain query component of a redirect URI
|
||||
if (!url.includes("?")) {
|
||||
@@ -94,7 +95,9 @@ export const redirectWithAuthCode = (
|
||||
if (oauth2State) {
|
||||
url += `&state=${encodeURIComponent(oauth2State)}`;
|
||||
}
|
||||
url += `&storeToken=true`;
|
||||
if (storeToken) {
|
||||
url += `&storeToken=true`;
|
||||
}
|
||||
|
||||
document.location.assign(url);
|
||||
};
|
||||
|
@@ -581,6 +581,28 @@ const clearEnergyCollectionPreferences = (hass: HomeAssistant) => {
|
||||
});
|
||||
};
|
||||
|
||||
const scheduleHourlyRefresh = (collection: EnergyCollection) => {
|
||||
if (collection._refreshTimeout) {
|
||||
clearTimeout(collection._refreshTimeout);
|
||||
}
|
||||
|
||||
if (collection._active && (!collection.end || collection.end > new Date())) {
|
||||
// The stats are created every hour
|
||||
// Schedule a refresh for 20 minutes past the hour
|
||||
// If the end is larger than the current time.
|
||||
const nextFetch = new Date();
|
||||
if (nextFetch.getMinutes() >= 20) {
|
||||
nextFetch.setHours(nextFetch.getHours() + 1);
|
||||
}
|
||||
nextFetch.setMinutes(20, 0, 0);
|
||||
|
||||
collection._refreshTimeout = window.setTimeout(
|
||||
() => collection.refresh(),
|
||||
nextFetch.getTime() - Date.now()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const getEnergyDataCollection = (
|
||||
hass: HomeAssistant,
|
||||
options: { prefs?: EnergyPreferences; key?: string } = {}
|
||||
@@ -609,28 +631,7 @@ export const getEnergyDataCollection = (
|
||||
collection.prefs = await getEnergyPreferences(hass);
|
||||
}
|
||||
|
||||
if (collection._refreshTimeout) {
|
||||
clearTimeout(collection._refreshTimeout);
|
||||
}
|
||||
|
||||
if (
|
||||
collection._active &&
|
||||
(!collection.end || collection.end > new Date())
|
||||
) {
|
||||
// The stats are created every hour
|
||||
// Schedule a refresh for 20 minutes past the hour
|
||||
// If the end is larger than the current time.
|
||||
const nextFetch = new Date();
|
||||
if (nextFetch.getMinutes() >= 20) {
|
||||
nextFetch.setHours(nextFetch.getHours() + 1);
|
||||
}
|
||||
nextFetch.setMinutes(20, 0, 0);
|
||||
|
||||
collection._refreshTimeout = window.setTimeout(
|
||||
() => collection.refresh(),
|
||||
nextFetch.getTime() - Date.now()
|
||||
);
|
||||
}
|
||||
scheduleHourlyRefresh(collection);
|
||||
|
||||
return getEnergyData(
|
||||
hass,
|
||||
@@ -647,6 +648,11 @@ export const getEnergyDataCollection = (
|
||||
collection.subscribe = (subscriber: (data: EnergyData) => void) => {
|
||||
const unsub = origSubscribe(subscriber);
|
||||
collection._active++;
|
||||
|
||||
if (collection._refreshTimeout === undefined) {
|
||||
scheduleHourlyRefresh(collection);
|
||||
}
|
||||
|
||||
return () => {
|
||||
collection._active--;
|
||||
if (collection._active < 1) {
|
||||
|
@@ -1,3 +1,6 @@
|
||||
import { formatDuration } from "../common/datetime/duration";
|
||||
import { FrontendLocaleData } from "./translation";
|
||||
|
||||
export const STATE_ATTRIBUTES = [
|
||||
"entity_id",
|
||||
"assumed_state",
|
||||
@@ -64,6 +67,7 @@ export const DOMAIN_ATTRIBUTES_UNITS = {
|
||||
color_temp_kelvin: "K",
|
||||
min_color_temp_kelvin: "K",
|
||||
max_color_temp_kelvin: "K",
|
||||
brightness: "%",
|
||||
},
|
||||
sun: {
|
||||
elevation: "°",
|
||||
@@ -74,4 +78,22 @@ export const DOMAIN_ATTRIBUTES_UNITS = {
|
||||
sensor: {
|
||||
battery_level: "%",
|
||||
},
|
||||
media_player: {
|
||||
volume_level: "%",
|
||||
},
|
||||
} as const satisfies Record<string, Record<string, string>>;
|
||||
|
||||
type Formatter = (value: number, locale: FrontendLocaleData) => string;
|
||||
|
||||
export const DOMAIN_ATTRIBUTES_FORMATERS: Record<
|
||||
string,
|
||||
Record<string, Formatter>
|
||||
> = {
|
||||
light: {
|
||||
brightness: (value) => Math.round((value / 255) * 100).toString(),
|
||||
},
|
||||
media_player: {
|
||||
volume_level: (value) => Math.round(value * 100).toString(),
|
||||
media_duration: (value) => formatDuration(value.toString(), "s"),
|
||||
},
|
||||
};
|
||||
|
@@ -49,6 +49,7 @@ export interface LineChartEntity {
|
||||
|
||||
export interface LineChartUnit {
|
||||
unit: string;
|
||||
device_class?: string;
|
||||
identifier: string;
|
||||
data: LineChartEntity[];
|
||||
}
|
||||
@@ -323,7 +324,8 @@ const processTimelineEntity = (
|
||||
};
|
||||
|
||||
const processLineChartEntities = (
|
||||
unit,
|
||||
unit: string,
|
||||
device_class: string | undefined,
|
||||
entities: HistoryStates,
|
||||
hassEntities: HassEntities
|
||||
): LineChartUnit => {
|
||||
@@ -391,6 +393,7 @@ const processLineChartEntities = (
|
||||
|
||||
return {
|
||||
unit,
|
||||
device_class,
|
||||
identifier: Object.keys(entities).join(""),
|
||||
data,
|
||||
};
|
||||
@@ -417,7 +420,8 @@ export const computeHistory = (
|
||||
hass: HomeAssistant,
|
||||
stateHistory: HistoryStates,
|
||||
localize: LocalizeFunc,
|
||||
sensorNumericalDeviceClasses: string[]
|
||||
sensorNumericalDeviceClasses: string[],
|
||||
splitDeviceClasses = false
|
||||
): HistoryResult => {
|
||||
const lineChartDevices: { [unit: string]: HistoryStates } = {};
|
||||
const timelineDevices: TimelineEntity[] = [];
|
||||
@@ -466,6 +470,12 @@ export const computeHistory = (
|
||||
}[domain];
|
||||
}
|
||||
|
||||
const deviceClass: string | undefined = (
|
||||
currentState?.attributes || numericStateFromHistory?.a
|
||||
)?.device_class;
|
||||
|
||||
const key = computeGroupKey(unit, deviceClass, splitDeviceClasses);
|
||||
|
||||
if (!unit) {
|
||||
timelineDevices.push(
|
||||
processTimelineEntity(
|
||||
@@ -478,19 +488,37 @@ export const computeHistory = (
|
||||
currentState
|
||||
)
|
||||
);
|
||||
} else if (unit in lineChartDevices && entityId in lineChartDevices[unit]) {
|
||||
lineChartDevices[unit][entityId].push(...stateInfo);
|
||||
} else {
|
||||
if (!(unit in lineChartDevices)) {
|
||||
lineChartDevices[unit] = {};
|
||||
} else if (
|
||||
key &&
|
||||
key in lineChartDevices &&
|
||||
entityId in lineChartDevices[key]
|
||||
) {
|
||||
lineChartDevices[key][entityId].push(...stateInfo);
|
||||
} else if (key) {
|
||||
if (!(key in lineChartDevices)) {
|
||||
lineChartDevices[key] = {};
|
||||
}
|
||||
lineChartDevices[unit][entityId] = stateInfo;
|
||||
lineChartDevices[key][entityId] = stateInfo;
|
||||
}
|
||||
});
|
||||
|
||||
const unitStates = Object.keys(lineChartDevices).map((unit) =>
|
||||
processLineChartEntities(unit, lineChartDevices[unit], hass.states)
|
||||
);
|
||||
const unitStates = Object.keys(lineChartDevices).map((key) => {
|
||||
const splitKey = key.split("_");
|
||||
const unit = splitKey[0];
|
||||
const deviceClass = splitKey[1] || undefined;
|
||||
return processLineChartEntities(
|
||||
unit,
|
||||
deviceClass,
|
||||
lineChartDevices[key],
|
||||
hass.states
|
||||
);
|
||||
});
|
||||
|
||||
return { line: unitStates, timeline: timelineDevices };
|
||||
};
|
||||
|
||||
export const computeGroupKey = (
|
||||
unit: string | undefined,
|
||||
device_class: string | undefined,
|
||||
splitDeviceClasses: boolean
|
||||
) => (splitDeviceClasses ? `${unit}_${device_class || ""}` : unit);
|
||||
|
@@ -2,6 +2,7 @@ import {
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { getExtendedEntityRegistryEntry } from "./entity_registry";
|
||||
import { showEnterCodeDialogDialog } from "../dialogs/enter-code/show-enter-code-dialog";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
@@ -30,15 +31,20 @@ export const callProtectedLockService = async (
|
||||
service: ProtectedLockService
|
||||
) => {
|
||||
let code: string | undefined;
|
||||
const lockRegistryEntry = await getExtendedEntityRegistryEntry(
|
||||
hass,
|
||||
stateObj.entity_id
|
||||
).catch(() => undefined);
|
||||
const defaultCode = lockRegistryEntry?.options?.lock?.default_code;
|
||||
|
||||
if (stateObj!.attributes.code_format) {
|
||||
if (stateObj!.attributes.code_format && !defaultCode) {
|
||||
const response = await showEnterCodeDialogDialog(element, {
|
||||
codeFormat: "text",
|
||||
codePattern: stateObj!.attributes.code_format,
|
||||
title: hass.localize(`ui.card.lock.${service}`),
|
||||
submitText: hass.localize(`ui.card.lock.${service}`),
|
||||
});
|
||||
if (!response) {
|
||||
if (response == null) {
|
||||
throw new Error("Code dialog closed");
|
||||
}
|
||||
code = response;
|
||||
|
@@ -1,11 +1,14 @@
|
||||
import { HomeAssistant } from "../types";
|
||||
|
||||
export interface Person {
|
||||
id: string;
|
||||
export interface BasePerson {
|
||||
name: string;
|
||||
picture?: string;
|
||||
}
|
||||
|
||||
export interface Person extends BasePerson {
|
||||
id: string;
|
||||
user_id?: string;
|
||||
device_trackers?: string[];
|
||||
picture?: string;
|
||||
}
|
||||
|
||||
export interface PersonMutableParams {
|
||||
@@ -21,9 +24,14 @@ export const fetchPersons = (hass: HomeAssistant) =>
|
||||
config: Person[];
|
||||
}>({ type: "person/list" });
|
||||
|
||||
export const listPersons = () =>
|
||||
export const listUserPersons = (): Promise<Record<string, BasePerson>> =>
|
||||
fetch("/api/person/list", {
|
||||
credentials: "same-origin",
|
||||
}).then((resp) => {
|
||||
if (resp.ok) {
|
||||
return resp.json();
|
||||
}
|
||||
throw new Error(resp.statusText);
|
||||
});
|
||||
|
||||
export const createPerson = (
|
||||
|
@@ -73,6 +73,7 @@ export class DialogAreaFilter
|
||||
animation: 150,
|
||||
fallbackClass: "sortable-fallback",
|
||||
handle: ".handle",
|
||||
draggable: ".draggable",
|
||||
onChoose: (evt: SortableEvent) => {
|
||||
(evt.item as any).placeholder =
|
||||
document.createComment("sort-placeholder");
|
||||
@@ -128,16 +129,21 @@ export class DialogAreaFilter
|
||||
const name = this.hass!.areas[area]?.name || area;
|
||||
return html`
|
||||
<ha-list-item
|
||||
class=${classMap({ hidden: !isVisible })}
|
||||
class=${classMap({
|
||||
hidden: !isVisible,
|
||||
draggable: isVisible,
|
||||
})}
|
||||
hasMeta
|
||||
graphic="icon"
|
||||
noninteractive
|
||||
>
|
||||
<ha-svg-icon
|
||||
class="handle"
|
||||
.path=${mdiDrag}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>
|
||||
${isVisible
|
||||
? html`<ha-svg-icon
|
||||
class="handle"
|
||||
.path=${mdiDrag}
|
||||
slot="graphic"
|
||||
></ha-svg-icon>`
|
||||
: nothing}
|
||||
${name}
|
||||
<ha-icon-button
|
||||
tabindex="0"
|
||||
@@ -177,6 +183,11 @@ export class DialogAreaFilter
|
||||
hidden.push(area);
|
||||
}
|
||||
this._hidden = hidden;
|
||||
const nonHiddenAreas = this._areas.filter(
|
||||
(ar) => !this._hidden.includes(ar)
|
||||
);
|
||||
const hiddenAreas = this._areas.filter((ar) => this._hidden.includes(ar));
|
||||
this._areas = [...nonHiddenAreas, ...hiddenAreas];
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
@@ -193,10 +204,10 @@ export class DialogAreaFilter
|
||||
overflow: visible;
|
||||
}
|
||||
.hidden {
|
||||
opacity: 0.3;
|
||||
color: var(--disabled-text-color);
|
||||
}
|
||||
.handle {
|
||||
cursor: grab;
|
||||
cursor: move;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
|
@@ -425,13 +425,6 @@ class DataEntryFlowDialog extends LitElement {
|
||||
);
|
||||
}
|
||||
);
|
||||
if (this._step?.flow_id) {
|
||||
await this._unsubDataEntryFlowProgressed;
|
||||
// fetch flow after we subscribe to the event, so we don't miss the first event
|
||||
this._processStep(
|
||||
this._params!.flowConfig.fetchFlow(this.hass, this._step.flow_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
@@ -90,7 +90,7 @@ class StepFlowForm extends LitElement {
|
||||
${this._loading
|
||||
? html`
|
||||
<div class="submit-spinner">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
|
@@ -27,7 +27,7 @@ class StepFlowLoading extends LitElement {
|
||||
return html`
|
||||
<div class="init-spinner">
|
||||
${description ? html`<div>${description}</div>` : ""}
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ class StepFlowProgress extends LitElement {
|
||||
${this.flowConfig.renderShowFormProgressHeader(this.hass, this.step)}
|
||||
</h2>
|
||||
<div class="content">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
${this.flowConfig.renderShowFormProgressDescription(
|
||||
this.hass,
|
||||
this.step
|
||||
|
@@ -99,6 +99,8 @@ export class DialogEnterCode
|
||||
id="code"
|
||||
.label=${this.hass.localize("ui.dialogs.enter_code.input_label")}
|
||||
type="password"
|
||||
autoValidate
|
||||
validateOnInitialRender
|
||||
pattern=${ifDefined(this._dialogParams.codePattern)}
|
||||
inputmode="text"
|
||||
></ha-textfield>
|
||||
|
@@ -6,16 +6,8 @@ import {
|
||||
mdiTuneVariant,
|
||||
mdiWaterPercent,
|
||||
} from "@mdi/js";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-control-select-menu";
|
||||
@@ -50,8 +42,6 @@ class MoreInfoClimate extends LitElement {
|
||||
|
||||
@state() private _mainControl: MainControl = "temperature";
|
||||
|
||||
private _resizeDebounce?: number;
|
||||
|
||||
protected render() {
|
||||
if (!this.stateObj) {
|
||||
return nothing;
|
||||
@@ -293,21 +283,6 @@ class MoreInfoClimate extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (!changedProps.has("stateObj") || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._resizeDebounce) {
|
||||
clearTimeout(this._resizeDebounce);
|
||||
}
|
||||
this._resizeDebounce = window.setTimeout(() => {
|
||||
fireEvent(this, "iron-resize");
|
||||
this._resizeDebounce = undefined;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private _setMainControl(ev: any) {
|
||||
ev.stopPropagation();
|
||||
this._mainControl = ev.currentTarget.control;
|
||||
|
@@ -53,8 +53,8 @@ export class MoreInfoConfigurator extends LitElement {
|
||||
>
|
||||
${this._isConfiguring
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
alt="Configuring"
|
||||
indeterminate
|
||||
aria-label="Configuring"
|
||||
></ha-circular-progress>`
|
||||
: ""}
|
||||
${this.stateObj.attributes.submit_caption}
|
||||
|
@@ -8,7 +8,6 @@ import {
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import "../../../components/ha-control-select-menu";
|
||||
@@ -38,8 +37,6 @@ class MoreInfoHumidifier extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
private _resizeDebounce?: number;
|
||||
|
||||
protected render() {
|
||||
if (!this.stateObj) {
|
||||
return nothing;
|
||||
@@ -135,21 +132,6 @@ class MoreInfoHumidifier extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (!changedProps.has("stateObj") || !this.stateObj) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._resizeDebounce) {
|
||||
clearTimeout(this._resizeDebounce);
|
||||
}
|
||||
this._resizeDebounce = window.setTimeout(() => {
|
||||
fireEvent(this, "iron-resize");
|
||||
this._resizeDebounce = undefined;
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private _handleStateChanged(ev) {
|
||||
const newVal = ev.target.value || null;
|
||||
this._callServiceHelper(
|
||||
|
@@ -103,8 +103,10 @@ class MoreInfoUpdate extends LitElement {
|
||||
: ""}
|
||||
${supportsFeature(this.stateObj!, UPDATE_SUPPORT_RELEASE_NOTES) &&
|
||||
!this._error
|
||||
? this._releaseNotes === undefined
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
? !this._releaseNotes
|
||||
? html`<div class="flex center">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>`
|
||||
: html`<hr />
|
||||
<ha-faded>
|
||||
<ha-markdown .content=${this._releaseNotes}></ha-markdown>
|
||||
@@ -254,9 +256,10 @@ class MoreInfoUpdate extends LitElement {
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-circular-progress {
|
||||
width: 100%;
|
||||
.flex.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
mwc-linear-progress {
|
||||
margin-bottom: -8px;
|
||||
|
@@ -214,7 +214,7 @@ export class QuickBar extends LitElement {
|
||||
${!items
|
||||
? html`<ha-circular-progress
|
||||
size="small"
|
||||
active
|
||||
indeterminate
|
||||
></ha-circular-progress>`
|
||||
: items.length === 0
|
||||
? html`
|
||||
@@ -375,7 +375,7 @@ export class QuickBar extends LitElement {
|
||||
const spinner = document.createElement("ha-circular-progress");
|
||||
spinner.size = "small";
|
||||
spinner.slot = "meta";
|
||||
spinner.active = true;
|
||||
spinner.indeterminate = true;
|
||||
this._getItemAtIndex(index)?.appendChild(spinner);
|
||||
}
|
||||
|
||||
|
@@ -91,7 +91,7 @@ class DialogRestart extends LitElement {
|
||||
${this._loadingHostInfo
|
||||
? html`
|
||||
<div class="loader">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
|
@@ -85,8 +85,7 @@ export class TTSTryDialog extends LitElement {
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
size="small"
|
||||
active
|
||||
alt=""
|
||||
indeterminate
|
||||
slot="primaryAction"
|
||||
class="loading"
|
||||
></ha-circular-progress>
|
||||
|
@@ -26,7 +26,8 @@
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
padding: 0 16px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -40,12 +41,6 @@
|
||||
height: 56px;
|
||||
width: 56px;
|
||||
}
|
||||
|
||||
@media (max-width: 592px) {
|
||||
.content {
|
||||
margin: 0 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
@@ -39,7 +39,7 @@
|
||||
align-items: center;
|
||||
}
|
||||
#ha-launch-screen svg {
|
||||
width: 170px;
|
||||
width: 112px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-spacer {
|
||||
|
@@ -22,7 +22,8 @@
|
||||
.content {
|
||||
max-width: 560px;
|
||||
margin: 0 auto;
|
||||
box-sizing: border-box;
|
||||
padding: 0 16px;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -36,12 +37,6 @@
|
||||
height: 56px;
|
||||
width: 56px;
|
||||
}
|
||||
|
||||
@media (max-width: 592px) {
|
||||
.content {
|
||||
margin: 0 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body id="particles">
|
||||
|
@@ -35,7 +35,7 @@ class HaInitPage extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<div id="progress-indicator-wrapper">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>
|
||||
<div id="loading-text">
|
||||
${this.migration
|
||||
|
@@ -46,7 +46,7 @@ class HassLoadingScreen extends LitElement {
|
||||
`}
|
||||
</div>`}
|
||||
<div class="content">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
${this.message
|
||||
? html`<div id="loading-text">${this.message}</div>`
|
||||
: nothing}
|
||||
|
@@ -218,7 +218,7 @@ class HaOnboarding extends litLocalizeLiteMixin(HassElement) {
|
||||
this._handleProgress(ev)
|
||||
);
|
||||
if (window.innerWidth > 450) {
|
||||
import("./particles");
|
||||
import("../resources/particles");
|
||||
}
|
||||
makeDialogManager(this, this.shadowRoot!);
|
||||
import("../components/ha-language-picker");
|
||||
|
@@ -57,7 +57,7 @@ class OnboardingCoreConfig extends LitElement {
|
||||
}
|
||||
if (this._skipCore) {
|
||||
return html`<div class="row center">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>`;
|
||||
}
|
||||
return html`
|
||||
|
@@ -123,7 +123,7 @@ class OnboardingLocation extends LitElement {
|
||||
? html`
|
||||
<ha-circular-progress
|
||||
slot="trailingIcon"
|
||||
active
|
||||
indeterminate
|
||||
size="small"
|
||||
></ha-circular-progress>
|
||||
`
|
||||
|
@@ -64,8 +64,7 @@ class PanelCalendar extends LitElement {
|
||||
private _end?: Date;
|
||||
|
||||
private _showPaneController = new ResizeController(this, {
|
||||
callback: (entries: ResizeObserverEntry[]) =>
|
||||
entries[0]?.contentRect.width > 750,
|
||||
callback: (entries) => entries[0]?.contentRect.width > 750,
|
||||
});
|
||||
|
||||
private _mql?: MediaQueryList;
|
||||
|
@@ -227,7 +227,7 @@ export class DialogAddApplicationCredential extends LitElement {
|
||||
${this._loading
|
||||
? html`
|
||||
<div slot="primaryAction" class="submit-spinner">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
|
@@ -101,7 +101,7 @@ export class HaBlueprintAutomationEditor extends LitElement {
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.blueprint.no_blueprints"
|
||||
)
|
||||
: html`<ha-circular-progress active></ha-circular-progress>`}
|
||||
: html`<ha-circular-progress indeterminate></ha-circular-progress>`}
|
||||
</div>
|
||||
|
||||
${this.config.use_blueprint.path
|
||||
|
@@ -25,19 +25,16 @@ import {
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { property, query, state } from "lit/decorators";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-fab";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||
import {
|
||||
AutomationConfig,
|
||||
AutomationEntity,
|
||||
@@ -112,8 +109,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
|
||||
@state() private _validationErrors?: (string | TemplateResult)[];
|
||||
|
||||
@query("ha-yaml-editor", true) private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
private _configSubscriptions: Record<
|
||||
string,
|
||||
(config?: AutomationConfig) => void
|
||||
@@ -342,8 +337,7 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
></manual-automation-editor>
|
||||
`
|
||||
: this._mode === "yaml"
|
||||
? html`
|
||||
${this._readOnly
|
||||
? html` ${this._readOnly
|
||||
? html`<ha-alert alert-type="warning">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.read_only"
|
||||
@@ -376,22 +370,13 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
`
|
||||
: ""}
|
||||
<ha-yaml-editor
|
||||
copyClipboard
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._preprocessYaml()}
|
||||
.readOnly=${this._readOnly}
|
||||
@value-changed=${this._yamlChanged}
|
||||
></ha-yaml-editor>
|
||||
<ha-card outlined>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._copyYaml}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.copy_to_clipboard"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: ``}
|
||||
></ha-yaml-editor>`
|
||||
: nothing}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
@@ -612,15 +597,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
return cleanConfig;
|
||||
}
|
||||
|
||||
private async _copyYaml(): Promise<void> {
|
||||
if (this._yamlEditor?.yaml) {
|
||||
await copyToClipboard(this._yamlEditor.yaml);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.isValid) {
|
||||
@@ -776,9 +752,6 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
ha-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
.content {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
@@ -796,13 +769,11 @@ export class HaAutomationEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
}
|
||||
ha-yaml-editor {
|
||||
flex-grow: 1;
|
||||
--actions-border-radius: 0;
|
||||
--code-mirror-height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
.yaml-mode ha-card {
|
||||
overflow: initial;
|
||||
--ha-card-border-radius: 0;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
|
@@ -158,7 +158,7 @@ class HaConfigBackup extends LitElement {
|
||||
${this._backupData.backing_up
|
||||
? html`<ha-circular-progress
|
||||
slot="icon"
|
||||
active
|
||||
indeterminate
|
||||
></ha-circular-progress>`
|
||||
: html`<ha-svg-icon slot="icon" .path=${mdiPlus}></ha-svg-icon>`}
|
||||
</ha-fab>
|
||||
|
@@ -163,9 +163,9 @@ class DialogImportBlueprint extends LitElement {
|
||||
>
|
||||
${this._importing
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
indeterminate
|
||||
size="small"
|
||||
.title=${this.hass.localize(
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.blueprint.add.importing"
|
||||
)}
|
||||
></ha-circular-progress>`
|
||||
@@ -183,9 +183,9 @@ class DialogImportBlueprint extends LitElement {
|
||||
>
|
||||
${this._saving
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
indeterminate
|
||||
size="small"
|
||||
.title=${this.hass.localize(
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.blueprint.add.saving"
|
||||
)}
|
||||
></ha-circular-progress>`
|
||||
|
@@ -93,7 +93,7 @@ export class CloudWebhooks extends LitElement {
|
||||
? html`
|
||||
<div class="progress">
|
||||
<ha-circular-progress
|
||||
active
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
</div>
|
||||
`
|
||||
|
@@ -108,7 +108,7 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
|
||||
></state-badge>
|
||||
${this.narrow && entity.attributes.in_progress
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
indeterminate
|
||||
size="small"
|
||||
slot="graphic"
|
||||
class="absolute"
|
||||
@@ -128,7 +128,7 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
|
||||
${!this.narrow
|
||||
? entity.attributes.in_progress
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
indeterminate
|
||||
size="small"
|
||||
slot="meta"
|
||||
></ha-circular-progress>`
|
||||
|
@@ -164,7 +164,9 @@ export class DialogHelperDetail extends LitElement {
|
||||
</mwc-button>
|
||||
`;
|
||||
} else if (this._loading || this._helperFlows === undefined) {
|
||||
content = html`<ha-circular-progress active></ha-circular-progress>`;
|
||||
content = html`<ha-circular-progress
|
||||
indeterminate
|
||||
></ha-circular-progress>`;
|
||||
} else {
|
||||
const items: [string, string][] = [];
|
||||
|
||||
|
@@ -226,10 +226,6 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
};
|
||||
});
|
||||
|
||||
if (!Object.keys(configEntriesCopy).length) {
|
||||
return states;
|
||||
}
|
||||
|
||||
const entries = Object.values(configEntriesCopy).map((configEntry) => ({
|
||||
id: configEntry.entry_id,
|
||||
entity_id: "",
|
||||
|
@@ -449,7 +449,9 @@ class AddIntegrationDialog extends LitElement {
|
||||
>
|
||||
</lit-virtualizer>
|
||||
</mwc-list>`
|
||||
: html`<ha-circular-progress active></ha-circular-progress>`} `;
|
||||
: html`<div class="flex center">
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>`} `;
|
||||
}
|
||||
|
||||
private _keyFunction = (integration: IntegrationListItem) =>
|
||||
@@ -682,10 +684,12 @@ class AddIntegrationDialog extends LitElement {
|
||||
p > a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-circular-progress {
|
||||
width: 100%;
|
||||
.flex.center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
ha-circular-progress {
|
||||
margin: 24px 0;
|
||||
}
|
||||
mwc-list {
|
||||
|
@@ -57,7 +57,7 @@ class DialogMatterAddDevice extends LitElement {
|
||||
)
|
||||
: html`<ha-circular-progress
|
||||
size="large"
|
||||
active
|
||||
indeterminate
|
||||
></ha-circular-progress>`}
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" @click=${this.closeDialog}>
|
||||
|
@@ -102,7 +102,7 @@ class DialogZHAReconfigureDevice extends LitElement {
|
||||
${this._status === "started"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
<div class="status">
|
||||
<p>
|
||||
<b>
|
||||
|
@@ -98,8 +98,8 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
)}
|
||||
</h1>
|
||||
<ha-circular-progress
|
||||
active
|
||||
alt="Searching"
|
||||
indeterminate
|
||||
aria-label="Searching"
|
||||
></ha-circular-progress>
|
||||
`
|
||||
: html`
|
||||
@@ -236,7 +236,7 @@ class ZHAAddDevicesPage extends LitElement {
|
||||
color: var(--error-color);
|
||||
}
|
||||
ha-circular-progress {
|
||||
padding: 20px;
|
||||
margin: 20px;
|
||||
}
|
||||
.searching {
|
||||
margin-top: 20px;
|
||||
|
@@ -98,9 +98,9 @@ export class ZHAAddGroupPage extends LitElement {
|
||||
>
|
||||
${this._processingAdd
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
indeterminate
|
||||
size="small"
|
||||
.title=${this.hass!.localize(
|
||||
.ariaLabel=${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.creating_group"
|
||||
)}
|
||||
></ha-circular-progress>`
|
||||
|
@@ -120,9 +120,8 @@ class ZHADeviceNeighbors extends LitElement {
|
||||
return html`
|
||||
${!this._devices
|
||||
? html`<ha-circular-progress
|
||||
alt="Loading"
|
||||
size="large"
|
||||
active
|
||||
indeterminate
|
||||
></ha-circular-progress>`
|
||||
: html`<ha-data-table
|
||||
.hass=${this.hass}
|
||||
|
@@ -169,12 +169,14 @@ export class ZHAGroupPage extends LitElement {
|
||||
@click=${this._removeMembersFromGroup}
|
||||
class="button"
|
||||
>
|
||||
<ha-circular-progress
|
||||
?active=${this._processingRemove}
|
||||
alt=${this.hass.localize(
|
||||
"ui.panel.config.zha.groups.removing_members"
|
||||
)}
|
||||
></ha-circular-progress>
|
||||
${this._processingRemove
|
||||
? html`<ha-circular-progress
|
||||
indeterminate
|
||||
.ariaLabel=${this.hass.localize(
|
||||
"ui.panel.config.zha.groups.removing_members"
|
||||
)}
|
||||
></ha-circular-progress>`
|
||||
: nothing}
|
||||
${this.hass!.localize(
|
||||
"ui.panel.config.zha.groups.remove_members"
|
||||
)}</mwc-button
|
||||
@@ -208,7 +210,7 @@ export class ZHAGroupPage extends LitElement {
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
size="small"
|
||||
title="Saving"
|
||||
aria-label="Saving"
|
||||
></ha-circular-progress>`
|
||||
: ""}
|
||||
${this.hass!.localize(
|
||||
|
@@ -116,7 +116,10 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
>
|
||||
${this._status === "loading"
|
||||
? html`<div style="display: flex; justify-content: center;">
|
||||
<ha-circular-progress size="large" active></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
size="large"
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
</div>`
|
||||
: this._status === "choose_strategy"
|
||||
? html`<h3>Choose strategy</h3>
|
||||
@@ -288,7 +291,9 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
"ui.panel.config.zwave_js.add_node.searching_device"
|
||||
)}
|
||||
</h3>
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.add_node.follow_device_instructions"
|
||||
@@ -304,7 +309,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
)}
|
||||
</h2>
|
||||
<ha-circular-progress
|
||||
active
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
@@ -358,7 +363,7 @@ class DialogZWaveJSAddNode extends LitElement {
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-circular-progress
|
||||
active
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
<div class="status">
|
||||
<p>
|
||||
|
@@ -97,7 +97,7 @@ class DialogZWaveJSRebuildNodeRoutes extends LitElement {
|
||||
${this._status === "started"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
<div class="status">
|
||||
<p>
|
||||
${this.hass.localize(
|
||||
|
@@ -68,7 +68,7 @@ class DialogZWaveJSReinterviewNode extends LitElement {
|
||||
${this._status === "started"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
<div class="status">
|
||||
<p>
|
||||
<b>
|
||||
|
@@ -91,7 +91,7 @@ class DialogZWaveJSRemoveFailedNode extends LitElement {
|
||||
${this._status === "started"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
<div class="status">
|
||||
<p>
|
||||
<b>
|
||||
|
@@ -71,7 +71,7 @@ class DialogZWaveJSRemoveNode extends LitElement {
|
||||
${this._status === "started"
|
||||
? html`
|
||||
<div class="flex-container">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
<div class="status">
|
||||
<p>
|
||||
<b
|
||||
|
@@ -171,7 +171,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
<div class="icon">
|
||||
${this._status === "disconnected"
|
||||
? html`<ha-circular-progress
|
||||
active
|
||||
indeterminate
|
||||
></ha-circular-progress>`
|
||||
: html`
|
||||
<ha-svg-icon
|
||||
@@ -457,7 +457,7 @@ class ZWaveJSConfigDashboard extends SubscribeMixin(LitElement) {
|
||||
: html`
|
||||
<ha-circular-progress
|
||||
size="small"
|
||||
active
|
||||
indeterminate
|
||||
></ha-circular-progress>
|
||||
`}
|
||||
</div>
|
||||
|
@@ -97,7 +97,7 @@ export class SystemLogCard extends LitElement {
|
||||
${this._items === undefined
|
||||
? html`
|
||||
<div class="loading-container">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
|
@@ -69,7 +69,7 @@ export class HassioHostname extends LitElement {
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._save} .disabled=${this._processing}>
|
||||
${this._processing
|
||||
? html`<ha-circular-progress active size="small">
|
||||
? html`<ha-circular-progress indeterminate size="small">
|
||||
</ha-circular-progress>`
|
||||
: this.hass.localize("ui.common.save")}
|
||||
</mwc-button>
|
||||
|
@@ -126,7 +126,7 @@ export class HassioNetwork extends LitElement {
|
||||
.disabled=${this._scanning}
|
||||
>
|
||||
${this._scanning
|
||||
? html`<ha-circular-progress active size="small">
|
||||
? html`<ha-circular-progress indeterminate size="small">
|
||||
</ha-circular-progress>`
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.network.supervisor.scan_ap"
|
||||
@@ -242,7 +242,7 @@ export class HassioNetwork extends LitElement {
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._updateNetwork} .disabled=${!this._dirty}>
|
||||
${this._processing
|
||||
? html`<ha-circular-progress active size="small">
|
||||
? html`<ha-circular-progress indeterminate size="small">
|
||||
</ha-circular-progress>`
|
||||
: this.hass.localize("ui.common.save")}
|
||||
</mwc-button>
|
||||
|
@@ -304,7 +304,7 @@ class DialogSystemInformation extends LitElement {
|
||||
if (!this._systemInfo) {
|
||||
sections.push(html`
|
||||
<div class="loading-container">
|
||||
<ha-circular-progress active></ha-circular-progress>
|
||||
<ha-circular-progress indeterminate></ha-circular-progress>
|
||||
</div>
|
||||
`);
|
||||
} else {
|
||||
@@ -324,7 +324,10 @@ class DialogSystemInformation extends LitElement {
|
||||
|
||||
if (info.type === "pending") {
|
||||
value = html`
|
||||
<ha-circular-progress active size="tiny"></ha-circular-progress>
|
||||
<ha-circular-progress
|
||||
indeterminate
|
||||
size="tiny"
|
||||
></ha-circular-progress>
|
||||
`;
|
||||
} else if (info.type === "failed") {
|
||||
value = html`
|
||||
|
@@ -80,7 +80,7 @@ export class HaBlueprintScriptEditor extends LitElement {
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.automation.editor.blueprint.no_blueprints"
|
||||
)
|
||||
: html`<ha-circular-progress active></ha-circular-progress>`}
|
||||
: html`<ha-circular-progress indeterminate></ha-circular-progress>`}
|
||||
</div>
|
||||
|
||||
${this.config.use_blueprint.path
|
||||
|
@@ -26,7 +26,6 @@ import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { computeRTL } from "../../../common/util/compute_rtl";
|
||||
import { copyToClipboard } from "../../../common/util/copy-clipboard";
|
||||
import { afterNextRender } from "../../../common/util/render-status";
|
||||
import "../../../components/ha-button-menu";
|
||||
import "../../../components/ha-card";
|
||||
@@ -38,7 +37,6 @@ import type {
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-yaml-editor";
|
||||
import type { HaYamlEditor } from "../../../components/ha-yaml-editor";
|
||||
import { validateConfig } from "../../../data/config";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import { EntityRegistryEntry } from "../../../data/entity_registry";
|
||||
@@ -94,8 +92,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
|
||||
@state() private _readOnly = false;
|
||||
|
||||
@query("ha-yaml-editor") private _yamlEditor?: HaYamlEditor;
|
||||
|
||||
@query("manual-script-editor")
|
||||
private _manualEditor?: HaManualScriptEditor;
|
||||
|
||||
@@ -405,24 +401,14 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
</div>
|
||||
`
|
||||
: this._mode === "yaml"
|
||||
? html`
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._preprocessYaml()}
|
||||
.readOnly=${this._readOnly}
|
||||
@value-changed=${this._yamlChanged}
|
||||
></ha-yaml-editor>
|
||||
<ha-card outlined>
|
||||
<div class="card-actions">
|
||||
<mwc-button @click=${this._copyYaml}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.copy_to_clipboard"
|
||||
)}
|
||||
</mwc-button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: ``}
|
||||
? html` <ha-yaml-editor
|
||||
copyClipboard
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._preprocessYaml()}
|
||||
.readOnly=${this._readOnly}
|
||||
@value-changed=${this._yamlChanged}
|
||||
></ha-yaml-editor>`
|
||||
: nothing}
|
||||
</div>
|
||||
<ha-fab
|
||||
slot="fab"
|
||||
@@ -735,15 +721,6 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
private async _copyYaml(): Promise<void> {
|
||||
if (this._yamlEditor?.yaml) {
|
||||
await copyToClipboard(this._yamlEditor.yaml);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _yamlChanged(ev: CustomEvent) {
|
||||
ev.stopPropagation();
|
||||
if (!ev.detail.isValid) {
|
||||
@@ -903,8 +880,11 @@ export class HaScriptEditor extends KeyboardShortcutMixin(LitElement) {
|
||||
}
|
||||
ha-yaml-editor {
|
||||
flex-grow: 1;
|
||||
--actions-border-radius: 0;
|
||||
--code-mirror-height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.yaml-mode ha-card {
|
||||
overflow: initial;
|
||||
|
@@ -106,7 +106,11 @@ class MoveDatadiskDialog extends LitElement {
|
||||
>
|
||||
${this._moving
|
||||
? html`
|
||||
<ha-circular-progress alt="Moving" size="large" active>
|
||||
<ha-circular-progress
|
||||
aria-label="Moving"
|
||||
size="large"
|
||||
indeterminate
|
||||
>
|
||||
</ha-circular-progress>
|
||||
<p class="progress-text">
|
||||
${this.hass.localize(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user