mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-30 21:27:21 +00:00
Compare commits
16 Commits
dev
...
calendar-s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
882682d0ae | ||
|
|
ec22937e71 | ||
|
|
53a87278dd | ||
|
|
31383c114b | ||
|
|
7e01777eaa | ||
|
|
9c64f5ac8b | ||
|
|
912e636207 | ||
|
|
43367350b7 | ||
|
|
64a25cf7f9 | ||
|
|
6de8f47e24 | ||
|
|
0427c17a76 | ||
|
|
0052f14521 | ||
|
|
792274a82a | ||
|
|
7b42b16de8 | ||
|
|
fe98c0bdc0 | ||
|
|
658955a1b9 |
@@ -53,6 +53,9 @@ export const enum CalendarEntityFeature {
|
|||||||
UPDATE_EVENT = 4,
|
UPDATE_EVENT = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Type for date values that can come from REST API or subscription */
|
||||||
|
type CalendarDateValue = string | { dateTime: string } | { date: string };
|
||||||
|
|
||||||
export const fetchCalendarEvents = async (
|
export const fetchCalendarEvents = async (
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
start: Date,
|
start: Date,
|
||||||
@@ -65,11 +68,11 @@ export const fetchCalendarEvents = async (
|
|||||||
|
|
||||||
const calEvents: CalendarEvent[] = [];
|
const calEvents: CalendarEvent[] = [];
|
||||||
const errors: string[] = [];
|
const errors: string[] = [];
|
||||||
const promises: Promise<CalendarEvent[]>[] = [];
|
const promises: Promise<CalendarEventApiData[]>[] = [];
|
||||||
|
|
||||||
calendars.forEach((cal) => {
|
calendars.forEach((cal) => {
|
||||||
promises.push(
|
promises.push(
|
||||||
hass.callApi<CalendarEvent[]>(
|
hass.callApi<CalendarEventApiData[]>(
|
||||||
"GET",
|
"GET",
|
||||||
`calendars/${cal.entity_id}${params}`
|
`calendars/${cal.entity_id}${params}`
|
||||||
)
|
)
|
||||||
@@ -77,7 +80,7 @@ export const fetchCalendarEvents = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
for (const [idx, promise] of promises.entries()) {
|
for (const [idx, promise] of promises.entries()) {
|
||||||
let result: CalendarEvent[];
|
let result: CalendarEventApiData[];
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line no-await-in-loop
|
// eslint-disable-next-line no-await-in-loop
|
||||||
result = await promise;
|
result = await promise;
|
||||||
@@ -87,53 +90,16 @@ export const fetchCalendarEvents = async (
|
|||||||
}
|
}
|
||||||
const cal = calendars[idx];
|
const cal = calendars[idx];
|
||||||
result.forEach((ev) => {
|
result.forEach((ev) => {
|
||||||
const eventStart = getCalendarDate(ev.start);
|
const normalized = normalizeSubscriptionEventData(ev, cal);
|
||||||
const eventEnd = getCalendarDate(ev.end);
|
if (normalized) {
|
||||||
if (!eventStart || !eventEnd) {
|
calEvents.push(normalized);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const eventData: CalendarEventData = {
|
|
||||||
uid: ev.uid,
|
|
||||||
summary: ev.summary,
|
|
||||||
description: ev.description,
|
|
||||||
dtstart: eventStart,
|
|
||||||
dtend: eventEnd,
|
|
||||||
recurrence_id: ev.recurrence_id,
|
|
||||||
rrule: ev.rrule,
|
|
||||||
};
|
|
||||||
const event: CalendarEvent = {
|
|
||||||
start: eventStart,
|
|
||||||
end: eventEnd,
|
|
||||||
title: ev.summary,
|
|
||||||
backgroundColor: cal.backgroundColor,
|
|
||||||
borderColor: cal.backgroundColor,
|
|
||||||
calendar: cal.entity_id,
|
|
||||||
eventData: eventData,
|
|
||||||
};
|
|
||||||
|
|
||||||
calEvents.push(event);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { events: calEvents, errors };
|
return { events: calEvents, errors };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCalendarDate = (dateObj: any): string | undefined => {
|
|
||||||
if (typeof dateObj === "string") {
|
|
||||||
return dateObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dateObj.dateTime) {
|
|
||||||
return dateObj.dateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dateObj.date) {
|
|
||||||
return dateObj.date;
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCalendars = (hass: HomeAssistant): Calendar[] =>
|
export const getCalendars = (hass: HomeAssistant): Calendar[] =>
|
||||||
Object.keys(hass.states)
|
Object.keys(hass.states)
|
||||||
.filter(
|
.filter(
|
||||||
@@ -191,3 +157,89 @@ export const deleteCalendarEvent = (
|
|||||||
recurrence_id,
|
recurrence_id,
|
||||||
recurrence_range,
|
recurrence_range,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calendar event data from both REST API and WebSocket subscription.
|
||||||
|
* Both APIs use the same data format.
|
||||||
|
*/
|
||||||
|
export interface CalendarEventApiData {
|
||||||
|
summary: string;
|
||||||
|
start: CalendarDateValue;
|
||||||
|
end: CalendarDateValue;
|
||||||
|
description?: string | null;
|
||||||
|
location?: string | null;
|
||||||
|
uid?: string | null;
|
||||||
|
recurrence_id?: string | null;
|
||||||
|
rrule?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CalendarEventSubscription {
|
||||||
|
events: CalendarEventApiData[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const subscribeCalendarEvents = (
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entity_id: string,
|
||||||
|
start: Date,
|
||||||
|
end: Date,
|
||||||
|
callback: (update: CalendarEventSubscription) => void
|
||||||
|
) =>
|
||||||
|
hass.connection.subscribeMessage<CalendarEventSubscription>(callback, {
|
||||||
|
type: "calendar/event/subscribe",
|
||||||
|
entity_id,
|
||||||
|
start: start.toISOString(),
|
||||||
|
end: end.toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCalendarDate = (dateObj: CalendarDateValue): string | undefined => {
|
||||||
|
if (typeof dateObj === "string") {
|
||||||
|
return dateObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("dateTime" in dateObj) {
|
||||||
|
return dateObj.dateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("date" in dateObj) {
|
||||||
|
return dateObj.date;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize calendar event data from API format to internal format.
|
||||||
|
* Handles both REST API format (with dateTime/date objects) and subscription format (strings).
|
||||||
|
* Converts to internal format with { dtstart, dtend, ... }
|
||||||
|
*/
|
||||||
|
export const normalizeSubscriptionEventData = (
|
||||||
|
eventData: CalendarEventApiData,
|
||||||
|
calendar: Calendar
|
||||||
|
): CalendarEvent | null => {
|
||||||
|
const eventStart = getCalendarDate(eventData.start);
|
||||||
|
const eventEnd = getCalendarDate(eventData.end);
|
||||||
|
|
||||||
|
if (!eventStart || !eventEnd) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalizedEventData: CalendarEventData = {
|
||||||
|
summary: eventData.summary,
|
||||||
|
dtstart: eventStart,
|
||||||
|
dtend: eventEnd,
|
||||||
|
description: eventData.description ?? undefined,
|
||||||
|
uid: eventData.uid ?? undefined,
|
||||||
|
recurrence_id: eventData.recurrence_id ?? undefined,
|
||||||
|
rrule: eventData.rrule ?? undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
start: eventStart,
|
||||||
|
end: eventEnd,
|
||||||
|
title: eventData.summary,
|
||||||
|
backgroundColor: calendar.backgroundColor,
|
||||||
|
borderColor: calendar.backgroundColor,
|
||||||
|
calendar: calendar.entity_id,
|
||||||
|
eventData: normalizedEventData,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
import { ResizeController } from "@lit-labs/observers/resize-controller";
|
||||||
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item";
|
||||||
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import { mdiChevronDown, mdiPlus, mdiRefresh } from "@mdi/js";
|
import { mdiChevronDown, mdiPlus, mdiRefresh } from "@mdi/js";
|
||||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||||
import { LitElement, css, html, nothing } from "lit";
|
import { LitElement, css, html, nothing } from "lit";
|
||||||
@@ -20,8 +21,17 @@ import "../../components/ha-menu-button";
|
|||||||
import "../../components/ha-state-icon";
|
import "../../components/ha-state-icon";
|
||||||
import "../../components/ha-svg-icon";
|
import "../../components/ha-svg-icon";
|
||||||
import "../../components/ha-two-pane-top-app-bar-fixed";
|
import "../../components/ha-two-pane-top-app-bar-fixed";
|
||||||
import type { Calendar, CalendarEvent } from "../../data/calendar";
|
import type {
|
||||||
import { fetchCalendarEvents, getCalendars } from "../../data/calendar";
|
Calendar,
|
||||||
|
CalendarEvent,
|
||||||
|
CalendarEventSubscription,
|
||||||
|
CalendarEventApiData,
|
||||||
|
} from "../../data/calendar";
|
||||||
|
import {
|
||||||
|
getCalendars,
|
||||||
|
normalizeSubscriptionEventData,
|
||||||
|
subscribeCalendarEvents,
|
||||||
|
} from "../../data/calendar";
|
||||||
import { fetchIntegrationManifest } from "../../data/integration";
|
import { fetchIntegrationManifest } from "../../data/integration";
|
||||||
import { showConfigFlowDialog } from "../../dialogs/config-flow/show-dialog-config-flow";
|
import { showConfigFlowDialog } from "../../dialogs/config-flow/show-dialog-config-flow";
|
||||||
import { haStyle } from "../../resources/styles";
|
import { haStyle } from "../../resources/styles";
|
||||||
@@ -42,6 +52,8 @@ class PanelCalendar extends LitElement {
|
|||||||
|
|
||||||
@state() private _error?: string = undefined;
|
@state() private _error?: string = undefined;
|
||||||
|
|
||||||
|
@state() private _errorCalendars: string[] = [];
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
@storage({
|
@storage({
|
||||||
key: "deSelectedCalendars",
|
key: "deSelectedCalendars",
|
||||||
@@ -53,6 +65,8 @@ class PanelCalendar extends LitElement {
|
|||||||
|
|
||||||
private _end?: Date;
|
private _end?: Date;
|
||||||
|
|
||||||
|
private _unsubs: Record<string, Promise<UnsubscribeFunc>> = {};
|
||||||
|
|
||||||
private _showPaneController = new ResizeController(this, {
|
private _showPaneController = new ResizeController(this, {
|
||||||
callback: (entries) => entries[0]?.contentRect.width > 750,
|
callback: (entries) => entries[0]?.contentRect.width > 750,
|
||||||
});
|
});
|
||||||
@@ -78,6 +92,7 @@ class PanelCalendar extends LitElement {
|
|||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this._mql?.removeListener(this._setIsMobile!);
|
this._mql?.removeListener(this._setIsMobile!);
|
||||||
this._mql = undefined;
|
this._mql = undefined;
|
||||||
|
this._unsubscribeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setIsMobile = (ev: MediaQueryListEvent) => {
|
private _setIsMobile = (ev: MediaQueryListEvent) => {
|
||||||
@@ -194,19 +209,95 @@ class PanelCalendar extends LitElement {
|
|||||||
.map((cal) => cal);
|
.map((cal) => cal);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchEvents(
|
private _subscribeCalendarEvents(calendars: Calendar[]): void {
|
||||||
start: Date | undefined,
|
if (!this._start || !this._end || calendars.length === 0) {
|
||||||
end: Date | undefined,
|
return;
|
||||||
calendars: Calendar[]
|
|
||||||
): Promise<{ events: CalendarEvent[]; errors: string[] }> {
|
|
||||||
if (!calendars.length || !start || !end) {
|
|
||||||
return { events: [], errors: [] };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetchCalendarEvents(this.hass, start, end, calendars);
|
this._error = undefined;
|
||||||
|
|
||||||
|
calendars.forEach((calendar) => {
|
||||||
|
// Unsubscribe existing subscription if any
|
||||||
|
if (calendar.entity_id in this._unsubs) {
|
||||||
|
this._unsubs[calendar.entity_id]
|
||||||
|
.then((unsubFunc) => unsubFunc())
|
||||||
|
.catch(() => {
|
||||||
|
// Subscription may have already been closed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsub = subscribeCalendarEvents(
|
||||||
|
this.hass,
|
||||||
|
calendar.entity_id,
|
||||||
|
this._start!,
|
||||||
|
this._end!,
|
||||||
|
(update: CalendarEventSubscription) => {
|
||||||
|
this._handleCalendarUpdate(calendar, update);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this._unsubs[calendar.entity_id] = unsub;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _requestSelected(ev: CustomEvent<RequestSelectedDetail>) {
|
private _handleCalendarUpdate(
|
||||||
|
calendar: Calendar,
|
||||||
|
update: CalendarEventSubscription
|
||||||
|
): void {
|
||||||
|
// Remove events from this calendar
|
||||||
|
this._events = this._events.filter(
|
||||||
|
(event) => event.calendar !== calendar.entity_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (update.events === null) {
|
||||||
|
// Error fetching events
|
||||||
|
if (!this._errorCalendars.includes(calendar.entity_id)) {
|
||||||
|
this._errorCalendars = [...this._errorCalendars, calendar.entity_id];
|
||||||
|
}
|
||||||
|
this._handleErrors(this._errorCalendars);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove from error list if successfully loaded
|
||||||
|
this._errorCalendars = this._errorCalendars.filter(
|
||||||
|
(id) => id !== calendar.entity_id
|
||||||
|
);
|
||||||
|
this._handleErrors(this._errorCalendars);
|
||||||
|
|
||||||
|
// Add new events from this calendar
|
||||||
|
const newEvents: CalendarEvent[] = update.events
|
||||||
|
.map((eventData: CalendarEventApiData) =>
|
||||||
|
normalizeSubscriptionEventData(eventData, calendar)
|
||||||
|
)
|
||||||
|
.filter((event): event is CalendarEvent => event !== null);
|
||||||
|
|
||||||
|
this._events = [...this._events, ...newEvents];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _unsubscribeAll(): Promise<void> {
|
||||||
|
await Promise.all(
|
||||||
|
Object.values(this._unsubs).map((unsub) =>
|
||||||
|
unsub
|
||||||
|
.then((unsubFunc) => unsubFunc())
|
||||||
|
.catch(() => {
|
||||||
|
// Subscription may have already been closed
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this._unsubs = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _unsubscribeCalendar(entityId: string): void {
|
||||||
|
if (entityId in this._unsubs) {
|
||||||
|
this._unsubs[entityId]
|
||||||
|
.then((unsubFunc) => unsubFunc())
|
||||||
|
.catch(() => {
|
||||||
|
// Subscription may have already been closed
|
||||||
|
});
|
||||||
|
delete this._unsubs[entityId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _requestSelected(ev: CustomEvent<RequestSelectedDetail>) {
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
const entityId = (ev.target as HaListItem).value;
|
const entityId = (ev.target as HaListItem).value;
|
||||||
if (ev.detail.selected) {
|
if (ev.detail.selected) {
|
||||||
@@ -223,13 +314,10 @@ class PanelCalendar extends LitElement {
|
|||||||
if (!calendar) {
|
if (!calendar) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const result = await this._fetchEvents(this._start, this._end, [
|
this._subscribeCalendarEvents([calendar]);
|
||||||
calendar,
|
|
||||||
]);
|
|
||||||
this._events = [...this._events, ...result.events];
|
|
||||||
this._handleErrors(result.errors);
|
|
||||||
} else {
|
} else {
|
||||||
this._deSelectedCalendars = [...this._deSelectedCalendars, entityId];
|
this._deSelectedCalendars = [...this._deSelectedCalendars, entityId];
|
||||||
|
this._unsubscribeCalendar(entityId);
|
||||||
this._events = this._events.filter(
|
this._events = this._events.filter(
|
||||||
(event) => event.calendar !== entityId
|
(event) => event.calendar !== entityId
|
||||||
);
|
);
|
||||||
@@ -254,23 +342,15 @@ class PanelCalendar extends LitElement {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
this._start = ev.detail.start;
|
this._start = ev.detail.start;
|
||||||
this._end = ev.detail.end;
|
this._end = ev.detail.end;
|
||||||
const result = await this._fetchEvents(
|
await this._unsubscribeAll();
|
||||||
this._start,
|
this._events = [];
|
||||||
this._end,
|
this._subscribeCalendarEvents(this._selectedCalendars);
|
||||||
this._selectedCalendars
|
|
||||||
);
|
|
||||||
this._events = result.events;
|
|
||||||
this._handleErrors(result.errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleRefresh(): Promise<void> {
|
private async _handleRefresh(): Promise<void> {
|
||||||
const result = await this._fetchEvents(
|
await this._unsubscribeAll();
|
||||||
this._start,
|
this._events = [];
|
||||||
this._end,
|
this._subscribeCalendarEvents(this._selectedCalendars);
|
||||||
this._selectedCalendars
|
|
||||||
);
|
|
||||||
this._events = result.events;
|
|
||||||
this._handleErrors(result.errors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleErrors(error_entity_ids: string[]) {
|
private _handleErrors(error_entity_ids: string[]) {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { UnsubscribeFunc } from "home-assistant-js-websocket";
|
||||||
import type { PropertyValues } from "lit";
|
import type { PropertyValues } from "lit";
|
||||||
import { css, html, LitElement, nothing } from "lit";
|
import { css, html, LitElement, nothing } from "lit";
|
||||||
import { classMap } from "lit/directives/class-map";
|
import { classMap } from "lit/directives/class-map";
|
||||||
@@ -7,8 +8,16 @@ import { applyThemesOnElement } from "../../../common/dom/apply_themes_on_elemen
|
|||||||
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
import type { HASSDomEvent } from "../../../common/dom/fire_event";
|
||||||
import { debounce } from "../../../common/util/debounce";
|
import { debounce } from "../../../common/util/debounce";
|
||||||
import "../../../components/ha-card";
|
import "../../../components/ha-card";
|
||||||
import type { Calendar, CalendarEvent } from "../../../data/calendar";
|
import type {
|
||||||
import { fetchCalendarEvents } from "../../../data/calendar";
|
Calendar,
|
||||||
|
CalendarEvent,
|
||||||
|
CalendarEventSubscription,
|
||||||
|
CalendarEventApiData,
|
||||||
|
} from "../../../data/calendar";
|
||||||
|
import {
|
||||||
|
normalizeSubscriptionEventData,
|
||||||
|
subscribeCalendarEvents,
|
||||||
|
} from "../../../data/calendar";
|
||||||
import type {
|
import type {
|
||||||
CalendarViewChanged,
|
CalendarViewChanged,
|
||||||
FullCalendarView,
|
FullCalendarView,
|
||||||
@@ -65,12 +74,16 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
|||||||
|
|
||||||
@state() private _error?: string = undefined;
|
@state() private _error?: string = undefined;
|
||||||
|
|
||||||
|
@state() private _errorCalendars: string[] = [];
|
||||||
|
|
||||||
private _startDate?: Date;
|
private _startDate?: Date;
|
||||||
|
|
||||||
private _endDate?: Date;
|
private _endDate?: Date;
|
||||||
|
|
||||||
private _resizeObserver?: ResizeObserver;
|
private _resizeObserver?: ResizeObserver;
|
||||||
|
|
||||||
|
private _unsubs: Record<string, Promise<UnsubscribeFunc>> = {};
|
||||||
|
|
||||||
public setConfig(config: CalendarCardConfig): void {
|
public setConfig(config: CalendarCardConfig): void {
|
||||||
if (!config.entities?.length) {
|
if (!config.entities?.length) {
|
||||||
throw new Error("Entities must be specified");
|
throw new Error("Entities must be specified");
|
||||||
@@ -86,7 +99,8 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
if (this._config?.entities !== config.entities) {
|
if (this._config?.entities !== config.entities) {
|
||||||
this._fetchCalendarEvents();
|
this._unsubscribeAll();
|
||||||
|
// Subscription will happen when view-changed event fires
|
||||||
}
|
}
|
||||||
|
|
||||||
this._config = { initial_view: "dayGridMonth", ...config };
|
this._config = { initial_view: "dayGridMonth", ...config };
|
||||||
@@ -115,6 +129,7 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
|||||||
if (this._resizeObserver) {
|
if (this._resizeObserver) {
|
||||||
this._resizeObserver.disconnect();
|
this._resizeObserver.disconnect();
|
||||||
}
|
}
|
||||||
|
this._unsubscribeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected render() {
|
protected render() {
|
||||||
@@ -170,31 +185,85 @@ export class HuiCalendarCard extends LitElement implements LovelaceCard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleViewChanged(ev: HASSDomEvent<CalendarViewChanged>): void {
|
private async _handleViewChanged(
|
||||||
|
ev: HASSDomEvent<CalendarViewChanged>
|
||||||
|
): Promise<void> {
|
||||||
this._startDate = ev.detail.start;
|
this._startDate = ev.detail.start;
|
||||||
this._endDate = ev.detail.end;
|
this._endDate = ev.detail.end;
|
||||||
this._fetchCalendarEvents();
|
await this._unsubscribeAll();
|
||||||
|
this._subscribeCalendarEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _fetchCalendarEvents(): Promise<void> {
|
private _subscribeCalendarEvents(): void {
|
||||||
if (!this._startDate || !this._endDate) {
|
if (!this.hass || !this._startDate || !this._endDate) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._error = undefined;
|
this._error = undefined;
|
||||||
const result = await fetchCalendarEvents(
|
|
||||||
this.hass!,
|
|
||||||
this._startDate,
|
|
||||||
this._endDate,
|
|
||||||
this._calendars
|
|
||||||
);
|
|
||||||
this._events = result.events;
|
|
||||||
|
|
||||||
if (result.errors.length > 0) {
|
this._calendars.forEach((calendar) => {
|
||||||
|
const unsub = subscribeCalendarEvents(
|
||||||
|
this.hass!,
|
||||||
|
calendar.entity_id,
|
||||||
|
this._startDate!,
|
||||||
|
this._endDate!,
|
||||||
|
(update: CalendarEventSubscription) => {
|
||||||
|
this._handleCalendarUpdate(calendar, update);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
this._unsubs[calendar.entity_id] = unsub;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _handleCalendarUpdate(
|
||||||
|
calendar: Calendar,
|
||||||
|
update: CalendarEventSubscription
|
||||||
|
): void {
|
||||||
|
// Remove events from this calendar
|
||||||
|
this._events = this._events.filter(
|
||||||
|
(event) => event.calendar !== calendar.entity_id
|
||||||
|
);
|
||||||
|
|
||||||
|
if (update.events === null) {
|
||||||
|
// Error fetching events
|
||||||
|
if (!this._errorCalendars.includes(calendar.entity_id)) {
|
||||||
|
this._errorCalendars = [...this._errorCalendars, calendar.entity_id];
|
||||||
|
}
|
||||||
this._error = `${this.hass!.localize(
|
this._error = `${this.hass!.localize(
|
||||||
"ui.components.calendar.event_retrieval_error"
|
"ui.components.calendar.event_retrieval_error"
|
||||||
)}`;
|
)}`;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove from error list if successfully loaded
|
||||||
|
this._errorCalendars = this._errorCalendars.filter(
|
||||||
|
(id) => id !== calendar.entity_id
|
||||||
|
);
|
||||||
|
if (this._errorCalendars.length === 0) {
|
||||||
|
this._error = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new events from this calendar
|
||||||
|
const newEvents: CalendarEvent[] = update.events
|
||||||
|
.map((eventData: CalendarEventApiData) =>
|
||||||
|
normalizeSubscriptionEventData(eventData, calendar)
|
||||||
|
)
|
||||||
|
.filter((event): event is CalendarEvent => event !== null);
|
||||||
|
|
||||||
|
this._events = [...this._events, ...newEvents];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _unsubscribeAll(): Promise<void> {
|
||||||
|
await Promise.all(
|
||||||
|
Object.values(this._unsubs).map((unsub) =>
|
||||||
|
unsub
|
||||||
|
.then((unsubFunc) => unsubFunc())
|
||||||
|
.catch(() => {
|
||||||
|
// Subscription may have already been closed
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this._unsubs = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
private _measureCard() {
|
private _measureCard() {
|
||||||
|
|||||||
Reference in New Issue
Block a user