Compare commits
1 Commits
20231005.0
...
delay-init
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7f2fcc73b5 |
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,7 +2,9 @@
|
||||
You are amazing! Thanks for contributing to our project!
|
||||
Please, DO NOT DELETE ANY TEXT from this template! (unless instructed).
|
||||
-->
|
||||
|
||||
## Breaking change
|
||||
|
||||
<!--
|
||||
If your PR contains a breaking change for existing users, it is important
|
||||
to tell them what breaks, how to make it work again and why we did this.
|
||||
@@ -11,8 +13,8 @@
|
||||
Note: Remove this section if this PR is NOT a breaking change.
|
||||
-->
|
||||
|
||||
|
||||
## Proposed change
|
||||
|
||||
<!--
|
||||
Describe the big picture of your changes here to communicate to the
|
||||
maintainers why we should accept this pull request. If it fixes a bug
|
||||
@@ -20,8 +22,8 @@
|
||||
in the additional information section.
|
||||
-->
|
||||
|
||||
|
||||
## Type of change
|
||||
|
||||
<!--
|
||||
What type of change does your PR introduce to the Home Assistant frontend?
|
||||
NOTE: Please, check only 1! box!
|
||||
@@ -36,6 +38,7 @@
|
||||
- [ ] Code quality improvements to existing code or addition of tests
|
||||
|
||||
## Example configuration
|
||||
|
||||
<!--
|
||||
Supplying a configuration snippet, makes it easier for a maintainer to test
|
||||
your PR.
|
||||
@@ -46,6 +49,7 @@
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
<!--
|
||||
Details are important, and help maintainers processing your PR.
|
||||
Please be sure to fill out additional details, if applicable.
|
||||
@@ -56,6 +60,7 @@
|
||||
- Link to documentation pull request:
|
||||
|
||||
## Checklist
|
||||
|
||||
<!--
|
||||
Put an `x` in the boxes that apply. You can also fill these out after
|
||||
creating the PR. If you're unsure about any of them, don't hesitate to ask.
|
||||
|
4
.github/workflows/cast_deployment.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
8
.github/workflows/ci.yaml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.8.1
|
||||
with:
|
||||
|
2
.github/workflows/codeql-analysis.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
with:
|
||||
# We must fetch at least the immediate parents so that if this is
|
||||
# a pull request then we can checkout the head.
|
||||
|
4
.github/workflows/demo_deployment.yaml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
with:
|
||||
ref: dev
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
with:
|
||||
ref: master
|
||||
|
||||
|
2
.github/workflows/design_deployment.yaml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
url: ${{ steps.deploy.outputs.NETLIFY_LIVE_URL || steps.deploy.outputs.NETLIFY_URL }}
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.8.1
|
||||
|
2
.github/workflows/design_preview.yaml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
if: github.repository == 'home-assistant/frontend' && contains(github.event.pull_request.labels.*.name, 'needs design preview')
|
||||
steps:
|
||||
- name: Check out files from GitHub
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v3.8.1
|
||||
|
2
.github/workflows/nightly.yaml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v4
|
||||
|
4
.github/workflows/release.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
contents: write # Required to upload release assets
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
|
||||
- name: Verify version
|
||||
uses: home-assistant/actions/helpers/verify-version@master
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||
|
||||
- name: Build wheels
|
||||
uses: home-assistant/wheels@2023.10.1
|
||||
uses: home-assistant/wheels@2023.04.0
|
||||
with:
|
||||
abi: cp311
|
||||
tag: musllinux_1_2
|
||||
|
2
.github/workflows/translations.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v4.1.0
|
||||
uses: actions/checkout@v4.0.0
|
||||
|
||||
- name: Upload Translations
|
||||
run: |
|
||||
|
@@ -1,4 +1,3 @@
|
||||
CLA.md
|
||||
CODE_OF_CONDUCT.md
|
||||
LICENSE.md
|
||||
PULL_REQUEST_TEMPLATE.md
|
@@ -165,7 +165,6 @@ const createWebpackConfig = ({
|
||||
"lit/directives/guard$": "lit/directives/guard.js",
|
||||
"lit/directives/cache$": "lit/directives/cache.js",
|
||||
"lit/directives/repeat$": "lit/directives/repeat.js",
|
||||
"lit/directives/live$": "lit/directives/live.js",
|
||||
"lit/polyfill-support$": "lit/polyfill-support.js",
|
||||
"@lit-labs/virtualizer/layouts/grid":
|
||||
"@lit-labs/virtualizer/layouts/grid.js",
|
||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
@@ -32,8 +32,6 @@ import { HassElement } from "../../../../src/state/hass-element";
|
||||
import { castContext } from "../cast_context";
|
||||
import "./hc-launch-screen";
|
||||
|
||||
const DEFAULT_STRATEGY = "original-states";
|
||||
|
||||
let resourcesLoaded = false;
|
||||
@customElement("hc-main")
|
||||
export class HcMain extends HassElement {
|
||||
@@ -260,7 +258,7 @@ export class HcMain extends HassElement {
|
||||
{
|
||||
strategy: {
|
||||
type: "energy",
|
||||
show_date_selection: true,
|
||||
options: { show_date_selection: true },
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -322,10 +320,10 @@ export class HcMain extends HassElement {
|
||||
this._handleNewLovelaceConfig(
|
||||
await generateLovelaceDashboardStrategy(
|
||||
{
|
||||
type: DEFAULT_STRATEGY,
|
||||
hass: this.hass!,
|
||||
narrow: false,
|
||||
},
|
||||
this.hass!,
|
||||
{ narrow: false }
|
||||
"original-states"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@@ -8,67 +8,25 @@
|
||||
"src": "/static/icons/favicon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/favicon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/favicon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/favicon-1024x1024.png",
|
||||
"sizes": "1024x1024",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
{
|
||||
"src": "/static/icons/maskable_icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
"purpose": "maskable any"
|
||||
}
|
||||
],
|
||||
"lang": "en-US",
|
||||
|
@@ -62,34 +62,10 @@
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
#ha-launch-screen svg {
|
||||
width: 170px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#ha-launch-screen .ha-launch-screen-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="ha-launch-screen">
|
||||
<div class="ha-launch-screen-spacer"></div>
|
||||
<svg
|
||||
viewBox="0 0 240 240"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M240 224.762C240 233.012 233.25 239.762 225 239.762H15C6.75 239.762 0 233.012 0 224.762V134.762C0 126.512 4.77 114.993 10.61 109.153L109.39 10.3725C115.22 4.5425 124.77 4.5425 130.6 10.3725L229.39 109.162C235.22 114.992 240 126.522 240 134.772V224.772V224.762Z"
|
||||
fill="#F2F4F9"
|
||||
/>
|
||||
<path
|
||||
d="M229.39 109.153L130.61 10.3725C124.78 4.5425 115.23 4.5425 109.4 10.3725L10.61 109.153C4.78 114.983 0 126.512 0 134.762V224.762C0 233.012 6.75 239.762 15 239.762H107.27L66.64 199.132C64.55 199.852 62.32 200.262 60 200.262C48.7 200.262 39.5 191.062 39.5 179.762C39.5 168.462 48.7 159.262 60 159.262C71.3 159.262 80.5 168.462 80.5 179.762C80.5 182.092 80.09 184.322 79.37 186.412L111 218.042V102.162C104.2 98.8225 99.5 91.8425 99.5 83.7725C99.5 72.4725 108.7 63.2725 120 63.2725C131.3 63.2725 140.5 72.4725 140.5 83.7725C140.5 91.8425 135.8 98.8225 129 102.162V183.432L160.46 151.972C159.84 150.012 159.5 147.932 159.5 145.772C159.5 134.472 168.7 125.272 180 125.272C191.3 125.272 200.5 134.472 200.5 145.772C200.5 157.072 191.3 166.272 180 166.272C177.5 166.272 175.12 165.802 172.91 164.982L129 208.892V239.772H225C233.25 239.772 240 233.022 240 224.772V134.772C240 126.522 235.23 115.002 229.39 109.162V109.153Z"
|
||||
fill="#18BCF2"
|
||||
/>
|
||||
</svg>
|
||||
<div id="ha-launch-screen-info-box" class="ha-launch-screen-spacer"></div>
|
||||
</div>
|
||||
<div id="ha-launch-screen"></div>
|
||||
<ha-demo></ha-demo>
|
||||
<%= renderTemplate("../../../src/html/_js_base.html.template") %>
|
||||
<%= renderTemplate("../../../src/html/_preload_roboto.html.template") %>
|
||||
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 25 KiB |
@@ -1,10 +1,10 @@
|
||||
import { mdiHomeAssistant } from "@mdi/js";
|
||||
import { css, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import "../../../../src/components/ha-card";
|
||||
import "../../../../src/components/ha-chip";
|
||||
import "../../../../src/components/ha-chip-set";
|
||||
import "../../../../src/components/ha-svg-icon";
|
||||
import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg";
|
||||
|
||||
const chips: {
|
||||
icon?: string;
|
||||
|
@@ -7,6 +7,7 @@ import {
|
||||
mdiDocker,
|
||||
mdiExclamationThick,
|
||||
mdiFlask,
|
||||
mdiHomeAssistant,
|
||||
mdiKey,
|
||||
mdiLinkLock,
|
||||
mdiNetwork,
|
||||
@@ -21,7 +22,7 @@ import {
|
||||
mdiPound,
|
||||
mdiShield,
|
||||
} from "@mdi/js";
|
||||
import { CSSResultGroup, LitElement, TemplateResult, css, html } from "lit";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -39,11 +40,11 @@ import "../../../../src/components/ha-svg-icon";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import {
|
||||
AddonCapability,
|
||||
fetchHassioAddonChangelog,
|
||||
fetchHassioAddonInfo,
|
||||
HassioAddonDetails,
|
||||
HassioAddonSetOptionParams,
|
||||
HassioAddonSetSecurityParams,
|
||||
fetchHassioAddonChangelog,
|
||||
fetchHassioAddonInfo,
|
||||
installHassioAddon,
|
||||
rebuildLocalAddon,
|
||||
restartHassioAddon,
|
||||
@@ -55,9 +56,9 @@ import {
|
||||
validateHassioAddonOption,
|
||||
} from "../../../../src/data/hassio/addon";
|
||||
import {
|
||||
HassioStats,
|
||||
extractApiErrorMessage,
|
||||
fetchHassioStats,
|
||||
HassioStats,
|
||||
} from "../../../../src/data/hassio/common";
|
||||
import {
|
||||
StoreAddon,
|
||||
@@ -68,7 +69,6 @@ import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../../../src/dialogs/generic/show-dialog-box";
|
||||
import { mdiHomeAssistant } from "../../../../src/resources/home-assistant-logo-svg";
|
||||
import { haStyle } from "../../../../src/resources/styles";
|
||||
import { HomeAssistant, Route } from "../../../../src/types";
|
||||
import { bytesToString } from "../../../../src/util/bytes-to-string";
|
||||
|
@@ -360,9 +360,11 @@ export class HassioBackups extends LitElement {
|
||||
if (this.supervisor!.info.state !== "running") {
|
||||
showAlertDialog(this, {
|
||||
title: this.supervisor!.localize("backup.could_not_create"),
|
||||
text: this.supervisor!.localize("backup.create_blocked_not_running", {
|
||||
state: this.supervisor!.info.state,
|
||||
}),
|
||||
text: this.supervisor!.localize(
|
||||
"backup.create_blocked_not_running",
|
||||
"state",
|
||||
this.supervisor!.info.state
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import { mdiFolder, mdiPuzzle } from "@mdi/js";
|
||||
import { mdiFolder, mdiHomeAssistant, mdiPuzzle } from "@mdi/js";
|
||||
import "@polymer/paper-input/paper-input";
|
||||
import type { PaperInputElement } from "@polymer/paper-input/paper-input";
|
||||
import {
|
||||
CSSResultGroup,
|
||||
LitElement,
|
||||
TemplateResult,
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { atLeastVersion } from "../../../src/common/config/version";
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
HassioPartialBackupCreateParams,
|
||||
} from "../../../src/data/hassio/backup";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { mdiHomeAssistant } from "../../../src/resources/home-assistant-logo-svg";
|
||||
import {
|
||||
HomeAssistant,
|
||||
TranslationDict,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import "@material/mwc-button";
|
||||
import { mdiHomeAssistant } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
HassioSupervisorInfo,
|
||||
} from "../../../src/data/hassio/supervisor";
|
||||
import { Supervisor } from "../../../src/data/supervisor/supervisor";
|
||||
import { mdiHomeAssistant } from "../../../src/resources/home-assistant-logo-svg";
|
||||
import { haStyle } from "../../../src/resources/styles";
|
||||
import { HomeAssistant } from "../../../src/types";
|
||||
import { hassioStyle } from "../resources/hassio-style";
|
||||
|
@@ -31,7 +31,6 @@ import { fileDownload } from "../../../../src/util/file_download";
|
||||
import "../../components/supervisor-backup-content";
|
||||
import type { SupervisorBackupContent } from "../../components/supervisor-backup-content";
|
||||
import { HassioBackupDialogParams } from "./show-dialog-hassio-backup";
|
||||
import { BackupOrRestoreKey } from "../../util/translations";
|
||||
|
||||
@customElement("dialog-hassio-backup")
|
||||
class HassioBackupDialog
|
||||
@@ -65,13 +64,6 @@ class HassioBackupDialog
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _localize(key: BackupOrRestoreKey) {
|
||||
return (
|
||||
this._dialogParams!.supervisor?.localize(`backup.${key}`) ||
|
||||
this._dialogParams!.localize!(`ui.panel.page-onboarding.restore.${key}`)
|
||||
);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._dialogParams || !this._backup) {
|
||||
return nothing;
|
||||
@@ -87,7 +79,7 @@ class HassioBackupDialog
|
||||
<ha-header-bar>
|
||||
<span slot="title">${this._backup.name}</span>
|
||||
<ha-icon-button
|
||||
.label=${this._localize("close")}
|
||||
.label=${this.hass?.localize("ui.common.close") || "Close"}
|
||||
.path=${mdiClose}
|
||||
slot="actionItems"
|
||||
dialogAction="cancel"
|
||||
@@ -95,31 +87,29 @@ class HassioBackupDialog
|
||||
</ha-header-bar>
|
||||
</div>
|
||||
${this._restoringBackup
|
||||
? html`<ha-circular-progress active></ha-circular-progress>`
|
||||
: html`
|
||||
<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
.backup=${this._backup}
|
||||
.onboarding=${this._dialogParams.onboarding || false}
|
||||
.localize=${this._dialogParams.localize}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</supervisor-backup-content>
|
||||
`}
|
||||
? html` <ha-circular-progress active></ha-circular-progress>`
|
||||
: html`<supervisor-backup-content
|
||||
.hass=${this.hass}
|
||||
.supervisor=${this._dialogParams.supervisor}
|
||||
.backup=${this._backup}
|
||||
.onboarding=${this._dialogParams.onboarding || false}
|
||||
.localize=${this._dialogParams.localize}
|
||||
dialogInitialFocus
|
||||
>
|
||||
</supervisor-backup-content>`}
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: nothing}
|
||||
: ""}
|
||||
|
||||
<mwc-button
|
||||
.disabled=${this._restoringBackup}
|
||||
slot="secondaryAction"
|
||||
@click=${this._restoreClicked}
|
||||
>
|
||||
${this._localize("restore")}
|
||||
Restore
|
||||
</mwc-button>
|
||||
|
||||
${!this._dialogParams.onboarding && this._dialogParams.supervisor
|
||||
${!this._dialogParams.onboarding
|
||||
? html`<ha-button-menu
|
||||
fixed
|
||||
slot="primaryAction"
|
||||
@@ -127,24 +117,22 @@ class HassioBackupDialog
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${this._dialogParams.supervisor.localize(
|
||||
"backup.more_actions"
|
||||
)}
|
||||
.label=${this.hass!.localize("ui.common.menu") || "Menu"}
|
||||
.path=${mdiDotsVertical}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
<mwc-list-item
|
||||
>${this._dialogParams.supervisor.localize(
|
||||
>${this._dialogParams.supervisor?.localize(
|
||||
"backup.download_backup"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
<mwc-list-item class="error"
|
||||
>${this._dialogParams.supervisor.localize(
|
||||
>${this._dialogParams.supervisor?.localize(
|
||||
"backup.delete_backup_title"
|
||||
)}</mwc-list-item
|
||||
>
|
||||
</ha-button-menu>`
|
||||
: nothing}
|
||||
: ""}
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
@@ -195,22 +183,21 @@ class HassioBackupDialog
|
||||
}
|
||||
|
||||
private async _partialRestoreClicked(backupDetails) {
|
||||
const supervisor = this._dialogParams?.supervisor;
|
||||
if (supervisor !== undefined && supervisor.info.state !== "running") {
|
||||
if (
|
||||
this._dialogParams?.supervisor !== undefined &&
|
||||
this._dialogParams?.supervisor.info.state !== "running"
|
||||
) {
|
||||
await showAlertDialog(this, {
|
||||
title: supervisor.localize("backup.could_not_restore"),
|
||||
text: supervisor.localize("backup.restore_blocked_not_running", {
|
||||
state: supervisor.info.state,
|
||||
}),
|
||||
title: "Could not restore backup",
|
||||
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: this._localize("confirm_restore_partial_backup_title"),
|
||||
text: this._localize("confirm_restore_partial_backup_text"),
|
||||
confirmText: this._localize("restore"),
|
||||
dismissText: this._localize("cancel"),
|
||||
title: "Are you sure you want to restore this partial backup?",
|
||||
confirmText: "restore",
|
||||
dismissText: "cancel",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
@@ -243,22 +230,22 @@ class HassioBackupDialog
|
||||
}
|
||||
|
||||
private async _fullRestoreClicked(backupDetails) {
|
||||
const supervisor = this._dialogParams?.supervisor;
|
||||
if (supervisor !== undefined && supervisor.info.state !== "running") {
|
||||
if (
|
||||
this._dialogParams?.supervisor !== undefined &&
|
||||
this._dialogParams?.supervisor.info.state !== "running"
|
||||
) {
|
||||
await showAlertDialog(this, {
|
||||
title: supervisor.localize("backup.could_not_restore"),
|
||||
text: supervisor.localize("backup.restore_blocked_not_running", {
|
||||
state: supervisor.info.state,
|
||||
}),
|
||||
title: "Could not restore backup",
|
||||
text: `Restoring a backup is not possible right now because the system is in ${this._dialogParams?.supervisor.info.state} state.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: this._localize("confirm_restore_full_backup_title"),
|
||||
text: this._localize("confirm_restore_full_backup_text"),
|
||||
confirmText: this._localize("restore"),
|
||||
dismissText: this._localize("cancel"),
|
||||
title:
|
||||
"Are you sure you want to wipe your system and restore this backup?",
|
||||
confirmText: "restore",
|
||||
dismissText: "cancel",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
@@ -292,15 +279,11 @@ class HassioBackupDialog
|
||||
}
|
||||
|
||||
private async _deleteClicked() {
|
||||
const supervisor = this._dialogParams?.supervisor;
|
||||
if (!supervisor) return;
|
||||
|
||||
if (
|
||||
!(await showConfirmationDialog(this, {
|
||||
title: supervisor!.localize("backup.confirm_delete_title"),
|
||||
text: supervisor!.localize("backup.confirm_delete_text"),
|
||||
confirmText: supervisor!.localize("backup.delete"),
|
||||
dismissText: supervisor!.localize("backup.cancel"),
|
||||
title: "Are you sure you want to delete this backup?",
|
||||
confirmText: "delete",
|
||||
dismissText: "cancel",
|
||||
}))
|
||||
) {
|
||||
return;
|
||||
@@ -318,9 +301,6 @@ class HassioBackupDialog
|
||||
}
|
||||
|
||||
private async _downloadClicked() {
|
||||
const supervisor = this._dialogParams?.supervisor;
|
||||
if (!supervisor) return;
|
||||
|
||||
let signedPath: { path: string };
|
||||
try {
|
||||
signedPath = await getSignedPath(
|
||||
@@ -340,10 +320,10 @@ class HassioBackupDialog
|
||||
|
||||
if (window.location.href.includes("ui.nabu.casa")) {
|
||||
const confirm = await showConfirmationDialog(this, {
|
||||
title: supervisor.localize("backup.remote_download_title"),
|
||||
text: supervisor.localize("backup.remote_download_text"),
|
||||
confirmText: supervisor.localize("backup.download"),
|
||||
dismissText: this._localize("cancel"),
|
||||
title: "Potential slow download",
|
||||
text: "Downloading backups over the Nabu Casa URL will take some time, it is recomended to use your local URL instead, do you want to continue?",
|
||||
confirmText: "continue",
|
||||
dismissText: "cancel",
|
||||
});
|
||||
if (!confirm) {
|
||||
return;
|
||||
|
@@ -89,7 +89,8 @@ class HassioCreateBackupDialog extends LitElement {
|
||||
),
|
||||
text: this._dialogParams!.supervisor.localize(
|
||||
"backup.create_blocked_not_running",
|
||||
{ state: this._dialogParams!.supervisor.info.state }
|
||||
"state",
|
||||
this._dialogParams!.supervisor.info.state
|
||||
),
|
||||
});
|
||||
return;
|
||||
|
@@ -1,4 +0,0 @@
|
||||
import type { TranslationDict } from "../../../src/types";
|
||||
|
||||
export type BackupOrRestoreKey = keyof TranslationDict["supervisor"]["backup"] &
|
||||
keyof TranslationDict["ui"]["panel"]["page-onboarding"]["restore"];
|
79
package.json
@@ -25,17 +25,17 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "7.23.1",
|
||||
"@babel/runtime": "7.22.15",
|
||||
"@braintree/sanitize-url": "6.0.4",
|
||||
"@codemirror/autocomplete": "6.9.1",
|
||||
"@codemirror/commands": "6.3.0",
|
||||
"@codemirror/language": "6.9.1",
|
||||
"@codemirror/commands": "6.2.5",
|
||||
"@codemirror/language": "6.9.0",
|
||||
"@codemirror/legacy-modes": "6.3.3",
|
||||
"@codemirror/search": "6.5.4",
|
||||
"@codemirror/search": "6.5.3",
|
||||
"@codemirror/state": "6.2.1",
|
||||
"@codemirror/view": "6.21.1",
|
||||
"@codemirror/view": "6.19.0",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "6.10.3",
|
||||
"@formatjs/intl-datetimeformat": "6.10.2",
|
||||
"@formatjs/intl-displaynames": "6.5.2",
|
||||
"@formatjs/intl-getcanonicallocales": "2.2.1",
|
||||
"@formatjs/intl-listformat": "7.4.2",
|
||||
@@ -43,12 +43,12 @@
|
||||
"@formatjs/intl-numberformat": "8.7.2",
|
||||
"@formatjs/intl-pluralrules": "5.2.6",
|
||||
"@formatjs/intl-relativetimeformat": "11.2.6",
|
||||
"@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.8",
|
||||
"@fullcalendar/daygrid": "6.1.8",
|
||||
"@fullcalendar/interaction": "6.1.8",
|
||||
"@fullcalendar/list": "6.1.8",
|
||||
"@fullcalendar/luxon3": "6.1.8",
|
||||
"@fullcalendar/timegrid": "6.1.8",
|
||||
"@lezer/highlight": "1.1.6",
|
||||
"@lit-labs/context": "0.4.1",
|
||||
"@lit-labs/motion": "1.0.4",
|
||||
@@ -62,7 +62,6 @@
|
||||
"@material/mwc-dialog": "0.27.0",
|
||||
"@material/mwc-drawer": "0.27.0",
|
||||
"@material/mwc-fab": "0.27.0",
|
||||
"@material/mwc-floating-label": "0.27.0",
|
||||
"@material/mwc-formfield": "0.27.0",
|
||||
"@material/mwc-icon-button": "0.27.0",
|
||||
"@material/mwc-linear-progress": "0.27.0",
|
||||
@@ -80,7 +79,7 @@
|
||||
"@material/mwc-top-app-bar": "0.27.0",
|
||||
"@material/mwc-top-app-bar-fixed": "0.27.0",
|
||||
"@material/top-app-bar": "=14.0.0-canary.53b3cad2f.0",
|
||||
"@material/web": "=1.0.0",
|
||||
"@material/web": "=1.0.0-pre.17",
|
||||
"@mdi/js": "7.2.96",
|
||||
"@mdi/svg": "7.2.96",
|
||||
"@polymer/iron-flex-layout": "3.0.1",
|
||||
@@ -94,8 +93,8 @@
|
||||
"@polymer/paper-toast": "3.0.1",
|
||||
"@polymer/polymer": "3.5.1",
|
||||
"@thomasloven/round-slider": "0.6.0",
|
||||
"@vaadin/combo-box": "24.1.10",
|
||||
"@vaadin/vaadin-themable-mixin": "24.1.10",
|
||||
"@vaadin/combo-box": "24.1.7",
|
||||
"@vaadin/vaadin-themable-mixin": "24.1.7",
|
||||
"@vibrant/color": "3.2.1-alpha.1",
|
||||
"@vibrant/core": "3.2.1-alpha.1",
|
||||
"@vibrant/quantizer-mmcq": "3.2.1-alpha.1",
|
||||
@@ -103,10 +102,10 @@
|
||||
"@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.3.3",
|
||||
"comlink": "4.4.1",
|
||||
"core-js": "3.32.2",
|
||||
"cropperjs": "1.6.1",
|
||||
"cropperjs": "1.6.0",
|
||||
"date-fns": "2.30.0",
|
||||
"date-fns-tz": "2.0.0",
|
||||
"deep-clone-simple": "1.1.1",
|
||||
@@ -116,13 +115,13 @@
|
||||
"hls.js": "1.4.12",
|
||||
"home-assistant-js-websocket": "8.2.0",
|
||||
"idb-keyval": "6.2.1",
|
||||
"intl-messageformat": "10.5.3",
|
||||
"intl-messageformat": "10.5.2",
|
||||
"js-yaml": "4.1.0",
|
||||
"leaflet": "1.9.4",
|
||||
"leaflet-draw": "1.0.4",
|
||||
"lit": "2.8.0",
|
||||
"luxon": "3.4.3",
|
||||
"marked": "9.0.3",
|
||||
"marked": "7.0.5",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "3.2.1-alpha.1",
|
||||
"proxy-polyfill": "0.3.2",
|
||||
@@ -154,16 +153,16 @@
|
||||
"xss": "1.0.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.23.0",
|
||||
"@babel/plugin-proposal-decorators": "7.23.0",
|
||||
"@babel/core": "7.22.20",
|
||||
"@babel/plugin-proposal-decorators": "7.22.15",
|
||||
"@babel/plugin-transform-runtime": "7.22.15",
|
||||
"@babel/preset-env": "7.22.20",
|
||||
"@babel/preset-typescript": "7.23.0",
|
||||
"@babel/preset-typescript": "7.22.15",
|
||||
"@koa/cors": "4.0.0",
|
||||
"@lokalise/node-api": "12.0.0",
|
||||
"@octokit/auth-oauth-device": "6.0.1",
|
||||
"@octokit/plugin-retry": "6.0.1",
|
||||
"@octokit/rest": "20.0.2",
|
||||
"@lokalise/node-api": "11.0.1",
|
||||
"@octokit/auth-oauth-device": "6.0.0",
|
||||
"@octokit/plugin-retry": "6.0.0",
|
||||
"@octokit/rest": "20.0.1",
|
||||
"@open-wc/dev-server-hmr": "0.1.4",
|
||||
"@rollup/plugin-babel": "6.0.3",
|
||||
"@rollup/plugin-commonjs": "25.0.4",
|
||||
@@ -173,29 +172,29 @@
|
||||
"@types/babel__plugin-transform-runtime": "7.9.3",
|
||||
"@types/chromecast-caf-receiver": "6.0.10",
|
||||
"@types/chromecast-caf-sender": "1.0.6",
|
||||
"@types/esprima": "4.0.4",
|
||||
"@types/esprima": "4.0.3",
|
||||
"@types/glob": "8.1.0",
|
||||
"@types/html-minifier-terser": "7.0.0",
|
||||
"@types/js-yaml": "4.0.6",
|
||||
"@types/leaflet": "1.9.6",
|
||||
"@types/leaflet": "1.9.4",
|
||||
"@types/leaflet-draw": "1.0.8",
|
||||
"@types/luxon": "3.3.2",
|
||||
"@types/mocha": "10.0.2",
|
||||
"@types/mocha": "10.0.1",
|
||||
"@types/qrcode": "1.5.2",
|
||||
"@types/serve-handler": "6.1.2",
|
||||
"@types/sortablejs": "1.15.3",
|
||||
"@types/serve-handler": "6.1.1",
|
||||
"@types/sortablejs": "1.15.2",
|
||||
"@types/tar": "6.1.6",
|
||||
"@types/ua-parser-js": "0.7.37",
|
||||
"@types/webspeechapi": "0.0.29",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.3",
|
||||
"@typescript-eslint/parser": "6.7.3",
|
||||
"@typescript-eslint/eslint-plugin": "6.7.0",
|
||||
"@typescript-eslint/parser": "6.7.0",
|
||||
"@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",
|
||||
"chai": "4.3.8",
|
||||
"del": "7.1.0",
|
||||
"eslint": "8.50.0",
|
||||
"eslint": "8.49.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-airbnb-typescript": "17.1.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
@@ -203,13 +202,13 @@
|
||||
"eslint-plugin-disable": "2.0.3",
|
||||
"eslint-plugin-import": "2.28.1",
|
||||
"eslint-plugin-lit": "1.9.1",
|
||||
"eslint-plugin-lit-a11y": "4.1.0",
|
||||
"eslint-plugin-lit-a11y": "3.0.0",
|
||||
"eslint-plugin-unused-imports": "3.0.0",
|
||||
"eslint-plugin-wc": "2.0.4",
|
||||
"eslint-plugin-wc": "1.5.0",
|
||||
"esprima": "4.0.1",
|
||||
"fancy-log": "2.0.0",
|
||||
"fs-extra": "11.1.1",
|
||||
"glob": "10.3.10",
|
||||
"glob": "10.3.4",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-flatmap": "1.0.2",
|
||||
"gulp-json-transform": "0.4.8",
|
||||
@@ -223,7 +222,7 @@
|
||||
"lint-staged": "14.0.1",
|
||||
"lit-analyzer": "2.0.0-pre.3",
|
||||
"lodash.template": "4.5.0",
|
||||
"magic-string": "0.30.4",
|
||||
"magic-string": "0.30.3",
|
||||
"map-stream": "0.0.7",
|
||||
"mocha": "10.2.0",
|
||||
"object-hash": "3.0.0",
|
||||
|
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square70x70logo src="/static/icons/tile-win-70x70.png"/>
|
||||
<square150x150logo src="/static/icons/tile-win-150x150.png"/>
|
||||
<wide310x150logo src="/static/icons/tile-win-310x150.png"/>
|
||||
<square310x310logo src="/static/icons/tile-win-310x310.png"/>
|
||||
<TileColor>#18bcf2</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 635 B |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
@@ -1,23 +1 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="480.000000pt" height="480.000000pt" viewBox="0 0 480.000000 480.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<g transform="translate(0.000000,480.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M2313 4666 c-23 -7 -56 -23 -75 -34 -47 -30 -2059 -2048 -2095 -2102
|
||||
-45 -67 -77 -135 -109 -230 l-29 -85 0 -995 0 -995 27 -51 c31 -59 93 -118
|
||||
152 -145 39 -18 83 -19 1001 -19 l960 0 -406 405 c-395 395 -406 406 -433 395
|
||||
-15 -5 -63 -10 -107 -10 -429 0 -566 577 -181 767 67 34 86 38 164 42 105 4
|
||||
165 -13 246 -67 113 -74 175 -190 176 -327 1 -44 -3 -96 -7 -115 l-8 -35 316
|
||||
-315 315 -315 0 1160 -1 1160 -51 35 c-260 177 -226 567 62 704 82 39 209 48
|
||||
293 21 239 -78 354 -352 242 -575 -32 -63 -89 -125 -141 -156 l-44 -26 0 -811
|
||||
0 -812 315 315 c218 217 313 320 309 330 -14 35 -16 134 -4 190 26 122 111
|
||||
227 230 284 82 39 209 48 293 21 115 -38 214 -130 258 -242 19 -46 23 -78 24
|
||||
-153 0 -86 -3 -101 -32 -163 -40 -84 -118 -163 -198 -202 -49 -23 -77 -29
|
||||
-150 -33 -50 -2 -108 1 -130 7 l-40 11 -437 -438 -438 -437 0 -307 0 -308 998
|
||||
0 c981 0 998 1 1042 21 58 26 115 81 148 144 l27 50 0 995 0 995 -33 95 c-72
|
||||
209 -6 135 -1147 1278 -840 843 -1040 1037 -1082 1059 -64 31 -159 39 -220 19z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path fill-rule="nonzero" fill="#000" d="M9,16 L9,17 L7,17 L7,16 L1,16 L1,9 L-1,9 L8.00163907,0 L13,4.785368 L13,3 L15,3 L15,7.035368 L17,9 L15,9 L15,16 L9,16 Z M9,16 L9,13.5 C9.49775077,13.0022492 10.1813086,12.3186914 11.0506735,11.4493265 C11.1951058,11.4824829 11.3455072,11.5 11.5,11.5 C12.6045695,11.5 13.5,10.6045695 13.5,9.5 C13.5,8.3954305 12.6045695,7.5 11.5,7.5 C10.3954305,7.5 9.5,8.3954305 9.5,9.5 C9.5,9.65449279 9.5175171,9.80489423 9.55067348,9.94932652 L9,10.5 L9,7.73243561 C9.59780137,7.38662619 10,6.74028236 10,6 C10,4.8954305 9.1045695,4 8,4 C6.8954305,4 6,4.8954305 6,6 C6,6.74028236 6.40219863,7.38662619 7,7.73243561 L7,10.5 L6.44932652,9.94932652 C6.4824829,9.80489423 6.5,9.65449279 6.5,9.5 C6.5,8.3954305 5.6045695,7.5 4.5,7.5 C3.3954305,7.5 2.5,8.3954305 2.5,9.5 C2.5,10.6045695 3.3954305,11.5 4.5,11.5 C4.65352068,11.5 4.80300134,11.4827027 4.9465994,11.4499505 C5.81726201,12.3268973 6.50172888,13.0147433 7,13.5134884 L7,16 L9,16 Z M11.5,10 C11.2238576,10 11,9.77614237 11,9.5 C11,9.22385763 11.2238576,9 11.5,9 C11.7761424,9 12,9.22385763 12,9.5 C12,9.77614237 11.7761424,10 11.5,10 Z M4.5,10 C4.22385763,10 4,9.77614237 4,9.5 C4,9.22385763 4.22385763,9 4.5,9 C4.77614237,9 5,9.22385763 5,9.5 C5,9.77614237 4.77614237,10 4.5,10 Z M8,6.5 C7.72385763,6.5 7.5,6.27614237 7.5,6 C7.5,5.72385763 7.72385763,5.5 8,5.5 C8.27614237,5.5 8.5,5.72385763 8.5,6 C8.5,6.27614237 8.27614237,6.5 8,6.5 Z" id="house-small-tree"/></svg>
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 992 B |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 477 B |
Before Width: | Height: | Size: 7.7 KiB |
Before Width: | Height: | Size: 597 B |
Before Width: | Height: | Size: 686 B |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 824 B |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.8 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 852 B |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20231005.0"
|
||||
version = "20230911.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
40
script/core
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
# Helper to start Home Assistant Core inside the devcontainer
|
||||
|
||||
# Stop on errors
|
||||
@@ -11,35 +11,11 @@ if [ -z "${DEVCONTAINER}" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Default to installing (or upgrading to) dev branch
|
||||
coreURL="https://github.com/home-assistant/core.git"
|
||||
ref="dev"
|
||||
|
||||
while getopts "hr:s" opt; do
|
||||
case $opt in
|
||||
h) # Help
|
||||
echo "Usage: $0 [-h|-r <ref>|-s]"
|
||||
echo -n "Install and run core at the given ref, i.e. branch, tag, or commit. The dev branch is used if no option is specified."
|
||||
echo "The -s flag skips the install/upgrade, using whatever version is currently installed."
|
||||
exit 0
|
||||
;;
|
||||
r) # Git ref
|
||||
ref="${OPTARG}"
|
||||
;;
|
||||
s) # Skip (use current install)
|
||||
ref=""
|
||||
;;
|
||||
*)
|
||||
echo "Try $0 -h for help" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -n "$ref" ]; then
|
||||
echo "Installing Home Assistant core at ${ref}..."
|
||||
python3 -m pip install --user --upgrade --src "$HOME/src" \
|
||||
--editable "git+${coreURL}@${ref}#egg=homeassistant"
|
||||
if [ -z $(which hass) ]; then
|
||||
echo "Installing Home Asstant core from dev."
|
||||
python3 -m pip install --upgrade \
|
||||
colorlog \
|
||||
git+https://github.com/home-assistant/home-assistant.git@dev
|
||||
fi
|
||||
|
||||
if [ ! -d "${WD}/config" ]; then
|
||||
@@ -54,7 +30,7 @@ logger:
|
||||
homeassistant.components.frontend: debug
|
||||
" >> "${WD}/config/configuration.yaml"
|
||||
|
||||
if [ -n "${HASSIO}" ]; then
|
||||
if [ ! -z "${HASSIO}" ]; then
|
||||
echo "
|
||||
# frontend:
|
||||
# development_repo: ${WD}
|
||||
@@ -70,7 +46,7 @@ frontend:
|
||||
# development_repo: ${WD}" >> "${WD}/config/configuration.yaml"
|
||||
fi
|
||||
|
||||
if [ -n "${CODESPACES}" ]; then
|
||||
if [ ! -z "${CODESPACES}" ]; then
|
||||
echo "
|
||||
http:
|
||||
use_x_forwarded_for: true
|
||||
|
@@ -1,12 +1,19 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import "@material/mwc-button";
|
||||
import { genClientId } from "home-assistant-js-websocket";
|
||||
import { html, LitElement, nothing, PropertyValues } from "lit";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-alert";
|
||||
import "../components/ha-checkbox";
|
||||
import { computeInitialHaFormData } from "../components/ha-form/compute-initial-ha-form-data";
|
||||
import "../components/ha-form/ha-form";
|
||||
import "../components/ha-formfield";
|
||||
import "../components/ha-markdown";
|
||||
import { AuthProvider, autocompleteLoginFields } from "../data/auth";
|
||||
@@ -14,7 +21,7 @@ import {
|
||||
DataEntryFlowStep,
|
||||
DataEntryFlowStepForm,
|
||||
} from "../data/data_entry_flow";
|
||||
import "./ha-auth-form";
|
||||
import "./ha-password-manager-polyfill";
|
||||
|
||||
type State = "loading" | "error" | "step";
|
||||
|
||||
@@ -42,10 +49,6 @@ export class HaAuthFlow extends LitElement {
|
||||
|
||||
@state() private _storeToken = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
willUpdate(changedProps: PropertyValues) {
|
||||
super.willUpdate(changedProps);
|
||||
|
||||
@@ -76,17 +79,13 @@ 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-top: 10px;
|
||||
margin-left: -16px;
|
||||
}
|
||||
</style>
|
||||
<form>${this._renderForm()}</form>
|
||||
<ha-password-manager-polyfill
|
||||
.step=${this._step}
|
||||
.stepData=${this._stepData}
|
||||
@form-submitted=${this._handleSubmit}
|
||||
@value-changed=${this._stepDataChanged}
|
||||
></ha-password-manager-polyfill>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -129,6 +128,12 @@ export class HaAuthFlow extends LitElement {
|
||||
(form as any).focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
setTimeout(() => {
|
||||
this.renderRoot.querySelector(
|
||||
"ha-password-manager-polyfill"
|
||||
)!.boundingRect = this.getBoundingClientRect();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private _renderForm() {
|
||||
@@ -200,7 +205,7 @@ export class HaAuthFlow extends LitElement {
|
||||
></ha-markdown>
|
||||
`
|
||||
: nothing}
|
||||
<ha-auth-form
|
||||
<ha-form
|
||||
.data=${this._stepData}
|
||||
.schema=${autocompleteLoginFields(step.data_schema)}
|
||||
.error=${step.errors}
|
||||
@@ -208,7 +213,7 @@ export class HaAuthFlow extends LitElement {
|
||||
.computeLabel=${this._computeLabelCallback(step)}
|
||||
.computeError=${this._computeErrorCallback(step)}
|
||||
@value-changed=${this._stepDataChanged}
|
||||
></ha-auth-form>
|
||||
></ha-form>
|
||||
${this.clientId === genClientId() &&
|
||||
!["select_mfa_module", "mfa"].includes(step.step_id)
|
||||
? html`
|
||||
@@ -390,6 +395,20 @@ export class HaAuthFlow extends LitElement {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
.action {
|
||||
margin: 24px 0 8px;
|
||||
text-align: center;
|
||||
}
|
||||
/* Align with the rest of the form. */
|
||||
.store-token {
|
||||
margin-top: 10px;
|
||||
margin-left: -16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,77 +0,0 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { HaFormString } from "../components/ha-form/ha-form-string";
|
||||
import "../components/ha-icon-button";
|
||||
import "./ha-auth-textfield";
|
||||
|
||||
@customElement("ha-auth-form-string")
|
||||
export class HaAuthFormString extends HaFormString {
|
||||
protected createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
ha-auth-form-string {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
ha-auth-form-string[own-margin] {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
ha-auth-form-string ha-auth-textfield {
|
||||
display: block !important;
|
||||
}
|
||||
ha-auth-form-string ha-icon-button {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 12px;
|
||||
--mdc-icon-button-size: 24px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-auth-form-string ha-icon-button {
|
||||
inset-inline-start: initial;
|
||||
inset-inline-end: 12px;
|
||||
direction: var(--direction);
|
||||
}
|
||||
</style>
|
||||
<ha-auth-textfield
|
||||
.type=${
|
||||
!this.isPassword
|
||||
? this.stringType
|
||||
: this.unmaskedPassword
|
||||
? "text"
|
||||
: "password"
|
||||
}
|
||||
.label=${this.label}
|
||||
.value=${this.data || ""}
|
||||
.helper=${this.helper}
|
||||
helperPersistent
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.schema.required}
|
||||
.autoValidate=${this.schema.required}
|
||||
.name=${this.schema.name}
|
||||
.autocomplete=${this.schema.autocomplete}
|
||||
.suffix=${
|
||||
this.isPassword
|
||||
? // reserve some space for the icon.
|
||||
html`<div style="width: 24px"></div>`
|
||||
: this.schema.description?.suffix
|
||||
}
|
||||
.validationMessage=${this.schema.required ? "Required" : undefined}
|
||||
@input=${this._valueChanged}
|
||||
@change=${this._valueChanged}
|
||||
></ha-auth-textfield>
|
||||
${this.renderIcon()}
|
||||
</ha-auth-textfield>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-auth-form-string": HaAuthFormString;
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import { html } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { HaForm } from "../components/ha-form/ha-form";
|
||||
import "./ha-auth-form-string";
|
||||
|
||||
@customElement("ha-auth-form")
|
||||
export class HaAuthForm extends HaForm {
|
||||
protected fieldElementName(type: string): string {
|
||||
if (type === "string") {
|
||||
return `ha-auth-form-${type}`;
|
||||
}
|
||||
return super.fieldElementName(type);
|
||||
}
|
||||
|
||||
protected createRenderRoot() {
|
||||
// attach it as soon as possible to make sure we fetch all events.
|
||||
this.addValueChangedListener(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<style>
|
||||
ha-auth-form .root > * {
|
||||
display: block;
|
||||
}
|
||||
ha-auth-form .root > *:not([own-margin]):not(:last-child) {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
ha-auth-form ha-alert[own-margin] {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
${super.render()}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-auth-form": HaAuthForm;
|
||||
}
|
||||
}
|
@@ -1,254 +0,0 @@
|
||||
/* eslint-disable lit/value-after-constraints */
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import { floatingLabel } from "@material/mwc-floating-label/mwc-floating-label-directive";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { live } from "lit/directives/live";
|
||||
import { HaTextField } from "../components/ha-textfield";
|
||||
|
||||
@customElement("ha-auth-textfield")
|
||||
export class HaAuthTextField extends HaTextField {
|
||||
protected renderLabel(): TemplateResult | string {
|
||||
return !this.label
|
||||
? ""
|
||||
: html`
|
||||
<span
|
||||
.floatingLabelFoundation=${floatingLabel(
|
||||
this.label
|
||||
) as unknown as any}
|
||||
.id=${this.name}
|
||||
>${this.label}</span
|
||||
>
|
||||
`;
|
||||
}
|
||||
|
||||
protected renderInput(shouldRenderHelperText: boolean): TemplateResult {
|
||||
const minOrUndef = this.minLength === -1 ? undefined : this.minLength;
|
||||
const maxOrUndef = this.maxLength === -1 ? undefined : this.maxLength;
|
||||
const autocapitalizeOrUndef = this.autocapitalize
|
||||
? (this.autocapitalize as
|
||||
| "off"
|
||||
| "none"
|
||||
| "on"
|
||||
| "sentences"
|
||||
| "words"
|
||||
| "characters")
|
||||
: undefined;
|
||||
const showValidationMessage = this.validationMessage && !this.isUiValid;
|
||||
const ariaLabelledbyOrUndef = this.label ? this.name : undefined;
|
||||
const ariaControlsOrUndef = shouldRenderHelperText
|
||||
? "helper-text"
|
||||
: undefined;
|
||||
const ariaDescribedbyOrUndef =
|
||||
this.focused || this.helperPersistent || showValidationMessage
|
||||
? "helper-text"
|
||||
: undefined;
|
||||
// TODO: live() directive needs casting for lit-analyzer
|
||||
// https://github.com/runem/lit-analyzer/pull/91/files
|
||||
// TODO: lit-analyzer labels min/max as (number|string) instead of string
|
||||
return html` <input
|
||||
aria-labelledby=${ifDefined(ariaLabelledbyOrUndef)}
|
||||
aria-controls=${ifDefined(ariaControlsOrUndef)}
|
||||
aria-describedby=${ifDefined(ariaDescribedbyOrUndef)}
|
||||
class="mdc-text-field__input"
|
||||
type=${this.type}
|
||||
.value=${live(this.value) as unknown as string}
|
||||
?disabled=${this.disabled}
|
||||
placeholder=${this.placeholder}
|
||||
?required=${this.required}
|
||||
?readonly=${this.readOnly}
|
||||
minlength=${ifDefined(minOrUndef)}
|
||||
maxlength=${ifDefined(maxOrUndef)}
|
||||
pattern=${ifDefined(this.pattern ? this.pattern : undefined)}
|
||||
min=${ifDefined(this.min === "" ? undefined : (this.min as number))}
|
||||
max=${ifDefined(this.max === "" ? undefined : (this.max as number))}
|
||||
step=${ifDefined(this.step === null ? undefined : (this.step as number))}
|
||||
size=${ifDefined(this.size === null ? undefined : this.size)}
|
||||
name=${ifDefined(this.name === "" ? undefined : this.name)}
|
||||
inputmode=${ifDefined(this.inputMode)}
|
||||
autocapitalize=${ifDefined(autocapitalizeOrUndef)}
|
||||
@input=${this.handleInputChange}
|
||||
@focus=${this.onInputFocus}
|
||||
@blur=${this.onInputBlur}
|
||||
/>`;
|
||||
}
|
||||
|
||||
public render() {
|
||||
return html`
|
||||
<style>
|
||||
ha-auth-textfield {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
outline: none;
|
||||
}
|
||||
ha-auth-textfield:not([disabled]):hover
|
||||
:not(.mdc-text-field--invalid):not(.mdc-text-field--focused)
|
||||
mwc-notched-outline {
|
||||
--mdc-notched-outline-border-color: var(
|
||||
--mdc-text-field-outlined-hover-border-color,
|
||||
rgba(0, 0, 0, 0.87)
|
||||
);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field:not(.mdc-text-field--outlined) {
|
||||
background-color: var(--mdc-text-field-fill-color, whitesmoke);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--invalid
|
||||
mwc-notched-outline {
|
||||
--mdc-notched-outline-border-color: var(
|
||||
--mdc-text-field-error-color,
|
||||
var(--mdc-theme-error, #b00020)
|
||||
);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--invalid
|
||||
+ .mdc-text-field-helper-line
|
||||
.mdc-text-field-character-counter,
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--invalid
|
||||
.mdc-text-field__icon {
|
||||
color: var(
|
||||
--mdc-text-field-error-color,
|
||||
var(--mdc-theme-error, #b00020)
|
||||
);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field:not(.mdc-text-field--invalid):not(
|
||||
.mdc-text-field--focused
|
||||
)
|
||||
.mdc-floating-label,
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field:not(.mdc-text-field--invalid):not(
|
||||
.mdc-text-field--focused
|
||||
)
|
||||
.mdc-floating-label::after {
|
||||
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--focused
|
||||
mwc-notched-outline {
|
||||
--mdc-notched-outline-stroke-width: 2px;
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
|
||||
mwc-notched-outline {
|
||||
--mdc-notched-outline-border-color: var(
|
||||
--mdc-text-field-focused-label-color,
|
||||
var(--mdc-theme-primary, rgba(98, 0, 238, 0.87))
|
||||
);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
|
||||
.mdc-floating-label {
|
||||
color: #6200ee;
|
||||
color: var(--mdc-theme-primary, #6200ee);
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field
|
||||
.mdc-text-field__input {
|
||||
color: var(--mdc-text-field-ink-color, rgba(0, 0, 0, 0.87));
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field
|
||||
.mdc-text-field__input::placeholder {
|
||||
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
|
||||
}
|
||||
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field-helper-line
|
||||
.mdc-text-field-helper-text:not(
|
||||
.mdc-text-field-helper-text--validation-msg
|
||||
),
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field-helper-line:not(.mdc-text-field--invalid)
|
||||
.mdc-text-field-character-counter {
|
||||
color: var(--mdc-text-field-label-ink-color, rgba(0, 0, 0, 0.6));
|
||||
}
|
||||
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field:not(.mdc-text-field--outlined) {
|
||||
background-color: var(--mdc-text-field-disabled-fill-color, #fafafa);
|
||||
}
|
||||
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field.mdc-text-field--outlined
|
||||
mwc-notched-outline {
|
||||
--mdc-notched-outline-border-color: var(
|
||||
--mdc-text-field-outlined-disabled-border-color,
|
||||
rgba(0, 0, 0, 0.06)
|
||||
);
|
||||
}
|
||||
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field:not(.mdc-text-field--invalid):not(
|
||||
.mdc-text-field--focused
|
||||
)
|
||||
.mdc-floating-label,
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field:not(.mdc-text-field--invalid):not(
|
||||
.mdc-text-field--focused
|
||||
)
|
||||
.mdc-floating-label::after {
|
||||
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
|
||||
}
|
||||
|
||||
ha-auth-textfield[disabled] .mdc-text-field .mdc-text-field__input,
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field
|
||||
.mdc-text-field__input::placeholder {
|
||||
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
|
||||
}
|
||||
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field-helper-line
|
||||
.mdc-text-field-helper-text,
|
||||
ha-auth-textfield[disabled]
|
||||
.mdc-text-field-helper-line
|
||||
.mdc-text-field-character-counter {
|
||||
color: var(--mdc-text-field-disabled-ink-color, rgba(0, 0, 0, 0.38));
|
||||
}
|
||||
ha-auth-textfield:not([disabled])
|
||||
.mdc-text-field.mdc-text-field--focused:not(.mdc-text-field--invalid)
|
||||
.mdc-floating-label {
|
||||
color: var(--mdc-theme-primary, #6200ee);
|
||||
}
|
||||
ha-auth-textfield[no-spinner] input::-webkit-outer-spin-button,
|
||||
ha-auth-textfield[no-spinner] input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
ha-auth-textfield[no-spinner] input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
</style>
|
||||
${super.render()}
|
||||
`;
|
||||
}
|
||||
|
||||
protected createRenderRoot() {
|
||||
// add parent style to light dom
|
||||
const style = document.createElement("style");
|
||||
style.textContent = HaTextField.elementStyles as unknown as string;
|
||||
this.append(style);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-auth-textfield": HaAuthTextField;
|
||||
}
|
||||
}
|
@@ -1,7 +1,13 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import { html, LitElement, nothing, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import punycode from "punycode";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { applyThemesOnElement } from "../common/dom/apply_themes_on_element";
|
||||
import { extractSearchParamsObject } from "../common/url/search-params";
|
||||
import "../components/ha-alert";
|
||||
@@ -55,27 +61,13 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
|
||||
protected render() {
|
||||
if (this._error) {
|
||||
return html`
|
||||
<style>
|
||||
ha-authorize ha-alert {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
}
|
||||
</style>
|
||||
<ha-alert alert-type="error"
|
||||
>${this._error} ${this.redirectUri}</ha-alert
|
||||
>
|
||||
`;
|
||||
return html`<ha-alert alert-type="error"
|
||||
>${this._error} ${this.redirectUri}</ha-alert
|
||||
>`;
|
||||
}
|
||||
|
||||
if (!this._authProviders) {
|
||||
return html`
|
||||
<style>
|
||||
ha-authorize p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
<p>${this.localize("ui.panel.page-authorize.initializing")}</p>
|
||||
`;
|
||||
}
|
||||
@@ -87,25 +79,6 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
const app = this.clientId && this.clientId in appNames;
|
||||
|
||||
return html`
|
||||
<style>
|
||||
ha-pick-auth-provider {
|
||||
display: block;
|
||||
margin-top: 48px;
|
||||
}
|
||||
ha-auth-flow {
|
||||
display: block;
|
||||
margin-top: 24px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
</style>
|
||||
|
||||
${!this._ownInstance
|
||||
? html`<ha-alert .alertType=${app ? "info" : "warning"}>
|
||||
${app
|
||||
@@ -150,10 +123,6 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
`;
|
||||
}
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues) {
|
||||
super.firstUpdated(changedProps);
|
||||
|
||||
@@ -248,4 +217,25 @@ export class HaAuthorize extends litLocalizeLiteMixin(LitElement) {
|
||||
private async _handleAuthProviderPick(ev) {
|
||||
this._authProvider = ev.detail;
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
ha-pick-auth-provider {
|
||||
display: block;
|
||||
margin-top: 48px;
|
||||
}
|
||||
ha-auth-flow {
|
||||
display: block;
|
||||
margin-top: 24px;
|
||||
}
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin: 16px 0;
|
||||
}
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
172
src/auth/ha-password-manager-polyfill.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { HaFormSchema } from "../components/ha-form/types";
|
||||
import { autocompleteLoginFields } from "../data/auth";
|
||||
import type { DataEntryFlowStep } from "../data/data_entry_flow";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-password-manager-polyfill": HaPasswordManagerPolyfill;
|
||||
}
|
||||
interface HASSDomEvents {
|
||||
"form-submitted": undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const ENABLED_HANDLERS = [
|
||||
"homeassistant",
|
||||
"legacy_api_password",
|
||||
"command_line",
|
||||
];
|
||||
|
||||
@customElement("ha-password-manager-polyfill")
|
||||
export class HaPasswordManagerPolyfill extends LitElement {
|
||||
@property({ attribute: false }) public step?: DataEntryFlowStep;
|
||||
|
||||
@property({ attribute: false }) public stepData: any;
|
||||
|
||||
@property({ attribute: false }) public boundingRect?: DOMRect;
|
||||
|
||||
private _styleElement?: HTMLStyleElement;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._styleElement = document.createElement("style");
|
||||
this._styleElement.textContent = css`
|
||||
/* Polyfill form is sized and vertically aligned with true form, then positioned offscreen
|
||||
rather than hiding so it does not create a new stacking context */
|
||||
.password-manager-polyfill {
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
/* Excluding our wrapper, move any children back on screen, including anything injected that might not already be positioned */
|
||||
.password-manager-polyfill > *:not(.wrapper),
|
||||
.password-manager-polyfill > .wrapper > * {
|
||||
position: relative;
|
||||
left: 10000px;
|
||||
}
|
||||
/* Size and hide our polyfill fields */
|
||||
.password-manager-polyfill .underneath {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
border: 0;
|
||||
z-index: -1;
|
||||
height: 21px;
|
||||
/* Transparency is only needed to hide during paint or in case of misalignment,
|
||||
but LastPass will fail if it's 0, so we use 1% */
|
||||
opacity: 0.01;
|
||||
}
|
||||
.password-manager-polyfill input.underneath {
|
||||
height: 28px;
|
||||
margin-bottom: 30.5px;
|
||||
}
|
||||
/* Button position is not important, but size should not be zero */
|
||||
.password-manager-polyfill > input.underneath[type="submit"] {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
margin: 0 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Ensure injected elements will be on top */
|
||||
.password-manager-polyfill > *:not(.underneath, .wrapper),
|
||||
.password-manager-polyfill > .wrapper > *:not(.underneath) {
|
||||
isolation: isolate;
|
||||
z-index: auto;
|
||||
}
|
||||
`.toString();
|
||||
document.head.append(this._styleElement);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._styleElement?.remove();
|
||||
delete this._styleElement;
|
||||
}
|
||||
|
||||
protected createRenderRoot() {
|
||||
// Add under document body so the element isn't placed inside any shadow roots
|
||||
return document.body;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (
|
||||
this.step &&
|
||||
this.step.type === "form" &&
|
||||
this.step.step_id === "init" &&
|
||||
ENABLED_HANDLERS.includes(this.step.handler[0])
|
||||
) {
|
||||
return html`
|
||||
<form
|
||||
class="password-manager-polyfill"
|
||||
style=${styleMap({
|
||||
top: `${this.boundingRect?.y || 148}px`,
|
||||
left: `calc(50% - ${
|
||||
(this.boundingRect?.width || 360) / 2
|
||||
}px - 10000px)`,
|
||||
width: `${this.boundingRect?.width || 360}px`,
|
||||
})}
|
||||
action="/auth"
|
||||
method="post"
|
||||
@submit=${this._handleSubmit}
|
||||
>
|
||||
${autocompleteLoginFields(this.step.data_schema).map((input) =>
|
||||
this.render_input(input)
|
||||
)}
|
||||
<input
|
||||
type="submit"
|
||||
value="Login"
|
||||
class="underneath"
|
||||
tabindex="-2"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</form>
|
||||
`;
|
||||
}
|
||||
return nothing;
|
||||
}
|
||||
|
||||
private render_input(schema: HaFormSchema) {
|
||||
const inputType = schema.name.includes("password") ? "password" : "text";
|
||||
if (schema.type !== "string") {
|
||||
return "";
|
||||
}
|
||||
return html`
|
||||
<!-- Label is a sibling so it can be stacked underneath without affecting injections adjacent to input (e.g. LastPass) -->
|
||||
<label for=${schema.name} class="underneath" aria-hidden="true">
|
||||
${schema.name}
|
||||
</label>
|
||||
<!-- LastPass fails if the input is hidden directly, so we trick it and hide a wrapper instead -->
|
||||
<div class="wrapper" aria-hidden="true">
|
||||
<!-- LastPass fails with tabindex of -1, so we trick with -2 -->
|
||||
<input
|
||||
class="underneath"
|
||||
tabindex="-2"
|
||||
.id=${schema.name}
|
||||
.name=${schema.name}
|
||||
.type=${inputType}
|
||||
.value=${this.stepData[schema.name] || ""}
|
||||
.autocomplete=${schema.autocomplete}
|
||||
@input=${this._valueChanged}
|
||||
@change=${this._valueChanged}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleSubmit(ev: SubmitEvent) {
|
||||
ev.preventDefault();
|
||||
fireEvent(this, "form-submitted");
|
||||
}
|
||||
|
||||
private _valueChanged(ev: Event) {
|
||||
const target = ev.target as HTMLInputElement;
|
||||
this.stepData = { ...this.stepData, [target.id]: target.value };
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.stepData,
|
||||
});
|
||||
}
|
||||
}
|
@@ -15,7 +15,6 @@ import {
|
||||
mdiCalendarClock,
|
||||
mdiCarCoolantLevel,
|
||||
mdiCash,
|
||||
mdiChatSleep,
|
||||
mdiClock,
|
||||
mdiCloudUpload,
|
||||
mdiCog,
|
||||
@@ -32,6 +31,7 @@ import {
|
||||
mdiGauge,
|
||||
mdiGoogleAssistant,
|
||||
mdiGoogleCirclesCommunities,
|
||||
mdiHomeAssistant,
|
||||
mdiHomeAutomation,
|
||||
mdiImage,
|
||||
mdiImageFilterFrames,
|
||||
@@ -70,8 +70,6 @@ import {
|
||||
mdiWifi,
|
||||
} from "@mdi/js";
|
||||
|
||||
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
||||
|
||||
// Constants should be alphabetically sorted by name.
|
||||
// Arrays with values should be alphabetically sorted if order doesn't matter.
|
||||
// Each constant should have a description what it is supposed to be used for.
|
||||
@@ -125,7 +123,6 @@ export const FIXED_DOMAIN_ICONS = {
|
||||
tts: mdiSpeakerMessage,
|
||||
updater: mdiCloudUpload,
|
||||
vacuum: mdiRobotVacuum,
|
||||
wake_word: mdiChatSleep,
|
||||
zone: mdiMapMarkerRadius,
|
||||
};
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { ThemeVars } from "../../data/ws-themes";
|
||||
import { darkStyles, derivedStyles } from "../../resources/styles-data";
|
||||
import { darkStyles, derivedStyles } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import {
|
||||
hex2rgb,
|
||||
|
@@ -193,7 +193,6 @@ export const computeStateDisplayFromEntityAttributes = (
|
||||
"scene",
|
||||
"stt",
|
||||
"tts",
|
||||
"wake_word",
|
||||
].includes(domain) ||
|
||||
(domain === "sensor" && attributes.device_class === "timestamp")
|
||||
) {
|
||||
|
@@ -7,7 +7,7 @@ export const computeStateNameFromEntityAttributes = (
|
||||
): string =>
|
||||
attributes.friendly_name === undefined
|
||||
? computeObjectId(entityId).replace(/_/g, " ")
|
||||
: (attributes.friendly_name ?? "").toString();
|
||||
: attributes.friendly_name || "";
|
||||
|
||||
export const computeStateName = (stateObj: HassEntity): string =>
|
||||
computeStateNameFromEntityAttributes(stateObj.entity_id, stateObj.attributes);
|
||||
|
@@ -53,14 +53,13 @@ export class HaChartBase extends LitElement {
|
||||
@state() private _hiddenDatasets: Set<number> = new Set();
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._releaseCanvas();
|
||||
super.disconnectedCallback();
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this.hasUpdated) {
|
||||
this._releaseCanvas();
|
||||
this._setupChart();
|
||||
}
|
||||
}
|
||||
@@ -111,7 +110,7 @@ export class HaChartBase extends LitElement {
|
||||
return;
|
||||
}
|
||||
if (changedProps.has("plugins") || changedProps.has("chartType")) {
|
||||
this._releaseCanvas();
|
||||
this.chart.destroy();
|
||||
this._setupChart();
|
||||
return;
|
||||
}
|
||||
|
@@ -35,8 +35,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public showNames = true;
|
||||
|
||||
@property({ type: Boolean }) public clickForMoreInfo = true;
|
||||
|
||||
@property({ attribute: false }) public startTime!: Date;
|
||||
|
||||
@property({ attribute: false }) public endTime!: Date;
|
||||
@@ -47,8 +45,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
|
||||
@state() private _chartData?: ChartData<"line">;
|
||||
|
||||
@state() private _entityIds: string[] = [];
|
||||
|
||||
@state() private _chartOptions?: ChartOptions;
|
||||
|
||||
@state() private _yWidth = 0;
|
||||
@@ -175,28 +171,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
onClick: (e: any) => {
|
||||
if (!this.clickForMoreInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chart = e.chart;
|
||||
|
||||
const points = chart.getElementsAtEventForMode(
|
||||
e,
|
||||
"nearest",
|
||||
{ intersect: true },
|
||||
true
|
||||
);
|
||||
|
||||
if (points.length) {
|
||||
const firstPoint = points[0];
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: this._entityIds[firstPoint.datasetIndex],
|
||||
});
|
||||
chart.canvas.dispatchEvent(new Event("mouseout")); // to hide tooltip
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
if (
|
||||
@@ -217,7 +191,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const entityStates = this.data;
|
||||
const datasets: ChartDataset<"line">[] = [];
|
||||
const entityIds: string[] = [];
|
||||
if (entityStates.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -269,7 +242,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
pointRadius: 0,
|
||||
data: [],
|
||||
});
|
||||
entityIds.push(states.entity_id);
|
||||
};
|
||||
|
||||
if (
|
||||
@@ -521,7 +493,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
this._chartData = {
|
||||
datasets,
|
||||
};
|
||||
this._entityIds = entityIds;
|
||||
}
|
||||
}
|
||||
customElements.define("state-history-chart-line", StateHistoryChartLine);
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import type { ChartData, ChartDataset, ChartOptions } from "chart.js";
|
||||
import { getRelativePosition } from "chart.js/helpers";
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
@@ -33,8 +32,6 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public showNames = true;
|
||||
|
||||
@property({ type: Boolean }) public clickForMoreInfo = true;
|
||||
|
||||
@property({ type: Boolean }) public chunked = false;
|
||||
|
||||
@property({ attribute: false }) public startTime!: Date;
|
||||
@@ -223,23 +220,6 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
},
|
||||
// @ts-expect-error
|
||||
locale: numberFormatToLocale(this.hass.locale),
|
||||
onClick: (e: any) => {
|
||||
if (!this.clickForMoreInfo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chart = e.chart;
|
||||
const canvasPosition = getRelativePosition(e, chart);
|
||||
|
||||
const index = Math.abs(
|
||||
chart.scales.y.getValueForPixel(canvasPosition.y)
|
||||
);
|
||||
fireEvent(this, "hass-more-info", {
|
||||
// @ts-ignore
|
||||
entityId: this._chartData?.datasets[index]?.label,
|
||||
});
|
||||
chart.canvas.dispatchEvent(new Event("mouseout")); // to hide tooltip
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -69,8 +69,6 @@ export class StateHistoryCharts extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public showNames = true;
|
||||
|
||||
@property({ type: Boolean }) public clickForMoreInfo = true;
|
||||
|
||||
@property({ type: Boolean }) public isLoadingData = false;
|
||||
|
||||
@state() private _computedStartTime!: Date;
|
||||
@@ -183,7 +181,6 @@ export class StateHistoryCharts extends LitElement {
|
||||
.paddingYAxis=${this._maxYWidth}
|
||||
.names=${this.names}
|
||||
.chartIndex=${index}
|
||||
.clickForMoreInfo=${this.clickForMoreInfo}
|
||||
@y-width-changed=${this._yWidthChanged}
|
||||
></state-history-chart-line>
|
||||
</div> `;
|
||||
@@ -200,7 +197,6 @@ export class StateHistoryCharts extends LitElement {
|
||||
.chunked=${this.virtualize}
|
||||
.paddingYAxis=${this._maxYWidth}
|
||||
.chartIndex=${index}
|
||||
.clickForMoreInfo=${this.clickForMoreInfo}
|
||||
@y-width-changed=${this._yWidthChanged}
|
||||
></state-history-chart-timeline>
|
||||
</div> `;
|
||||
|
@@ -131,15 +131,11 @@ export class StateBadge extends LitElement {
|
||||
if (this.hass) {
|
||||
imageUrl = this.hass.hassUrl(imageUrl);
|
||||
}
|
||||
const domain = computeDomain(stateObj.entity_id);
|
||||
if (domain === "camera") {
|
||||
if (computeDomain(stateObj.entity_id) === "camera") {
|
||||
imageUrl = cameraUrlWithWidthHeight(imageUrl, 80, 80);
|
||||
}
|
||||
hostStyle.backgroundImage = `url(${imageUrl})`;
|
||||
this._showIcon = false;
|
||||
if (domain === "update") {
|
||||
hostStyle.borderRadius = "0";
|
||||
}
|
||||
} else if (this.color) {
|
||||
// Externally provided overriding color wins over state color
|
||||
iconStyle.color = this.color;
|
||||
|
@@ -41,6 +41,8 @@ export class HaClickableListItem extends HaListItem {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: var(--mdc-list-side-padding, 20px);
|
||||
padding-right: var(--mdc-list-side-padding, 20px);
|
||||
overflow: hidden;
|
||||
}
|
||||
`,
|
||||
|
@@ -1,13 +1,11 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import { mdiEye, mdiEyeOff } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
@@ -34,7 +32,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@state() protected unmaskedPassword = false;
|
||||
@state() private _unmaskedPassword = false;
|
||||
|
||||
@query("ha-textfield") private _input?: HaTextField;
|
||||
|
||||
@@ -45,11 +43,14 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const isPassword = MASKED_FIELDS.some((field) =>
|
||||
this.schema.name.includes(field)
|
||||
);
|
||||
return html`
|
||||
<ha-textfield
|
||||
.type=${!this.isPassword
|
||||
? this.stringType
|
||||
: this.unmaskedPassword
|
||||
.type=${!isPassword
|
||||
? this._stringType
|
||||
: this._unmaskedPassword
|
||||
? "text"
|
||||
: "password"}
|
||||
.label=${this.label}
|
||||
@@ -61,7 +62,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
.autoValidate=${this.schema.required}
|
||||
.name=${this.schema.name}
|
||||
.autocomplete=${this.schema.autocomplete}
|
||||
.suffix=${this.isPassword
|
||||
.suffix=${isPassword
|
||||
? // reserve some space for the icon.
|
||||
html`<div style="width: 24px"></div>`
|
||||
: this.schema.description?.suffix}
|
||||
@@ -69,19 +70,14 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
@input=${this._valueChanged}
|
||||
@change=${this._valueChanged}
|
||||
></ha-textfield>
|
||||
${this.renderIcon()}
|
||||
`;
|
||||
}
|
||||
|
||||
protected renderIcon() {
|
||||
if (!this.isPassword) return nothing;
|
||||
return html`
|
||||
<ha-icon-button
|
||||
toggles
|
||||
.label=${`${this.unmaskedPassword ? "Hide" : "Show"} password`}
|
||||
@click=${this.toggleUnmaskedPassword}
|
||||
.path=${this.unmaskedPassword ? mdiEyeOff : mdiEye}
|
||||
></ha-icon-button>
|
||||
${isPassword
|
||||
? html`<ha-icon-button
|
||||
toggles
|
||||
.label=${`${this._unmaskedPassword ? "Hide" : "Show"} password`}
|
||||
@click=${this._toggleUnmaskedPassword}
|
||||
.path=${this._unmaskedPassword ? mdiEyeOff : mdiEye}
|
||||
></ha-icon-button>`
|
||||
: ""}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -91,11 +87,11 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
}
|
||||
}
|
||||
|
||||
protected toggleUnmaskedPassword(): void {
|
||||
this.unmaskedPassword = !this.unmaskedPassword;
|
||||
private _toggleUnmaskedPassword(): void {
|
||||
this._unmaskedPassword = !this._unmaskedPassword;
|
||||
}
|
||||
|
||||
protected _valueChanged(ev: Event): void {
|
||||
private _valueChanged(ev: Event): void {
|
||||
let value: string | undefined = (ev.target as HaTextField).value;
|
||||
if (this.data === value) {
|
||||
return;
|
||||
@@ -108,7 +104,7 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
});
|
||||
}
|
||||
|
||||
protected get stringType(): string {
|
||||
private get _stringType(): string {
|
||||
if (this.schema.format) {
|
||||
if (["email", "url"].includes(this.schema.format)) {
|
||||
return this.schema.format;
|
||||
@@ -120,10 +116,6 @@ export class HaFormString extends LitElement implements HaFormElement {
|
||||
return "text";
|
||||
}
|
||||
|
||||
protected get isPassword(): boolean {
|
||||
return MASKED_FIELDS.some((field) => this.schema.name.includes(field));
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return css`
|
||||
:host {
|
||||
|
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable lit/prefer-static-styles */
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
@@ -136,7 +135,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
.required=${item.required || false}
|
||||
.context=${this._generateContext(item)}
|
||||
></ha-selector>`
|
||||
: dynamicElement(this.fieldElementName(item.type), {
|
||||
: dynamicElement(`ha-form-${item.type}`, {
|
||||
schema: item,
|
||||
data: getValue(this.data, item),
|
||||
label: this._computeLabel(item, this.data),
|
||||
@@ -153,10 +152,6 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
`;
|
||||
}
|
||||
|
||||
protected fieldElementName(type: string): string {
|
||||
return `ha-form-${type}`;
|
||||
}
|
||||
|
||||
private _generateContext(
|
||||
schema: HaFormSchema
|
||||
): Record<string, any> | undefined {
|
||||
@@ -174,30 +169,19 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
protected createRenderRoot() {
|
||||
const root = super.createRenderRoot();
|
||||
// attach it as soon as possible to make sure we fetch all events.
|
||||
this.addValueChangedListener(root);
|
||||
return root;
|
||||
}
|
||||
|
||||
protected addValueChangedListener(element: Element | ShadowRoot) {
|
||||
element.addEventListener("value-changed", (ev) => {
|
||||
root.addEventListener("value-changed", (ev) => {
|
||||
ev.stopPropagation();
|
||||
const schema = (ev.target as HaFormElement).schema as HaFormSchema;
|
||||
|
||||
if (ev.target === this) return;
|
||||
|
||||
const newValue = !schema.name
|
||||
? ev.detail.value
|
||||
: { [schema.name]: ev.detail.value };
|
||||
|
||||
this.data = {
|
||||
...this.data,
|
||||
...newValue,
|
||||
};
|
||||
|
||||
fireEvent(this, "value-changed", {
|
||||
value: this.data,
|
||||
value: { ...this.data, ...newValue },
|
||||
});
|
||||
});
|
||||
return root;
|
||||
}
|
||||
|
||||
private _computeLabel(schema: HaFormSchema, data: HaFormDataContainer) {
|
||||
|
@@ -7,25 +7,22 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
@customElement("ha-formfield")
|
||||
export class HaFormfield extends FormfieldBase {
|
||||
protected _labelClick() {
|
||||
const input = this.input as HTMLInputElement | undefined;
|
||||
if (!input) return;
|
||||
|
||||
input.focus();
|
||||
if (input.disabled) {
|
||||
return;
|
||||
}
|
||||
switch (input.tagName) {
|
||||
case "HA-CHECKBOX":
|
||||
input.checked = !input.checked;
|
||||
fireEvent(input, "change");
|
||||
break;
|
||||
case "HA-RADIO":
|
||||
input.checked = true;
|
||||
fireEvent(input, "change");
|
||||
break;
|
||||
default:
|
||||
input.click();
|
||||
break;
|
||||
const input = this.input;
|
||||
if (input) {
|
||||
input.focus();
|
||||
switch (input.tagName) {
|
||||
case "HA-CHECKBOX":
|
||||
case "HA-RADIO":
|
||||
if ((input as any).disabled) {
|
||||
break;
|
||||
}
|
||||
(input as any).checked = !(input as any).checked;
|
||||
fireEvent(input, "change");
|
||||
break;
|
||||
default:
|
||||
input.click();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -12,8 +12,19 @@ export class HaIconButtonArrowNext extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@state() private _icon =
|
||||
document.dir === "ltr" ? mdiArrowRight : mdiArrowLeft;
|
||||
@state() private _icon = mdiArrowRight;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this._icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? mdiArrowRight
|
||||
: mdiArrowLeft;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
|
@@ -12,8 +12,19 @@ export class HaIconButtonArrowPrev extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@state() private _icon =
|
||||
document.dir === "ltr" ? mdiArrowLeft : mdiArrowRight;
|
||||
@state() private _icon = mdiArrowLeft;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this._icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? mdiArrowLeft
|
||||
: mdiArrowRight;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
|
@@ -12,8 +12,19 @@ export class HaIconButtonNext extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@state() private _icon =
|
||||
document.dir === "ltr" ? mdiChevronRight : mdiChevronLeft;
|
||||
@state() private _icon = mdiChevronRight;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this._icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? mdiChevronRight
|
||||
: mdiChevronLeft;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
|
@@ -12,8 +12,19 @@ export class HaIconButtonPrev extends LitElement {
|
||||
|
||||
@property() public label?: string;
|
||||
|
||||
@state() private _icon =
|
||||
document.dir === "ltr" ? mdiChevronLeft : mdiChevronRight;
|
||||
@state() private _icon = mdiChevronLeft;
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this._icon =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? mdiChevronLeft
|
||||
: mdiChevronRight;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
|
@@ -1,11 +1,20 @@
|
||||
import { mdiChevronLeft, mdiChevronRight } from "@mdi/js";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { HaSvgIcon } from "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-icon-next")
|
||||
export class HaIconNext extends HaSvgIcon {
|
||||
@property() public override path =
|
||||
document.dir === "ltr" ? mdiChevronRight : mdiChevronLeft;
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.path =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? mdiChevronRight
|
||||
: mdiChevronLeft;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -1,11 +1,20 @@
|
||||
import { mdiChevronLeft, mdiChevronRight } from "@mdi/js";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement } from "lit/decorators";
|
||||
import { HaSvgIcon } from "./ha-svg-icon";
|
||||
|
||||
@customElement("ha-icon-prev")
|
||||
export class HaIconPrev extends HaSvgIcon {
|
||||
@property() public override path =
|
||||
document.dir === "ltr" ? mdiChevronLeft : mdiChevronRight;
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
// wait to check for direction since otherwise direction is wrong even though top level is RTL
|
||||
setTimeout(() => {
|
||||
this.path =
|
||||
window.getComputedStyle(this).direction === "ltr"
|
||||
? mdiChevronLeft
|
||||
: mdiChevronRight;
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
@@ -124,17 +124,6 @@ export class HaIcon extends LitElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (iconName === "home-assistant") {
|
||||
const icon = (await import("../resources/home-assistant-logo-svg"))
|
||||
.mdiHomeAssistant;
|
||||
|
||||
if (this.icon === requestedIcon) {
|
||||
this._path = icon;
|
||||
}
|
||||
cachedIcons[iconName] = icon;
|
||||
return;
|
||||
}
|
||||
|
||||
let databaseIcon: string | undefined;
|
||||
try {
|
||||
databaseIcon = await getIcon(iconName);
|
||||
|
@@ -29,6 +29,11 @@ export class HaOutlinedIconButton extends IconButton {
|
||||
--md-ripple-hover-opacity: 0;
|
||||
--md-ripple-pressed-opacity: 0;
|
||||
}
|
||||
.outlined {
|
||||
/* Fix md-outlined-icon-button padding and margin for iOS */
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@@ -26,7 +26,6 @@ export class HaLocationSelector extends LitElement {
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
<p>${this.label ? this.label : ""}</p>
|
||||
<ha-locations-editor
|
||||
class="flex"
|
||||
.hass=${this.hass}
|
||||
@@ -79,13 +78,10 @@ export class HaLocationSelector extends LitElement {
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-locations-editor {
|
||||
:host {
|
||||
display: block;
|
||||
height: 400px;
|
||||
}
|
||||
p {
|
||||
margin-top: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@@ -237,6 +237,8 @@ export class HaTargetPicker extends LitElement {
|
||||
: html`<span role="gridcell">
|
||||
<ha-icon-button
|
||||
class="expand-btn mdc-chip__icon mdc-chip__icon--trailing"
|
||||
tabindex="-1"
|
||||
role="button"
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.target-picker.expand"
|
||||
)}
|
||||
@@ -255,6 +257,8 @@ export class HaTargetPicker extends LitElement {
|
||||
<span role="gridcell">
|
||||
<ha-icon-button
|
||||
class="mdc-chip__icon mdc-chip__icon--trailing"
|
||||
tabindex="-1"
|
||||
role="button"
|
||||
.label=${this.hass.localize("ui.components.target-picker.remove")}
|
||||
.path=${mdiClose}
|
||||
hideTooltip
|
||||
|
@@ -15,7 +15,7 @@ class HaEntityMarker extends LitElement {
|
||||
protected render() {
|
||||
return html`
|
||||
<div
|
||||
class="marker ${this.entityPicture ? "picture" : ""}"
|
||||
class="marker"
|
||||
style=${styleMap({ "border-color": this.entityColor })}
|
||||
@click=${this._badgeTap}
|
||||
>
|
||||
@@ -45,6 +45,7 @@ class HaEntityMarker extends LitElement {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: var(--ha-marker-font-size, 1.5em);
|
||||
@@ -53,9 +54,6 @@ class HaEntityMarker extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
background-color: var(--card-background-color);
|
||||
}
|
||||
.marker.picture {
|
||||
overflow: hidden;
|
||||
}
|
||||
.entity-picture {
|
||||
background-size: cover;
|
||||
height: 100%;
|
||||
|
@@ -37,9 +37,6 @@ export interface HaMapPaths {
|
||||
export interface HaMapEntity {
|
||||
entity_id: string;
|
||||
color: string;
|
||||
label_mode?: "name" | "state";
|
||||
name?: string;
|
||||
focus?: boolean;
|
||||
}
|
||||
|
||||
@customElement("ha-map")
|
||||
@@ -74,8 +71,6 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
private _mapItems: Array<Marker | Circle> = [];
|
||||
|
||||
private _mapFocusItems: Array<Marker | Circle> = [];
|
||||
|
||||
private _mapZones: Array<Marker | Circle> = [];
|
||||
|
||||
private _mapPaths: Array<Polyline | CircleMarker> = [];
|
||||
@@ -173,7 +168,7 @@ export class HaMap extends ReactiveElement {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._mapFocusItems.length && !this.layers?.length) {
|
||||
if (!this._mapItems.length && !this.layers?.length) {
|
||||
this.leafletMap.setView(
|
||||
new this.Leaflet.LatLng(
|
||||
this.hass.config.latitude,
|
||||
@@ -185,9 +180,7 @@ export class HaMap extends ReactiveElement {
|
||||
}
|
||||
|
||||
let bounds = this.Leaflet.latLngBounds(
|
||||
this._mapFocusItems
|
||||
? this._mapFocusItems.map((item) => item.getLatLng())
|
||||
: []
|
||||
this._mapItems ? this._mapItems.map((item) => item.getLatLng()) : []
|
||||
);
|
||||
|
||||
if (this.fitZones) {
|
||||
@@ -331,7 +324,6 @@ export class HaMap extends ReactiveElement {
|
||||
if (this._mapItems.length) {
|
||||
this._mapItems.forEach((marker) => marker.remove());
|
||||
this._mapItems = [];
|
||||
this._mapFocusItems = [];
|
||||
}
|
||||
|
||||
if (this._mapZones.length) {
|
||||
@@ -361,8 +353,7 @@ export class HaMap extends ReactiveElement {
|
||||
if (!stateObj) {
|
||||
continue;
|
||||
}
|
||||
const customTitle = typeof entity !== "string" ? entity.name : undefined;
|
||||
const title = customTitle ?? computeStateName(stateObj);
|
||||
const title = computeStateName(stateObj);
|
||||
const {
|
||||
latitude,
|
||||
longitude,
|
||||
@@ -422,20 +413,17 @@ export class HaMap extends ReactiveElement {
|
||||
|
||||
// DRAW ENTITY
|
||||
// create icon
|
||||
const entityName =
|
||||
typeof entity !== "string" && entity.label_mode === "state"
|
||||
? this.hass.formatEntityState(stateObj)
|
||||
: customTitle ??
|
||||
title
|
||||
.split(" ")
|
||||
.map((part) => part[0])
|
||||
.join("")
|
||||
.substr(0, 3);
|
||||
const entityName = title
|
||||
.split(" ")
|
||||
.map((part) => part[0])
|
||||
.join("")
|
||||
.substr(0, 3);
|
||||
|
||||
// create marker with the icon
|
||||
const marker = Leaflet.marker([latitude, longitude], {
|
||||
icon: Leaflet.divIcon({
|
||||
html: `
|
||||
this._mapItems.push(
|
||||
Leaflet.marker([latitude, longitude], {
|
||||
icon: Leaflet.divIcon({
|
||||
html: `
|
||||
<ha-entity-marker
|
||||
entity-id="${getEntityId(entity)}"
|
||||
entity-name="${entityName}"
|
||||
@@ -449,15 +437,12 @@ export class HaMap extends ReactiveElement {
|
||||
}
|
||||
></ha-entity-marker>
|
||||
`,
|
||||
iconSize: [48, 48],
|
||||
className: "",
|
||||
}),
|
||||
title: title,
|
||||
});
|
||||
this._mapItems.push(marker);
|
||||
if (typeof entity === "string" || entity.focus !== false) {
|
||||
this._mapFocusItems.push(marker);
|
||||
}
|
||||
iconSize: [48, 48],
|
||||
className: "",
|
||||
}),
|
||||
title: computeStateName(stateObj),
|
||||
})
|
||||
);
|
||||
|
||||
// create circle around if entity has accuracy
|
||||
if (gpsAccuracy) {
|
||||
|
@@ -14,8 +14,6 @@ export interface AssistPipeline {
|
||||
tts_engine: string | null;
|
||||
tts_language: string | null;
|
||||
tts_voice: string | null;
|
||||
wake_word_entity: string | null;
|
||||
wake_word_id: string | null;
|
||||
}
|
||||
|
||||
export interface AssistPipelineMutableParams {
|
||||
@@ -28,8 +26,6 @@ export interface AssistPipelineMutableParams {
|
||||
tts_engine: string | null;
|
||||
tts_language: string | null;
|
||||
tts_voice: string | null;
|
||||
wake_word_entity: string | null;
|
||||
wake_word_id: string | null;
|
||||
}
|
||||
|
||||
export interface assistRunListing {
|
||||
@@ -65,19 +61,6 @@ interface PipelineErrorEvent extends PipelineEventBase {
|
||||
};
|
||||
}
|
||||
|
||||
interface PipelineWakeWordStartEvent extends PipelineEventBase {
|
||||
type: "wake_word-start";
|
||||
data: {
|
||||
engine: string;
|
||||
metadata: SpeechMetadata;
|
||||
};
|
||||
}
|
||||
|
||||
interface PipelineWakeWordEndEvent extends PipelineEventBase {
|
||||
type: "wake_word-end";
|
||||
data: { wake_word_output: { ww_id: string; timestamp: number } };
|
||||
}
|
||||
|
||||
interface PipelineSTTStartEvent extends PipelineEventBase {
|
||||
type: "stt-start";
|
||||
data: {
|
||||
@@ -127,8 +110,6 @@ export type PipelineRunEvent =
|
||||
| PipelineRunStartEvent
|
||||
| PipelineRunEndEvent
|
||||
| PipelineErrorEvent
|
||||
| PipelineWakeWordStartEvent
|
||||
| PipelineWakeWordEndEvent
|
||||
| PipelineSTTStartEvent
|
||||
| PipelineSTTEndEvent
|
||||
| PipelineIntentStartEvent
|
||||
@@ -145,14 +126,6 @@ export type PipelineRunOptions = (
|
||||
start_stage: "stt";
|
||||
input: { sample_rate: number };
|
||||
}
|
||||
| {
|
||||
start_stage: "wake_word";
|
||||
input: {
|
||||
sample_rate: number;
|
||||
timeout?: number;
|
||||
audio_seconds_to_buffer?: number;
|
||||
};
|
||||
}
|
||||
) & {
|
||||
end_stage: "stt" | "intent" | "tts";
|
||||
pipeline?: string;
|
||||
@@ -162,11 +135,9 @@ export type PipelineRunOptions = (
|
||||
export interface PipelineRun {
|
||||
init_options?: PipelineRunOptions;
|
||||
events: PipelineRunEvent[];
|
||||
stage: "ready" | "wake_word" | "stt" | "intent" | "tts" | "done" | "error";
|
||||
stage: "ready" | "stt" | "intent" | "tts" | "done" | "error";
|
||||
run: PipelineRunStartEvent["data"];
|
||||
error?: PipelineErrorEvent["data"];
|
||||
wake_word?: PipelineWakeWordStartEvent["data"] &
|
||||
Partial<PipelineWakeWordEndEvent["data"]> & { done: boolean };
|
||||
stt?: PipelineSTTStartEvent["data"] &
|
||||
Partial<PipelineSTTEndEvent["data"]> & { done: boolean };
|
||||
intent?: PipelineIntentStartEvent["data"] &
|
||||
@@ -196,18 +167,7 @@ export const processEvent = (
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (event.type === "wake_word-start") {
|
||||
run = {
|
||||
...run,
|
||||
stage: "wake_word",
|
||||
wake_word: { ...event.data, done: false },
|
||||
};
|
||||
} else if (event.type === "wake_word-end") {
|
||||
run = {
|
||||
...run,
|
||||
wake_word: { ...run.wake_word!, ...event.data, done: true },
|
||||
};
|
||||
} else if (event.type === "stt-start") {
|
||||
if (event.type === "stt-start") {
|
||||
run = {
|
||||
...run,
|
||||
stage: "stt",
|
||||
|
@@ -19,10 +19,6 @@ import {
|
||||
} from "./device_automation";
|
||||
import { EntityRegistryEntry } from "./entity_registry";
|
||||
import { FrontendLocaleData } from "./translation";
|
||||
import {
|
||||
formatListWithAnds,
|
||||
formatListWithOrs,
|
||||
} from "../common/string/format-list";
|
||||
|
||||
const triggerTranslationBaseKey =
|
||||
"ui.panel.config.automation.editor.triggers.type";
|
||||
@@ -108,6 +104,11 @@ const tryDescribeTrigger = (
|
||||
return trigger.alias;
|
||||
}
|
||||
|
||||
const disjunctionFormatter = new Intl.ListFormat("en", {
|
||||
style: "long",
|
||||
type: "disjunction",
|
||||
});
|
||||
|
||||
// Event Trigger
|
||||
if (trigger.platform === "event" && trigger.event_type) {
|
||||
const eventTypes: string[] = [];
|
||||
@@ -120,7 +121,7 @@ const tryDescribeTrigger = (
|
||||
eventTypes.push(trigger.event_type);
|
||||
}
|
||||
|
||||
const eventTypesString = formatListWithOrs(hass.locale, eventTypes);
|
||||
const eventTypesString = disjunctionFormatter.format(eventTypes);
|
||||
return hass.localize(
|
||||
`${triggerTranslationBaseKey}.event.description.full`,
|
||||
{ eventTypes: eventTypesString }
|
||||
@@ -138,54 +139,41 @@ const tryDescribeTrigger = (
|
||||
|
||||
// Numeric State Trigger
|
||||
if (trigger.platform === "numeric_state" && trigger.entity_id) {
|
||||
let base = "When";
|
||||
const stateObj = hass.states[trigger.entity_id];
|
||||
const entity = stateObj ? computeStateName(stateObj) : trigger.entity_id;
|
||||
|
||||
const attribute = trigger.attribute
|
||||
? computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
trigger.attribute
|
||||
)
|
||||
: undefined;
|
||||
if (trigger.attribute) {
|
||||
base += ` ${computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
trigger.attribute
|
||||
)} from`;
|
||||
}
|
||||
|
||||
const duration = trigger.for ? describeDuration(trigger.for) : undefined;
|
||||
base += ` ${entity} is`;
|
||||
|
||||
if (trigger.above && trigger.below) {
|
||||
return hass.localize(
|
||||
`${triggerTranslationBaseKey}.numeric_state.description.above-below`,
|
||||
{
|
||||
attribute: attribute,
|
||||
entity: entity,
|
||||
above: trigger.above,
|
||||
below: trigger.below,
|
||||
duration: duration,
|
||||
}
|
||||
);
|
||||
if (trigger.above !== undefined) {
|
||||
base += ` above ${trigger.above}`;
|
||||
}
|
||||
if (trigger.above) {
|
||||
return hass.localize(
|
||||
`${triggerTranslationBaseKey}.numeric_state.description.above`,
|
||||
{
|
||||
attribute: attribute,
|
||||
entity: entity,
|
||||
above: trigger.above,
|
||||
duration: duration,
|
||||
}
|
||||
);
|
||||
|
||||
if (trigger.below !== undefined && trigger.above !== undefined) {
|
||||
base += " and";
|
||||
}
|
||||
if (trigger.below) {
|
||||
return hass.localize(
|
||||
`${triggerTranslationBaseKey}.numeric_state.description.below`,
|
||||
{
|
||||
attribute: attribute,
|
||||
entity: entity,
|
||||
below: trigger.below,
|
||||
duration: duration,
|
||||
}
|
||||
);
|
||||
|
||||
if (trigger.below !== undefined) {
|
||||
base += ` below ${trigger.below}`;
|
||||
}
|
||||
|
||||
if (trigger.for) {
|
||||
const duration = describeDuration(trigger.for);
|
||||
if (duration) {
|
||||
base += ` for ${duration}`;
|
||||
}
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
// State Trigger
|
||||
@@ -254,7 +242,7 @@ const tryDescribeTrigger = (
|
||||
);
|
||||
}
|
||||
if (from.length !== 0) {
|
||||
const fromString = formatListWithOrs(hass.locale, from);
|
||||
const fromString = disjunctionFormatter.format(from);
|
||||
base += ` from ${fromString}`;
|
||||
}
|
||||
} else {
|
||||
@@ -295,7 +283,7 @@ const tryDescribeTrigger = (
|
||||
);
|
||||
}
|
||||
if (to.length !== 0) {
|
||||
const toString = formatListWithOrs(hass.locale, to);
|
||||
const toString = disjunctionFormatter.format(to);
|
||||
base += ` to ${toString}`;
|
||||
}
|
||||
} else {
|
||||
@@ -368,7 +356,7 @@ const tryDescribeTrigger = (
|
||||
);
|
||||
|
||||
return hass.localize(`${triggerTranslationBaseKey}.time.description.full`, {
|
||||
time: formatListWithOrs(hass.locale, result),
|
||||
time: disjunctionFormatter.format(result),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -517,12 +505,11 @@ const tryDescribeTrigger = (
|
||||
);
|
||||
}
|
||||
|
||||
return hass.localize(`${triggerTranslationBaseKey}.zone.description.full`, {
|
||||
entity: formatListWithOrs(hass.locale, entities),
|
||||
event: trigger.event.toString(),
|
||||
zone: formatListWithOrs(hass.locale, zones),
|
||||
numberOfZones: zones.length,
|
||||
});
|
||||
const entitiesString = disjunctionFormatter.format(entities);
|
||||
const zonesString = disjunctionFormatter.format(zones);
|
||||
return `When ${entitiesString} ${trigger.event}s ${zonesString} ${
|
||||
zones.length > 1 ? "zones" : "zone"
|
||||
}`;
|
||||
}
|
||||
|
||||
// Geo Location Trigger
|
||||
@@ -553,15 +540,11 @@ const tryDescribeTrigger = (
|
||||
);
|
||||
}
|
||||
|
||||
return hass.localize(
|
||||
`${triggerTranslationBaseKey}.geo_location.description.full`,
|
||||
{
|
||||
source: formatListWithOrs(hass.locale, sources),
|
||||
event: trigger.event.toString(),
|
||||
zone: formatListWithOrs(hass.locale, zones),
|
||||
numberOfZones: zones.length,
|
||||
}
|
||||
);
|
||||
const sourcesString = disjunctionFormatter.format(sources);
|
||||
const zonesString = disjunctionFormatter.format(zones);
|
||||
return `When ${sourcesString} ${trigger.event}s ${zonesString} ${
|
||||
zones.length > 1 ? "zones" : "zone"
|
||||
}`;
|
||||
}
|
||||
|
||||
// MQTT Trigger
|
||||
@@ -600,8 +583,7 @@ const tryDescribeTrigger = (
|
||||
return hass.localize(
|
||||
`${triggerTranslationBaseKey}.conversation.description.full`,
|
||||
{
|
||||
sentence: formatListWithOrs(
|
||||
hass.locale,
|
||||
sentence: disjunctionFormatter.format(
|
||||
ensureArray(trigger.command).map((cmd) => `'${cmd}'`)
|
||||
),
|
||||
}
|
||||
@@ -610,9 +592,7 @@ const tryDescribeTrigger = (
|
||||
|
||||
// Persistent Notification Trigger
|
||||
if (trigger.platform === "persistent_notification") {
|
||||
return hass.localize(
|
||||
`${triggerTranslationBaseKey}.persistent_notification.description.full`
|
||||
);
|
||||
return "When a persistent notification is updated";
|
||||
}
|
||||
|
||||
// Device Trigger
|
||||
@@ -670,6 +650,15 @@ const tryDescribeCondition = (
|
||||
return condition.alias;
|
||||
}
|
||||
|
||||
const conjunctionFormatter = new Intl.ListFormat("en", {
|
||||
style: "long",
|
||||
type: "conjunction",
|
||||
});
|
||||
const disjunctionFormatter = new Intl.ListFormat("en", {
|
||||
style: "long",
|
||||
type: "disjunction",
|
||||
});
|
||||
|
||||
if (!condition.condition) {
|
||||
const shorthands: Array<"and" | "or" | "not"> = ["and", "or", "not"];
|
||||
for (const key of shorthands) {
|
||||
@@ -767,8 +756,8 @@ const tryDescribeCondition = (
|
||||
if (entities.length !== 0) {
|
||||
const entitiesString =
|
||||
condition.match === "any"
|
||||
? formatListWithOrs(hass.locale, entities)
|
||||
: formatListWithAnds(hass.locale, entities);
|
||||
? disjunctionFormatter.format(entities)
|
||||
: conjunctionFormatter.format(entities);
|
||||
base += ` ${entitiesString} ${
|
||||
condition.entity_id.length > 1 ? "are" : "is"
|
||||
}`;
|
||||
@@ -823,7 +812,7 @@ const tryDescribeCondition = (
|
||||
states.push("a state");
|
||||
}
|
||||
|
||||
const statesString = formatListWithOrs(hass.locale, states);
|
||||
const statesString = disjunctionFormatter.format(states);
|
||||
base += ` ${statesString}`;
|
||||
|
||||
if (condition.for) {
|
||||
@@ -838,49 +827,29 @@ const tryDescribeCondition = (
|
||||
|
||||
// Numeric State Condition
|
||||
if (condition.condition === "numeric_state" && condition.entity_id) {
|
||||
let base = "Confirm";
|
||||
const stateObj = hass.states[condition.entity_id];
|
||||
const entity = stateObj ? computeStateName(stateObj) : condition.entity_id;
|
||||
|
||||
const attribute = condition.attribute
|
||||
? computeAttributeNameDisplay(
|
||||
hass.localize,
|
||||
stateObj,
|
||||
hass.entities,
|
||||
condition.attribute
|
||||
)
|
||||
: undefined;
|
||||
if ("attribute" in condition) {
|
||||
base += ` ${condition.attribute} from`;
|
||||
}
|
||||
|
||||
if (condition.above && condition.below) {
|
||||
return hass.localize(
|
||||
`${conditionsTranslationBaseKey}.numeric_state.description.above-below`,
|
||||
{
|
||||
attribute: attribute,
|
||||
entity: entity,
|
||||
above: condition.above,
|
||||
below: condition.below,
|
||||
}
|
||||
);
|
||||
base += ` ${entity} is`;
|
||||
|
||||
if ("above" in condition) {
|
||||
base += ` above ${condition.above}`;
|
||||
}
|
||||
if (condition.above) {
|
||||
return hass.localize(
|
||||
`${conditionsTranslationBaseKey}.numeric_state.description.above`,
|
||||
{
|
||||
attribute: attribute,
|
||||
entity: entity,
|
||||
above: condition.above,
|
||||
}
|
||||
);
|
||||
|
||||
if ("below" in condition && "above" in condition) {
|
||||
base += " and";
|
||||
}
|
||||
if (condition.below) {
|
||||
return hass.localize(
|
||||
`${conditionsTranslationBaseKey}.numeric_state.description.below`,
|
||||
{
|
||||
attribute: attribute,
|
||||
entity: entity,
|
||||
below: condition.below,
|
||||
}
|
||||
);
|
||||
|
||||
if ("below" in condition) {
|
||||
base += ` below ${condition.below}`;
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
// Time condition
|
||||
@@ -933,7 +902,7 @@ const tryDescribeCondition = (
|
||||
`ui.panel.config.automation.editor.conditions.type.time.weekdays.${d}`
|
||||
)
|
||||
);
|
||||
result += " day is " + formatListWithOrs(hass.locale, localizedDays);
|
||||
result += " day is " + disjunctionFormatter.format(localizedDays);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1012,8 +981,8 @@ const tryDescribeCondition = (
|
||||
);
|
||||
}
|
||||
|
||||
const entitiesString = formatListWithOrs(hass.locale, entities);
|
||||
const zonesString = formatListWithOrs(hass.locale, zones);
|
||||
const entitiesString = disjunctionFormatter.format(entities);
|
||||
const zonesString = disjunctionFormatter.format(zones);
|
||||
return hass.localize(
|
||||
`${conditionsTranslationBaseKey}.zone.description.full`,
|
||||
{
|
||||
|
@@ -17,14 +17,12 @@ export interface LovelacePanelConfig {
|
||||
mode: "yaml" | "storage";
|
||||
}
|
||||
|
||||
export type LovelaceStrategyConfig = {
|
||||
type: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export interface LovelaceConfig {
|
||||
title?: string;
|
||||
strategy?: LovelaceStrategyConfig;
|
||||
strategy?: {
|
||||
type: string;
|
||||
options?: Record<string, unknown>;
|
||||
};
|
||||
views: LovelaceViewConfig[];
|
||||
background?: string;
|
||||
}
|
||||
@@ -83,7 +81,10 @@ export interface LovelaceViewConfig {
|
||||
index?: number;
|
||||
title?: string;
|
||||
type?: string;
|
||||
strategy?: LovelaceStrategyConfig;
|
||||
strategy?: {
|
||||
type: string;
|
||||
options?: Record<string, unknown>;
|
||||
};
|
||||
badges?: Array<string | LovelaceBadgeConfig>;
|
||||
cards?: LovelaceCardConfig[];
|
||||
path?: string;
|
||||
|
@@ -5,6 +5,7 @@ import {
|
||||
mdiCodeBraces,
|
||||
mdiDevices,
|
||||
mdiGestureDoubleTap,
|
||||
mdiHomeAssistant,
|
||||
mdiMapMarker,
|
||||
mdiMapMarkerRadius,
|
||||
mdiMessageAlert,
|
||||
@@ -17,8 +18,6 @@ import {
|
||||
mdiWebhook,
|
||||
} from "@mdi/js";
|
||||
|
||||
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
|
||||
|
||||
export const TRIGGER_TYPES = {
|
||||
calendar: mdiCalendar,
|
||||
device: mdiDevices,
|
||||
|
@@ -1,12 +0,0 @@
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
export interface WakeWord {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const fetchWakeWordInfo = (hass: HomeAssistant, entity_id: string) =>
|
||||
hass.callWS<{ wake_words: WakeWord[] }>({
|
||||
type: "wake_word/info",
|
||||
entity_id,
|
||||
});
|
@@ -192,7 +192,7 @@ export interface ZWaveJSController {
|
||||
supported_function_types: number[];
|
||||
suc_node_id: number;
|
||||
supports_timers: boolean;
|
||||
is_rebuilding_routes: boolean;
|
||||
is_heal_network_active: boolean;
|
||||
inclusion_state: InclusionState;
|
||||
nodes: ZWaveJSNodeStatus[];
|
||||
}
|
||||
@@ -278,9 +278,9 @@ export interface ZWaveJSRefreshNodeStatusMessage {
|
||||
stage?: string;
|
||||
}
|
||||
|
||||
export interface ZWaveJSRebuildRoutesStatusMessage {
|
||||
export interface ZWaveJSHealNetworkStatusMessage {
|
||||
event: string;
|
||||
rebuild_routes_status: { [key: number]: string };
|
||||
heal_node_status: { [key: number]: string };
|
||||
}
|
||||
|
||||
export interface ZWaveJSControllerStatisticsUpdatedMessage {
|
||||
@@ -651,12 +651,12 @@ export const reinterviewZwaveNode = (
|
||||
}
|
||||
);
|
||||
|
||||
export const rebuildZwaveNodeRoutes = (
|
||||
export const healZwaveNode = (
|
||||
hass: HomeAssistant,
|
||||
device_id: string
|
||||
): Promise<boolean> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/rebuild_node_routes",
|
||||
type: "zwave_js/heal_node",
|
||||
device_id,
|
||||
});
|
||||
|
||||
@@ -673,33 +673,33 @@ export const removeFailedZwaveNode = (
|
||||
}
|
||||
);
|
||||
|
||||
export const rebuildZwaveNetworkRoutes = (
|
||||
export const healZwaveNetwork = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/begin_rebuilding_routes",
|
||||
type: "zwave_js/begin_healing_network",
|
||||
entry_id,
|
||||
});
|
||||
|
||||
export const stopRebuildingZwaveNetworkRoutes = (
|
||||
export const stopHealZwaveNetwork = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.callWS({
|
||||
type: "zwave_js/stop_rebuilding_routes",
|
||||
type: "zwave_js/stop_healing_network",
|
||||
entry_id,
|
||||
});
|
||||
|
||||
export const subscribeRebuildZwaveNetworkRoutesProgress = (
|
||||
export const subscribeHealZwaveNetworkProgress = (
|
||||
hass: HomeAssistant,
|
||||
entry_id: string,
|
||||
callbackFunction: (message: ZWaveJSRebuildRoutesStatusMessage) => void
|
||||
callbackFunction: (message: ZWaveJSHealNetworkStatusMessage) => void
|
||||
): Promise<UnsubscribeFunc> =>
|
||||
hass.connection.subscribeMessage(
|
||||
(message: any) => callbackFunction(message),
|
||||
{
|
||||
type: "zwave_js/subscribe_rebuild_routes_progress",
|
||||
type: "zwave_js/subscribe_heal_network_progress",
|
||||
entry_id,
|
||||
}
|
||||
);
|
||||
|
137
src/dialogs/aliases/dialog-aliases.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import "@material/mwc-button/mwc-button";
|
||||
import { CSSResultGroup, LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-dialog";
|
||||
import { haStyle, haStyleDialog } from "../../resources/styles";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { AliasesDialogParams } from "./show-dialog-aliases";
|
||||
import "../../components/ha-aliases-editor";
|
||||
|
||||
@customElement("dialog-aliases")
|
||||
class DialogAliases extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _params?: AliasesDialogParams;
|
||||
|
||||
@state() private _aliases!: string[];
|
||||
|
||||
@state() private _submitting = false;
|
||||
|
||||
public async showDialog(params: AliasesDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._error = undefined;
|
||||
this._aliases =
|
||||
this._params.aliases?.length > 0
|
||||
? [...this._params.aliases].sort()
|
||||
: [""];
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this._error = "";
|
||||
this._params = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
open
|
||||
@closed=${this.closeDialog}
|
||||
.heading=${this.hass.localize("ui.dialogs.aliases.heading", {
|
||||
name: this._params.name,
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
${this._error
|
||||
? html`<ha-alert alert-type="error">${this._error}</ha-alert>`
|
||||
: ""}
|
||||
<ha-aliases-editor
|
||||
.hass=${this.hass}
|
||||
.aliases=${this._aliases}
|
||||
@value-changed=${this._aliasesChanged}
|
||||
></ha-aliases-editor>
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="secondaryAction"
|
||||
@click=${this.closeDialog}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateAliases}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize("ui.dialogs.aliases.save")}
|
||||
</mwc-button>
|
||||
</ha-dialog>
|
||||
`;
|
||||
}
|
||||
|
||||
private _aliasesChanged(ev: CustomEvent): void {
|
||||
this._aliases = ev.detail.value;
|
||||
}
|
||||
|
||||
private async _updateAliases(): Promise<void> {
|
||||
this._submitting = true;
|
||||
const noEmptyAliases = this._aliases
|
||||
.map((alias) => alias.trim())
|
||||
.filter((alias) => alias);
|
||||
|
||||
try {
|
||||
await this._params!.updateAliases(noEmptyAliases);
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error =
|
||||
err.message || this.hass.localize("ui.dialogs.aliases.unknown_error");
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
haStyleDialog,
|
||||
css`
|
||||
.row {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
ha-textfield {
|
||||
display: block;
|
||||
}
|
||||
ha-icon-button {
|
||||
display: block;
|
||||
}
|
||||
mwc-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
#alias_input {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.alias {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
--mdc-icon-button-size: 24px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"dialog-aliases": DialogAliases;
|
||||
}
|
||||
}
|
20
src/dialogs/aliases/show-dialog-aliases.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
|
||||
export interface AliasesDialogParams {
|
||||
name: string;
|
||||
aliases: string[];
|
||||
updateAliases: (aliases: string[]) => Promise<unknown>;
|
||||
}
|
||||
|
||||
export const loadAliasesDialog = () => import("./dialog-aliases");
|
||||
|
||||
export const showAliasesDialog = (
|
||||
element: HTMLElement,
|
||||
aliasesParams: AliasesDialogParams
|
||||
): void => {
|
||||
fireEvent(element, "show-dialog", {
|
||||
dialogTag: "dialog-aliases",
|
||||
dialogImport: loadAliasesDialog,
|
||||
dialogParams: aliasesParams,
|
||||
});
|
||||
};
|
@@ -60,8 +60,7 @@ export const moreInfoControlCircularSliderStyle = css`
|
||||
justify-content: space-between;
|
||||
}
|
||||
.buttons ha-outlined-icon-button {
|
||||
--md-outlined-icon-button-container-width: 48px;
|
||||
--md-outlined-icon-button-container-height: 48px;
|
||||
--md-outlined-icon-button-container-size: 48px;
|
||||
--md-outlined-icon-button-icon-size: 24px;
|
||||
}
|
||||
/* Accessibility */
|
||||
|
@@ -53,8 +53,8 @@ class DialogLightColorFavorite extends LitElement {
|
||||
): Promise<void> {
|
||||
this._entry = dialogParams.entry;
|
||||
this._dialogParams = dialogParams;
|
||||
this._color = dialogParams.initialColor ?? this._computeCurrentColor();
|
||||
this._updateModes();
|
||||
this._updateModes(dialogParams.defaultMode);
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@@ -64,7 +64,7 @@ class DialogLightColorFavorite extends LitElement {
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
private _updateModes() {
|
||||
private _updateModes(defaultMode?: LightPickerMode) {
|
||||
const supportsTemp = lightSupportsColorMode(
|
||||
this.stateObj!,
|
||||
LightColorMode.COLOR_TEMP
|
||||
@@ -81,40 +81,13 @@ class DialogLightColorFavorite extends LitElement {
|
||||
}
|
||||
|
||||
this._modes = modes;
|
||||
|
||||
if (this._color) {
|
||||
this._mode = "color_temp_kelvin" in this._color ? "color_temp" : "color";
|
||||
} else {
|
||||
this._mode = this._modes[0];
|
||||
}
|
||||
}
|
||||
|
||||
private _computeCurrentColor() {
|
||||
const attributes = this.stateObj!.attributes;
|
||||
const color_mode = attributes.color_mode;
|
||||
|
||||
let currentColor: LightColor | undefined;
|
||||
if (color_mode === LightColorMode.XY) {
|
||||
// XY color not supported for favorites. Try to grab the hs or rgb instead.
|
||||
if (attributes.hs_color) {
|
||||
currentColor = { hs_color: attributes.hs_color };
|
||||
} else if (attributes.rgb_color) {
|
||||
currentColor = { rgb_color: attributes.rgb_color };
|
||||
}
|
||||
} else if (
|
||||
color_mode === LightColorMode.COLOR_TEMP &&
|
||||
attributes.color_temp_kelvin
|
||||
) {
|
||||
currentColor = {
|
||||
color_temp_kelvin: attributes.color_temp_kelvin,
|
||||
};
|
||||
} else if (attributes[color_mode + "_color"]) {
|
||||
currentColor = {
|
||||
[color_mode + "_color"]: attributes[color_mode + "_color"],
|
||||
} as LightColor;
|
||||
}
|
||||
|
||||
return currentColor;
|
||||
this._mode =
|
||||
defaultMode ??
|
||||
(this.stateObj!.attributes.color_mode
|
||||
? this.stateObj!.attributes.color_mode === LightColorMode.COLOR_TEMP
|
||||
? LightColorMode.COLOR_TEMP
|
||||
: "color"
|
||||
: this._modes[0]);
|
||||
}
|
||||
|
||||
private _colorChanged(ev: CustomEvent) {
|
||||
@@ -225,10 +198,7 @@ class DialogLightColorFavorite extends LitElement {
|
||||
<ha-button slot="secondaryAction" dialogAction="cancel">
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
.disabled=${!this._color}
|
||||
<ha-button slot="primaryAction" @click=${this._save}
|
||||
>${this.hass.localize("ui.common.save")}</ha-button
|
||||
>
|
||||
</ha-dialog>
|
||||
|
@@ -1,12 +1,12 @@
|
||||
import { mdiCheck, mdiMinus, mdiPlus } from "@mdi/js";
|
||||
import {
|
||||
css,
|
||||
CSSResultGroup,
|
||||
html,
|
||||
LitElement,
|
||||
nothing,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
} from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -19,17 +19,18 @@ import {
|
||||
updateEntityRegistryEntry,
|
||||
} from "../../../../data/entity_registry";
|
||||
import {
|
||||
computeDefaultFavoriteColors,
|
||||
LightColor,
|
||||
LightEntity,
|
||||
computeDefaultFavoriteColors,
|
||||
} from "../../../../data/light";
|
||||
import { actionHandler } from "../../../../panels/lovelace/common/directives/action-handler-directive";
|
||||
import {
|
||||
SortableInstance,
|
||||
loadSortable,
|
||||
SortableInstance,
|
||||
} from "../../../../resources/sortable.ondemand";
|
||||
import { HomeAssistant } from "../../../../types";
|
||||
import { showConfirmationDialog } from "../../../generic/show-dialog-box";
|
||||
import type { LightPickerMode } from "./dialog-light-color-favorite";
|
||||
import "./ha-favorite-color-button";
|
||||
import { showLightColorFavoriteDialog } from "./show-dialog-light-color-favorite";
|
||||
|
||||
@@ -154,9 +155,13 @@ export class HaMoreInfoLightFavoriteColors extends LitElement {
|
||||
// Make sure the current favorite color is set
|
||||
fireEvent(this, "favorite-color-edit-started");
|
||||
await this._apply(index);
|
||||
const defaultMode: LightPickerMode =
|
||||
"color_temp_kelvin" in this._favoriteColors[index]
|
||||
? "color_temp"
|
||||
: "color";
|
||||
const color = await showLightColorFavoriteDialog(this, {
|
||||
entry: this.entry!,
|
||||
initialColor: this._favoriteColors[index],
|
||||
defaultMode,
|
||||
title: this.hass.localize(
|
||||
"ui.dialogs.more_info_control.light.favorite_color.edit_title"
|
||||
),
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { ExtEntityRegistryEntry } from "../../../../data/entity_registry";
|
||||
import { LightColor } from "../../../../data/light";
|
||||
import type { LightPickerMode } from "./dialog-light-color-favorite";
|
||||
|
||||
export interface LightColorFavoriteDialogParams {
|
||||
entry: ExtEntityRegistryEntry;
|
||||
title: string;
|
||||
initialColor?: LightColor;
|
||||
defaultMode?: LightPickerMode;
|
||||
submit?: (color?: LightColor) => void;
|
||||
cancel?: () => void;
|
||||
}
|
||||
|
@@ -99,7 +99,6 @@ export class MoreInfoHistory extends LitElement {
|
||||
.historyData=${this._stateHistory}
|
||||
.isLoadingData=${!this._stateHistory}
|
||||
.showNames=${false}
|
||||
.clickForMoreInfo=${false}
|
||||
></state-history-charts>`}`
|
||||
: ""}`;
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ import {
|
||||
mdiReload,
|
||||
mdiServerNetwork,
|
||||
} from "@mdi/js";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
@@ -23,8 +23,8 @@ import { domainIcon } from "../../common/entity/domain_icon";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import { caseInsensitiveStringCompare } from "../../common/string/compare";
|
||||
import {
|
||||
ScorableTextItem,
|
||||
fuzzyFilterSort,
|
||||
ScorableTextItem,
|
||||
} from "../../common/string/filter/sequence-matching";
|
||||
import { debounce } from "../../common/util/debounce";
|
||||
import "../../components/ha-chip";
|
||||
@@ -40,7 +40,10 @@ import { configSections } from "../../panels/config/ha-panel-config";
|
||||
import { haStyleDialog, haStyleScrollbar } from "../../resources/styles";
|
||||
import { loadVirtualizer } from "../../resources/virtualizer";
|
||||
import { HomeAssistant } from "../../types";
|
||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||
import {
|
||||
ConfirmationDialogParams,
|
||||
showConfirmationDialog,
|
||||
} from "../generic/show-dialog-box";
|
||||
import { QuickBarParams } from "./show-dialog-quick-bar";
|
||||
|
||||
interface QuickBarItem extends ScorableTextItem {
|
||||
@@ -116,8 +119,6 @@ export class QuickBar extends LitElement {
|
||||
this._focusSet = false;
|
||||
this._filter = "";
|
||||
this._search = "";
|
||||
this._entityItems = undefined;
|
||||
this._commandItems = undefined;
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
@@ -599,30 +600,16 @@ export class QuickBar extends LitElement {
|
||||
`ui.dialogs.quick-bar.commands.types.${categoryKey}`
|
||||
),
|
||||
categoryKey,
|
||||
action: async () => {
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
`ui.dialogs.restart.${action}.confirm_title`
|
||||
),
|
||||
text: this.hass.localize(
|
||||
`ui.dialogs.restart.${action}.confirm_description`
|
||||
),
|
||||
confirmText: this.hass.localize(
|
||||
`ui.dialogs.restart.${action}.confirm_action`
|
||||
),
|
||||
destructive: true,
|
||||
});
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
this.hass.callService("homeassistant", action);
|
||||
},
|
||||
action: () => this.hass.callService("homeassistant", action),
|
||||
};
|
||||
|
||||
return {
|
||||
...item,
|
||||
strings: [`${item.categoryText} ${item.primaryText}`],
|
||||
};
|
||||
return this._generateConfirmationCommand(
|
||||
{
|
||||
...item,
|
||||
strings: [`${item.categoryText} ${item.primaryText}`],
|
||||
},
|
||||
this.hass.localize("ui.dialogs.generic.ok")
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -728,6 +715,20 @@ export class QuickBar extends LitElement {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private _generateConfirmationCommand(
|
||||
item: CommandItem,
|
||||
confirmText: ConfirmationDialogParams["confirmText"]
|
||||
): CommandItem {
|
||||
return {
|
||||
...item,
|
||||
action: () =>
|
||||
showConfirmationDialog(this, {
|
||||
confirmText,
|
||||
confirm: item.action,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
private _finalizeNavigationCommands(
|
||||
items: BaseNavigationCommand[]
|
||||
): CommandItem[] {
|
||||
|
@@ -271,7 +271,6 @@ export const provideHass = (
|
||||
},
|
||||
dockedSidebar: "auto",
|
||||
vibrate: true,
|
||||
debugConnection: false,
|
||||
suspendWhenHidden: false,
|
||||
moreInfoEntityId: null as any,
|
||||
// @ts-ignore
|
||||
|