mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-28 19:56:42 +00:00
Merge branch 'dev' into rc
This commit is contained in:
commit
f416b1b5da
@ -27,7 +27,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "7.26.0",
|
"@babel/runtime": "7.26.0",
|
||||||
"@braintree/sanitize-url": "7.1.0",
|
"@braintree/sanitize-url": "7.1.1",
|
||||||
"@codemirror/autocomplete": "6.18.4",
|
"@codemirror/autocomplete": "6.18.4",
|
||||||
"@codemirror/commands": "6.7.1",
|
"@codemirror/commands": "6.7.1",
|
||||||
"@codemirror/language": "6.10.7",
|
"@codemirror/language": "6.10.7",
|
||||||
@ -139,7 +139,7 @@
|
|||||||
"tinykeys": "3.0.0",
|
"tinykeys": "3.0.0",
|
||||||
"tsparticles-engine": "2.12.0",
|
"tsparticles-engine": "2.12.0",
|
||||||
"tsparticles-preset-links": "2.12.0",
|
"tsparticles-preset-links": "2.12.0",
|
||||||
"ua-parser-js": "1.0.39",
|
"ua-parser-js": "1.0.40",
|
||||||
"vis-data": "7.1.9",
|
"vis-data": "7.1.9",
|
||||||
"vis-network": "9.1.9",
|
"vis-network": "9.1.9",
|
||||||
"vue": "2.7.16",
|
"vue": "2.7.16",
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "home-assistant-frontend"
|
name = "home-assistant-frontend"
|
||||||
version = "20241223.1"
|
version = "20241224.0"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "The Home Assistant frontend"
|
description = "The Home Assistant frontend"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -61,6 +61,8 @@ export class HaChartBase extends LitElement {
|
|||||||
|
|
||||||
@state() private _chartHeight?: number;
|
@state() private _chartHeight?: number;
|
||||||
|
|
||||||
|
@state() private _legendHeight?: number;
|
||||||
|
|
||||||
@state() private _tooltip?: Tooltip;
|
@state() private _tooltip?: Tooltip;
|
||||||
|
|
||||||
@state() private _hiddenDatasets: Set<number> = new Set();
|
@state() private _hiddenDatasets: Set<number> = new Set();
|
||||||
@ -214,10 +216,22 @@ export class HaChartBase extends LitElement {
|
|||||||
this.chart.update("none");
|
this.chart.update("none");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected updated(changedProperties: PropertyValues): void {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
if (changedProperties.has("data") || changedProperties.has("options")) {
|
||||||
|
if (this.options?.plugins?.legend?.display) {
|
||||||
|
this._legendHeight =
|
||||||
|
this.renderRoot.querySelector(".chart-legend")?.clientHeight;
|
||||||
|
} else {
|
||||||
|
this._legendHeight = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
${this.options?.plugins?.legend?.display === true
|
${this.options?.plugins?.legend?.display === true
|
||||||
? html`<div class="chartLegend">
|
? html`<div class="chart-legend">
|
||||||
<ul>
|
<ul>
|
||||||
${this._datasetOrder.map((index) => {
|
${this._datasetOrder.map((index) => {
|
||||||
const dataset = this.data.datasets[index];
|
const dataset = this.data.datasets[index];
|
||||||
@ -249,7 +263,7 @@ export class HaChartBase extends LitElement {
|
|||||||
</div>`
|
</div>`
|
||||||
: ""}
|
: ""}
|
||||||
<div
|
<div
|
||||||
class="animationContainer"
|
class="animation-container"
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
height: `${this.height || this._chartHeight || 0}px`,
|
height: `${this.height || this._chartHeight || 0}px`,
|
||||||
overflow: this._chartHeight ? "initial" : "hidden",
|
overflow: this._chartHeight ? "initial" : "hidden",
|
||||||
@ -288,7 +302,7 @@ export class HaChartBase extends LitElement {
|
|||||||
</div>
|
</div>
|
||||||
${this._tooltip
|
${this._tooltip
|
||||||
? html`<div
|
? html`<div
|
||||||
class="chartTooltip ${classMap({
|
class="chart-tooltip ${classMap({
|
||||||
[this._tooltip.yAlign]: true,
|
[this._tooltip.yAlign]: true,
|
||||||
})}"
|
})}"
|
||||||
style=${styleMap({
|
style=${styleMap({
|
||||||
@ -298,7 +312,7 @@ export class HaChartBase extends LitElement {
|
|||||||
>
|
>
|
||||||
<div class="title">${this._tooltip.title}</div>
|
<div class="title">${this._tooltip.title}</div>
|
||||||
${this._tooltip.beforeBody
|
${this._tooltip.beforeBody
|
||||||
? html`<div class="beforeBody">
|
? html`<div class="before-body">
|
||||||
${this._tooltip.beforeBody}
|
${this._tooltip.beforeBody}
|
||||||
</div>`
|
</div>`
|
||||||
: ""}
|
: ""}
|
||||||
@ -456,6 +470,7 @@ export class HaChartBase extends LitElement {
|
|||||||
|
|
||||||
private _handleChartScroll(ev: MouseEvent) {
|
private _handleChartScroll(ev: MouseEvent) {
|
||||||
const modifier = isMac ? "metaKey" : "ctrlKey";
|
const modifier = isMac ? "metaKey" : "ctrlKey";
|
||||||
|
this._tooltip = undefined;
|
||||||
if (!ev[modifier] && !this._showZoomHint) {
|
if (!ev[modifier] && !this._showZoomHint) {
|
||||||
this._showZoomHint = true;
|
this._showZoomHint = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -498,15 +513,20 @@ export class HaChartBase extends LitElement {
|
|||||||
this._tooltip = undefined;
|
this._tooltip = undefined;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const boundingBox = this.getBoundingClientRect();
|
||||||
this._tooltip = {
|
this._tooltip = {
|
||||||
...context.tooltip,
|
...context.tooltip,
|
||||||
top: this.chart!.canvas.offsetTop + context.tooltip.caretY + 12 + "px",
|
top:
|
||||||
|
boundingBox.y +
|
||||||
|
(this._legendHeight || 0) +
|
||||||
|
context.tooltip.caretY +
|
||||||
|
12 +
|
||||||
|
"px",
|
||||||
left:
|
left:
|
||||||
this.chart!.canvas.offsetLeft +
|
|
||||||
clamp(
|
clamp(
|
||||||
context.tooltip.caretX,
|
boundingBox.x + context.tooltip.caretX,
|
||||||
100,
|
boundingBox.x + 100,
|
||||||
this.clientWidth - 100 - this._paddingYAxisInternal
|
boundingBox.x + boundingBox.width - 100
|
||||||
) -
|
) -
|
||||||
100 +
|
100 +
|
||||||
"px",
|
"px",
|
||||||
@ -525,16 +545,13 @@ export class HaChartBase extends LitElement {
|
|||||||
return css`
|
return css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
position: var(--chart-base-position, relative);
|
position: relative;
|
||||||
}
|
}
|
||||||
.animationContainer {
|
.animation-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
height: 0;
|
height: 0;
|
||||||
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
.chart-container {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
canvas {
|
canvas {
|
||||||
max-height: var(--chart-max-height, 400px);
|
max-height: var(--chart-max-height, 400px);
|
||||||
}
|
}
|
||||||
@ -542,10 +559,10 @@ export class HaChartBase extends LitElement {
|
|||||||
/* allow scrolling if the chart is not zoomed */
|
/* allow scrolling if the chart is not zoomed */
|
||||||
touch-action: pan-y !important;
|
touch-action: pan-y !important;
|
||||||
}
|
}
|
||||||
.chartLegend {
|
.chart-legend {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
.chartLegend li {
|
.chart-legend li {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
@ -554,16 +571,16 @@ export class HaChartBase extends LitElement {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--secondary-text-color);
|
color: var(--secondary-text-color);
|
||||||
}
|
}
|
||||||
.chartLegend .hidden {
|
.chart-legend .hidden {
|
||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
.chartLegend .label {
|
.chart-legend .label {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.chartLegend .bullet,
|
.chart-legend .bullet,
|
||||||
.chartTooltip .bullet {
|
.chart-tooltip .bullet {
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
@ -577,13 +594,13 @@ export class HaChartBase extends LitElement {
|
|||||||
margin-inline-start: initial;
|
margin-inline-start: initial;
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
.chartTooltip .bullet {
|
.chart-tooltip .bullet {
|
||||||
align-self: baseline;
|
align-self: baseline;
|
||||||
}
|
}
|
||||||
.chartTooltip {
|
.chart-tooltip {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
position: absolute;
|
position: fixed;
|
||||||
background: rgba(80, 80, 80, 0.9);
|
background: rgba(80, 80, 80, 0.9);
|
||||||
color: white;
|
color: white;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -596,17 +613,17 @@ export class HaChartBase extends LitElement {
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
direction: var(--direction);
|
direction: var(--direction);
|
||||||
}
|
}
|
||||||
.chartLegend ul,
|
.chart-legend ul,
|
||||||
.chartTooltip ul {
|
.chart-tooltip ul {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0 0px;
|
padding: 0 0px;
|
||||||
margin: 8px 0 0 0;
|
margin: 8px 0 0 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.chartTooltip ul {
|
.chart-tooltip ul {
|
||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
}
|
}
|
||||||
.chartTooltip li {
|
.chart-tooltip li {
|
||||||
display: flex;
|
display: flex;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
@ -614,16 +631,16 @@ export class HaChartBase extends LitElement {
|
|||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
.chartTooltip .title {
|
.chart-tooltip .title {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
.chartTooltip .footer {
|
.chart-tooltip .footer {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
.chartTooltip .beforeBody {
|
.chart-tooltip .before-body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
|
@ -210,11 +210,9 @@ class DialogMediaManage extends LitElement {
|
|||||||
href="/config/storage"
|
href="/config/storage"
|
||||||
@click=${this.closeDialog}
|
@click=${this.closeDialog}
|
||||||
>
|
>
|
||||||
${this.hass
|
${this.hass.localize(
|
||||||
.localize(
|
|
||||||
"ui.components.media-browser.file_management.tip_storage_panel"
|
"ui.components.media-browser.file_management.tip_storage_panel"
|
||||||
)
|
)}
|
||||||
.toLowerCase()}
|
|
||||||
</a>`,
|
</a>`,
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
|
@ -1,6 +1,16 @@
|
|||||||
|
import { setHours, setMinutes } from "date-fns";
|
||||||
|
import type { HassConfig } from "home-assistant-js-websocket";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
|
import { formatTime } from "../common/datetime/format_time";
|
||||||
import type { LocalizeFunc } from "../common/translations/localize";
|
import type { LocalizeFunc } from "../common/translations/localize";
|
||||||
import type { HomeAssistant } from "../types";
|
import type { HomeAssistant } from "../types";
|
||||||
import { domainToName } from "./integration";
|
import { domainToName } from "./integration";
|
||||||
|
import type { FrontendLocaleData } from "./translation";
|
||||||
|
import {
|
||||||
|
formatDateTime,
|
||||||
|
formatDateTimeNumeric,
|
||||||
|
} from "../common/datetime/format_date_time";
|
||||||
|
import { fileDownload } from "../util/file_download";
|
||||||
|
|
||||||
export const enum BackupScheduleState {
|
export const enum BackupScheduleState {
|
||||||
NEVER = "never",
|
NEVER = "never",
|
||||||
@ -282,3 +292,49 @@ export const generateEncryptionKey = () => {
|
|||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const generateEmergencyKit = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
encryptionKey: string
|
||||||
|
) =>
|
||||||
|
"data:text/plain;charset=utf-8," +
|
||||||
|
encodeURIComponent(`Home Assistant Backup Emergency Kit
|
||||||
|
|
||||||
|
This emergency kit contains your backup encryption key. You need this key
|
||||||
|
to be able to restore your Home Assistant backups.
|
||||||
|
|
||||||
|
Date: ${formatDateTime(new Date(), hass.locale, hass.config)}
|
||||||
|
|
||||||
|
Instance:
|
||||||
|
${hass.config.location_name}
|
||||||
|
|
||||||
|
URL:
|
||||||
|
${hass.auth.data.hassUrl}
|
||||||
|
|
||||||
|
Encryption key:
|
||||||
|
${encryptionKey}
|
||||||
|
|
||||||
|
For more information visit: https://www.home-assistant.io/more-info/backup-emergency-kit`);
|
||||||
|
|
||||||
|
export const geneateEmergencyKitFileName = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
append?: string
|
||||||
|
) =>
|
||||||
|
`home_assistant_backup_emergency_kit_${append ? `${append}_` : ""}${formatDateTimeNumeric(new Date(), hass.locale, hass.config).replace(",", "").replace(" ", "_")}.txt`;
|
||||||
|
|
||||||
|
export const downloadEmergencyKit = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
key: string,
|
||||||
|
appendFileName?: string
|
||||||
|
) =>
|
||||||
|
fileDownload(
|
||||||
|
generateEmergencyKit(hass, key),
|
||||||
|
geneateEmergencyKitFileName(hass, appendFileName)
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getFormattedBackupTime = memoizeOne(
|
||||||
|
(locale: FrontendLocaleData, config: HassConfig) => {
|
||||||
|
const date = setMinutes(setHours(new Date(), 4), 45);
|
||||||
|
return formatTime(date, locale, config);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
@ -50,7 +50,7 @@ export const showConfigFlowDialog = (
|
|||||||
|
|
||||||
return description
|
return description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown allowsvg breaks .content=${description}></ha-markdown>
|
<ha-markdown allow-svg breaks .content=${description}></ha-markdown>
|
||||||
`
|
`
|
||||||
: step.reason;
|
: step.reason;
|
||||||
},
|
},
|
||||||
@ -71,7 +71,7 @@ export const showConfigFlowDialog = (
|
|||||||
);
|
);
|
||||||
return description
|
return description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown allowsvg breaks .content=${description}></ha-markdown>
|
<ha-markdown allow-svg breaks .content=${description}></ha-markdown>
|
||||||
`
|
`
|
||||||
: "";
|
: "";
|
||||||
},
|
},
|
||||||
@ -163,7 +163,7 @@ export const showConfigFlowDialog = (
|
|||||||
${description
|
${description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
allowsvg
|
allow-svg
|
||||||
breaks
|
breaks
|
||||||
.content=${description}
|
.content=${description}
|
||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
@ -184,7 +184,7 @@ export const showConfigFlowDialog = (
|
|||||||
${description
|
${description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
allowsvg
|
allow-svg
|
||||||
breaks
|
breaks
|
||||||
.content=${description}
|
.content=${description}
|
||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
@ -214,7 +214,7 @@ export const showConfigFlowDialog = (
|
|||||||
);
|
);
|
||||||
return description
|
return description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown allowsvg breaks .content=${description}></ha-markdown>
|
<ha-markdown allow-svg breaks .content=${description}></ha-markdown>
|
||||||
`
|
`
|
||||||
: "";
|
: "";
|
||||||
},
|
},
|
||||||
@ -234,7 +234,7 @@ export const showConfigFlowDialog = (
|
|||||||
);
|
);
|
||||||
return description
|
return description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown allowsvg breaks .content=${description}></ha-markdown>
|
<ha-markdown allow-svg breaks .content=${description}></ha-markdown>
|
||||||
`
|
`
|
||||||
: "";
|
: "";
|
||||||
},
|
},
|
||||||
|
@ -61,7 +61,7 @@ export const showOptionsFlowDialog = (
|
|||||||
? html`
|
? html`
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
breaks
|
breaks
|
||||||
allowsvg
|
allow-svg
|
||||||
.content=${description}
|
.content=${description}
|
||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
`
|
`
|
||||||
@ -85,7 +85,7 @@ export const showOptionsFlowDialog = (
|
|||||||
return description
|
return description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
allowsvg
|
allow-svg
|
||||||
breaks
|
breaks
|
||||||
.content=${description}
|
.content=${description}
|
||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
@ -183,7 +183,7 @@ export const showOptionsFlowDialog = (
|
|||||||
return description
|
return description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
allowsvg
|
allow-svg
|
||||||
breaks
|
breaks
|
||||||
.content=${description}
|
.content=${description}
|
||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
@ -207,7 +207,7 @@ export const showOptionsFlowDialog = (
|
|||||||
return description
|
return description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
allowsvg
|
allow-svg
|
||||||
breaks
|
breaks
|
||||||
.content=${description}
|
.content=${description}
|
||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
|
@ -51,7 +51,6 @@ class StepFlowAbort extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _handleMissingCreds() {
|
private async _handleMissingCreds() {
|
||||||
this._flowDone();
|
|
||||||
// Prompt to enter credentials and restart integration setup
|
// Prompt to enter credentials and restart integration setup
|
||||||
showAddApplicationCredentialDialog(this.params.dialogParentElement!, {
|
showAddApplicationCredentialDialog(this.params.dialogParentElement!, {
|
||||||
selectedDomain: this.domain,
|
selectedDomain: this.domain,
|
||||||
@ -64,6 +63,7 @@ class StepFlowAbort extends LitElement {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
this._flowDone();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _flowDone(): void {
|
private _flowDone(): void {
|
||||||
|
@ -213,9 +213,10 @@ class MoreInfoMediaPlayer extends LitElement {
|
|||||||
|
|
||||||
ha-icon-button[action="turn_off"],
|
ha-icon-button[action="turn_off"],
|
||||||
ha-icon-button[action="turn_on"] {
|
ha-icon-button[action="turn_on"] {
|
||||||
margin-inline-end: auto;
|
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
margin-left: inherit;
|
margin-left: inherit;
|
||||||
|
margin-inline-start: inherit;
|
||||||
|
margin-inline-end: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
|
@ -545,11 +545,7 @@ export class MoreInfoDialog extends LitElement {
|
|||||||
/* Set the top top of the dialog to a fixed position, so it doesnt jump when the content changes size */
|
/* Set the top top of the dialog to a fixed position, so it doesnt jump when the content changes size */
|
||||||
--vertical-align-dialog: flex-start;
|
--vertical-align-dialog: flex-start;
|
||||||
--dialog-surface-margin-top: 40px;
|
--dialog-surface-margin-top: 40px;
|
||||||
/* This is needed for the tooltip of the history charts to be positioned correctly */
|
|
||||||
--dialog-surface-position: static;
|
|
||||||
--dialog-content-position: static;
|
|
||||||
--dialog-content-padding: 0;
|
--dialog-content-padding: 0;
|
||||||
--chart-base-position: static;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
|
@ -40,6 +40,7 @@ import { loadVirtualizer } from "../../resources/virtualizer";
|
|||||||
import type { HomeAssistant } from "../../types";
|
import type { HomeAssistant } from "../../types";
|
||||||
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
import { showConfirmationDialog } from "../generic/show-dialog-box";
|
||||||
import { QuickBarMode, type QuickBarParams } from "./show-dialog-quick-bar";
|
import { QuickBarMode, type QuickBarParams } from "./show-dialog-quick-bar";
|
||||||
|
import { computeDeviceName } from "../../data/device_registry";
|
||||||
|
|
||||||
interface QuickBarItem extends ScorableTextItem {
|
interface QuickBarItem extends ScorableTextItem {
|
||||||
primaryText: string;
|
primaryText: string;
|
||||||
@ -522,12 +523,14 @@ export class QuickBar extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _generateDeviceItems(): DeviceItem[] {
|
private _generateDeviceItems(): DeviceItem[] {
|
||||||
return Object.keys(this.hass.devices)
|
return Object.values(this.hass.devices)
|
||||||
.map((deviceId) => {
|
.filter((device) => !device.disabled_by)
|
||||||
const device = this.hass.devices[deviceId];
|
.map((device) => {
|
||||||
const area = this.hass.areas[device.area_id!];
|
const area = device.area_id
|
||||||
|
? this.hass.areas[device.area_id]
|
||||||
|
: undefined;
|
||||||
const deviceItem = {
|
const deviceItem = {
|
||||||
primaryText: device.name!,
|
primaryText: computeDeviceName(device, this.hass),
|
||||||
deviceId: device.id,
|
deviceId: device.id,
|
||||||
area: area?.name,
|
area: area?.name,
|
||||||
action: () => navigate(`/config/devices/device/${device.id}`),
|
action: () => navigate(`/config/devices/device/${device.id}`),
|
||||||
|
@ -14,6 +14,7 @@ import "../../category/ha-category-picker";
|
|||||||
import "../../../../components/ha-expansion-panel";
|
import "../../../../components/ha-expansion-panel";
|
||||||
import "../../../../components/chips/ha-chip-set";
|
import "../../../../components/chips/ha-chip-set";
|
||||||
import "../../../../components/chips/ha-assist-chip";
|
import "../../../../components/chips/ha-assist-chip";
|
||||||
|
import "../../../../components/ha-area-picker";
|
||||||
|
|
||||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||||
@ -57,6 +58,7 @@ class DialogAutomationRename extends LitElement implements HassDialog {
|
|||||||
);
|
);
|
||||||
this._newDescription = params.config.description || "";
|
this._newDescription = params.config.description || "";
|
||||||
this._entryUpdates = params.entityRegistryUpdate || {
|
this._entryUpdates = params.entityRegistryUpdate || {
|
||||||
|
area: params.entityRegistryEntry?.area_id || "",
|
||||||
labels: params.entityRegistryEntry?.labels || [],
|
labels: params.entityRegistryEntry?.labels || [],
|
||||||
category: params.entityRegistryEntry?.categories[params.domain] || "",
|
category: params.entityRegistryEntry?.categories[params.domain] || "",
|
||||||
};
|
};
|
||||||
@ -66,6 +68,7 @@ class DialogAutomationRename extends LitElement implements HassDialog {
|
|||||||
this._newIcon ? "icon" : "",
|
this._newIcon ? "icon" : "",
|
||||||
this._entryUpdates.category ? "category" : "",
|
this._entryUpdates.category ? "category" : "",
|
||||||
this._entryUpdates.labels.length > 0 ? "labels" : "",
|
this._entryUpdates.labels.length > 0 ? "labels" : "",
|
||||||
|
this._entryUpdates.area ? "area" : "",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,6 +196,14 @@ class DialogAutomationRename extends LitElement implements HassDialog {
|
|||||||
@value-changed=${this._registryEntryChanged}
|
@value-changed=${this._registryEntryChanged}
|
||||||
></ha-labels-picker>`
|
></ha-labels-picker>`
|
||||||
: nothing}
|
: nothing}
|
||||||
|
${this._visibleOptionals.includes("area")
|
||||||
|
? html` <ha-area-picker
|
||||||
|
id="area"
|
||||||
|
.hass=${this.hass}
|
||||||
|
.value=${this._entryUpdates.area}
|
||||||
|
@value-changed=${this._registryEntryChanged}
|
||||||
|
></ha-area-picker>`
|
||||||
|
: nothing}
|
||||||
|
|
||||||
<ha-chip-set>
|
<ha-chip-set>
|
||||||
${this._renderOptionalChip(
|
${this._renderOptionalChip(
|
||||||
@ -209,6 +220,12 @@ class DialogAutomationRename extends LitElement implements HassDialog {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
: nothing}
|
: nothing}
|
||||||
|
${this._renderOptionalChip(
|
||||||
|
"area",
|
||||||
|
this.hass.localize(
|
||||||
|
"ui.panel.config.automation.editor.dialog.add_area"
|
||||||
|
)
|
||||||
|
)}
|
||||||
${this._renderOptionalChip(
|
${this._renderOptionalChip(
|
||||||
"category",
|
"category",
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
@ -311,12 +328,14 @@ class DialogAutomationRename extends LitElement implements HassDialog {
|
|||||||
ha-icon-picker,
|
ha-icon-picker,
|
||||||
ha-category-picker,
|
ha-category-picker,
|
||||||
ha-labels-picker,
|
ha-labels-picker,
|
||||||
|
ha-area-picker,
|
||||||
ha-chip-set {
|
ha-chip-set {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
ha-icon-picker,
|
ha-icon-picker,
|
||||||
ha-category-picker,
|
ha-category-picker,
|
||||||
ha-labels-picker,
|
ha-labels-picker,
|
||||||
|
ha-area-picker,
|
||||||
ha-chip-set {
|
ha-chip-set {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ interface BaseRenameDialogParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityRegistryUpdate {
|
export interface EntityRegistryUpdate {
|
||||||
|
area: string;
|
||||||
labels: string[];
|
labels: string[];
|
||||||
category: string;
|
category: string;
|
||||||
}
|
}
|
||||||
|
@ -167,10 +167,11 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
|||||||
if (
|
if (
|
||||||
this._entityRegCreated &&
|
this._entityRegCreated &&
|
||||||
this._newAutomationId &&
|
this._newAutomationId &&
|
||||||
changedProps.has("entityRegistry")
|
changedProps.has("_entityRegistry")
|
||||||
) {
|
) {
|
||||||
const automation = this._entityRegistry.find(
|
const automation = this._entityRegistry.find(
|
||||||
(entity: EntityRegistryEntry) =>
|
(entity: EntityRegistryEntry) =>
|
||||||
|
entity.platform === "automation" &&
|
||||||
entity.unique_id === this._newAutomationId
|
entity.unique_id === this._newAutomationId
|
||||||
);
|
);
|
||||||
if (automation) {
|
if (automation) {
|
||||||
@ -927,6 +928,14 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
|||||||
this._saving = true;
|
this._saving = true;
|
||||||
this._validationErrors = undefined;
|
this._validationErrors = undefined;
|
||||||
|
|
||||||
|
let entityRegPromise: Promise<EntityRegistryEntry> | undefined;
|
||||||
|
if (this._entityRegistryUpdate !== undefined && !this._entityId) {
|
||||||
|
this._newAutomationId = id;
|
||||||
|
entityRegPromise = new Promise<EntityRegistryEntry>((resolve) => {
|
||||||
|
this._entityRegCreated = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await saveAutomationConfig(this.hass, id, this._config!);
|
await saveAutomationConfig(this.hass, id, this._config!);
|
||||||
|
|
||||||
@ -934,13 +943,8 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
|||||||
let entityId = this._entityId;
|
let entityId = this._entityId;
|
||||||
|
|
||||||
// wait for automation to appear in entity registry when creating a new automation
|
// wait for automation to appear in entity registry when creating a new automation
|
||||||
if (!entityId) {
|
if (entityRegPromise) {
|
||||||
this._newAutomationId = id;
|
const automation = await entityRegPromise;
|
||||||
const automation = await new Promise<EntityRegistryEntry>(
|
|
||||||
(resolve) => {
|
|
||||||
this._entityRegCreated = resolve;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
entityId = automation.entity_id;
|
entityId = automation.entity_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -950,6 +954,7 @@ export class HaAutomationEditor extends PreventUnsavedMixin(
|
|||||||
automation: this._entityRegistryUpdate.category || null,
|
automation: this._entityRegistryUpdate.category || null,
|
||||||
},
|
},
|
||||||
labels: this._entityRegistryUpdate.labels || [],
|
labels: this._entityRegistryUpdate.labels || [],
|
||||||
|
area_id: this._entityRegistryUpdate.area || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ class HaBackupConfigAgents extends LitElement {
|
|||||||
|
|
||||||
private _description(agentId: string) {
|
private _description(agentId: string) {
|
||||||
if (agentId === CLOUD_AGENT) {
|
if (agentId === CLOUD_AGENT) {
|
||||||
return "Note: It stores only one backup, regardless of your settings.";
|
return "Note: It stores only one backup with a maximum size of 5 GB, regardless of your settings.";
|
||||||
}
|
}
|
||||||
if (isNetworkMountAgent(agentId)) {
|
if (isNetworkMountAgent(agentId)) {
|
||||||
return "Network storage";
|
return "Network storage";
|
||||||
|
@ -6,9 +6,10 @@ import "../../../../../components/ha-md-list";
|
|||||||
import "../../../../../components/ha-md-list-item";
|
import "../../../../../components/ha-md-list-item";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import { showChangeBackupEncryptionKeyDialog } from "../../dialogs/show-dialog-change-backup-encryption-key";
|
import { showChangeBackupEncryptionKeyDialog } from "../../dialogs/show-dialog-change-backup-encryption-key";
|
||||||
import { fileDownload } from "../../../../../util/file_download";
|
|
||||||
import { showSetBackupEncryptionKeyDialog } from "../../dialogs/show-dialog-set-backup-encryption-key";
|
import { showSetBackupEncryptionKeyDialog } from "../../dialogs/show-dialog-set-backup-encryption-key";
|
||||||
|
|
||||||
|
import { downloadEmergencyKit } from "../../../../../data/backup";
|
||||||
|
|
||||||
@customElement("ha-backup-config-encryption-key")
|
@customElement("ha-backup-config-encryption-key")
|
||||||
class HaBackupConfigEncryptionKey extends LitElement {
|
class HaBackupConfigEncryptionKey extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
@ -64,10 +65,7 @@ class HaBackupConfigEncryptionKey extends LitElement {
|
|||||||
if (!this._value) {
|
if (!this._value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileDownload(
|
downloadEmergencyKit(this.hass, this._value);
|
||||||
"data:text/plain;charset=utf-8," + encodeURIComponent(this._value),
|
|
||||||
"emergency_kit.txt"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _change() {
|
private _change() {
|
||||||
|
@ -3,18 +3,21 @@ import { css, html, LitElement, nothing } from "lit";
|
|||||||
import { customElement, property, state } from "lit/decorators";
|
import { customElement, property, state } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||||
|
import { clamp } from "../../../../../common/number/clamp";
|
||||||
import type { HaCheckbox } from "../../../../../components/ha-checkbox";
|
import type { HaCheckbox } from "../../../../../components/ha-checkbox";
|
||||||
import "../../../../../components/ha-md-list";
|
import "../../../../../components/ha-md-list";
|
||||||
import "../../../../../components/ha-md-list-item";
|
import "../../../../../components/ha-md-list-item";
|
||||||
import "../../../../../components/ha-md-select";
|
import "../../../../../components/ha-md-select";
|
||||||
import "../../../../../components/ha-md-textfield";
|
|
||||||
import type { HaMdSelect } from "../../../../../components/ha-md-select";
|
import type { HaMdSelect } from "../../../../../components/ha-md-select";
|
||||||
import "../../../../../components/ha-md-select-option";
|
import "../../../../../components/ha-md-select-option";
|
||||||
|
import "../../../../../components/ha-md-textfield";
|
||||||
import "../../../../../components/ha-switch";
|
import "../../../../../components/ha-switch";
|
||||||
import type { BackupConfig } from "../../../../../data/backup";
|
import type { BackupConfig } from "../../../../../data/backup";
|
||||||
import { BackupScheduleState } from "../../../../../data/backup";
|
import {
|
||||||
|
BackupScheduleState,
|
||||||
|
getFormattedBackupTime,
|
||||||
|
} from "../../../../../data/backup";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import { clamp } from "../../../../../common/number/clamp";
|
|
||||||
|
|
||||||
export type BackupConfigSchedule = Pick<BackupConfig, "schedule" | "retention">;
|
export type BackupConfigSchedule = Pick<BackupConfig, "schedule" | "retention">;
|
||||||
|
|
||||||
@ -120,13 +123,12 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
protected render() {
|
protected render() {
|
||||||
const data = this._getData(this.value);
|
const data = this._getData(this.value);
|
||||||
|
|
||||||
|
const time = getFormattedBackupTime(this.hass.locale, this.hass.config);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<span slot="headline">Use automatic backups</span>
|
<span slot="headline">Use automatic backups</span>
|
||||||
<span slot="supporting-text">
|
|
||||||
How often you want to create a backup.
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<ha-switch
|
<ha-switch
|
||||||
slot="end"
|
slot="end"
|
||||||
@ -148,35 +150,36 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
.value=${data.schedule}
|
.value=${data.schedule}
|
||||||
>
|
>
|
||||||
<ha-md-select-option .value=${BackupScheduleState.DAILY}>
|
<ha-md-select-option .value=${BackupScheduleState.DAILY}>
|
||||||
<div slot="headline">Daily at 04:45</div>
|
<div slot="headline">Daily at ${time}</div>
|
||||||
</ha-md-select-option>
|
</ha-md-select-option>
|
||||||
<ha-md-select-option .value=${BackupScheduleState.MONDAY}>
|
<ha-md-select-option .value=${BackupScheduleState.MONDAY}>
|
||||||
<div slot="headline">Monday at 04:45</div>
|
<div slot="headline">Monday at ${time}</div>
|
||||||
</ha-md-select-option>
|
</ha-md-select-option>
|
||||||
<ha-md-select-option .value=${BackupScheduleState.TUESDAY}>
|
<ha-md-select-option .value=${BackupScheduleState.TUESDAY}>
|
||||||
<div slot="headline">Tuesday at 04:45</div>
|
<div slot="headline">Tuesday at ${time}</div>
|
||||||
</ha-md-select-option>
|
</ha-md-select-option>
|
||||||
<ha-md-select-option .value=${BackupScheduleState.WEDNESDAY}>
|
<ha-md-select-option .value=${BackupScheduleState.WEDNESDAY}>
|
||||||
<div slot="headline">Wednesday at 04:45</div>
|
<div slot="headline">Wednesday at ${time}</div>
|
||||||
</ha-md-select-option>
|
</ha-md-select-option>
|
||||||
<ha-md-select-option .value=${BackupScheduleState.THURSDAY}>
|
<ha-md-select-option .value=${BackupScheduleState.THURSDAY}>
|
||||||
<div slot="headline">Thursday at 04:45</div>
|
<div slot="headline">Thursday at ${time}</div>
|
||||||
</ha-md-select-option>
|
</ha-md-select-option>
|
||||||
<ha-md-select-option .value=${BackupScheduleState.FRIDAY}>
|
<ha-md-select-option .value=${BackupScheduleState.FRIDAY}>
|
||||||
<div slot="headline">Friday at 04:45</div>
|
<div slot="headline">Friday at ${time}</div>
|
||||||
</ha-md-select-option>
|
</ha-md-select-option>
|
||||||
<ha-md-select-option .value=${BackupScheduleState.SATURDAY}>
|
<ha-md-select-option .value=${BackupScheduleState.SATURDAY}>
|
||||||
<div slot="headline">Saturday at 04:45</div>
|
<div slot="headline">Saturday at ${time}</div>
|
||||||
</ha-md-select-option>
|
</ha-md-select-option>
|
||||||
<ha-md-select-option .value=${BackupScheduleState.SUNDAY}>
|
<ha-md-select-option .value=${BackupScheduleState.SUNDAY}>
|
||||||
<div slot="headline">Sunday at 04:45</div>
|
<div slot="headline">Sunday at ${time}</div>
|
||||||
</ha-md-select-option>
|
</ha-md-select-option>
|
||||||
</ha-md-select>
|
</ha-md-select>
|
||||||
</ha-md-list-item>
|
</ha-md-list-item>
|
||||||
<ha-md-list-item>
|
<ha-md-list-item>
|
||||||
<span slot="headline">Backups to keep</span>
|
<span slot="headline">Backups to keep</span>
|
||||||
<span slot="supporting-text">
|
<span slot="supporting-text">
|
||||||
The number of backups that are saved
|
Based on the maximum number of backups or how many days they
|
||||||
|
should be kept.
|
||||||
</span>
|
</span>
|
||||||
<ha-md-select
|
<ha-md-select
|
||||||
slot="end"
|
slot="end"
|
||||||
@ -326,16 +329,13 @@ class HaBackupConfigSchedule extends LitElement {
|
|||||||
@media all and (max-width: 450px) {
|
@media all and (max-width: 450px) {
|
||||||
ha-md-select {
|
ha-md-select {
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
width: 160px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ha-md-textfield#value {
|
ha-md-textfield#value {
|
||||||
min-width: 70px;
|
min-width: 70px;
|
||||||
width: 70px;
|
|
||||||
}
|
}
|
||||||
ha-md-select#type {
|
ha-md-select#type {
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
width: 100px;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@ import { mdiPuzzle } from "@mdi/js";
|
|||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { LitElement, css, html } from "lit";
|
import { LitElement, css, html } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
|
import memoizeOne from "memoize-one";
|
||||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||||
|
import { stringCompare } from "../../../../common/string/compare";
|
||||||
import "../../../../components/ha-checkbox";
|
import "../../../../components/ha-checkbox";
|
||||||
import type { HaCheckbox } from "../../../../components/ha-checkbox";
|
import type { HaCheckbox } from "../../../../components/ha-checkbox";
|
||||||
import "../../../../components/ha-formfield";
|
import "../../../../components/ha-formfield";
|
||||||
@ -29,10 +31,16 @@ export class HaBackupAddonsPicker extends LitElement {
|
|||||||
@property({ attribute: "hide-version", type: Boolean })
|
@property({ attribute: "hide-version", type: Boolean })
|
||||||
public hideVersion = false;
|
public hideVersion = false;
|
||||||
|
|
||||||
|
private _addons = memoizeOne((addons: BackupAddonItem[]) =>
|
||||||
|
addons.sort((a, b) =>
|
||||||
|
stringCompare(a.name, b.name, this.hass.locale.language)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
return html`
|
return html`
|
||||||
<div class="items">
|
<div class="items">
|
||||||
${this.addons.map(
|
${this._addons(this.addons).map(
|
||||||
(item) => html`
|
(item) => html`
|
||||||
<ha-formfield>
|
<ha-formfield>
|
||||||
<ha-backup-formfield-label
|
<ha-backup-formfield-label
|
||||||
|
@ -300,22 +300,22 @@ export class HaBackupDataPicker extends LitElement {
|
|||||||
static get styles(): CSSResultGroup {
|
static get styles(): CSSResultGroup {
|
||||||
return css`
|
return css`
|
||||||
.section {
|
.section {
|
||||||
margin-inline-start: -16px;
|
|
||||||
margin-inline-end: 0;
|
|
||||||
margin-left: -16px;
|
margin-left: -16px;
|
||||||
|
margin-inline-start: -16px;
|
||||||
|
margin-inline-end: initial;
|
||||||
}
|
}
|
||||||
.items {
|
.items {
|
||||||
padding-inline-start: 40px;
|
|
||||||
padding-inline-end: 0;
|
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
|
padding-inline-start: 40px;
|
||||||
|
padding-inline-end: initial;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
ha-backup-addons-picker {
|
ha-backup-addons-picker {
|
||||||
display: block;
|
display: block;
|
||||||
padding-inline-start: 40px;
|
|
||||||
padding-inline-end: 0;
|
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
|
padding-inline-start: 40px;
|
||||||
|
padding-inline-end: initial;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,9 @@ class HaBackupSummaryCard extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static styles = css`
|
static styles = css`
|
||||||
|
ha-card {
|
||||||
|
min-height: 74px;
|
||||||
|
}
|
||||||
.summary {
|
.summary {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -13,6 +13,7 @@ import type { BackupConfig } from "../../../../../data/backup";
|
|||||||
import {
|
import {
|
||||||
BackupScheduleState,
|
BackupScheduleState,
|
||||||
computeBackupAgentName,
|
computeBackupAgentName,
|
||||||
|
getFormattedBackupTime,
|
||||||
isLocalAgent,
|
isLocalAgent,
|
||||||
} from "../../../../../data/backup";
|
} from "../../../../../data/backup";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
@ -43,30 +44,32 @@ class HaBackupBackupsSummary extends LitElement {
|
|||||||
copiesText = `and keep backups for ${days} day(s)`;
|
copiesText = `and keep backups for ${days} day(s)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const time = getFormattedBackupTime(this.hass.locale, this.hass.config);
|
||||||
|
|
||||||
let scheduleText = "";
|
let scheduleText = "";
|
||||||
if (schedule === BackupScheduleState.DAILY) {
|
if (schedule === BackupScheduleState.DAILY) {
|
||||||
scheduleText = `Daily at 04:45`;
|
scheduleText = `Daily at ${time}`;
|
||||||
}
|
}
|
||||||
if (schedule === BackupScheduleState.MONDAY) {
|
if (schedule === BackupScheduleState.MONDAY) {
|
||||||
scheduleText = `Weekly on Mondays at 04:45`;
|
scheduleText = `Weekly on Mondays at ${time}`;
|
||||||
}
|
}
|
||||||
if (schedule === BackupScheduleState.TUESDAY) {
|
if (schedule === BackupScheduleState.TUESDAY) {
|
||||||
scheduleText = `Weekly on Thuesdays at 04:45`;
|
scheduleText = `Weekly on Tuesdays at ${time}`;
|
||||||
}
|
}
|
||||||
if (schedule === BackupScheduleState.WEDNESDAY) {
|
if (schedule === BackupScheduleState.WEDNESDAY) {
|
||||||
scheduleText = `Weekly on Wednesdays at 04:45`;
|
scheduleText = `Weekly on Wednesdays at ${time}`;
|
||||||
}
|
}
|
||||||
if (schedule === BackupScheduleState.THURSDAY) {
|
if (schedule === BackupScheduleState.THURSDAY) {
|
||||||
scheduleText = `Weekly on Thursdays at 04:45`;
|
scheduleText = `Weekly on Thursdays at ${time}`;
|
||||||
}
|
}
|
||||||
if (schedule === BackupScheduleState.FRIDAY) {
|
if (schedule === BackupScheduleState.FRIDAY) {
|
||||||
scheduleText = `Weekly on Fridays at 04:45`;
|
scheduleText = `Weekly on Fridays at ${time}`;
|
||||||
}
|
}
|
||||||
if (schedule === BackupScheduleState.SATURDAY) {
|
if (schedule === BackupScheduleState.SATURDAY) {
|
||||||
scheduleText = `Weekly on Saturdays at 04:45`;
|
scheduleText = `Weekly on Saturdays at ${time}`;
|
||||||
}
|
}
|
||||||
if (schedule === BackupScheduleState.SUNDAY) {
|
if (schedule === BackupScheduleState.SUNDAY) {
|
||||||
scheduleText = `Weekly on Sundays at 04:45`;
|
scheduleText = `Weekly on Sundays at ${time}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return scheduleText + " " + copiesText;
|
return scheduleText + " " + copiesText;
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { mdiBackupRestore, mdiCalendar } from "@mdi/js";
|
import { mdiBackupRestore, mdiCalendar } from "@mdi/js";
|
||||||
import { addHours, differenceInDays, setHours, setMinutes } from "date-fns";
|
import { addHours, differenceInDays } from "date-fns";
|
||||||
import type { CSSResultGroup } from "lit";
|
import type { CSSResultGroup } from "lit";
|
||||||
import { css, html, LitElement } from "lit";
|
import { css, html, LitElement } from "lit";
|
||||||
import { customElement, property } from "lit/decorators";
|
import { customElement, property } from "lit/decorators";
|
||||||
import memoizeOne from "memoize-one";
|
import memoizeOne from "memoize-one";
|
||||||
import { formatTime } from "../../../../../common/datetime/format_time";
|
|
||||||
import { relativeTime } from "../../../../../common/datetime/relative_time";
|
import { relativeTime } from "../../../../../common/datetime/relative_time";
|
||||||
import "../../../../../components/ha-button";
|
import "../../../../../components/ha-button";
|
||||||
import "../../../../../components/ha-card";
|
import "../../../../../components/ha-card";
|
||||||
@ -12,7 +11,10 @@ import "../../../../../components/ha-md-list";
|
|||||||
import "../../../../../components/ha-md-list-item";
|
import "../../../../../components/ha-md-list-item";
|
||||||
import "../../../../../components/ha-svg-icon";
|
import "../../../../../components/ha-svg-icon";
|
||||||
import type { BackupConfig, BackupContent } from "../../../../../data/backup";
|
import type { BackupConfig, BackupContent } from "../../../../../data/backup";
|
||||||
import { BackupScheduleState } from "../../../../../data/backup";
|
import {
|
||||||
|
BackupScheduleState,
|
||||||
|
getFormattedBackupTime,
|
||||||
|
} from "../../../../../data/backup";
|
||||||
import { haStyle } from "../../../../../resources/styles";
|
import { haStyle } from "../../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../../types";
|
import type { HomeAssistant } from "../../../../../types";
|
||||||
import "../ha-backup-summary-card";
|
import "../ha-backup-summary-card";
|
||||||
@ -29,20 +31,16 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
|
|
||||||
@property({ type: Boolean }) public fetching = false;
|
@property({ type: Boolean }) public fetching = false;
|
||||||
|
|
||||||
private _lastBackup = memoizeOne((backups: BackupContent[]) => {
|
private _lastSuccessfulBackup = memoizeOne((backups: BackupContent[]) => {
|
||||||
const sortedBackups = backups
|
const sortedBackups = backups
|
||||||
.filter(
|
.filter((backup) => backup.with_automatic_settings)
|
||||||
(backup) =>
|
|
||||||
backup.with_automatic_settings && !backup.failed_agent_ids?.length
|
|
||||||
)
|
|
||||||
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
||||||
|
|
||||||
return sortedBackups[0] as BackupContent | undefined;
|
return sortedBackups[0] as BackupContent | undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
private _nextBackupDescription(schedule: BackupScheduleState) {
|
private _nextBackupDescription(schedule: BackupScheduleState) {
|
||||||
const newDate = setMinutes(setHours(new Date(), 4), 45);
|
const time = getFormattedBackupTime(this.hass.locale, this.hass.config);
|
||||||
const time = formatTime(newDate, this.hass.locale, this.hass.config);
|
|
||||||
|
|
||||||
switch (schedule) {
|
switch (schedule) {
|
||||||
case BackupScheduleState.DAILY:
|
case BackupScheduleState.DAILY:
|
||||||
@ -84,37 +82,27 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastBackup = this._lastBackup(this.backups);
|
const lastSuccessfulBackup = this._lastSuccessfulBackup(this.backups);
|
||||||
|
|
||||||
if (!lastBackup) {
|
const lastSuccessfulBackupDate = lastSuccessfulBackup
|
||||||
return html`
|
? new Date(lastSuccessfulBackup.date)
|
||||||
<ha-backup-summary-card
|
: new Date(0);
|
||||||
heading="No automatic backup available"
|
|
||||||
description="You have no automatic backups yet."
|
|
||||||
status="warning"
|
|
||||||
>
|
|
||||||
</ha-backup-summary-card>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastBackupDate = new Date(lastBackup.date);
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
const lastBackupDescription = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and stored to ${lastBackup.agent_ids?.length} locations.`;
|
|
||||||
const nextBackupDescription = this._nextBackupDescription(
|
|
||||||
this.config.schedule.state
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastAttempt = this.config.last_attempted_automatic_backup
|
const lastAttempt = this.config.last_attempted_automatic_backup
|
||||||
? new Date(this.config.last_attempted_automatic_backup)
|
? new Date(this.config.last_attempted_automatic_backup)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
if (lastAttempt && lastAttempt > lastBackupDate) {
|
const now = new Date();
|
||||||
|
|
||||||
|
const lastBackupDescription = lastSuccessfulBackup
|
||||||
|
? `Last successful backup ${relativeTime(lastSuccessfulBackupDate, this.hass.locale, now, true)} and stored to ${lastSuccessfulBackup.agent_ids?.length} locations.`
|
||||||
|
: "You have no successful backups.";
|
||||||
|
|
||||||
|
if (lastAttempt && lastAttempt > lastSuccessfulBackupDate) {
|
||||||
const lastAttemptDescription = `The last automatic backup trigged ${relativeTime(lastAttempt, this.hass.locale, now, true)} wasn't successful.`;
|
const lastAttemptDescription = `The last automatic backup trigged ${relativeTime(lastAttempt, this.hass.locale, now, true)} wasn't successful.`;
|
||||||
return html`
|
return html`
|
||||||
<ha-backup-summary-card
|
<ha-backup-summary-card
|
||||||
heading=${`Last automatic backup failed`}
|
heading="Last automatic backup failed"
|
||||||
status="error"
|
status="error"
|
||||||
>
|
>
|
||||||
<ha-md-list>
|
<ha-md-list>
|
||||||
@ -131,10 +119,25 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!lastSuccessfulBackup) {
|
||||||
|
return html`
|
||||||
|
<ha-backup-summary-card
|
||||||
|
heading="No automatic backup available"
|
||||||
|
description="You have no automatic backups yet."
|
||||||
|
status="warning"
|
||||||
|
>
|
||||||
|
</ha-backup-summary-card>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextBackupDescription = this._nextBackupDescription(
|
||||||
|
this.config.schedule.state
|
||||||
|
);
|
||||||
|
|
||||||
const numberOfDays = differenceInDays(
|
const numberOfDays = differenceInDays(
|
||||||
// Subtract a few hours to avoid showing as overdue if it's just a few hours (e.g. daylight saving)
|
// Subtract a few hours to avoid showing as overdue if it's just a few hours (e.g. daylight saving)
|
||||||
addHours(now, -OVERDUE_MARGIN_HOURS),
|
addHours(now, -OVERDUE_MARGIN_HOURS),
|
||||||
lastBackupDate
|
lastSuccessfulBackupDate
|
||||||
);
|
);
|
||||||
|
|
||||||
const isOverdue =
|
const isOverdue =
|
||||||
@ -216,12 +219,13 @@ class HaBackupOverviewBackups extends LitElement {
|
|||||||
animation-timing-function: linear;
|
animation-timing-function: linear;
|
||||||
animation-duration: 1.2s;
|
animation-duration: 1.2s;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
height: 20px;
|
height: 16px;
|
||||||
|
margin: 2px 0;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
to right,
|
to right,
|
||||||
rgb(247, 249, 250) 8%,
|
var(--card-background-color) 8%,
|
||||||
rgb(235, 238, 240) 18%,
|
var(--secondary-background-color) 18%,
|
||||||
rgb(247, 249, 250) 33%
|
var(--card-background-color) 33%
|
||||||
)
|
)
|
||||||
0% 0% / 936px 104px;
|
0% 0% / 936px 104px;
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
BackupScheduleState,
|
BackupScheduleState,
|
||||||
CLOUD_AGENT,
|
CLOUD_AGENT,
|
||||||
CORE_LOCAL_AGENT,
|
CORE_LOCAL_AGENT,
|
||||||
|
downloadEmergencyKit,
|
||||||
generateEncryptionKey,
|
generateEncryptionKey,
|
||||||
HASSIO_LOCAL_AGENT,
|
HASSIO_LOCAL_AGENT,
|
||||||
updateBackupConfig,
|
updateBackupConfig,
|
||||||
@ -31,7 +32,6 @@ import {
|
|||||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { fileDownload } from "../../../../util/file_download";
|
|
||||||
import { showToast } from "../../../../util/toast";
|
import { showToast } from "../../../../util/toast";
|
||||||
import "../components/config/ha-backup-config-agents";
|
import "../components/config/ha-backup-config-agents";
|
||||||
import "../components/config/ha-backup-config-data";
|
import "../components/config/ha-backup-config-data";
|
||||||
@ -101,7 +101,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
|||||||
agents.push(CORE_LOCAL_AGENT);
|
agents.push(CORE_LOCAL_AGENT);
|
||||||
}
|
}
|
||||||
// Enable cloud location if logged in
|
// Enable cloud location if logged in
|
||||||
if (this._params.cloudStatus.logged_in) {
|
if (this._params.cloudStatus?.logged_in) {
|
||||||
agents.push(CLOUD_AGENT);
|
agents.push(CLOUD_AGENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,12 +327,6 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
|||||||
`;
|
`;
|
||||||
case "setup":
|
case "setup":
|
||||||
return html`
|
return html`
|
||||||
<p>
|
|
||||||
It is recommended that you create a backup every day. You should
|
|
||||||
keep three backups in at least two different locations, one of which
|
|
||||||
should be off-site. Once you make your selection, your first backup
|
|
||||||
will begin.
|
|
||||||
</p>
|
|
||||||
<ha-md-list class="full">
|
<ha-md-list class="full">
|
||||||
<ha-md-list-item type="button" @click=${this._done}>
|
<ha-md-list-item type="button" @click=${this._done}>
|
||||||
<span slot="headline">Recommended settings</span>
|
<span slot="headline">Recommended settings</span>
|
||||||
@ -398,14 +392,11 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
|||||||
if (!key) {
|
if (!key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileDownload(
|
downloadEmergencyKit(this.hass, key);
|
||||||
"data:text/plain;charset=utf-8," + encodeURIComponent(key),
|
|
||||||
"emergency_kit.txt"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _copyKeyToClipboard() {
|
private async _copyKeyToClipboard() {
|
||||||
copyToClipboard(this._config!.create_backup.password!);
|
await copyToClipboard(this._config!.create_backup.password!);
|
||||||
showToast(this, {
|
showToast(this, {
|
||||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||||
});
|
});
|
||||||
@ -471,6 +462,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
|
|||||||
width: 90vw;
|
width: 90vw;
|
||||||
max-width: 560px;
|
max-width: 560px;
|
||||||
--dialog-content-padding: 8px 24px;
|
--dialog-content-padding: 8px 24px;
|
||||||
|
max-height: min(605px, 100% - 48px);
|
||||||
}
|
}
|
||||||
ha-md-list {
|
ha-md-list {
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -13,11 +13,13 @@ import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
|||||||
import "../../../../components/ha-md-list";
|
import "../../../../components/ha-md-list";
|
||||||
import "../../../../components/ha-md-list-item";
|
import "../../../../components/ha-md-list-item";
|
||||||
import "../../../../components/ha-password-field";
|
import "../../../../components/ha-password-field";
|
||||||
import { generateEncryptionKey } from "../../../../data/backup";
|
import {
|
||||||
|
downloadEmergencyKit,
|
||||||
|
generateEncryptionKey,
|
||||||
|
} from "../../../../data/backup";
|
||||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { fileDownload } from "../../../../util/file_download";
|
|
||||||
import { showToast } from "../../../../util/toast";
|
import { showToast } from "../../../../util/toast";
|
||||||
import type { ChangeBackupEncryptionKeyDialogParams } from "./show-dialog-change-backup-encryption-key";
|
import type { ChangeBackupEncryptionKeyDialogParams } from "./show-dialog-change-backup-encryption-key";
|
||||||
|
|
||||||
@ -203,18 +205,18 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
return nothing;
|
return nothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _copyKeyToClipboard() {
|
private async _copyKeyToClipboard() {
|
||||||
copyToClipboard(this._newEncryptionKey);
|
await copyToClipboard(this._newEncryptionKey);
|
||||||
showToast(this, {
|
showToast(this, {
|
||||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _copyOldKeyToClipboard() {
|
private async _copyOldKeyToClipboard() {
|
||||||
if (!this._params?.currentKey) {
|
if (!this._params?.currentKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
copyToClipboard(this._params.currentKey);
|
await copyToClipboard(this._params.currentKey);
|
||||||
showToast(this, {
|
showToast(this, {
|
||||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||||
});
|
});
|
||||||
@ -224,22 +226,14 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
if (!this._params?.currentKey) {
|
if (!this._params?.currentKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileDownload(
|
downloadEmergencyKit(this.hass, this._params.currentKey, "old");
|
||||||
"data:text/plain;charset=utf-8," +
|
|
||||||
encodeURIComponent(this._params.currentKey),
|
|
||||||
"emergency_kit_old.txt"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _downloadNew() {
|
private _downloadNew() {
|
||||||
if (!this._newEncryptionKey) {
|
if (!this._newEncryptionKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileDownload(
|
downloadEmergencyKit(this.hass, this._newEncryptionKey);
|
||||||
"data:text/plain;charset=utf-8," +
|
|
||||||
encodeURIComponent(this._newEncryptionKey),
|
|
||||||
"emergency_kit.txt"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _submit() {
|
private async _submit() {
|
||||||
|
@ -99,7 +99,9 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
|
|||||||
const { agents } = await fetchBackupAgentsInfo(this.hass);
|
const { agents } = await fetchBackupAgentsInfo(this.hass);
|
||||||
this._agentIds = agents
|
this._agentIds = agents
|
||||||
.map((agent) => agent.agent_id)
|
.map((agent) => agent.agent_id)
|
||||||
.filter((id) => id !== CLOUD_AGENT || this._params?.cloudStatus.logged_in)
|
.filter(
|
||||||
|
(id) => id !== CLOUD_AGENT || this._params?.cloudStatus?.logged_in
|
||||||
|
)
|
||||||
.sort(compareAgents);
|
.sort(compareAgents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,11 +11,13 @@ import type { HaMdDialog } from "../../../../components/ha-md-dialog";
|
|||||||
import "../../../../components/ha-md-list";
|
import "../../../../components/ha-md-list";
|
||||||
import "../../../../components/ha-md-list-item";
|
import "../../../../components/ha-md-list-item";
|
||||||
import "../../../../components/ha-password-field";
|
import "../../../../components/ha-password-field";
|
||||||
import { generateEncryptionKey } from "../../../../data/backup";
|
import {
|
||||||
|
downloadEmergencyKit,
|
||||||
|
generateEncryptionKey,
|
||||||
|
} from "../../../../data/backup";
|
||||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||||
import type { HomeAssistant } from "../../../../types";
|
import type { HomeAssistant } from "../../../../types";
|
||||||
import { fileDownload } from "../../../../util/file_download";
|
|
||||||
import type { SetBackupEncryptionKeyDialogParams } from "./show-dialog-set-backup-encryption-key";
|
import type { SetBackupEncryptionKeyDialogParams } from "./show-dialog-set-backup-encryption-key";
|
||||||
|
|
||||||
const STEPS = ["new", "save"] as const;
|
const STEPS = ["new", "save"] as const;
|
||||||
@ -162,11 +164,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
|
|||||||
if (!this._newEncryptionKey) {
|
if (!this._newEncryptionKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fileDownload(
|
downloadEmergencyKit(this.hass, this._newEncryptionKey);
|
||||||
"data:text/plain;charset=utf-8," +
|
|
||||||
encodeURIComponent(this._newEncryptionKey),
|
|
||||||
"emergency_kit.txt"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _encryptionKeyChanged(ev) {
|
private _encryptionKeyChanged(ev) {
|
||||||
|
@ -4,7 +4,7 @@ import type { CloudStatus } from "../../../../data/cloud";
|
|||||||
export interface BackupOnboardingDialogParams {
|
export interface BackupOnboardingDialogParams {
|
||||||
submit?: (value: boolean) => void;
|
submit?: (value: boolean) => void;
|
||||||
cancel?: () => void;
|
cancel?: () => void;
|
||||||
cloudStatus: CloudStatus;
|
cloudStatus?: CloudStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadDialog = () => import("./dialog-backup-onboarding");
|
const loadDialog = () => import("./dialog-backup-onboarding");
|
||||||
|
@ -5,7 +5,7 @@ import type { CloudStatus } from "../../../../data/cloud";
|
|||||||
export interface GenerateBackupDialogParams {
|
export interface GenerateBackupDialogParams {
|
||||||
submit?: (response: GenerateBackupParams) => void;
|
submit?: (response: GenerateBackupParams) => void;
|
||||||
cancel?: () => void;
|
cancel?: () => void;
|
||||||
cloudStatus: CloudStatus;
|
cloudStatus?: CloudStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const loadGenerateBackupDialog = () =>
|
export const loadGenerateBackupDialog = () =>
|
||||||
|
@ -78,7 +78,7 @@ const TYPE_ORDER: Array<BackupType> = ["automatic", "manual", "imported"];
|
|||||||
class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public cloudStatus!: CloudStatus;
|
@property({ attribute: false }) public cloudStatus?: CloudStatus;
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
@ -167,7 +167,6 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
title: "Locations",
|
title: "Locations",
|
||||||
showNarrow: true,
|
showNarrow: true,
|
||||||
minWidth: "60px",
|
minWidth: "60px",
|
||||||
maxWidth: "120px",
|
|
||||||
template: (backup) => html`
|
template: (backup) => html`
|
||||||
<div style="display: flex; gap: 4px;">
|
<div style="display: flex; gap: 4px;">
|
||||||
${(backup.agent_ids || []).map((agentId) => {
|
${(backup.agent_ids || []).map((agentId) => {
|
||||||
@ -181,7 +180,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
.path=${mdiHarddisk}
|
.path=${mdiHarddisk}
|
||||||
title=${name}
|
title=${name}
|
||||||
slot="graphic"
|
style="flex-shrink: 0;"
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -190,7 +189,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
<ha-svg-icon
|
<ha-svg-icon
|
||||||
.path=${mdiNas}
|
.path=${mdiNas}
|
||||||
title=${name}
|
title=${name}
|
||||||
slot="graphic"
|
style="flex-shrink: 0;"
|
||||||
></ha-svg-icon>
|
></ha-svg-icon>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@ -209,6 +208,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) {
|
|||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
alt=${name}
|
alt=${name}
|
||||||
slot="graphic"
|
slot="graphic"
|
||||||
|
style="flex-shrink: 0;"
|
||||||
/>
|
/>
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
|
@ -26,7 +26,6 @@ import "../../../layouts/hass-subpage";
|
|||||||
import "../../../layouts/hass-tabs-subpage-data-table";
|
import "../../../layouts/hass-tabs-subpage-data-table";
|
||||||
import { haStyle } from "../../../resources/styles";
|
import { haStyle } from "../../../resources/styles";
|
||||||
import type { HomeAssistant, Route } from "../../../types";
|
import type { HomeAssistant, Route } from "../../../types";
|
||||||
import "./components/ha-backup-summary-card";
|
|
||||||
import "./components/overview/ha-backup-overview-backups";
|
import "./components/overview/ha-backup-overview-backups";
|
||||||
import "./components/overview/ha-backup-overview-onboarding";
|
import "./components/overview/ha-backup-overview-onboarding";
|
||||||
import "./components/overview/ha-backup-overview-progress";
|
import "./components/overview/ha-backup-overview-progress";
|
||||||
@ -42,7 +41,7 @@ import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup";
|
|||||||
class HaConfigBackupOverview extends LitElement {
|
class HaConfigBackupOverview extends LitElement {
|
||||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||||
|
|
||||||
@property({ attribute: false }) public cloudStatus!: CloudStatus;
|
@property({ attribute: false }) public cloudStatus?: CloudStatus;
|
||||||
|
|
||||||
@property({ type: Boolean }) public narrow = false;
|
@property({ type: Boolean }) public narrow = false;
|
||||||
|
|
||||||
|
@ -141,9 +141,9 @@ class HaConfigBackupSettings extends LitElement {
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>
|
<p>
|
||||||
Keep this encryption key in a safe place, as you will need it to
|
Keep this encryption key in a safe place, as you will need it to
|
||||||
access your backup, allowing it to be restored. Either record
|
access your backup, allowing it to be restored. Download them as
|
||||||
the characters below or download them as an emergency kit file.
|
an emergency kit file and store it somewhere safe. Encryption
|
||||||
Encryption keeps your backups private and secure.
|
keeps your backups private and secure.
|
||||||
</p>
|
</p>
|
||||||
<ha-backup-config-encryption-key
|
<ha-backup-config-encryption-key
|
||||||
.hass=${this.hass}
|
.hass=${this.hass}
|
||||||
|
@ -75,7 +75,9 @@ export class DialogTryTts extends LitElement {
|
|||||||
<ha-textarea
|
<ha-textarea
|
||||||
autogrow
|
autogrow
|
||||||
id="message"
|
id="message"
|
||||||
label="Message"
|
.label=${this.hass.localize(
|
||||||
|
"ui.panel.config.cloud.account.tts.dialog.message"
|
||||||
|
)}
|
||||||
.value=${this._message ||
|
.value=${this._message ||
|
||||||
this.hass.localize(
|
this.hass.localize(
|
||||||
"ui.panel.config.cloud.account.tts.dialog.example_message",
|
"ui.panel.config.cloud.account.tts.dialog.example_message",
|
||||||
|
@ -247,6 +247,13 @@ export class SystemLogCard extends LitElement {
|
|||||||
padding-top: 8px;
|
padding-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host {
|
||||||
|
direction: var(--direction);
|
||||||
|
}
|
||||||
|
mwc-list {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -293,13 +300,7 @@ export class SystemLogCard extends LitElement {
|
|||||||
border-top: 1px solid var(--divider-color);
|
border-top: 1px solid var(--divider-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-actions,
|
|
||||||
.empty-content {
|
|
||||||
direction: var(--direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-secondary {
|
.row-secondary {
|
||||||
direction: var(--direction);
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
@ -105,6 +105,9 @@ export class HaConfigLovelaceRescources extends LitElement {
|
|||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
title: "",
|
title: "",
|
||||||
|
label: localize(
|
||||||
|
"ui.panel.config.lovelace.resources.picker.headers.delete"
|
||||||
|
),
|
||||||
type: "icon-button",
|
type: "icon-button",
|
||||||
minWidth: "48px",
|
minWidth: "48px",
|
||||||
maxWidth: "48px",
|
maxWidth: "48px",
|
||||||
|
@ -99,7 +99,7 @@ class DialogRepairsIssue extends LitElement {
|
|||||||
: ""}
|
: ""}
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
id="dialog-repairs-issue-description"
|
id="dialog-repairs-issue-description"
|
||||||
allowsvg
|
allow-svg
|
||||||
breaks
|
breaks
|
||||||
@click=${this._clickHandler}
|
@click=${this._clickHandler}
|
||||||
.content=${this.hass.localize(
|
.content=${this.hass.localize(
|
||||||
|
@ -90,7 +90,7 @@ export const showRepairsFlowDialog = (
|
|||||||
? html`
|
? html`
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
breaks
|
breaks
|
||||||
allowsvg
|
allow-svg
|
||||||
.content=${description}
|
.content=${description}
|
||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
`
|
`
|
||||||
@ -123,7 +123,7 @@ export const showRepairsFlowDialog = (
|
|||||||
${description
|
${description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
allowsvg
|
allow-svg
|
||||||
breaks
|
breaks
|
||||||
.content=${description}
|
.content=${description}
|
||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
@ -220,7 +220,7 @@ export const showRepairsFlowDialog = (
|
|||||||
return html`${renderIssueDescription(hass, issue)}${description
|
return html`${renderIssueDescription(hass, issue)}${description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
allowsvg
|
allow-svg
|
||||||
breaks
|
breaks
|
||||||
.content=${description}
|
.content=${description}
|
||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
@ -254,7 +254,7 @@ export const showRepairsFlowDialog = (
|
|||||||
${description
|
${description
|
||||||
? html`
|
? html`
|
||||||
<ha-markdown
|
<ha-markdown
|
||||||
allowsvg
|
allow-svg
|
||||||
breaks
|
breaks
|
||||||
.content=${description}
|
.content=${description}
|
||||||
></ha-markdown>
|
></ha-markdown>
|
||||||
|
@ -139,7 +139,8 @@ export class HaScriptEditor extends SubscribeMixin(
|
|||||||
changedProps.has("entityRegistry")
|
changedProps.has("entityRegistry")
|
||||||
) {
|
) {
|
||||||
const script = this.entityRegistry.find(
|
const script = this.entityRegistry.find(
|
||||||
(entity: EntityRegistryEntry) => entity.unique_id === this._newScriptId
|
(entity: EntityRegistryEntry) =>
|
||||||
|
entity.platform === "script" && entity.unique_id === this._newScriptId
|
||||||
);
|
);
|
||||||
if (script) {
|
if (script) {
|
||||||
this._entityRegCreated(script);
|
this._entityRegCreated(script);
|
||||||
@ -164,7 +165,8 @@ export class HaScriptEditor extends SubscribeMixin(
|
|||||||
.narrow=${this.narrow}
|
.narrow=${this.narrow}
|
||||||
.route=${this.route}
|
.route=${this.route}
|
||||||
.backCallback=${this._backTapped}
|
.backCallback=${this._backTapped}
|
||||||
.header=${!this._config.alias ? "" : this._config.alias}
|
.header=${this._config.alias ||
|
||||||
|
this.hass.localize("ui.panel.config.script.editor.default_name")}
|
||||||
>
|
>
|
||||||
${this.scriptId && !this.narrow
|
${this.scriptId && !this.narrow
|
||||||
? html`
|
? html`
|
||||||
@ -487,9 +489,7 @@ export class HaScriptEditor extends SubscribeMixin(
|
|||||||
if (changedProps.has("scriptId") && !this.scriptId && this.hass) {
|
if (changedProps.has("scriptId") && !this.scriptId && this.hass) {
|
||||||
const initData = getScriptEditorInitData();
|
const initData = getScriptEditorInitData();
|
||||||
this._dirty = !!initData;
|
this._dirty = !!initData;
|
||||||
const baseConfig: Partial<ScriptConfig> = {
|
const baseConfig: Partial<ScriptConfig> = {};
|
||||||
alias: this.hass.localize("ui.panel.config.script.editor.default_name"),
|
|
||||||
};
|
|
||||||
if (!initData || !("use_blueprint" in initData)) {
|
if (!initData || !("use_blueprint" in initData)) {
|
||||||
baseConfig.sequence = [];
|
baseConfig.sequence = [];
|
||||||
}
|
}
|
||||||
@ -894,6 +894,15 @@ export class HaScriptEditor extends SubscribeMixin(
|
|||||||
const id = this.scriptId || this._entityId || Date.now();
|
const id = this.scriptId || this._entityId || Date.now();
|
||||||
|
|
||||||
this._saving = true;
|
this._saving = true;
|
||||||
|
|
||||||
|
let entityRegPromise: Promise<EntityRegistryEntry> | undefined;
|
||||||
|
if (this._entityRegistryUpdate !== undefined && !this.scriptId) {
|
||||||
|
this._newScriptId = id.toString();
|
||||||
|
entityRegPromise = new Promise<EntityRegistryEntry>((resolve) => {
|
||||||
|
this._entityRegCreated = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.hass!.callApi(
|
await this.hass!.callApi(
|
||||||
"POST",
|
"POST",
|
||||||
@ -902,23 +911,20 @@ export class HaScriptEditor extends SubscribeMixin(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (this._entityRegistryUpdate !== undefined) {
|
if (this._entityRegistryUpdate !== undefined) {
|
||||||
let entityId = id.toString().startsWith("script.")
|
let entityId = this._entityId;
|
||||||
? id.toString()
|
|
||||||
: `script.${id}`;
|
|
||||||
|
|
||||||
// wait for new script to appear in entity registry
|
// wait for new script to appear in entity registry
|
||||||
if (!this.scriptId) {
|
if (entityRegPromise) {
|
||||||
const script = await new Promise<EntityRegistryEntry>((resolve) => {
|
const script = await entityRegPromise;
|
||||||
this._entityRegCreated = resolve;
|
|
||||||
});
|
|
||||||
entityId = script.entity_id;
|
entityId = script.entity_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
await updateEntityRegistryEntry(this.hass, entityId, {
|
await updateEntityRegistryEntry(this.hass, entityId!, {
|
||||||
categories: {
|
categories: {
|
||||||
script: this._entityRegistryUpdate.category || null,
|
script: this._entityRegistryUpdate.category || null,
|
||||||
},
|
},
|
||||||
labels: this._entityRegistryUpdate.labels || [],
|
labels: this._entityRegistryUpdate.labels || [],
|
||||||
|
area_id: this._entityRegistryUpdate.area || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,7 +178,9 @@ export class HaScriptTrace extends LitElement {
|
|||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.disabled=${this._traces[this._traces.length - 1].run_id ===
|
.disabled=${this._traces[this._traces.length - 1].run_id ===
|
||||||
this._runId}
|
this._runId}
|
||||||
label="Older trace"
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.older_trace"
|
||||||
|
)}
|
||||||
@click=${this._pickOlderTrace}
|
@click=${this._pickOlderTrace}
|
||||||
.path=${mdiRayEndArrow}
|
.path=${mdiRayEndArrow}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
@ -198,7 +200,9 @@ export class HaScriptTrace extends LitElement {
|
|||||||
</select>
|
</select>
|
||||||
<ha-icon-button
|
<ha-icon-button
|
||||||
.disabled=${this._traces[0].run_id === this._runId}
|
.disabled=${this._traces[0].run_id === this._runId}
|
||||||
label="Newer trace"
|
.label=${this.hass!.localize(
|
||||||
|
"ui.panel.config.automation.trace.newer_trace"
|
||||||
|
)}
|
||||||
@click=${this._pickNewerTrace}
|
@click=${this._pickNewerTrace}
|
||||||
.path=${mdiRayStartArrow}
|
.path=${mdiRayStartArrow}
|
||||||
></ha-icon-button>
|
></ha-icon-button>
|
||||||
|
@ -324,10 +324,10 @@ class HaPanelDevState extends LitElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _copyEntity(ev) {
|
private async _copyEntity(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
const entity = (ev.currentTarget! as any).entity;
|
const entity = (ev.currentTarget! as any).entity;
|
||||||
copyToClipboard(entity.entity_id);
|
await copyToClipboard(entity.entity_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _entitySelected(ev) {
|
private _entitySelected(ev) {
|
||||||
|
@ -23,7 +23,7 @@ export class HuiGenericEntityRow extends LitElement {
|
|||||||
|
|
||||||
@property({ attribute: false }) public config?: EntitiesCardEntityConfig;
|
@property({ attribute: false }) public config?: EntitiesCardEntityConfig;
|
||||||
|
|
||||||
@property({ attribute: false }) public secondaryText?: string;
|
@property({ attribute: "secondary-text" }) public secondaryText?: string;
|
||||||
|
|
||||||
@property({ attribute: "hide-name", type: Boolean }) public hideName = false;
|
@property({ attribute: "hide-name", type: Boolean }) public hideName = false;
|
||||||
|
|
||||||
|
@ -115,14 +115,24 @@ export class HuiViewBackgroundEditor extends LitElement {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!background) {
|
||||||
|
background = {
|
||||||
|
transparency: 33,
|
||||||
|
alignment: "center",
|
||||||
|
size: "cover",
|
||||||
|
repeat: "repeat",
|
||||||
|
attachment: "fixed",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
background = {
|
background = {
|
||||||
transparency: 100,
|
transparency: 100,
|
||||||
alignment: "center",
|
alignment: "center",
|
||||||
size: "auto",
|
size: "cover",
|
||||||
repeat: "no-repeat",
|
repeat: "no-repeat",
|
||||||
attachment: "scroll",
|
attachment: "scroll",
|
||||||
...background,
|
...background,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<ha-form
|
<ha-form
|
||||||
|
@ -52,8 +52,8 @@ export class HUIViewBackground extends LitElement {
|
|||||||
background?: string | LovelaceViewBackgroundConfig
|
background?: string | LovelaceViewBackgroundConfig
|
||||||
) {
|
) {
|
||||||
if (typeof background === "object" && background.image) {
|
if (typeof background === "object" && background.image) {
|
||||||
const size = background.size ?? "auto";
|
|
||||||
const alignment = background.alignment ?? "center";
|
const alignment = background.alignment ?? "center";
|
||||||
|
const size = background.size ?? "cover";
|
||||||
const repeat = background.repeat ?? "no-repeat";
|
const repeat = background.repeat ?? "no-repeat";
|
||||||
return `${alignment} / ${size} ${repeat} url('${background.image}')`;
|
return `${alignment} / ${size} ${repeat} url('${background.image}')`;
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ class HaMfaModuleSetupFlow extends LitElement {
|
|||||||
</div>`
|
</div>`
|
||||||
: html`${this._step.type === "abort"
|
: html`${this._step.type === "abort"
|
||||||
? html` <ha-markdown
|
? html` <ha-markdown
|
||||||
allowsvg
|
allow-svg
|
||||||
breaks
|
breaks
|
||||||
.content=${this.hass.localize(
|
.content=${this.hass.localize(
|
||||||
`component.auth.mfa_setup.${this._step.handler}.abort.${this._step.reason}`
|
`component.auth.mfa_setup.${this._step.handler}.abort.${this._step.reason}`
|
||||||
@ -103,7 +103,7 @@ class HaMfaModuleSetupFlow extends LitElement {
|
|||||||
</p>`
|
</p>`
|
||||||
: this._step.type === "form"
|
: this._step.type === "form"
|
||||||
? html`<ha-markdown
|
? html`<ha-markdown
|
||||||
allowsvg
|
allow-svg
|
||||||
breaks
|
breaks
|
||||||
.content=${this.hass.localize(
|
.content=${this.hass.localize(
|
||||||
`component.auth.mfa_setup.${
|
`component.auth.mfa_setup.${
|
||||||
|
@ -936,7 +936,7 @@
|
|||||||
"delete": "Delete {count}",
|
"delete": "Delete {count}",
|
||||||
"deleting": "Deleting {count}",
|
"deleting": "Deleting {count}",
|
||||||
"tip_media_storage": "[%key:ui::panel::config::tips::media_storage%]",
|
"tip_media_storage": "[%key:ui::panel::config::tips::media_storage%]",
|
||||||
"tip_storage_panel": "[%key:ui::panel::config::storage::caption%]"
|
"tip_storage_panel": "storage"
|
||||||
},
|
},
|
||||||
"class": {
|
"class": {
|
||||||
"album": "Album",
|
"album": "Album",
|
||||||
@ -2685,7 +2685,8 @@
|
|||||||
"picker": {
|
"picker": {
|
||||||
"headers": {
|
"headers": {
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"type": "Type"
|
"type": "Type",
|
||||||
|
"delete": "Delete"
|
||||||
},
|
},
|
||||||
"no_resources": "No resources",
|
"no_resources": "No resources",
|
||||||
"add_resource": "Add resource"
|
"add_resource": "Add resource"
|
||||||
@ -2957,8 +2958,8 @@
|
|||||||
"traces_not_available": "[%key:ui::panel::config::automation::editor::traces_not_available%]",
|
"traces_not_available": "[%key:ui::panel::config::automation::editor::traces_not_available%]",
|
||||||
"edit_category": "Edit category",
|
"edit_category": "Edit category",
|
||||||
"assign_category": "Assign category",
|
"assign_category": "Assign category",
|
||||||
"no_category_support": "You can't assign an category to this automation",
|
"no_category_support": "You can't assign a category to this automation",
|
||||||
"no_category_entity_reg": "To assign an category to an automation it needs to have a unique ID.",
|
"no_category_entity_reg": "To assign a category to an automation it needs to have a unique ID.",
|
||||||
"search": "Search {number} automations",
|
"search": "Search {number} automations",
|
||||||
"headers": {
|
"headers": {
|
||||||
"toggle": "Enable/disable",
|
"toggle": "Enable/disable",
|
||||||
@ -3717,7 +3718,8 @@
|
|||||||
"add_description": "Add description",
|
"add_description": "Add description",
|
||||||
"add_icon": "Add icon",
|
"add_icon": "Add icon",
|
||||||
"add_category": "Add category",
|
"add_category": "Add category",
|
||||||
"add_labels": "Add labels"
|
"add_labels": "Add labels",
|
||||||
|
"add_area": "Add area"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"trace": {
|
"trace": {
|
||||||
@ -4117,6 +4119,7 @@
|
|||||||
"try": "Try",
|
"try": "Try",
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"header": "Try text-to-speech",
|
"header": "Try text-to-speech",
|
||||||
|
"message": "Message",
|
||||||
"example_message": "Hello {name}, you can play any text on any supported media player!",
|
"example_message": "Hello {name}, you can play any text on any supported media player!",
|
||||||
"target": "Target",
|
"target": "Target",
|
||||||
"target_browser": "Browser",
|
"target_browser": "Browser",
|
||||||
|
20
yarn.lock
20
yarn.lock
@ -1234,10 +1234,10 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@braintree/sanitize-url@npm:7.1.0":
|
"@braintree/sanitize-url@npm:7.1.1":
|
||||||
version: 7.1.0
|
version: 7.1.1
|
||||||
resolution: "@braintree/sanitize-url@npm:7.1.0"
|
resolution: "@braintree/sanitize-url@npm:7.1.1"
|
||||||
checksum: 10/b25cc5358bedfd97d8378d23ab43493e56a805bd82fdb092088bdd9db6aa3f6c32859d36526f570fb2c67a5a4f9ce579aacd52c3872db4285e4c34fb9947dfc0
|
checksum: 10/a8a5535c5a0a459ba593a018c554b35493dff004fd09d7147db67243df83bce3d410b89ee7dc2d95cce195b85b877c72f8ca149e1040110a945d193c67293af0
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
@ -9117,7 +9117,7 @@ __metadata:
|
|||||||
"@babel/preset-env": "npm:7.26.0"
|
"@babel/preset-env": "npm:7.26.0"
|
||||||
"@babel/preset-typescript": "npm:7.26.0"
|
"@babel/preset-typescript": "npm:7.26.0"
|
||||||
"@babel/runtime": "npm:7.26.0"
|
"@babel/runtime": "npm:7.26.0"
|
||||||
"@braintree/sanitize-url": "npm:7.1.0"
|
"@braintree/sanitize-url": "npm:7.1.1"
|
||||||
"@bundle-stats/plugin-webpack-filter": "npm:4.17.0"
|
"@bundle-stats/plugin-webpack-filter": "npm:4.17.0"
|
||||||
"@codemirror/autocomplete": "npm:6.18.4"
|
"@codemirror/autocomplete": "npm:6.18.4"
|
||||||
"@codemirror/commands": "npm:6.7.1"
|
"@codemirror/commands": "npm:6.7.1"
|
||||||
@ -9296,7 +9296,7 @@ __metadata:
|
|||||||
tsparticles-engine: "npm:2.12.0"
|
tsparticles-engine: "npm:2.12.0"
|
||||||
tsparticles-preset-links: "npm:2.12.0"
|
tsparticles-preset-links: "npm:2.12.0"
|
||||||
typescript: "npm:5.7.2"
|
typescript: "npm:5.7.2"
|
||||||
ua-parser-js: "npm:1.0.39"
|
ua-parser-js: "npm:1.0.40"
|
||||||
vis-data: "npm:7.1.9"
|
vis-data: "npm:7.1.9"
|
||||||
vis-network: "npm:9.1.9"
|
vis-network: "npm:9.1.9"
|
||||||
vitest: "npm:2.1.8"
|
vitest: "npm:2.1.8"
|
||||||
@ -14440,12 +14440,12 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ua-parser-js@npm:1.0.39":
|
"ua-parser-js@npm:1.0.40":
|
||||||
version: 1.0.39
|
version: 1.0.40
|
||||||
resolution: "ua-parser-js@npm:1.0.39"
|
resolution: "ua-parser-js@npm:1.0.40"
|
||||||
bin:
|
bin:
|
||||||
ua-parser-js: script/cli.js
|
ua-parser-js: script/cli.js
|
||||||
checksum: 10/dd4026b6ece8a34a0d39b6de5542154c4506077d8def8647a300a29e1b3ffa0e23f5c8eeeb8101df6162b7b3eb3597d0b4adb031ae6104cbdb730d6ebc07f3c0
|
checksum: 10/7fced5f74ed570c83addffd4d367888d90c58803ff4bdd4a7b04b3f01d293263b8605e92ac560eb1c6a201ef3b11fcc46f3dbcbe764fbe54974924d542bc0135
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user