mirror of
https://github.com/home-assistant/frontend.git
synced 2025-10-24 02:59:47 +00:00
Compare commits
47 Commits
20250925.1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e749ec085 | ||
|
|
ee2ec00069 | ||
|
|
0aa2941868 | ||
|
|
46cd1d5156 | ||
|
|
07a5c41fd4 | ||
|
|
4ad3c553d5 | ||
|
|
d40cc448a5 | ||
|
|
e2f3f9d348 | ||
|
|
98d44950f8 | ||
|
|
8ae9edb1ef | ||
|
|
84c4396c13 | ||
|
|
2b937a30e3 | ||
|
|
b7815bfd86 | ||
|
|
d94fa03411 | ||
|
|
0a7007ef9e | ||
|
|
dd12136dee | ||
|
|
6e2f89fe3d | ||
|
|
092085b9af | ||
|
|
1c06eb8661 | ||
|
|
c7e87b06b5 | ||
|
|
38c738c199 | ||
|
|
e899587307 | ||
|
|
c9feb0b75f | ||
|
|
10718c35d1 | ||
|
|
4dc6a37bad | ||
|
|
ac49fc7aba | ||
|
|
e4f008800b | ||
|
|
0b0ffd7bab | ||
|
|
dfa77526a2 | ||
|
|
9a3bd6c613 | ||
|
|
1161de5746 | ||
|
|
9df8e20391 | ||
|
|
11047a9c95 | ||
|
|
18fa66f61c | ||
|
|
758a048f34 | ||
|
|
ee0fc360b0 | ||
|
|
4012f95ec1 | ||
|
|
0336ce4606 | ||
|
|
9ba36ab7e2 | ||
|
|
fe7a08a1b0 | ||
|
|
87a8f9cedc | ||
|
|
01df7e20ca | ||
|
|
d181219522 | ||
|
|
6ae24b8135 | ||
|
|
8e009f24f9 | ||
|
|
53031f44ac | ||
|
|
af5a988457 |
4
.github/workflows/cast_deployment.yaml
vendored
4
.github/workflows/cast_deployment.yaml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=cast/dist --alias dev
|
||||
npx -y netlify-cli@23.7.3 deploy --dir=cast/dist --alias dev
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=cast/dist --prod
|
||||
npx -y netlify-cli@23.7.3 deploy --dir=cast/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_CAST_SITE_ID }}
|
||||
|
||||
4
.github/workflows/demo_deployment.yaml
vendored
4
.github/workflows/demo_deployment.yaml
vendored
@@ -43,7 +43,7 @@ jobs:
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=demo/dist --prod
|
||||
npx -y netlify-cli@23.7.3 deploy --dir=demo/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_DEV_SITE_ID }}
|
||||
@@ -78,7 +78,7 @@ jobs:
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=demo/dist --prod
|
||||
npx -y netlify-cli@23.7.3 deploy --dir=demo/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DEMO_SITE_ID }}
|
||||
|
||||
2
.github/workflows/design_deployment.yaml
vendored
2
.github/workflows/design_deployment.yaml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Deploy to Netlify
|
||||
id: deploy
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=gallery/dist --prod
|
||||
npx -y netlify-cli@23.7.3 deploy --dir=gallery/dist --prod
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_GALLERY_SITE_ID }}
|
||||
|
||||
2
.github/workflows/design_preview.yaml
vendored
2
.github/workflows/design_preview.yaml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
- name: Deploy preview to Netlify
|
||||
id: deploy
|
||||
run: |
|
||||
npx -y netlify-cli deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}" \
|
||||
npx -y netlify-cli@23.7.3 deploy --dir=gallery/dist --alias "deploy-preview-${{ github.event.number }}" \
|
||||
--json > deploy_output.json
|
||||
env:
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
|
||||
@@ -34,3 +34,5 @@ Check the [webawesome documentation](https://webawesome.com/docs/components/slid
|
||||
**CSS Custom Properties**
|
||||
|
||||
- `--ha-slider-track-size` - Height of the slider track. Defaults to `4px`.
|
||||
- `--ha-slider-thumb-color` - Color of the slider thumb. Defaults to `var(--primary-color)`.
|
||||
- `--ha-slider-indicator-color` - Color of the filled portion of the slider track. Defaults to `var(--primary-color)`.
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"@fullcalendar/list": "6.1.19",
|
||||
"@fullcalendar/luxon3": "6.1.19",
|
||||
"@fullcalendar/timegrid": "6.1.19",
|
||||
"@home-assistant/webawesome": "3.0.0-beta.4.ha.3",
|
||||
"@home-assistant/webawesome": "3.0.0-beta.6.ha.4",
|
||||
"@lezer/highlight": "1.2.1",
|
||||
"@lit-labs/motion": "1.0.9",
|
||||
"@lit-labs/observers": "2.0.6",
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20250925.1"
|
||||
version = "20251001.4"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE*"]
|
||||
description = "The Home Assistant frontend"
|
||||
|
||||
8
src/common/util/xss.ts
Normal file
8
src/common/util/xss.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import xss from "xss";
|
||||
|
||||
export const filterXSS = (html: string) =>
|
||||
xss(html, {
|
||||
whiteList: {},
|
||||
stripIgnoreTag: true,
|
||||
stripIgnoreTagBody: true,
|
||||
});
|
||||
@@ -25,6 +25,7 @@ import type { ECOption } from "../../resources/echarts";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { isMac } from "../../util/is_mac";
|
||||
import "../ha-icon-button";
|
||||
import { filterXSS } from "../../common/util/xss";
|
||||
import { formatTimeLabel } from "./axis-label";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import "../chips/ha-assist-chip";
|
||||
@@ -811,7 +812,8 @@ export class HaChartBase extends LitElement {
|
||||
};
|
||||
}
|
||||
}
|
||||
return { ...s, data };
|
||||
const name = filterXSS(String(s.name ?? s.id ?? ""));
|
||||
return { ...s, name, data };
|
||||
});
|
||||
return series as ECOption["series"];
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { ECOption } from "../../resources/echarts";
|
||||
import { measureTextWidth } from "../../util/text";
|
||||
import { filterXSS } from "../../common/util/xss";
|
||||
import "./ha-chart-base";
|
||||
import { NODE_SIZE } from "../trace/hat-graph-const";
|
||||
import "../ha-alert";
|
||||
@@ -92,12 +93,12 @@ export class HaSankeyChart extends LitElement {
|
||||
: data.value;
|
||||
if (data.id) {
|
||||
const node = this.data.nodes.find((n) => n.id === data.id);
|
||||
return `${params.marker} ${node?.label ?? data.id}<br>${value}`;
|
||||
return `${params.marker} ${filterXSS(node?.label ?? data.id)}<br>${value}`;
|
||||
}
|
||||
if (data.source && data.target) {
|
||||
const source = this.data.nodes.find((n) => n.id === data.source);
|
||||
const target = this.data.nodes.find((n) => n.id === data.target);
|
||||
return `${source?.label ?? data.source} → ${target?.label ?? data.target}<br>${value}`;
|
||||
return `${filterXSS(source?.label ?? data.source)} → ${filterXSS(target?.label ?? data.target)}<br>${value}`;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -42,8 +42,8 @@ export class HaBottomSheet extends LitElement {
|
||||
static styles = css`
|
||||
wa-drawer {
|
||||
--wa-color-surface-raised: var(
|
||||
--ha-dialog-surface-background,
|
||||
var(--mdc-theme-surface, #fff)
|
||||
--ha-bottom-sheet-surface-background,
|
||||
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||
);
|
||||
--spacing: 0;
|
||||
--size: auto;
|
||||
@@ -51,8 +51,14 @@ export class HaBottomSheet extends LitElement {
|
||||
--hide-duration: ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms;
|
||||
}
|
||||
wa-drawer::part(dialog) {
|
||||
border-top-left-radius: var(--ha-border-radius-lg);
|
||||
border-top-right-radius: var(--ha-border-radius-lg);
|
||||
border-top-left-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
border-top-right-radius: var(
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
max-height: 90vh;
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
|
||||
@@ -41,8 +41,7 @@ export class HaButton extends Button {
|
||||
return [
|
||||
Button.styles,
|
||||
css`
|
||||
.button {
|
||||
/* set theme vars */
|
||||
:host {
|
||||
--wa-form-control-padding-inline: 16px;
|
||||
--wa-font-weight-action: var(--ha-font-weight-medium);
|
||||
--wa-form-control-border-radius: var(
|
||||
@@ -54,7 +53,8 @@ export class HaButton extends Button {
|
||||
--ha-button-height,
|
||||
var(--button-height, 40px)
|
||||
);
|
||||
|
||||
}
|
||||
.button {
|
||||
font-size: var(--ha-font-size-m);
|
||||
line-height: 1;
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ export class HaDialog extends DialogBase {
|
||||
position: var(--dialog-surface-position, relative);
|
||||
top: var(--dialog-surface-top);
|
||||
margin-top: var(--dialog-surface-margin-top);
|
||||
min-width: var(--mdc-dialog-min-width, 100vw);
|
||||
min-width: var(--mdc-dialog-min-width, auto);
|
||||
min-height: var(--mdc-dialog-min-height, auto);
|
||||
border-radius: var(--ha-dialog-border-radius, 24px);
|
||||
-webkit-backdrop-filter: var(--ha-dialog-surface-backdrop-filter, none);
|
||||
@@ -130,23 +130,13 @@ export class HaDialog extends DialogBase {
|
||||
--ha-dialog-surface-background,
|
||||
var(--mdc-theme-surface, #fff)
|
||||
);
|
||||
padding: var(--dialog-surface-padding);
|
||||
}
|
||||
:host([flexContent]) .mdc-dialog .mdc-dialog__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@media all and (max-width: 450px), all and (max-height: 500px) {
|
||||
.mdc-dialog .mdc-dialog__surface {
|
||||
min-height: 100vh;
|
||||
max-height: 100vh;
|
||||
padding-top: var(--safe-area-inset-top);
|
||||
padding-bottom: var(--safe-area-inset-bottom);
|
||||
padding-left: var(--safe-area-inset-left);
|
||||
padding-right: var(--safe-area-inset-right);
|
||||
}
|
||||
}
|
||||
|
||||
.header_title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { BOTTOM_SHEET_ANIMATION_DURATION_MS } from "./ha-bottom-sheet";
|
||||
|
||||
@@ -37,13 +36,14 @@ export class HaResizableBottomSheet extends LitElement {
|
||||
return html`<dialog
|
||||
open
|
||||
@transitionend=${this._handleTransitionEnd}
|
||||
style=${styleMap({
|
||||
height: this._dialogViewportHeight
|
||||
? `${this._dialogViewportHeight}vh`
|
||||
: "auto",
|
||||
maxHeight: `${this._dialogMaxViewpointHeight}vh`,
|
||||
minHeight: `${this._dialogMinViewpointHeight}vh`,
|
||||
})}
|
||||
style=${`
|
||||
--height: ${this._dialogViewportHeight}vh;
|
||||
--height: ${this._dialogViewportHeight}dvh;
|
||||
--max-height: ${this._dialogMaxViewpointHeight}vh;
|
||||
--max-height: ${this._dialogMaxViewpointHeight}dvh;
|
||||
--min-height: ${this._dialogMinViewpointHeight}vh;
|
||||
--min-height: ${this._dialogMinViewpointHeight}dvh;
|
||||
`}
|
||||
>
|
||||
<div class="handle-wrapper">
|
||||
<div
|
||||
@@ -213,12 +213,14 @@ export class HaResizableBottomSheet extends LitElement {
|
||||
cursor: grabbing;
|
||||
}
|
||||
dialog {
|
||||
height: auto;
|
||||
max-height: 70vh;
|
||||
min-height: 30vh;
|
||||
height: var(--height, auto);
|
||||
max-height: var(--max-height, 70vh);
|
||||
max-height: var(--max-height, 70dvh);
|
||||
min-height: var(--min-height, 30vh);
|
||||
min-height: var(--min-height, 30dvh);
|
||||
background-color: var(
|
||||
--ha-dialog-surface-background,
|
||||
var(--mdc-theme-surface, #fff)
|
||||
--ha-bottom-sheet-surface-background,
|
||||
var(--ha-dialog-surface-background, var(--mdc-theme-surface, #fff)),
|
||||
);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -239,12 +241,12 @@ export class HaResizableBottomSheet extends LitElement {
|
||||
inset-inline-start: 0;
|
||||
box-shadow: 0px -8px 16px rgba(0, 0, 0, 0.2);
|
||||
border-top-left-radius: var(
|
||||
--ha-dialog-border-radius,
|
||||
var(--ha-border-radius-2xl)
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
border-top-right-radius: var(
|
||||
--ha-dialog-border-radius,
|
||||
var(--ha-border-radius-2xl)
|
||||
--ha-bottom-sheet-border-radius,
|
||||
var(--ha-dialog-border-radius, var(--ha-border-radius-2xl))
|
||||
);
|
||||
transform: translateY(100%);
|
||||
transition: transform ${BOTTOM_SHEET_ANIMATION_DURATION_MS}ms ease;
|
||||
@@ -254,7 +256,6 @@ export class HaResizableBottomSheet extends LitElement {
|
||||
border-bottom-width: 0;
|
||||
border-style: var(--ha-bottom-sheet-border-style);
|
||||
border-color: var(--ha-bottom-sheet-border-color);
|
||||
margin-bottom: var(--safe-area-inset-bottom);
|
||||
margin-left: var(--safe-area-inset-left);
|
||||
margin-right: var(--safe-area-inset-right);
|
||||
}
|
||||
|
||||
@@ -82,12 +82,12 @@ export class HaNumberSelector extends LitElement {
|
||||
labeled
|
||||
.min=${this.selector.number!.min}
|
||||
.max=${this.selector.number!.max}
|
||||
.value=${this.value ?? ""}
|
||||
.value=${this.value}
|
||||
.step=${sliderStep}
|
||||
.disabled=${this.disabled}
|
||||
.required=${this.required}
|
||||
@change=${this._handleSliderChange}
|
||||
.ticks=${this.selector.number?.slider_ticks}
|
||||
.withMarkers=${this.selector.number?.slider_ticks || false}
|
||||
>
|
||||
</ha-slider>
|
||||
`
|
||||
|
||||
@@ -19,7 +19,6 @@ export class HaSlider extends Slider {
|
||||
Slider.styles,
|
||||
css`
|
||||
:host {
|
||||
--wa-form-control-activated-color: var(--primary-color);
|
||||
--track-size: var(--ha-slider-track-size, 4px);
|
||||
--marker-height: calc(var(--ha-slider-track-size, 4px) / 2);
|
||||
--marker-width: calc(var(--ha-slider-track-size, 4px) / 2);
|
||||
@@ -54,6 +53,7 @@ export class HaSlider extends Slider {
|
||||
|
||||
#thumb {
|
||||
border: none;
|
||||
background-color: var(--ha-slider-thumb-color, var(--primary-color));
|
||||
}
|
||||
|
||||
#slider:focus-visible:not(.disabled) #thumb,
|
||||
@@ -62,14 +62,21 @@ export class HaSlider extends Slider {
|
||||
outline: var(--wa-focus-ring);
|
||||
}
|
||||
|
||||
#indicator {
|
||||
background-color: var(
|
||||
--ha-slider-indicator-color,
|
||||
var(--primary-color)
|
||||
);
|
||||
}
|
||||
|
||||
:host([size="medium"]) {
|
||||
--thumb-width: var(--ha-font-size-l, 1.25em);
|
||||
--thumb-height: var(--ha-font-size-l, 1.25em);
|
||||
--thumb-width: 20px;
|
||||
--thumb-height: 20px;
|
||||
}
|
||||
|
||||
:host([size="small"]) {
|
||||
--thumb-width: var(--ha-font-size-m, 1em);
|
||||
--thumb-height: var(--ha-font-size-m, 1em);
|
||||
--thumb-width: 16px;
|
||||
--thumb-height: 16px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -17,7 +17,7 @@ export class HaTooltip extends Tooltip {
|
||||
css`
|
||||
:host {
|
||||
--wa-tooltip-background-color: var(--secondary-background-color);
|
||||
--wa-tooltip-color: var(--primary-text-color);
|
||||
--wa-tooltip-content-color: var(--primary-text-color);
|
||||
--wa-tooltip-font-family: var(
|
||||
--ha-tooltip-font-family,
|
||||
var(--ha-font-family-body)
|
||||
|
||||
@@ -12,6 +12,7 @@ export const DISCOVERY_SOURCES = [
|
||||
"bluetooth",
|
||||
"dhcp",
|
||||
"discovery",
|
||||
"esphome",
|
||||
"hardware",
|
||||
"hassio",
|
||||
"homekit",
|
||||
|
||||
@@ -26,20 +26,21 @@ class StepFlowLoading extends LitElement {
|
||||
this.step
|
||||
);
|
||||
return html`
|
||||
<div class="init-spinner">
|
||||
<div class="content">
|
||||
<ha-spinner size="large"></ha-spinner>
|
||||
${description ? html`<div>${description}</div>` : ""}
|
||||
<ha-spinner></ha-spinner>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.init-spinner {
|
||||
.content {
|
||||
margin-top: 0;
|
||||
padding: 50px 100px;
|
||||
text-align: center;
|
||||
}
|
||||
ha-spinner {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ class StepFlowProgress extends LitElement {
|
||||
configFlowContentStyles,
|
||||
css`
|
||||
.content {
|
||||
margin-top: 0;
|
||||
padding: 50px 100px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,9 @@ import {
|
||||
mdiVolumeOff,
|
||||
mdiVolumePlus,
|
||||
} from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { stateActive } from "../../../common/entity/state_active";
|
||||
@@ -19,7 +20,7 @@ import { formatDurationDigital } from "../../../common/datetime/format_duration"
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-list-item";
|
||||
import "../../../components/ha-select";
|
||||
import "../../../components/ha-slider";
|
||||
import type { HaSlider } from "../../../components/ha-slider";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { showMediaBrowserDialog } from "../../../components/media-player/show-media-browser-dialog";
|
||||
@@ -30,6 +31,8 @@ import type {
|
||||
MediaPlayerEntity,
|
||||
} from "../../../data/media-player";
|
||||
import {
|
||||
cleanupMediaTitle,
|
||||
computeMediaDescription,
|
||||
computeMediaControls,
|
||||
handleMediaControlClick,
|
||||
MediaPlayerEntityFeature,
|
||||
@@ -48,10 +51,20 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public stateObj?: MediaPlayerEntity;
|
||||
|
||||
private _formateDuration(duration: number) {
|
||||
@query("#position-slider")
|
||||
private _positionSlider?: HaSlider;
|
||||
|
||||
protected firstUpdated(_changedProperties: PropertyValues) {
|
||||
if (this._positionSlider) {
|
||||
this._positionSlider.valueFormatter = (value: number) =>
|
||||
this._formatDuration(value);
|
||||
}
|
||||
}
|
||||
|
||||
private _formatDuration(duration: number) {
|
||||
const hours = Math.floor(duration / 3600);
|
||||
const minutes = Math.floor((duration % 3600) / 60);
|
||||
const seconds = duration % 60;
|
||||
const seconds = Math.floor(duration % 60);
|
||||
return formatDurationDigital(this.hass.locale, {
|
||||
hours,
|
||||
minutes,
|
||||
@@ -258,16 +271,19 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
|
||||
const stateObj = this.stateObj;
|
||||
const controls = computeMediaControls(stateObj, true);
|
||||
const coverUrl = stateObj.attributes.entity_picture || "";
|
||||
const coverUrl =
|
||||
stateObj.attributes.entity_picture_local ||
|
||||
stateObj.attributes.entity_picture ||
|
||||
"";
|
||||
const playerObj = new HassMediaPlayerEntity(this.hass, this.stateObj);
|
||||
const position = Math.floor(playerObj.currentProgress) || 0;
|
||||
const duration = stateObj.attributes.media_duration || 0;
|
||||
const remaining = duration - position;
|
||||
const durationFormated =
|
||||
remaining > 0 ? this._formateDuration(remaining) : 0;
|
||||
const postionFormated = this._formateDuration(position);
|
||||
const primaryTitle = playerObj.primaryTitle;
|
||||
const secondaryTitle = playerObj.secondaryTitle;
|
||||
|
||||
const position = Math.max(Math.floor(playerObj.currentProgress || 0), 0);
|
||||
const duration = Math.max(stateObj.attributes.media_duration || 0, 0);
|
||||
const remaining = Math.max(duration - position, 0);
|
||||
const remainingFormatted = this._formatDuration(remaining);
|
||||
const positionFormatted = this._formatDuration(position);
|
||||
const primaryTitle = cleanupMediaTitle(stateObj.attributes.media_title);
|
||||
const secondaryTitle = computeMediaDescription(stateObj);
|
||||
const turnOn = controls?.find((c) => c.action === "turn_on");
|
||||
const turnOff = controls?.find((c) => c.action === "turn_off");
|
||||
|
||||
@@ -313,6 +329,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
? html`
|
||||
<div class="position-bar">
|
||||
<ha-slider
|
||||
id="position-slider"
|
||||
min="0"
|
||||
max=${duration}
|
||||
step="1"
|
||||
@@ -323,11 +340,10 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
@change=${this._handleMediaSeekChanged}
|
||||
?disabled=${!stateActive(stateObj) ||
|
||||
!supportsFeature(stateObj, MediaPlayerEntityFeature.SEEK)}
|
||||
></ha-slider>
|
||||
<div class="position-info-row">
|
||||
<span class="position-time">${postionFormated}</span>
|
||||
<span class="duration-time">${durationFormated}</span>
|
||||
</div>
|
||||
>
|
||||
<span slot="reference">${positionFormatted}</span>
|
||||
<span slot="reference">${remainingFormatted}</span>
|
||||
</ha-slider>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
@@ -348,8 +364,12 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
</ha-icon-button>`
|
||||
: html`<span class="spacer"></span>`;
|
||||
})}
|
||||
${["media_play_pause", "media_pause", "media_play"].map(
|
||||
(action) => {
|
||||
${[
|
||||
"media_play_pause",
|
||||
"media_pause",
|
||||
"media_play",
|
||||
"media_stop",
|
||||
].map((action) => {
|
||||
const control = controls?.find((c) => c.action === action);
|
||||
return control
|
||||
? html`<ha-button
|
||||
@@ -368,8 +388,7 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
></ha-svg-icon>
|
||||
</ha-button>`
|
||||
: nothing;
|
||||
}
|
||||
)}
|
||||
})}
|
||||
${["media_next_track", "shuffle_set"].map((action) => {
|
||||
const control = controls?.find((c) => c.action === action);
|
||||
return control
|
||||
@@ -477,6 +496,22 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
height: 320px;
|
||||
}
|
||||
|
||||
@media (max-height: 750px) {
|
||||
.cover-container {
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.cover-image {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.cover-image--playing {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-cover {
|
||||
background-color: var(--secondary-background-color);
|
||||
font-size: 1.5em;
|
||||
@@ -548,13 +583,8 @@ class MoreInfoMediaPlayer extends LitElement {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.position-info-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
.position-bar ha-slider::part(references) {
|
||||
color: var(--secondary-text-color);
|
||||
padding: 0 8px;
|
||||
font-size: var(--ha-font-size-s);
|
||||
}
|
||||
|
||||
.media-info-row {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createCloseHeading } from "../../components/ha-dialog";
|
||||
import "../../components/ha-textarea";
|
||||
import type { HaTextArea } from "../../components/ha-textarea";
|
||||
import { convertTextToSpeech } from "../../data/tts";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { showAlertDialog } from "../generic/show-dialog-box";
|
||||
import type { TTSTryDialogParams } from "./show-dialog-tts-try";
|
||||
@@ -149,7 +150,9 @@ export class TTSTryDialog extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
static styles = [
|
||||
haStyleDialog,
|
||||
css`
|
||||
ha-dialog {
|
||||
--mdc-dialog-max-width: 500px;
|
||||
}
|
||||
@@ -163,7 +166,8 @@ export class TTSTryDialog extends LitElement {
|
||||
.loading {
|
||||
height: 36px;
|
||||
}
|
||||
`;
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -5,8 +5,8 @@ import { restoreScroll } from "../common/decorators/restore-scroll";
|
||||
import { goBack } from "../common/navigate";
|
||||
import "../components/ha-icon-button-arrow-prev";
|
||||
import "../components/ha-menu-button";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { haStyleScrollbar } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
@customElement("hass-subpage")
|
||||
class HassSubpage extends LitElement {
|
||||
@@ -154,9 +154,15 @@ class HassSubpage extends LitElement {
|
||||
1px - var(--header-height, 0px) - var(
|
||||
--safe-area-inset-top,
|
||||
0px
|
||||
) - var(--safe-area-inset-bottom, 0px)
|
||||
) - var(
|
||||
--hass-subpage-bottom-inset,
|
||||
var(--safe-area-inset-bottom, 0px)
|
||||
)
|
||||
);
|
||||
margin-bottom: var(
|
||||
--hass-subpage-bottom-inset,
|
||||
var(--safe-area-inset-bottom)
|
||||
);
|
||||
margin-bottom: var(--safe-area-inset-bottom);
|
||||
margin-right: var(--safe-area-inset-right);
|
||||
overflow-y: auto;
|
||||
overflow: auto;
|
||||
|
||||
@@ -12,11 +12,11 @@ export const KeyboardShortcutMixin = <T extends Constructor<LitElement>>(
|
||||
class extends superClass {
|
||||
private _keydownEvent = (event: KeyboardEvent) => {
|
||||
const supportedShortcuts = this.supportedShortcuts();
|
||||
const key = event.shiftKey ? event.key.toUpperCase() : event.key;
|
||||
if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
!event.shiftKey &&
|
||||
!event.altKey &&
|
||||
event.key in supportedShortcuts
|
||||
key in supportedShortcuts
|
||||
) {
|
||||
// Only capture the event if the user is not focused on an input
|
||||
if (!canOverrideAlphanumericInput(event.composedPath())) {
|
||||
@@ -27,14 +27,14 @@ export const KeyboardShortcutMixin = <T extends Constructor<LitElement>>(
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
supportedShortcuts[event.key]();
|
||||
supportedShortcuts[key]();
|
||||
return;
|
||||
}
|
||||
|
||||
const supportedSingleKeyShortcuts = this.supportedSingleKeyShortcuts();
|
||||
if (event.key in supportedSingleKeyShortcuts) {
|
||||
if (key in supportedSingleKeyShortcuts) {
|
||||
event.preventDefault();
|
||||
supportedSingleKeyShortcuts[event.key]();
|
||||
supportedSingleKeyShortcuts[key]();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiCog,
|
||||
mdiContentSave,
|
||||
mdiDebugStepOver,
|
||||
@@ -73,8 +74,10 @@ import { showMoreInfoDialog } from "../../../dialogs/more-info/show-ha-more-info
|
||||
import "../../../layouts/hass-subpage";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin";
|
||||
import { UndoRedoMixin } from "../../../mixins/undo-redo-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { Entries, HomeAssistant, Route } from "../../../types";
|
||||
import { isMac } from "../../../util/is_mac";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { showAssignCategoryDialog } from "../category/show-dialog-assign-category";
|
||||
import "../ha-config-section";
|
||||
@@ -86,7 +89,6 @@ import {
|
||||
import "./blueprint-automation-editor";
|
||||
import "./manual-automation-editor";
|
||||
import type { HaManualAutomationEditor } from "./manual-automation-editor";
|
||||
import { UndoRedoMixin } from "../../../mixins/undo-redo-mixin";
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -215,6 +217,10 @@ export class HaAutomationEditor extends UndoRedoMixin<
|
||||
: undefined;
|
||||
|
||||
const useBlueprint = "use_blueprint" in this._config;
|
||||
const shortcutIcon = isMac
|
||||
? html`<ha-svg-icon .path=${mdiAppleKeyboardCommand}></ha-svg-icon>`
|
||||
: this.hass.localize("ui.panel.config.automation.editor.ctrl");
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
@@ -231,16 +237,42 @@ export class HaAutomationEditor extends UndoRedoMixin<
|
||||
.path=${mdiUndo}
|
||||
@click=${this.undo}
|
||||
.disabled=${!this.canUndo}
|
||||
id="button-undo"
|
||||
>
|
||||
</ha-icon-button>
|
||||
<ha-tooltip placement="bottom" for="button-undo">
|
||||
${this.hass.localize("ui.common.undo")}
|
||||
<span class="shortcut"
|
||||
>(
|
||||
<span>${shortcutIcon}</span>
|
||||
<span>+</span>
|
||||
<span>Z</span>)
|
||||
</span>
|
||||
</ha-tooltip>
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
.label=${this.hass.localize("ui.common.redo")}
|
||||
.path=${mdiRedo}
|
||||
@click=${this.redo}
|
||||
.disabled=${!this.canRedo}
|
||||
id="button-redo"
|
||||
>
|
||||
</ha-icon-button>`
|
||||
</ha-icon-button>
|
||||
<ha-tooltip placement="bottom" for="button-redo">
|
||||
${this.hass.localize("ui.common.redo")}
|
||||
<span class="shortcut">
|
||||
(
|
||||
${isMac
|
||||
? html`<span>${shortcutIcon}</span>
|
||||
<span>+</span>
|
||||
<span>Shift</span>
|
||||
<span>+</span>
|
||||
<span>Z</span>`
|
||||
: html`<span>${shortcutIcon}</span>
|
||||
<span>+</span>
|
||||
<span>Y</span>`})
|
||||
</span>
|
||||
</ha-tooltip>`
|
||||
: nothing}
|
||||
${this._config?.id && !this.narrow
|
||||
? html`
|
||||
@@ -1171,6 +1203,7 @@ export class HaAutomationEditor extends UndoRedoMixin<
|
||||
Delete: () => this._deleteSelectedRow(),
|
||||
Backspace: () => this._deleteSelectedRow(),
|
||||
z: () => this.undo(),
|
||||
Z: () => this.redo(),
|
||||
y: () => this.redo(),
|
||||
};
|
||||
}
|
||||
@@ -1224,6 +1257,7 @@ export class HaAutomationEditor extends UndoRedoMixin<
|
||||
--ha-automation-editor-width,
|
||||
1540px
|
||||
);
|
||||
--hass-subpage-bottom-inset: 0px;
|
||||
}
|
||||
ha-fade-in {
|
||||
display: flex;
|
||||
@@ -1292,6 +1326,15 @@ export class HaAutomationEditor extends UndoRedoMixin<
|
||||
ha-fab.dirty {
|
||||
bottom: calc(16px + var(--safe-area-inset-bottom, 0px));
|
||||
}
|
||||
ha-tooltip ha-svg-icon {
|
||||
width: 12px;
|
||||
}
|
||||
ha-tooltip .shortcut {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -292,9 +292,7 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
:host {
|
||||
z-index: 6;
|
||||
outline: none;
|
||||
height: calc(
|
||||
100% - var(--safe-area-inset-top) - var(--safe-area-inset-bottom)
|
||||
);
|
||||
height: calc(100% - var(--safe-area-inset-top, 0px));
|
||||
--ha-card-border-radius: var(
|
||||
--ha-dialog-border-radius,
|
||||
var(--ha-border-radius-2xl)
|
||||
@@ -304,7 +302,6 @@ export default class HaAutomationSidebar extends LitElement {
|
||||
--ha-bottom-sheet-border-style: solid;
|
||||
--ha-bottom-sheet-border-color: var(--primary-color);
|
||||
margin-top: var(--safe-area-inset-top);
|
||||
margin-bottom: var(--safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
@media all and (max-width: 870px) {
|
||||
|
||||
@@ -145,24 +145,19 @@ export const manualEditorStyles = css`
|
||||
|
||||
.content {
|
||||
padding-top: 24px;
|
||||
padding-bottom: 72px;
|
||||
padding-bottom: max(var(--safe-area-inset-bottom), 32px);
|
||||
transition: padding-bottom 180ms ease-in-out;
|
||||
}
|
||||
|
||||
.content.has-bottom-sheet {
|
||||
padding-bottom: calc(90vh - 72px);
|
||||
padding-bottom: calc(90vh - max(var(--safe-area-inset-bottom), 32px));
|
||||
}
|
||||
|
||||
ha-automation-sidebar {
|
||||
position: fixed;
|
||||
top: calc(var(--header-height) + 16px);
|
||||
height: calc(
|
||||
-81px +
|
||||
100dvh - var(--safe-area-inset-top, 0px) - var(
|
||||
--safe-area-inset-bottom,
|
||||
0px
|
||||
)
|
||||
);
|
||||
height: calc(-81px + 100vh - var(--safe-area-inset-top, 0px));
|
||||
height: calc(-81px + 100dvh - var(--safe-area-inset-top, 0px));
|
||||
width: var(--sidebar-width);
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -213,6 +213,7 @@ class HaConfigEnergy extends LitElement {
|
||||
this.hass.states[key],
|
||||
])
|
||||
),
|
||||
issues: this._validationResult,
|
||||
};
|
||||
const json = JSON.stringify(data, null, 2);
|
||||
const blob = new Blob([json], { type: "application/json" });
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { consume } from "@lit/context";
|
||||
import {
|
||||
mdiAppleKeyboardCommand,
|
||||
mdiCog,
|
||||
mdiContentSave,
|
||||
mdiDebugStepOver,
|
||||
@@ -64,8 +65,10 @@ import "../../../layouts/hass-subpage";
|
||||
import { KeyboardShortcutMixin } from "../../../mixins/keyboard-shortcut-mixin";
|
||||
import { PreventUnsavedMixin } from "../../../mixins/prevent-unsaved-mixin";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { UndoRedoMixin } from "../../../mixins/undo-redo-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { Entries, HomeAssistant, Route } from "../../../types";
|
||||
import { isMac } from "../../../util/is_mac";
|
||||
import { showToast } from "../../../util/toast";
|
||||
import { showAutomationModeDialog } from "../automation/automation-mode-dialog/show-dialog-automation-mode";
|
||||
import type { EntityRegistryUpdate } from "../automation/automation-save-dialog/show-dialog-automation-save";
|
||||
@@ -74,7 +77,6 @@ import { showAssignCategoryDialog } from "../category/show-dialog-assign-categor
|
||||
import "./blueprint-script-editor";
|
||||
import "./manual-script-editor";
|
||||
import type { HaManualScriptEditor } from "./manual-script-editor";
|
||||
import { UndoRedoMixin } from "../../../mixins/undo-redo-mixin";
|
||||
|
||||
const baseEditorMixins = SubscribeMixin(
|
||||
PreventUnsavedMixin(KeyboardShortcutMixin(LitElement))
|
||||
@@ -168,6 +170,10 @@ export class HaScriptEditor extends UndoRedoMixin<
|
||||
: undefined;
|
||||
|
||||
const useBlueprint = "use_blueprint" in this._config;
|
||||
const shortcutIcon = isMac
|
||||
? html`<ha-svg-icon .path=${mdiAppleKeyboardCommand}></ha-svg-icon>`
|
||||
: this.hass.localize("ui.panel.config.automation.editor.ctrl");
|
||||
|
||||
return html`
|
||||
<hass-subpage
|
||||
.hass=${this.hass}
|
||||
@@ -184,16 +190,41 @@ export class HaScriptEditor extends UndoRedoMixin<
|
||||
.path=${mdiUndo}
|
||||
@click=${this.undo}
|
||||
.disabled=${!this.canUndo}
|
||||
id="button-undo"
|
||||
>
|
||||
</ha-icon-button>
|
||||
<ha-tooltip placement="bottom" for="button-undo">
|
||||
${this.hass.localize("ui.common.undo")}
|
||||
<span class="shortcut">
|
||||
(<span>${shortcutIcon}</span>
|
||||
<span>+</span>
|
||||
<span>Z</span>)
|
||||
</span>
|
||||
</ha-tooltip>
|
||||
<ha-icon-button
|
||||
slot="toolbar-icon"
|
||||
.label=${this.hass.localize("ui.common.redo")}
|
||||
.path=${mdiRedo}
|
||||
@click=${this.redo}
|
||||
.disabled=${!this.canRedo}
|
||||
id="button-redo"
|
||||
>
|
||||
</ha-icon-button>`
|
||||
</ha-icon-button>
|
||||
<ha-tooltip placement="bottom" for="button-redo">
|
||||
${this.hass.localize("ui.common.redo")}
|
||||
<span class="shortcut"
|
||||
>(
|
||||
${isMac
|
||||
? html`<span>${shortcutIcon}</span>
|
||||
<span>+</span>
|
||||
<span>Shift</span>
|
||||
<span>+</span>
|
||||
<span>Z</span>`
|
||||
: html`<span>${shortcutIcon}</span>
|
||||
<span>+</span>
|
||||
<span>Y</span>`})
|
||||
</span>
|
||||
</ha-tooltip>`
|
||||
: nothing}
|
||||
${this.scriptId && !this.narrow
|
||||
? html`
|
||||
@@ -1080,6 +1111,7 @@ export class HaScriptEditor extends UndoRedoMixin<
|
||||
Delete: () => this._deleteSelectedRow(),
|
||||
Backspace: () => this._deleteSelectedRow(),
|
||||
z: () => this.undo(),
|
||||
Z: () => this.redo(),
|
||||
y: () => this.redo(),
|
||||
};
|
||||
}
|
||||
@@ -1133,6 +1165,7 @@ export class HaScriptEditor extends UndoRedoMixin<
|
||||
--ha-automation-editor-width,
|
||||
1540px
|
||||
);
|
||||
--hass-subpage-bottom-inset: 0px;
|
||||
}
|
||||
.yaml-mode {
|
||||
height: 100%;
|
||||
@@ -1233,6 +1266,15 @@ export class HaScriptEditor extends UndoRedoMixin<
|
||||
text-decoration: none;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
ha-tooltip ha-svg-icon {
|
||||
width: 12px;
|
||||
}
|
||||
ha-tooltip .shortcut {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
import { mdiWaterBoiler } from "@mdi/js";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { stopPropagation } from "../../../common/dom/stop_propagation";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { stateColorCss } from "../../../common/entity/state_color";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-button-group";
|
||||
import "../../../components/ha-control-select";
|
||||
import type { ControlSelectOption } from "../../../components/ha-control-select";
|
||||
import "../../../components/ha-control-slider";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import "../../../components/ha-control-select-menu";
|
||||
import type { HaControlSelectMenu } from "../../../components/ha-control-select-menu";
|
||||
import "../../../components/ha-list-item";
|
||||
import type {
|
||||
OperationMode,
|
||||
WaterHeaterEntity,
|
||||
} from "../../../data/water_heater";
|
||||
import {
|
||||
compareWaterHeaterOperationMode,
|
||||
computeOperationModeIcon,
|
||||
compareWaterHeaterOperationMode,
|
||||
} from "../../../data/water_heater";
|
||||
import { UNAVAILABLE } from "../../../data/entity";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types";
|
||||
import { cardFeatureStyles } from "./common/card-feature-styles";
|
||||
import { filterModes } from "./common/filter-modes";
|
||||
import type {
|
||||
LovelaceCardFeatureContext,
|
||||
WaterHeaterOperationModesCardFeatureConfig,
|
||||
LovelaceCardFeatureContext,
|
||||
} from "./types";
|
||||
|
||||
export const supportsWaterHeaterOperationModesCardFeature = (
|
||||
@@ -52,6 +54,9 @@ class HuiWaterHeaterOperationModeCardFeature
|
||||
|
||||
@state() _currentOperationMode?: OperationMode;
|
||||
|
||||
@query("ha-control-select-menu", true)
|
||||
private _haSelect?: HaControlSelectMenu;
|
||||
|
||||
private get _stateObj() {
|
||||
if (!this.hass || !this.context || !this.context.entity_id) {
|
||||
return undefined;
|
||||
@@ -97,8 +102,23 @@ class HuiWaterHeaterOperationModeCardFeature
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues) {
|
||||
super.updated(changedProps);
|
||||
if (this._haSelect && changedProps.has("hass")) {
|
||||
const oldHass = changedProps.get("hass") as HomeAssistant | undefined;
|
||||
if (
|
||||
this.hass &&
|
||||
this.hass.formatEntityAttributeValue !==
|
||||
oldHass?.formatEntityAttributeValue
|
||||
) {
|
||||
this._haSelect.layoutOptions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async _valueChanged(ev: CustomEvent) {
|
||||
const mode = (ev.detail as any).value as OperationMode;
|
||||
const mode =
|
||||
(ev.detail as any).value ?? ((ev.target as any).value as OperationMode);
|
||||
|
||||
if (mode === this._stateObj!.state) return;
|
||||
|
||||
@@ -143,9 +163,48 @@ class HuiWaterHeaterOperationModeCardFeature
|
||||
).map<ControlSelectOption>((mode) => ({
|
||||
value: mode,
|
||||
label: this.hass!.formatEntityState(this._stateObj!, mode),
|
||||
path: computeOperationModeIcon(mode as OperationMode),
|
||||
icon: html`
|
||||
<ha-svg-icon
|
||||
slot="graphic"
|
||||
.path=${computeOperationModeIcon(mode as OperationMode)}
|
||||
></ha-svg-icon>
|
||||
`,
|
||||
}));
|
||||
|
||||
if (this._config.style === "dropdown") {
|
||||
return html`
|
||||
<ha-control-select-menu
|
||||
show-arrow
|
||||
hide-label
|
||||
.label=${this.hass.localize("ui.card.water_heater.mode")}
|
||||
.value=${this._currentOperationMode}
|
||||
.disabled=${this._stateObj.state === UNAVAILABLE}
|
||||
fixedMenuPosition
|
||||
naturalMenuWidth
|
||||
@selected=${this._valueChanged}
|
||||
@closed=${stopPropagation}
|
||||
>
|
||||
${this._currentOperationMode
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
slot="icon"
|
||||
.path=${computeOperationModeIcon(this._currentOperationMode)}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: html`
|
||||
<ha-svg-icon slot="icon" .path=${mdiWaterBoiler}></ha-svg-icon>
|
||||
`}
|
||||
${options.map(
|
||||
(option) => html`
|
||||
<ha-list-item .value=${option.value} graphic="icon">
|
||||
${option.icon}${option.label}
|
||||
</ha-list-item>
|
||||
`
|
||||
)}
|
||||
</ha-control-select-menu>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-control-select
|
||||
.options=${options}
|
||||
|
||||
@@ -140,6 +140,7 @@ export interface ToggleCardFeatureConfig {
|
||||
|
||||
export interface WaterHeaterOperationModesCardFeatureConfig {
|
||||
type: "water-heater-operation-modes";
|
||||
style?: "dropdown" | "icons";
|
||||
operation_modes?: OperationMode[];
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
} from "../../../../../common/datetime/format_date";
|
||||
import { formatTime } from "../../../../../common/datetime/format_time";
|
||||
import type { ECOption } from "../../../../../resources/echarts";
|
||||
import { filterXSS } from "../../../../../common/util/xss";
|
||||
|
||||
export function getSuggestedMax(dayDifference: number, end: Date): number {
|
||||
let suggestedMax = new Date(end);
|
||||
@@ -191,7 +192,7 @@ function formatTooltip(
|
||||
countNegative++;
|
||||
}
|
||||
}
|
||||
return `${param.marker} ${param.seriesName}: ${value} ${unit}`;
|
||||
return `${param.marker} ${filterXSS(param.seriesName!)}: ${value} ${unit}`;
|
||||
})
|
||||
.filter(Boolean);
|
||||
let footer = "";
|
||||
|
||||
@@ -6,6 +6,7 @@ import { classMap } from "lit/directives/class-map";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { BarSeriesOption } from "echarts/charts";
|
||||
import type { ECElementEvent } from "echarts/types/dist/shared";
|
||||
import { filterXSS } from "../../../../common/util/xss";
|
||||
import { getGraphColorByIndex } from "../../../../common/color/colors";
|
||||
import { formatNumber } from "../../../../common/number/format_number";
|
||||
import "../../../../components/chart/ha-chart-base";
|
||||
@@ -96,9 +97,8 @@ export class HuiEnergyDevicesGraphCard
|
||||
}
|
||||
|
||||
private _renderTooltip(params: any) {
|
||||
const title = `<h4 style="text-align: center; margin: 0;">${this._getDeviceName(
|
||||
params.value[1]
|
||||
)}</h4>`;
|
||||
const deviceName = filterXSS(this._getDeviceName(params.value[1]));
|
||||
const title = `<h4 style="text-align: center; margin: 0;">${deviceName}</h4>`;
|
||||
const value = `${formatNumber(
|
||||
params.value[0] as number,
|
||||
this.hass.locale,
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
} from "../../card-features/types";
|
||||
import type { LovelaceCardFeatureEditor } from "../../types";
|
||||
import { compareWaterHeaterOperationMode } from "../../../../data/water_heater";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
|
||||
type WaterHeaterOperationModesCardFeatureData =
|
||||
WaterHeaterOperationModesCardFeatureConfig & {
|
||||
@@ -39,11 +40,27 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor
|
||||
|
||||
private _schema = memoizeOne(
|
||||
(
|
||||
localize: LocalizeFunc,
|
||||
formatEntityState: FormatEntityStateFunc,
|
||||
stateObj: HassEntity | undefined,
|
||||
customizeModes: boolean
|
||||
) =>
|
||||
[
|
||||
{
|
||||
name: "style",
|
||||
selector: {
|
||||
select: {
|
||||
multiple: false,
|
||||
mode: "list",
|
||||
options: ["dropdown", "icons"].map((mode) => ({
|
||||
value: mode,
|
||||
label: localize(
|
||||
`ui.panel.lovelace.editor.features.types.water-heater-operation-modes.style_list.${mode}`
|
||||
),
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "customize_modes",
|
||||
selector: {
|
||||
@@ -85,11 +102,13 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor
|
||||
: undefined;
|
||||
|
||||
const data: WaterHeaterOperationModesCardFeatureData = {
|
||||
style: "icons",
|
||||
...this._config,
|
||||
customize_modes: this._config.operation_modes !== undefined,
|
||||
};
|
||||
|
||||
const schema = this._schema(
|
||||
this.hass.localize,
|
||||
this.hass.formatEntityState,
|
||||
stateObj,
|
||||
data.customize_modes
|
||||
@@ -131,6 +150,7 @@ export class HuiWaterHeaterOperationModesCardFeatureEditor
|
||||
) => {
|
||||
switch (schema.name) {
|
||||
case "operation_modes":
|
||||
case "style":
|
||||
case "customize_modes":
|
||||
return this.hass!.localize(
|
||||
`ui.panel.lovelace.editor.features.types.water-heater-operation-modes.${schema.name}`
|
||||
|
||||
@@ -318,7 +318,7 @@ class HUIRoot extends LitElement {
|
||||
menu-corner="END"
|
||||
>
|
||||
<ha-icon-button
|
||||
.label=${label}
|
||||
.id="button-${index}"
|
||||
.path=${item.icon}
|
||||
slot="trigger"
|
||||
></ha-icon-button>
|
||||
@@ -340,6 +340,9 @@ class HUIRoot extends LitElement {
|
||||
`
|
||||
)}
|
||||
</ha-button-menu>
|
||||
<ha-tooltip placement="bottom" .for="button-${index}">
|
||||
${label}
|
||||
</ha-tooltip>
|
||||
`
|
||||
: html`
|
||||
<ha-icon-button
|
||||
|
||||
@@ -14,7 +14,15 @@ import {
|
||||
polyfillTimeZoneData,
|
||||
} from "./locale-data-polyfill";
|
||||
|
||||
let polyfilled = false;
|
||||
|
||||
const _polyfillTimeZoneData = polyfillTimeZoneData;
|
||||
|
||||
const polyfillIntl = async () => {
|
||||
if (polyfilled) {
|
||||
return;
|
||||
}
|
||||
polyfilled = true;
|
||||
const locale = getLocalLanguage();
|
||||
const polyfills: Promise<unknown>[] = [];
|
||||
if (shouldPolyfillGetCanonicalLocales()) {
|
||||
@@ -26,7 +34,7 @@ const polyfillIntl = async () => {
|
||||
if (shouldPolyfillDateTimeFormat(locale)) {
|
||||
polyfills.push(
|
||||
import("@formatjs/intl-datetimeformat/polyfill-force").then(() =>
|
||||
polyfillTimeZoneData()
|
||||
_polyfillTimeZoneData()
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -58,7 +66,7 @@ const polyfillIntl = async () => {
|
||||
if (polyfills.length === 0) {
|
||||
return;
|
||||
}
|
||||
await Promise.all(polyfills).then(() =>
|
||||
await Promise.allSettled(polyfills).then(() =>
|
||||
// Load the default language
|
||||
polyfillLocaleData(locale)
|
||||
);
|
||||
|
||||
@@ -157,8 +157,13 @@ export const haStyleDialog = css`
|
||||
ha-dialog {
|
||||
--mdc-dialog-min-width: 100vw;
|
||||
--mdc-dialog-max-width: 100vw;
|
||||
--mdc-dialog-min-height: 100%;
|
||||
--mdc-dialog-max-height: 100%;
|
||||
--mdc-dialog-min-height: 100vh;
|
||||
--mdc-dialog-min-height: 100svh;
|
||||
--mdc-dialog-max-height: 100vh;
|
||||
--mdc-dialog-max-height: 100svh;
|
||||
--dialog-surface-padding: var(--safe-area-inset-top)
|
||||
var(--safe-area-inset-right) var(--safe-area-inset-bottom)
|
||||
var(--safe-area-inset-left);
|
||||
--vertical-align-dialog: flex-end;
|
||||
--ha-dialog-border-radius: 0;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,29 @@ export const coreStyles = css`
|
||||
--ha-border-radius-pill: 9999px;
|
||||
--ha-border-radius-circle: 50%;
|
||||
--ha-border-radius-square: 0;
|
||||
|
||||
/* Spacing */
|
||||
--ha-space-0: 0px;
|
||||
--ha-space-1: 4px;
|
||||
--ha-space-2: 8px;
|
||||
--ha-space-3: 12px;
|
||||
--ha-space-4: 16px;
|
||||
--ha-space-5: 20px;
|
||||
--ha-space-6: 24px;
|
||||
--ha-space-7: 28px;
|
||||
--ha-space-8: 32px;
|
||||
--ha-space-9: 36px;
|
||||
--ha-space-10: 40px;
|
||||
--ha-space-11: 44px;
|
||||
--ha-space-12: 48px;
|
||||
--ha-space-13: 52px;
|
||||
--ha-space-14: 56px;
|
||||
--ha-space-15: 60px;
|
||||
--ha-space-16: 64px;
|
||||
--ha-space-17: 68px;
|
||||
--ha-space-18: 72px;
|
||||
--ha-space-19: 76px;
|
||||
--ha-space-20: 80px;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
@@ -8217,7 +8217,12 @@
|
||||
"water-heater-operation-modes": {
|
||||
"label": "Water heater operation modes",
|
||||
"operation_modes": "Operation modes",
|
||||
"customize_modes": "Customize operation modes"
|
||||
"customize_modes": "Customize operation modes",
|
||||
"style": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style%]",
|
||||
"style_list": {
|
||||
"dropdown": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::dropdown%]",
|
||||
"icons": "[%key:ui::panel::lovelace::editor::features::types::climate-preset-modes::style_list::icons%]"
|
||||
}
|
||||
},
|
||||
"lawn-mower-commands": {
|
||||
"label": "Lawn mower commands",
|
||||
|
||||
28
yarn.lock
28
yarn.lock
@@ -1351,10 +1351,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ctrl/tinycolor@npm:^4.1.0":
|
||||
version: 4.2.0
|
||||
resolution: "@ctrl/tinycolor@npm:4.2.0"
|
||||
checksum: 10/1be14de7d7e8184c0bc5c8d7e3486cc8186e6702e8ca899c7239f328bb1df9a15d1575e2af7b4c6ba020727fa78f5a9f887555971f30a2890cece9e4253a9d3a
|
||||
"@ctrl/tinycolor@npm:4.1.0":
|
||||
version: 4.1.0
|
||||
resolution: "@ctrl/tinycolor@npm:4.1.0"
|
||||
checksum: 10/e64569399139ef0abd2eb0ec9fb7267dfd7820f7ad7d4567a63e5fc35e5cfdcb8ecdb3bad65cb9244b47ba6c77bc51085826c00e981acf263a3221dc89343adc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1940,11 +1940,11 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3":
|
||||
version: 3.0.0-beta.4.ha.3
|
||||
resolution: "@home-assistant/webawesome@npm:3.0.0-beta.4.ha.3"
|
||||
"@home-assistant/webawesome@npm:3.0.0-beta.6.ha.4":
|
||||
version: 3.0.0-beta.6.ha.4
|
||||
resolution: "@home-assistant/webawesome@npm:3.0.0-beta.6.ha.4"
|
||||
dependencies:
|
||||
"@ctrl/tinycolor": "npm:^4.1.0"
|
||||
"@ctrl/tinycolor": "npm:4.1.0"
|
||||
"@floating-ui/dom": "npm:^1.6.13"
|
||||
"@lit/react": "npm:^1.0.8"
|
||||
"@shoelace-style/animations": "npm:^1.2.0"
|
||||
@@ -1953,8 +1953,7 @@ __metadata:
|
||||
lit: "npm:^3.2.1"
|
||||
nanoid: "npm:^5.1.5"
|
||||
qr-creator: "npm:^1.0.0"
|
||||
style-observer: "npm:^0.0.7"
|
||||
checksum: 10/b9241821ed471ccbad86b0ea4697a2d41395f05fdc26f46e5edbc7f6b5eeab5d248251ef702326312ded00d5bf850ce0dcdcf7cd5e2e542b9d9cb9a84f3726da
|
||||
checksum: 10/d9072b321126ef458468ed2cf040e0b04cb2aff73336c6e742c0cfb25d9fb674b7672e7c9abcf5bcb0aa0b2fe953c20186f0910f485024c827bfe4cf399f10a4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9397,7 +9396,7 @@ __metadata:
|
||||
"@fullcalendar/list": "npm:6.1.19"
|
||||
"@fullcalendar/luxon3": "npm:6.1.19"
|
||||
"@fullcalendar/timegrid": "npm:6.1.19"
|
||||
"@home-assistant/webawesome": "npm:3.0.0-beta.4.ha.3"
|
||||
"@home-assistant/webawesome": "npm:3.0.0-beta.6.ha.4"
|
||||
"@lezer/highlight": "npm:1.2.1"
|
||||
"@lit-labs/motion": "npm:1.0.9"
|
||||
"@lit-labs/observers": "npm:2.0.6"
|
||||
@@ -14003,13 +14002,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"style-observer@npm:^0.0.7":
|
||||
version: 0.0.7
|
||||
resolution: "style-observer@npm:0.0.7"
|
||||
checksum: 10/bb57f98bae4463c1e1b57234f8ffe72ec0de27fb08b032c1919910129c210aacd1ddd615432b9453d491e10d3b719cf6c2a68a97165ca55d6fc9b86c0fca37fb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"style-observer@npm:^0.0.8":
|
||||
version: 0.0.8
|
||||
resolution: "style-observer@npm:0.0.8"
|
||||
|
||||
Reference in New Issue
Block a user