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