mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-24 09:46:36 +00:00
20221205.0 (#14562)
This commit is contained in:
commit
bbdb84482a
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v3.0.0
|
||||
- uses: dessant/lock-threads@v4.0.0
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: "30"
|
||||
|
@ -28,6 +28,7 @@ class HassioDashboard extends LitElement {
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
.route=${this.route}
|
||||
back-path="/config"
|
||||
.header=${this.supervisor.localize("panel.addons")}
|
||||
>
|
||||
<hassio-addons
|
||||
|
@ -24,7 +24,7 @@
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^5.0.2",
|
||||
"@braintree/sanitize-url": "^6.0.0",
|
||||
"@codemirror/autocomplete": "^0.19.12",
|
||||
"@codemirror/commands": "^0.19.8",
|
||||
"@codemirror/gutter": "^0.19.9",
|
||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "home-assistant-frontend"
|
||||
version = "20221201.1"
|
||||
version = "20221205.0"
|
||||
license = {text = "Apache-2.0"}
|
||||
description = "The Home Assistant frontend"
|
||||
readme = "README.md"
|
||||
|
5
src/common/array/literal-includes.ts
Normal file
5
src/common/array/literal-includes.ts
Normal file
@ -0,0 +1,5 @@
|
||||
// Creates a type predicate function for determining if an array literal includes a given value
|
||||
export const arrayLiteralIncludes =
|
||||
<T extends readonly unknown[]>(array: T) =>
|
||||
(searchElement: unknown, fromIndex?: number): searchElement is T[number] =>
|
||||
array.includes(searchElement as T[number], fromIndex);
|
@ -7,10 +7,12 @@ if (__BUILD__ === "latest" && polyfillsLoaded) {
|
||||
}
|
||||
|
||||
// Tuesday, August 10
|
||||
export const formatDateWeekday = (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateWeekdayMem(locale).format(dateObj);
|
||||
export const formatDateWeekdayDay = (
|
||||
dateObj: Date,
|
||||
locale: FrontendLocaleData
|
||||
) => formatDateWeekdayDayMem(locale).format(dateObj);
|
||||
|
||||
const formatDateWeekdayMem = memoizeOne(
|
||||
const formatDateWeekdayDayMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
weekday: "long",
|
||||
@ -92,3 +94,14 @@ const formatDateYearMem = memoizeOne(
|
||||
year: "numeric",
|
||||
})
|
||||
);
|
||||
|
||||
// Monday
|
||||
export const formatDateWeekday = (dateObj: Date, locale: FrontendLocaleData) =>
|
||||
formatDateWeekdayMem(locale).format(dateObj);
|
||||
|
||||
const formatDateWeekdayMem = memoizeOne(
|
||||
(locale: FrontendLocaleData) =>
|
||||
new Intl.DateTimeFormat(locale.language, {
|
||||
weekday: "long",
|
||||
})
|
||||
);
|
||||
|
@ -261,6 +261,11 @@ export const getStates = (
|
||||
result.push(...state.attributes.activity_list);
|
||||
}
|
||||
break;
|
||||
case "sensor":
|
||||
if (!attribute && state.attributes.device_class === "enum") {
|
||||
result.push(...state.attributes.options);
|
||||
}
|
||||
break;
|
||||
case "vacuum":
|
||||
if (attribute === "fan_speed") {
|
||||
result.push(...state.attributes.fan_speed_list);
|
||||
|
10
src/common/translations/day_names.ts
Normal file
10
src/common/translations/day_names.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { addDays, startOfWeek } from "date-fns";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { FrontendLocaleData } from "../../data/translation";
|
||||
import { formatDateWeekday } from "../datetime/format_date";
|
||||
|
||||
export const dayNames = memoizeOne((locale: FrontendLocaleData): string[] =>
|
||||
Array.from({ length: 7 }, (_, d) =>
|
||||
formatDateWeekday(addDays(startOfWeek(new Date()), d), locale)
|
||||
)
|
||||
);
|
@ -18,6 +18,7 @@ export type LocalizeKeys =
|
||||
| `ui.card.alarm_control_panel.${string}`
|
||||
| `ui.card.weather.attributes.${string}`
|
||||
| `ui.card.weather.cardinal_direction.${string}`
|
||||
| `ui.components.calendar.event.rrule.${string}`
|
||||
| `ui.components.logbook.${string}`
|
||||
| `ui.components.selectors.file.${string}`
|
||||
| `ui.dialogs.entity_registry.editor.${string}`
|
||||
@ -30,7 +31,6 @@ export type LocalizeKeys =
|
||||
| `ui.panel.config.dashboard.${string}`
|
||||
| `ui.panel.config.devices.${string}`
|
||||
| `ui.panel.config.energy.${string}`
|
||||
| `ui.panel.config.helpers.${string}`
|
||||
| `ui.panel.config.info.${string}`
|
||||
| `ui.panel.config.logs.${string}`
|
||||
| `ui.panel.config.lovelace.${string}`
|
||||
|
10
src/common/translations/month_names.ts
Normal file
10
src/common/translations/month_names.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { addMonths, startOfYear } from "date-fns";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { FrontendLocaleData } from "../../data/translation";
|
||||
import { formatDateMonth } from "../datetime/format_date";
|
||||
|
||||
export const monthNames = memoizeOne((locale: FrontendLocaleData): string[] =>
|
||||
Array.from({ length: 12 }, (_, m) =>
|
||||
formatDateMonth(addMonths(startOfYear(new Date()), m), locale)
|
||||
)
|
||||
);
|
@ -40,7 +40,7 @@ import {
|
||||
formatDateMonth,
|
||||
formatDateMonthYear,
|
||||
formatDateShort,
|
||||
formatDateWeekday,
|
||||
formatDateWeekdayDay,
|
||||
formatDateYear,
|
||||
} from "../../common/datetime/format_date";
|
||||
import {
|
||||
@ -92,7 +92,7 @@ _adapters._date.override({
|
||||
case "hour":
|
||||
return formatTime(new Date(time), this.options.locale);
|
||||
case "weekday":
|
||||
return formatDateWeekday(new Date(time), this.options.locale);
|
||||
return formatDateWeekdayDay(new Date(time), this.options.locale);
|
||||
case "date":
|
||||
return formatDate(new Date(time), this.options.locale);
|
||||
case "day":
|
||||
|
@ -3,7 +3,7 @@ import { html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { ComboBoxLitRenderer } from "@vaadin/combo-box/lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../common/ensure-array";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { stringCompare } from "../../common/string/compare";
|
||||
import {
|
||||
|
@ -77,6 +77,10 @@ export class HaChip extends LitElement {
|
||||
span[role="gridcell"] {
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
:host {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ export class HaExpansionPanel extends LitElement {
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div class="top">
|
||||
<div class="top ${classMap({ expanded: this.expanded })}">
|
||||
<div
|
||||
id="summary"
|
||||
@click=${this._toggleContainer}
|
||||
@ -147,6 +147,12 @@ export class HaExpansionPanel extends LitElement {
|
||||
.top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: var(--ha-card-border-radius, 12px);
|
||||
}
|
||||
|
||||
.top.expanded {
|
||||
border-bottom-left-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
.top.focused {
|
||||
|
@ -85,7 +85,7 @@ export class HaForm extends LitElement implements HaFormElement {
|
||||
.selector=${item.selector}
|
||||
.value=${getValue(this.data, item)}
|
||||
.label=${this._computeLabel(item, this.data)}
|
||||
.disabled=${this.disabled || item.disabled}
|
||||
.disabled=${item.disabled || this.disabled}
|
||||
.helper=${this._computeHelper(item)}
|
||||
.required=${item.required || false}
|
||||
.context=${this._generateContext(item)}
|
||||
|
@ -18,7 +18,7 @@ import { css, CSSResultGroup, html, LitElement, unsafeCSS } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { ensureArray } from "../common/ensure-array";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import {
|
||||
|
@ -25,7 +25,7 @@ import {
|
||||
import { css, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { ensureArray } from "../../common/ensure-array";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { Condition, Trigger } from "../../data/automation";
|
||||
import {
|
||||
Action,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { formatDuration } from "../common/datetime/format_duration";
|
||||
import secondsToDuration from "../common/datetime/seconds_to_duration";
|
||||
import { ensureArray } from "../common/ensure-array";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { Condition, Trigger } from "./automation";
|
||||
|
@ -29,6 +29,7 @@ export interface CalendarEventData {
|
||||
dtstart: string;
|
||||
dtend: string;
|
||||
rrule?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface CalendarEventMutableParams {
|
||||
@ -36,6 +37,7 @@ export interface CalendarEventMutableParams {
|
||||
dtstart: string;
|
||||
dtend: string;
|
||||
rrule?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// The scope of a delete/update for a recurring event
|
||||
@ -84,6 +86,7 @@ export const fetchCalendarEvents = async (
|
||||
const eventData: CalendarEventData = {
|
||||
uid: ev.uid,
|
||||
summary: ev.summary,
|
||||
description: ev.description,
|
||||
dtstart: eventStart,
|
||||
dtend: eventEnd,
|
||||
recurrence_id: ev.recurrence_id,
|
||||
|
@ -60,7 +60,7 @@ export const getCloudTtsSupportedGenders = (
|
||||
genders.push([
|
||||
gender,
|
||||
gender === "male" || gender === "female"
|
||||
? localize(`ui.panel.config.cloud.account.tts.${gender}`)
|
||||
? localize(`ui.components.media-browser.tts.gender_${gender}`)
|
||||
: gender,
|
||||
]);
|
||||
}
|
||||
|
@ -210,7 +210,10 @@ export const getCurrentProgress = (stateObj: MediaPlayerEntity): number => {
|
||||
(Date.now() -
|
||||
new Date(stateObj.attributes.media_position_updated_at!).getTime()) /
|
||||
1000.0;
|
||||
return progress;
|
||||
// Prevent negative values, so we do not go back to 59:59 at the start
|
||||
// for example if there are slight clock sync deltas between backend and frontend and
|
||||
// therefore media_position_updated_at might be slightly larger than Date.now().
|
||||
return progress < 0 ? 0 : progress;
|
||||
};
|
||||
|
||||
export const computeMediaDescription = (
|
||||
@ -402,7 +405,13 @@ export const cleanupMediaTitle = (title?: string): string | undefined => {
|
||||
}
|
||||
|
||||
const index = title.indexOf("?authSig=");
|
||||
return index > 0 ? title.slice(0, index) : title;
|
||||
let cleanTitle = index > 0 ? title.slice(0, index) : title;
|
||||
|
||||
if (cleanTitle.startsWith("http")) {
|
||||
cleanTitle = decodeURIComponent(cleanTitle.split("/").pop()!);
|
||||
}
|
||||
|
||||
return cleanTitle;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -267,7 +267,7 @@ export const adjustStatisticsSum = (
|
||||
return hass.callWS({
|
||||
type: "recorder/adjust_sum_statistics",
|
||||
statistic_id,
|
||||
start_time_iso,
|
||||
start_time: start_time_iso,
|
||||
adjustment,
|
||||
adjustment_unit_of_measurement,
|
||||
});
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
Describe,
|
||||
boolean,
|
||||
} from "superstruct";
|
||||
import { arrayLiteralIncludes } from "../common/array/literal-includes";
|
||||
import { navigate } from "../common/navigate";
|
||||
import { HomeAssistant } from "../types";
|
||||
import {
|
||||
@ -28,11 +29,7 @@ import { BlueprintInput } from "./blueprint";
|
||||
|
||||
export const MODES = ["single", "restart", "queued", "parallel"] as const;
|
||||
export const MODES_MAX = ["queued", "parallel"] as const;
|
||||
|
||||
export const isMaxMode = (
|
||||
mode: typeof MODES[number]
|
||||
): mode is typeof MODES_MAX[number] =>
|
||||
MODES_MAX.includes(mode as typeof MODES_MAX[number]);
|
||||
export const isMaxMode = arrayLiteralIncludes(MODES_MAX);
|
||||
|
||||
export const baseActionStruct = object({
|
||||
alias: optional(string()),
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { formatDuration } from "../common/datetime/format_duration";
|
||||
import secondsToDuration from "../common/datetime/seconds_to_duration";
|
||||
import { ensureArray } from "../common/ensure-array";
|
||||
import { ensureArray } from "../common/array/ensure-array";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
import { isTemplate } from "../common/string/has-template";
|
||||
import { HomeAssistant } from "../types";
|
||||
|
@ -39,6 +39,15 @@ export const addItem = (
|
||||
name,
|
||||
});
|
||||
|
||||
export const removeItem = (
|
||||
hass: HomeAssistant,
|
||||
item_id: string
|
||||
): Promise<ShoppingListItem> =>
|
||||
hass.callWS({
|
||||
type: "shopping_list/items/remove",
|
||||
item_id,
|
||||
});
|
||||
|
||||
export const reorderItems = (
|
||||
hass: HomeAssistant,
|
||||
itemIds: string[]
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { formatDateWeekday } from "../../../common/datetime/format_date";
|
||||
import { formatDateWeekdayDay } from "../../../common/datetime/format_date";
|
||||
import { formatTimeWeekday } from "../../../common/datetime/format_time";
|
||||
import { formatNumber } from "../../../common/number/format_number";
|
||||
import "../../../components/ha-svg-icon";
|
||||
@ -170,7 +170,7 @@ class MoreInfoWeather extends LitElement {
|
||||
`
|
||||
: html`
|
||||
<div class="main">
|
||||
${formatDateWeekday(
|
||||
${formatDateWeekdayDay(
|
||||
new Date(item.datetime),
|
||||
this.hass.locale
|
||||
)}
|
||||
|
@ -3,12 +3,15 @@ import { mdiCalendarClock, mdiClose } from "@mdi/js";
|
||||
import { addDays, isSameDay } from "date-fns/esm";
|
||||
import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { RRule } from "rrule";
|
||||
import { RRule, Weekday } from "rrule";
|
||||
import { formatDate } from "../../common/datetime/format_date";
|
||||
import { formatDateTime } from "../../common/datetime/format_date_time";
|
||||
import { formatTime } from "../../common/datetime/format_time";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { capitalizeFirstLetter } from "../../common/string/capitalize-first-letter";
|
||||
import { isDate } from "../../common/string/is_date";
|
||||
import { dayNames } from "../../common/translations/day_names";
|
||||
import { monthNames } from "../../common/translations/month_names";
|
||||
import "../../components/entity/state-info";
|
||||
import "../../components/ha-date-input";
|
||||
import "../../components/ha-time-input";
|
||||
@ -84,8 +87,13 @@ class DialogCalendarEventDetail extends LitElement {
|
||||
<div class="value">
|
||||
${this._formatDateRange()}<br />
|
||||
${this._data!.rrule
|
||||
? this._renderRruleAsText(this._data.rrule)
|
||||
? this._renderRRuleAsText(this._data.rrule)
|
||||
: ""}
|
||||
${this._data.description
|
||||
? html`<br />
|
||||
<div class="description">${this._data.description}</div>
|
||||
<br />`
|
||||
: html``}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -108,7 +116,8 @@ class DialogCalendarEventDetail extends LitElement {
|
||||
${this.hass.localize("ui.components.calendar.event.delete")}
|
||||
</mwc-button>
|
||||
`
|
||||
: ""}${this._params.canEdit
|
||||
: ""}
|
||||
${this._params.canEdit
|
||||
? html`<mwc-button
|
||||
slot="primaryAction"
|
||||
@click=${this._editEvent}
|
||||
@ -121,17 +130,59 @@ class DialogCalendarEventDetail extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderRruleAsText(value: string) {
|
||||
// TODO: Make sure this handles translations
|
||||
try {
|
||||
const readableText =
|
||||
value === "" ? "" : RRule.fromString(`RRULE:${value}`).toText();
|
||||
return html`<div id="text">${readableText}</div>`;
|
||||
} catch (e) {
|
||||
private _renderRRuleAsText(value: string) {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
const rule = RRule.fromString(`RRULE:${value}`);
|
||||
if (rule.isFullyConvertibleToText()) {
|
||||
return html`<div id="text">
|
||||
${capitalizeFirstLetter(
|
||||
rule.toText(
|
||||
this._translateRRuleElement,
|
||||
{
|
||||
dayNames: dayNames(this.hass.locale),
|
||||
monthNames: monthNames(this.hass.locale),
|
||||
tokens: {},
|
||||
},
|
||||
this._formatDate
|
||||
)
|
||||
)}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return html`<div id="text">Cannot convert recurrence rule</div>`;
|
||||
} catch (e) {
|
||||
return "Error while processing the rule";
|
||||
}
|
||||
}
|
||||
|
||||
private _translateRRuleElement = (id: string | number | Weekday): string => {
|
||||
if (typeof id === "string") {
|
||||
return this.hass.localize(`ui.components.calendar.event.rrule.${id}`);
|
||||
}
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
private _formatDate = (year: number, month: string, day: number): string => {
|
||||
if (!year || !month || !day) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Build date so we can then format it
|
||||
const date = new Date();
|
||||
date.setFullYear(year);
|
||||
// As input we already get the localized month name, so we now unfortunately
|
||||
// need to convert it back to something Date can work with. The already localized
|
||||
// months names are a must in the RRule.Language structure (an empty string[] would
|
||||
// mean we get undefined months input in this method here).
|
||||
date.setMonth(monthNames(this.hass.locale).indexOf(month));
|
||||
date.setDate(day);
|
||||
return formatDate(date, this.hass.locale);
|
||||
};
|
||||
|
||||
private _formatDateRange() {
|
||||
const start = new Date(this._data!.dtstart);
|
||||
// All day events should be displayed as a day earlier
|
||||
@ -227,7 +278,7 @@ class DialogCalendarEventDetail extends LitElement {
|
||||
ha-svg-icon {
|
||||
width: 40px;
|
||||
margin-right: 8px;
|
||||
margin-inline-end: 8px;
|
||||
margin-inline-end: 16px;
|
||||
margin-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
vertical-align: top;
|
||||
@ -235,6 +286,11 @@ class DialogCalendarEventDetail extends LitElement {
|
||||
.field {
|
||||
display: flex;
|
||||
}
|
||||
.description {
|
||||
color: var(--secondary-text-color);
|
||||
max-width: 300px;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { isDate } from "../../common/string/is_date";
|
||||
import "../../components/ha-date-input";
|
||||
import "../../components/ha-textarea";
|
||||
import "../../components/ha-time-input";
|
||||
import {
|
||||
Calendar,
|
||||
@ -42,6 +43,8 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
|
||||
@state() private _summary = "";
|
||||
|
||||
@state() private _description = "";
|
||||
|
||||
@state() private _rrule?: string;
|
||||
|
||||
@state() private _allDay = false;
|
||||
@ -73,7 +76,11 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
}
|
||||
} else {
|
||||
this._allDay = false;
|
||||
this._dtstart = startOfHour(new Date());
|
||||
// If we have been provided a selected date (e.g. based on the currently displayed
|
||||
// day in a calendar view), use that as the starting value.
|
||||
this._dtstart = startOfHour(
|
||||
params.selectedDate ? params.selectedDate : new Date()
|
||||
);
|
||||
this._dtend = addHours(this._dtstart, 1);
|
||||
}
|
||||
}
|
||||
@ -123,6 +130,15 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
error-message=${this.hass.localize("ui.common.error_required")}
|
||||
dialogInitialFocus
|
||||
></ha-textfield>
|
||||
<ha-textarea
|
||||
class="description"
|
||||
name="description"
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.calendar.event.description"
|
||||
)}
|
||||
@change=${this._handleDescriptionChanged}
|
||||
autogrow
|
||||
></ha-textarea>
|
||||
<ha-combo-box
|
||||
name="calendar"
|
||||
.hass=${this.hass}
|
||||
@ -189,6 +205,7 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
</div>
|
||||
<ha-recurrence-rule-editor
|
||||
.locale=${this.hass.locale}
|
||||
.timezone=${this.hass.config.time_zone}
|
||||
.value=${this._rrule || ""}
|
||||
@value-changed=${this._handleRRuleChanged}
|
||||
>
|
||||
@ -247,6 +264,10 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
this._summary = ev.target.value;
|
||||
}
|
||||
|
||||
private _handleDescriptionChanged(ev) {
|
||||
this._description = ev.target.value;
|
||||
}
|
||||
|
||||
private _handleRRuleChanged(ev) {
|
||||
this._rrule = ev.detail.value;
|
||||
}
|
||||
@ -286,6 +307,7 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
);
|
||||
const data: CalendarEventMutableParams = {
|
||||
summary: this._summary,
|
||||
description: this._description,
|
||||
rrule: this._rrule,
|
||||
dtstart: "",
|
||||
dtend: "",
|
||||
@ -308,6 +330,13 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
}
|
||||
|
||||
private async _createEvent() {
|
||||
if (!this._summary || !this._calendarId) {
|
||||
this._error = this.hass.localize(
|
||||
"ui.components.calendar.event.not_all_required_fields"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this._submitting = true;
|
||||
try {
|
||||
await createCalendarEvent(
|
||||
@ -385,6 +414,7 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
this._dtstart = undefined;
|
||||
this._dtend = undefined;
|
||||
this._summary = "";
|
||||
this._description = "";
|
||||
this._rrule = undefined;
|
||||
}
|
||||
|
||||
@ -395,9 +425,16 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
state-info {
|
||||
line-height: 40px;
|
||||
}
|
||||
ha-textfield {
|
||||
ha-alert {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-textfield,
|
||||
ha-textarea {
|
||||
display: block;
|
||||
}
|
||||
ha-textarea {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
ha-formfield {
|
||||
display: block;
|
||||
@ -430,12 +467,11 @@ class DialogCalendarEventEditor extends LitElement {
|
||||
}
|
||||
ha-combo-box {
|
||||
display: block;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
ha-svg-icon {
|
||||
width: 40px;
|
||||
margin-right: 8px;
|
||||
margin-inline-end: 8px;
|
||||
margin-inline-end: 16px;
|
||||
margin-inline-start: initial;
|
||||
direction: var(--direction);
|
||||
vertical-align: top;
|
||||
|
@ -29,28 +29,29 @@ import {
|
||||
} from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import memoize from "memoize-one";
|
||||
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
|
||||
import { useAmPm } from "../../common/datetime/use_am_pm";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { LocalizeFunc } from "../../common/translations/localize";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import "../../components/ha-button-toggle-group";
|
||||
import "../../components/ha-fab";
|
||||
import "../../components/ha-icon-button-prev";
|
||||
import "../../components/ha-icon-button-next";
|
||||
import "../../components/ha-icon-button-prev";
|
||||
import type {
|
||||
Calendar as CalendarData,
|
||||
CalendarEvent,
|
||||
} from "../../data/calendar";
|
||||
import { CalendarEntityFeature } from "../../data/calendar";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { computeRTLDirection } from "../../common/util/compute_rtl";
|
||||
import type {
|
||||
CalendarViewChanged,
|
||||
FullCalendarView,
|
||||
HomeAssistant,
|
||||
ToggleButton,
|
||||
} from "../../types";
|
||||
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
|
||||
import { supportsFeature } from "../../common/entity/supports-feature";
|
||||
import { showCalendarEventDetailDialog } from "./show-dialog-calendar-event-detail";
|
||||
import type {
|
||||
Calendar as CalendarData,
|
||||
CalendarEvent,
|
||||
} from "../../data/calendar";
|
||||
import { CalendarEntityFeature } from "../../data/calendar";
|
||||
import { showCalendarEventEditDialog } from "./show-dialog-calendar-event-editor";
|
||||
|
||||
declare global {
|
||||
@ -62,15 +63,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const getListWeekRange = (currentDate: Date): { start: Date; end: Date } => {
|
||||
const startDate = new Date(currentDate.valueOf());
|
||||
const endDate = new Date(currentDate.valueOf());
|
||||
|
||||
endDate.setDate(endDate.getDate() + 7);
|
||||
|
||||
return { start: startDate, end: endDate };
|
||||
};
|
||||
|
||||
const defaultFullCalendarConfig: CalendarOptions = {
|
||||
headerToolbar: false,
|
||||
plugins: [dayGridPlugin, listPlugin, interactionPlugin],
|
||||
@ -80,19 +72,13 @@ const defaultFullCalendarConfig: CalendarOptions = {
|
||||
eventDisplay: "list-item",
|
||||
locales: allLocales,
|
||||
views: {
|
||||
list: {
|
||||
visibleRange: getListWeekRange,
|
||||
listWeek: {
|
||||
type: "list",
|
||||
duration: { days: 7 },
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const viewButtons: ToggleButton[] = [
|
||||
{ label: "Month View", value: "dayGridMonth", iconPath: mdiViewModule },
|
||||
{ label: "Week View", value: "dayGridWeek", iconPath: mdiViewWeek },
|
||||
{ label: "Day View", value: "dayGridDay", iconPath: mdiViewDay },
|
||||
{ label: "List View", value: "list", iconPath: mdiViewAgenda },
|
||||
];
|
||||
|
||||
export class HAFullCalendar extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@ -106,12 +92,15 @@ export class HAFullCalendar extends LitElement {
|
||||
"dayGridMonth",
|
||||
"dayGridWeek",
|
||||
"dayGridDay",
|
||||
"listWeek",
|
||||
];
|
||||
|
||||
@property() public initialView: FullCalendarView = "dayGridMonth";
|
||||
|
||||
private calendar?: Calendar;
|
||||
|
||||
private _viewButtons?: ToggleButton[];
|
||||
|
||||
@state() private _activeView = this.initialView;
|
||||
|
||||
public updateSize(): void {
|
||||
@ -119,7 +108,10 @@ export class HAFullCalendar extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const viewToggleButtons = this._viewToggleButtons(this.views);
|
||||
const viewToggleButtons = this._viewToggleButtons(
|
||||
this.views,
|
||||
this.hass.localize
|
||||
);
|
||||
|
||||
return html`
|
||||
${this.calendar
|
||||
@ -276,8 +268,19 @@ export class HAFullCalendar extends LitElement {
|
||||
}
|
||||
|
||||
private _createEvent(_info) {
|
||||
// Logic for selectedDate: In week and day view, use the start of the week or the selected day.
|
||||
// If we are in month view, we only use the start of the month, if we are not showing the
|
||||
// current actual month, as for that one the current day is automatically highlighted and
|
||||
// defaulting to a different day in the event creation dialog would be weird.
|
||||
showCalendarEventEditDialog(this, {
|
||||
calendars: this._mutableCalendars,
|
||||
selectedDate:
|
||||
this._activeView === "dayGridWeek" ||
|
||||
this._activeView === "dayGridDay" ||
|
||||
(this._activeView === "dayGridMonth" &&
|
||||
this.calendar!.view.currentStart.getMonth() !== new Date().getMonth())
|
||||
? this.calendar!.view.currentStart
|
||||
: undefined,
|
||||
updated: () => {
|
||||
this._fireViewChanged();
|
||||
},
|
||||
@ -338,11 +341,44 @@ export class HAFullCalendar extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
private _viewToggleButtons = memoize((views) =>
|
||||
viewButtons.filter((button) =>
|
||||
private _viewToggleButtons = memoize((views, localize: LocalizeFunc) => {
|
||||
if (!this._viewButtons) {
|
||||
this._viewButtons = [
|
||||
{
|
||||
label: localize(
|
||||
"ui.panel.lovelace.editor.card.calendar.views.dayGridMonth"
|
||||
),
|
||||
value: "dayGridMonth",
|
||||
iconPath: mdiViewModule,
|
||||
},
|
||||
{
|
||||
label: localize(
|
||||
"ui.panel.lovelace.editor.card.calendar.views.dayGridWeek"
|
||||
),
|
||||
value: "dayGridWeek",
|
||||
iconPath: mdiViewWeek,
|
||||
},
|
||||
{
|
||||
label: localize(
|
||||
"ui.panel.lovelace.editor.card.calendar.views.dayGridDay"
|
||||
),
|
||||
value: "dayGridDay",
|
||||
iconPath: mdiViewDay,
|
||||
},
|
||||
{
|
||||
label: localize(
|
||||
"ui.panel.lovelace.editor.card.calendar.views.listWeek"
|
||||
),
|
||||
value: "listWeek",
|
||||
iconPath: mdiViewAgenda,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
return this._viewButtons.filter((button) =>
|
||||
views.includes(button.value as FullCalendarView)
|
||||
)
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
@ -380,7 +416,7 @@ export class HAFullCalendar extends LitElement {
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-text-color);
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.controls {
|
||||
@ -442,6 +478,12 @@ export class HAFullCalendar extends LitElement {
|
||||
|
||||
.fc-theme-standard .fc-scrollgrid {
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: var(--mdc-shape-small, 4px);
|
||||
}
|
||||
|
||||
.fc-theme-standard td {
|
||||
border-bottom-left-radius: var(--mdc-shape-small, 4px);
|
||||
border-bottom-right-radius: var(--mdc-shape-small, 4px);
|
||||
}
|
||||
|
||||
.fc-scrollgrid-section-header td {
|
||||
@ -449,9 +491,10 @@ export class HAFullCalendar extends LitElement {
|
||||
}
|
||||
|
||||
th.fc-col-header-cell.fc-day {
|
||||
color: var(--secondary-text-color);
|
||||
background-color: var(--table-header-background-color);
|
||||
color: var(--primary-text-color);
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,8 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
|
||||
@property({ attribute: false }) public locale!: HomeAssistant["locale"];
|
||||
|
||||
@property() public timezone?: string;
|
||||
|
||||
@state() private _computedRRule = "";
|
||||
|
||||
@state() private _freq?: RepeatFrequency = "none";
|
||||
@ -292,6 +294,7 @@ export class RecurrenceRuleEditor extends LitElement {
|
||||
byweekday: ruleByWeekDay(this._weekday),
|
||||
count: this._count,
|
||||
until: this._until,
|
||||
tzid: this.timezone,
|
||||
};
|
||||
const contentline = RRule.optionsToString(options);
|
||||
return contentline.slice(6); // Strip "RRULE:" prefix
|
||||
|
@ -2,7 +2,7 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { Calendar, CalendarEventData } from "../../data/calendar";
|
||||
|
||||
export interface CalendarEventDetailDialogParams {
|
||||
calendars: Calendar[]; // When creating new events, is the list of events that support creation
|
||||
calendars: Calendar[]; // When creating new events, is the list of calendar entities that support creation
|
||||
calendarId?: string;
|
||||
entry?: CalendarEventData;
|
||||
canDelete?: boolean;
|
||||
|
@ -2,8 +2,9 @@ import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { Calendar, CalendarEventData } from "../../data/calendar";
|
||||
|
||||
export interface CalendarEventEditDialogParams {
|
||||
calendars: Calendar[]; // When creating new events, is the list of events that support creation
|
||||
calendars: Calendar[]; // When creating new events, is the list of calendar entities that support creation
|
||||
calendarId?: string;
|
||||
selectedDate?: Date; // When provided is used as the pre-filled date for the event creation dialog
|
||||
entry?: CalendarEventData;
|
||||
canDelete?: boolean;
|
||||
updated: () => void;
|
||||
|
@ -2,7 +2,7 @@ import { mdiDelete, mdiPlus } from "@mdi/js";
|
||||
import { css, CSSResultGroup, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { ensureArray } from "../../../../../common/ensure-array";
|
||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import { Condition } from "../../../../../data/automation";
|
||||
import { Action, ChooseAction } from "../../../../../data/script";
|
||||
|
@ -1,22 +1,13 @@
|
||||
import { css, CSSResultGroup, html, LitElement, PropertyValues } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { any, assert, object, optional, string } from "superstruct";
|
||||
import { assert } from "superstruct";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { hasTemplate } from "../../../../../common/string/has-template";
|
||||
import { entityIdOrAll } from "../../../../../common/structs/is-entity-id";
|
||||
import "../../../../../components/ha-service-control";
|
||||
import { ServiceAction } from "../../../../../data/script";
|
||||
import { ServiceAction, serviceActionStruct } from "../../../../../data/script";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { ActionElement } from "../ha-automation-action-row";
|
||||
|
||||
const actionStruct = object({
|
||||
alias: optional(string()),
|
||||
service: optional(string()),
|
||||
entity_id: optional(entityIdOrAll()),
|
||||
target: optional(any()),
|
||||
data: optional(any()),
|
||||
});
|
||||
|
||||
@customElement("ha-automation-action-service")
|
||||
export class HaServiceAction extends LitElement implements ActionElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@ -38,7 +29,7 @@ export class HaServiceAction extends LitElement implements ActionElement {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
assert(this.action, actionStruct);
|
||||
assert(this.action, serviceActionStruct);
|
||||
} catch (err: any) {
|
||||
fireEvent(this, "ui-mode-not-available", err);
|
||||
return;
|
||||
|
@ -10,7 +10,7 @@ import { ActionElement, handleChangeEvent } from "../ha-automation-action-row";
|
||||
import "../../../../../components/ha-duration-input";
|
||||
import { createDurationData } from "../../../../../common/datetime/create_duration_data";
|
||||
import { TimeChangedEvent } from "../../../../../components/ha-base-time-input";
|
||||
import { ensureArray } from "../../../../../common/ensure-array";
|
||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||
|
||||
@customElement("ha-automation-action-wait_for_trigger")
|
||||
export class HaWaitForTriggerAction
|
||||
|
@ -3,7 +3,7 @@ import { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { ensureArray } from "../../../../../common/ensure-array";
|
||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||
import "../../../../../components/ha-select";
|
||||
import type {
|
||||
AutomationConfig,
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
union,
|
||||
} from "superstruct";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { ensureArray } from "../../../../../common/ensure-array";
|
||||
import { ensureArray } from "../../../../../common/array/ensure-array";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { hasTemplate } from "../../../../../common/string/has-template";
|
||||
import { StateTrigger } from "../../../../../data/automation";
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { arrayLiteralIncludes } from "../../../common/array/literal-includes";
|
||||
import type { Counter } from "../../../data/counter";
|
||||
import type { InputBoolean } from "../../../data/input_boolean";
|
||||
import type { InputButton } from "../../../data/input_button";
|
||||
@ -18,7 +19,10 @@ export const HELPER_DOMAINS = [
|
||||
"counter",
|
||||
"timer",
|
||||
"schedule",
|
||||
];
|
||||
] as const;
|
||||
|
||||
export type HelperDomain = typeof HELPER_DOMAINS[number];
|
||||
export const isHelperDomain = arrayLiteralIncludes(HELPER_DOMAINS);
|
||||
|
||||
export type Helper =
|
||||
| InputBoolean
|
||||
|
@ -25,7 +25,7 @@ import { showConfigFlowDialog } from "../../../dialogs/config-flow/show-dialog-c
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { brandsUrl } from "../../../util/brands-url";
|
||||
import { Helper } from "./const";
|
||||
import { Helper, HelperDomain } from "./const";
|
||||
import "./forms/ha-counter-form";
|
||||
import "./forms/ha-input_boolean-form";
|
||||
import "./forms/ha-input_button-form";
|
||||
@ -37,7 +37,18 @@ import "./forms/ha-schedule-form";
|
||||
import "./forms/ha-timer-form";
|
||||
import type { ShowDialogHelperDetailParams } from "./show-dialog-helper-detail";
|
||||
|
||||
const HELPERS = {
|
||||
type HelperCreators = {
|
||||
[domain in HelperDomain]: (
|
||||
hass: HomeAssistant,
|
||||
// Not properly typed because there is currently a mismatch for this._item between:
|
||||
// 1. Type passed to form should be Helper
|
||||
// 2. Type received by creator should be MutableParams version
|
||||
// The two are not compatible.
|
||||
params: any
|
||||
) => Promise<Helper>;
|
||||
};
|
||||
|
||||
const HELPERS: HelperCreators = {
|
||||
input_boolean: createInputBoolean,
|
||||
input_button: createInputButton,
|
||||
input_text: createInputText,
|
||||
@ -57,7 +68,7 @@ export class DialogHelperDetail extends LitElement {
|
||||
|
||||
@state() private _opened = false;
|
||||
|
||||
@state() private _domain?: string;
|
||||
@state() private _domain?: HelperDomain;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@ -127,7 +138,7 @@ export class DialogHelperDetail extends LitElement {
|
||||
} else {
|
||||
const items: [string, string][] = [];
|
||||
|
||||
for (const helper of Object.keys(HELPERS)) {
|
||||
for (const helper of Object.keys(HELPERS) as (keyof typeof HELPERS)[]) {
|
||||
items.push([
|
||||
helper,
|
||||
this.hass.localize(`ui.panel.config.helpers.types.${helper}`) ||
|
||||
|
@ -208,7 +208,10 @@ class HaScheduleForm extends LitElement {
|
||||
private get _events() {
|
||||
const events: any[] = [];
|
||||
const currentDay = new Date().getDay();
|
||||
const baseDay = currentDay === 0 ? 7 : currentDay;
|
||||
const baseDay =
|
||||
currentDay === 0 && firstWeekdayIndex(this.hass.locale) === 1
|
||||
? 7
|
||||
: currentDay;
|
||||
|
||||
for (const [i, day] of weekdays.entries()) {
|
||||
if (!this[`_${day}`].length) {
|
||||
@ -216,8 +219,11 @@ class HaScheduleForm extends LitElement {
|
||||
}
|
||||
|
||||
this[`_${day}`].forEach((item: ScheduleDay, index: number) => {
|
||||
// Add 7 to 0 because we start the calendar on Monday
|
||||
const distance = i - baseDay + (i === 0 ? 7 : 0);
|
||||
// Add 7 to 0 because we start the calendar on Monday, except when the locale says otherwise (firstWeekdayIndex() != 1)
|
||||
const distance =
|
||||
i -
|
||||
baseDay +
|
||||
(i === 0 && firstWeekdayIndex(this.hass.locale) === 1 ? 7 : 0);
|
||||
|
||||
const start = new Date();
|
||||
start.setDate(start.getDate() + distance);
|
||||
@ -388,6 +394,8 @@ class HaScheduleForm extends LitElement {
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
--fc-border-color: var(--divider-color);
|
||||
--fc-event-border-color: var(--divider-color);
|
||||
}
|
||||
.fc-scroller {
|
||||
overflow-x: visible !important;
|
||||
@ -395,6 +403,18 @@ class HaScheduleForm extends LitElement {
|
||||
.fc-v-event .fc-event-time {
|
||||
white-space: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
th.fc-col-header-cell.fc-day {
|
||||
background-color: var(--table-header-background-color);
|
||||
color: var(--primary-text-color);
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import "../integrations/ha-integration-overflow-menu";
|
||||
import { HELPER_DOMAINS } from "./const";
|
||||
import { HelperDomain, isHelperDomain } from "./const";
|
||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||
|
||||
// This groups items by a key but only returns last entry per key.
|
||||
@ -118,7 +118,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
sortable: true,
|
||||
width: "25%",
|
||||
filterable: true,
|
||||
template: (type, row) =>
|
||||
template: (type: HelperDomain, row) =>
|
||||
row.configEntry
|
||||
? domainToName(localize, type)
|
||||
: html`
|
||||
@ -243,7 +243,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
if (!domain) {
|
||||
return;
|
||||
}
|
||||
if (HELPER_DOMAINS.includes(domain)) {
|
||||
if (isHelperDomain(domain)) {
|
||||
showHelperDetailDialog(this, {
|
||||
domain,
|
||||
});
|
||||
@ -330,7 +330,7 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
const newStates = Object.values(this.hass!.states).filter(
|
||||
(entity) =>
|
||||
extraEntities.has(entity.entity_id) ||
|
||||
HELPER_DOMAINS.includes(computeStateDomain(entity))
|
||||
isHelperDomain(computeStateDomain(entity))
|
||||
);
|
||||
|
||||
if (
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { DataEntryFlowDialogParams } from "../../../dialogs/config-flow/show-dialog-data-entry-flow";
|
||||
import { HelperDomain } from "./const";
|
||||
|
||||
export const loadHelperDetailDialog = () => import("./dialog-helper-detail");
|
||||
|
||||
export interface ShowDialogHelperDetailParams {
|
||||
domain?: string;
|
||||
domain?: HelperDomain;
|
||||
// Only used for config entries
|
||||
dialogClosedCallback?: DataEntryFlowDialogParams["dialogClosedCallback"];
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { haStyle } from "../../../resources/styles";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { configSections } from "../ha-panel-config";
|
||||
import { HELPER_DOMAINS } from "../helpers/const";
|
||||
import { isHelperDomain } from "../helpers/const";
|
||||
import "./ha-config-flow-card";
|
||||
import "./ha-ignored-config-entry-card";
|
||||
import "./ha-integration-card";
|
||||
@ -785,7 +785,7 @@ class HaConfigIntegrations extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
|
||||
// If not an integration or supported brand, try helper else show alert
|
||||
if (HELPER_DOMAINS.includes(domain)) {
|
||||
if (isHelperDomain(domain)) {
|
||||
navigate(`/config/helpers/add?domain=${domain}`, {
|
||||
replace: true,
|
||||
});
|
||||
|
@ -548,6 +548,9 @@ export class HaIntegrationCard extends LitElement {
|
||||
}
|
||||
|
||||
private async _handleDisableDebugLogging(ev: MouseEvent) {
|
||||
// Stop propagation since otherwise we end up here twice while we await the log level change
|
||||
// and trigger two identical debug log downloads.
|
||||
ev.stopPropagation();
|
||||
const configEntry = ((ev.target as HTMLElement).closest("ha-card") as any)
|
||||
.configEntry;
|
||||
const integration = configEntry.domain;
|
||||
|
@ -82,7 +82,7 @@ class HaPanelDevMqtt extends LitElement {
|
||||
}
|
||||
|
||||
private _handleTopic(ev: CustomEvent) {
|
||||
this.topic = ev.detail.value;
|
||||
this.topic = (ev.target! as any).value;
|
||||
if (localStorage && this.inited) {
|
||||
localStorage["panel-dev-mqtt-topic"] = this.topic;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import { css, html, LitElement, PropertyValues } from "lit";
|
||||
import { property, state } from "lit/decorators";
|
||||
import { firstWeekdayIndex } from "../../common/datetime/first_weekday";
|
||||
import { LocalStorage } from "../../common/decorators/local-storage";
|
||||
import { ensureArray } from "../../common/ensure-array";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { navigate } from "../../common/navigate";
|
||||
import {
|
||||
createSearchParam,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { css, html, LitElement, PropertyValues, TemplateResult } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { ensureArray } from "../../common/ensure-array";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { computeStateDomain } from "../../common/entity/compute_state_domain";
|
||||
import { throttle } from "../../common/util/throttle";
|
||||
import "../../components/ha-circular-progress";
|
||||
|
@ -119,8 +119,8 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
||||
}
|
||||
|
||||
const views: FullCalendarView[] = this._veryNarrow
|
||||
? ["list"]
|
||||
: ["list", "dayGridMonth", "dayGridDay"];
|
||||
? ["listWeek"]
|
||||
: ["dayGridMonth", "dayGridDay", "listWeek"];
|
||||
|
||||
return html`
|
||||
<ha-card>
|
||||
|
@ -8,32 +8,33 @@ import {
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from "lit";
|
||||
import { customElement, property, state, query } from "lit/decorators";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { guard } from "lit/directives/guard";
|
||||
import { repeat } from "lit/directives/repeat";
|
||||
import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_element";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-checkbox";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-textfield";
|
||||
import type { HaTextField } from "../../../components/ha-textfield";
|
||||
import {
|
||||
addItem,
|
||||
clearItems,
|
||||
fetchItems,
|
||||
removeItem,
|
||||
reorderItems,
|
||||
ShoppingListItem,
|
||||
updateItem,
|
||||
} from "../../../data/shopping-list";
|
||||
import { SubscribeMixin } from "../../../mixins/subscribe-mixin";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { SensorCardConfig, ShoppingListCardConfig } from "./types";
|
||||
import type { HaTextField } from "../../../components/ha-textfield";
|
||||
import {
|
||||
loadSortable,
|
||||
SortableInstance,
|
||||
} from "../../../resources/sortable.ondemand";
|
||||
import { HomeAssistant } from "../../../types";
|
||||
import { LovelaceCard, LovelaceCardEditor } from "../types";
|
||||
import { SensorCardConfig, ShoppingListCardConfig } from "./types";
|
||||
|
||||
@customElement("hui-shopping-list-card")
|
||||
class HuiShoppingListCard
|
||||
@ -264,9 +265,14 @@ class HuiShoppingListCard
|
||||
}
|
||||
|
||||
private _saveEdit(ev): void {
|
||||
updateItem(this.hass!, ev.target.itemId, {
|
||||
name: ev.target.value,
|
||||
}).catch(() => this._fetchData());
|
||||
// If name is not empty, update the item otherwise remove it
|
||||
if (ev.target.value) {
|
||||
updateItem(this.hass!, ev.target.itemId, {
|
||||
name: ev.target.value,
|
||||
}).catch(() => this._fetchData());
|
||||
} else {
|
||||
removeItem(this.hass!, ev.target.itemId).catch(() => this._fetchData());
|
||||
}
|
||||
|
||||
ev.target.blur();
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ const cardConfigStruct = assign(
|
||||
})
|
||||
);
|
||||
|
||||
const views = ["dayGridMonth", "dayGridDay", "list"] as const;
|
||||
const views = ["dayGridMonth", "dayGridDay", "listWeek"] as const;
|
||||
|
||||
@customElement("hui-calendar-card-editor")
|
||||
export class HuiCalendarCardEditor
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
union,
|
||||
} from "superstruct";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { ensureArray } from "../../../../common/ensure-array";
|
||||
import { ensureArray } from "../../../../common/array/ensure-array";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import { deepEqual } from "../../../../common/util/deep-equal";
|
||||
import {
|
||||
|
@ -212,7 +212,7 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
||||
const mediaDescription = computeMediaDescription(stateObj);
|
||||
const mediaDuration = formatMediaTime(stateObj.attributes.media_duration);
|
||||
const mediaTitleClean = cleanupMediaTitle(
|
||||
stateObj.attributes.media_title || ""
|
||||
stateObj.attributes.media_title || stateObj.attributes.media_content_id
|
||||
);
|
||||
const mediaArt =
|
||||
stateObj.attributes.entity_picture_local ||
|
||||
@ -232,7 +232,6 @@ export class BarMediaPlayer extends SubscribeMixin(LitElement) {
|
||||
<hui-marquee
|
||||
.text=${mediaTitleClean ||
|
||||
mediaDescription ||
|
||||
cleanupMediaTitle(stateObj.attributes.media_content_id) ||
|
||||
(stateObj.state !== "playing" && stateObj.state !== "on"
|
||||
? this.hass.localize(`ui.card.media_player.nothing_playing`)
|
||||
: "")}
|
||||
|
@ -75,6 +75,7 @@ export const derivedStyles = {
|
||||
"paper-listbox-background-color": "var(--card-background-color)",
|
||||
"paper-item-icon-color": "var(--state-icon-color)",
|
||||
"paper-item-icon-active-color": "var(--state-icon-active-color)",
|
||||
"table-header-background-color": "var(--input-fill-color)",
|
||||
"table-row-background-color": "var(--primary-background-color)",
|
||||
"table-row-alternative-background-color": "var(--secondary-background-color)",
|
||||
"paper-slider-knob-color": "var(--slider-color)",
|
||||
|
@ -642,6 +642,7 @@
|
||||
"all_day": "All Day",
|
||||
"start": "Start",
|
||||
"end": "End",
|
||||
"not_all_required_fields": "Not all required fields are filled in.",
|
||||
"confirm_delete": {
|
||||
"delete": "Delete Event",
|
||||
"delete_this": "Delete Only This Event",
|
||||
@ -666,7 +667,32 @@
|
||||
"daily": "days"
|
||||
}
|
||||
},
|
||||
"summary": "Summary"
|
||||
"rrule": {
|
||||
"every": "every",
|
||||
"years": "years",
|
||||
"year": "year",
|
||||
"months": "months",
|
||||
"month": "month",
|
||||
"weeks": "weeks",
|
||||
"week": "week",
|
||||
"weekdays": "weekdays",
|
||||
"weekday": "weekday",
|
||||
"days": "days",
|
||||
"day": "day",
|
||||
"until": "until",
|
||||
"for": "for",
|
||||
"in": "in",
|
||||
"on": "on",
|
||||
"on the": "on the",
|
||||
"and": "and",
|
||||
"or": "or",
|
||||
"at": "at",
|
||||
"last": "last",
|
||||
"time": "time",
|
||||
"times": "times"
|
||||
},
|
||||
"summary": "Summary",
|
||||
"description": "Description"
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
@ -2579,8 +2605,6 @@
|
||||
"info": "Bring personality to your home by having it speak to you by using our Text-to-Speech services. You can use this in automations and scripts by using the {service} service.",
|
||||
"default_language": "Default language to use",
|
||||
"default_gender": "Default gender to use",
|
||||
"male": "Male",
|
||||
"female": "Female",
|
||||
"try": "Try",
|
||||
"dialog": {
|
||||
"header": "Try Text to Speech",
|
||||
@ -4009,8 +4033,9 @@
|
||||
"calendar_entities": "Calendar Entities",
|
||||
"views": {
|
||||
"dayGridMonth": "Month",
|
||||
"dayGridWeek": "Week",
|
||||
"dayGridDay": "Day",
|
||||
"list": "List"
|
||||
"listWeek": "List (7 days)"
|
||||
}
|
||||
},
|
||||
"conditional": {
|
||||
|
@ -122,7 +122,7 @@ export type FullCalendarView =
|
||||
| "dayGridMonth"
|
||||
| "dayGridWeek"
|
||||
| "dayGridDay"
|
||||
| "list";
|
||||
| "listWeek";
|
||||
|
||||
export interface ToggleButton {
|
||||
label: string;
|
||||
|
26
yarn.lock
26
yarn.lock
@ -1358,10 +1358,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@braintree/sanitize-url@npm:^5.0.2":
|
||||
version: 5.0.2
|
||||
resolution: "@braintree/sanitize-url@npm:5.0.2"
|
||||
checksum: c033f9a0e6dd6fbd4022df2d3916a278510f759971b1e8ab278b3ce1123a3816d5fdd9d84c5c9fbcd6c94c05f8421c4c669f110c8db67eaf58f3018825af514e
|
||||
"@braintree/sanitize-url@npm:^6.0.0":
|
||||
version: 6.0.2
|
||||
resolution: "@braintree/sanitize-url@npm:6.0.2"
|
||||
checksum: 6a9dfd4081cc96516eeb281d1a83d3b5f1ad3d2837adf968fcc2ba18889ee833554f9c641b4083c36d3360a932e4504ddf25b0b51e9933c3742622df82cf7c9a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -5729,11 +5729,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"async@npm:^2.6.2":
|
||||
version: 2.6.2
|
||||
resolution: "async@npm:2.6.2"
|
||||
version: 2.6.4
|
||||
resolution: "async@npm:2.6.4"
|
||||
dependencies:
|
||||
lodash: ^4.17.11
|
||||
checksum: e5e90a3bcc4d9bf964bfc6b77d63b8f5bee8c14e9a51c3317dbcace44d5b6b1fe01cd4fd347449704a107da7fcd25e1382ee8545957b2702782ae720605cf7a4
|
||||
lodash: ^4.17.14
|
||||
checksum: a52083fb32e1ebe1d63e5c5624038bb30be68ff07a6c8d7dfe35e47c93fc144bd8652cbec869e0ac07d57dde387aa5f1386be3559cdee799cb1f789678d88e19
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -7026,9 +7026,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"decode-uri-component@npm:^0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "decode-uri-component@npm:0.2.0"
|
||||
checksum: f3749344ab9305ffcfe4bfe300e2dbb61fc6359e2b736812100a3b1b6db0a5668cba31a05e4b45d4d63dbf1a18dfa354cd3ca5bb3ededddabb8cd293f4404f94
|
||||
version: 0.2.2
|
||||
resolution: "decode-uri-component@npm:0.2.2"
|
||||
checksum: 95476a7d28f267292ce745eac3524a9079058bbb35767b76e3ee87d42e34cd0275d2eb19d9d08c3e167f97556e8a2872747f5e65cbebcac8b0c98d83e285f139
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -9282,7 +9282,7 @@ fsevents@^1.2.7:
|
||||
"@babel/plugin-syntax-top-level-await": ^7.14.5
|
||||
"@babel/preset-env": ^7.20.2
|
||||
"@babel/preset-typescript": ^7.18.6
|
||||
"@braintree/sanitize-url": ^5.0.2
|
||||
"@braintree/sanitize-url": ^6.0.0
|
||||
"@codemirror/autocomplete": ^0.19.12
|
||||
"@codemirror/commands": ^0.19.8
|
||||
"@codemirror/gutter": ^0.19.9
|
||||
@ -11294,7 +11294,7 @@ fsevents@^1.2.7:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lodash@npm:^4.17.11, lodash@npm:^4.17.20, lodash@npm:^4.17.21":
|
||||
"lodash@npm:^4.17.14, lodash@npm:^4.17.20, lodash@npm:^4.17.21":
|
||||
version: 4.17.21
|
||||
resolution: "lodash@npm:4.17.21"
|
||||
checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7
|
||||
|
Loading…
x
Reference in New Issue
Block a user