From 6b8380defe290dc860b66e455d2a12c1c62d0806 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 19 Oct 2023 12:13:28 +0200 Subject: [PATCH 1/8] Add two pane view to calendar panel --- build-scripts/webpack.cjs | 2 + package.json | 2 + src/components/ha-button-menu.ts | 4 +- src/components/ha-button.ts | 3 + .../ha-two-pane-top-app-bar-fixed.ts | 315 ++++++++++++++++++ src/data/calendar.ts | 2 +- src/panels/calendar/ha-full-calendar.ts | 11 +- src/panels/calendar/ha-panel-calendar.ts | 269 ++++++++------- src/state/logging-mixin.ts | 2 + yarn.lock | 24 +- 10 files changed, 511 insertions(+), 123 deletions(-) create mode 100644 src/components/ha-two-pane-top-app-bar-fixed.ts diff --git a/build-scripts/webpack.cjs b/build-scripts/webpack.cjs index 985036313abc..17f65387a398 100644 --- a/build-scripts/webpack.cjs +++ b/build-scripts/webpack.cjs @@ -182,6 +182,8 @@ const createWebpackConfig = ({ "@lit-labs/virtualizer/layouts/grid.js", "@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver": "@lit-labs/virtualizer/polyfills/resize-observer-polyfill/ResizeObserver.js", + "@lit-labs/observers/resize-controller": + "@lit-labs/observers/resize-controller.js", }, }, output: { diff --git a/package.json b/package.json index b9f428a884fc..3d968d22badd 100644 --- a/package.json +++ b/package.json @@ -52,10 +52,12 @@ "@lezer/highlight": "1.1.6", "@lit-labs/context": "0.4.1", "@lit-labs/motion": "1.0.4", + "@lit-labs/observers": "2.0.1", "@lit-labs/virtualizer": "2.0.7", "@lrnwebcomponents/simple-tooltip": "7.0.18", "@material/chips": "=14.0.0-canary.53b3cad2f.0", "@material/data-table": "=14.0.0-canary.53b3cad2f.0", + "@material/mwc-base": "0.27.0", "@material/mwc-button": "0.27.0", "@material/mwc-checkbox": "0.27.0", "@material/mwc-circular-progress": "0.27.0", diff --git a/src/components/ha-button-menu.ts b/src/components/ha-button-menu.ts index b06b0e0467a2..a686957bdad4 100644 --- a/src/components/ha-button-menu.ts +++ b/src/components/ha-button-menu.ts @@ -26,6 +26,8 @@ export class HaButtonMenu extends LitElement { @property({ type: Boolean }) public fixed = false; + @property({ type: Boolean }) public noAnchor = false; + @query("mwc-menu", true) private _menu?: Menu; public get items() { @@ -82,7 +84,7 @@ export class HaButtonMenu extends LitElement { if (this.disabled) { return; } - this._menu!.anchor = this; + this._menu!.anchor = this.noAnchor ? null : this; this._menu!.show(); } diff --git a/src/components/ha-button.ts b/src/components/ha-button.ts index df7bdf46e580..473d08dd1589 100644 --- a/src/components/ha-button.ts +++ b/src/components/ha-button.ts @@ -17,6 +17,9 @@ export class HaButton extends Button { .mdc-button { height: var(--button-height, 36px); } + .trailing-icon { + display: flex; + } `, ]; } diff --git a/src/components/ha-two-pane-top-app-bar-fixed.ts b/src/components/ha-two-pane-top-app-bar-fixed.ts new file mode 100644 index 000000000000..9f3b924662d1 --- /dev/null +++ b/src/components/ha-two-pane-top-app-bar-fixed.ts @@ -0,0 +1,315 @@ +import { + addHasRemoveClass, + BaseElement, +} from "@material/mwc-base/base-element"; +import { supportsPassiveEventListener } from "@material/mwc-base/utils"; +import { MDCTopAppBarAdapter } from "@material/top-app-bar/adapter"; +import { strings } from "@material/top-app-bar/constants"; +import MDCFixedTopAppBarFoundation from "@material/top-app-bar/fixed/foundation"; +import { html, css, nothing } from "lit"; +import { property, query, customElement } from "lit/decorators"; +import { classMap } from "lit/directives/class-map"; +import { styles } from "@material/mwc-top-app-bar/mwc-top-app-bar.css"; +import { haStyleScrollbar } from "../resources/styles"; + +export const passiveEventOptionsIfSupported = supportsPassiveEventListener + ? { passive: true } + : undefined; + +@customElement("ha-two-pane-top-app-bar-fixed") +export abstract class TopAppBarBaseBase extends BaseElement { + protected override mdcFoundation!: MDCFixedTopAppBarFoundation; + + protected override mdcFoundationClass = MDCFixedTopAppBarFoundation; + + @query(".mdc-top-app-bar") protected mdcRoot!: HTMLElement; + + // _actionItemsSlot should have type HTMLSlotElement, but when TypeScript's + // emitDecoratorMetadata is enabled, the HTMLSlotElement constructor will + // be emitted into the runtime, which will cause an "HTMLSlotElement is + // undefined" error in browsers that don't define it (e.g. IE11). + @query('slot[name="actionItems"]') protected _actionItemsSlot!: HTMLElement; + + protected _scrollTarget!: HTMLElement | Window; + + @property({ type: Boolean }) centerTitle = false; + + @property({ type: Boolean, reflect: true }) prominent = false; + + @property({ type: Boolean, reflect: true }) dense = false; + + @property({ type: Boolean }) pane = false; + + @property({ type: Boolean }) footer = false; + + @query(".content") private _contentElement!: HTMLElement; + + @query(".pane .ha-scrollbar") private _paneElement?: HTMLElement; + + @property({ type: Object }) + get scrollTarget() { + return this._scrollTarget || window; + } + + set scrollTarget(value) { + this.unregisterListeners(); + const old = this.scrollTarget; + this._scrollTarget = value; + this.updateRootPosition(); + this.requestUpdate("scrollTarget", old); + this.registerListeners(); + } + + protected updateRootPosition() { + if (this.mdcRoot) { + const windowScroller = this.scrollTarget === window; + // we add support for top-app-bar's tied to an element scroller. + this.mdcRoot.style.position = windowScroller ? "" : "absolute"; + } + } + + protected barClasses() { + return { + "mdc-top-app-bar--dense": this.dense, + "mdc-top-app-bar--prominent": this.prominent, + "center-title": this.centerTitle, + "mdc-top-app-bar--fixed": true, + "mdc-top-app-bar--pane": this.pane, + }; + } + + protected contentClasses() { + return { + "mdc-top-app-bar--fixed-adjust": !this.dense && !this.prominent, + "mdc-top-app-bar--dense-fixed-adjust": this.dense && !this.prominent, + "mdc-top-app-bar--prominent-fixed-adjust": !this.dense && this.prominent, + "mdc-top-app-bar--dense-prominent-fixed-adjust": + this.dense && this.prominent, + "mdc-top-app-bar--pane": this.pane, + }; + } + + protected override render() { + const title = html``; + return html` +
+
+ ${this.pane + ? html`
+ + ${title} +
` + : nothing} + + +
+
+
+ ${this.pane + ? html`
+
+
+ +
+ ${this.footer + ? html`` + : nothing} +
` + : nothing} +
+ ${this.pane ? html`
` : nothing} +
+ +
+
+
+ `; + } + + protected updated(changedProperties) { + super.updated(changedProperties); + if ( + changedProperties.has("pane") && + changedProperties.get("pane") !== undefined + ) { + this.unregisterListeners(); + this.registerListeners(); + } + } + + protected createAdapter(): MDCTopAppBarAdapter { + return { + ...addHasRemoveClass(this.mdcRoot), + setStyle: (prprty: string, value: string) => + this.mdcRoot.style.setProperty(prprty, value), + getTopAppBarHeight: () => this.mdcRoot.clientHeight, + notifyNavigationIconClicked: () => { + this.dispatchEvent( + new Event(strings.NAVIGATION_EVENT, { + bubbles: true, + cancelable: true, + }) + ); + }, + getViewportScrollY: () => + this.scrollTarget instanceof Window + ? this.scrollTarget.pageYOffset + : this.scrollTarget.scrollTop, + getTotalActionItems: () => + (this._actionItemsSlot as HTMLSlotElement).assignedNodes({ + flatten: true, + }).length, + }; + } + + protected handleTargetScroll = () => { + this.mdcFoundation.handleTargetScroll(); + }; + + protected handlePaneScroll = (ev) => { + if (ev.target.scrollTop > 0) { + ev.target.parentElement.classList.add("scrolled"); + } else { + ev.target.parentElement.classList.remove("scrolled"); + } + }; + + protected handleNavigationClick = () => { + this.mdcFoundation.handleNavigationClick(); + }; + + protected registerListeners() { + if (this.pane) { + this._paneElement!.addEventListener( + "scroll", + this.handlePaneScroll, + passiveEventOptionsIfSupported + ); + this._contentElement.addEventListener( + "scroll", + this.handlePaneScroll, + passiveEventOptionsIfSupported + ); + return; + } + this.scrollTarget.addEventListener( + "scroll", + this.handleTargetScroll, + passiveEventOptionsIfSupported + ); + } + + protected unregisterListeners() { + this._paneElement?.removeEventListener("scroll", this.handlePaneScroll); + this._contentElement.removeEventListener("scroll", this.handlePaneScroll); + this.scrollTarget.removeEventListener("scroll", this.handleTargetScroll); + } + + protected override firstUpdated() { + super.firstUpdated(); + this.updateRootPosition(); + this.registerListeners(); + } + + override disconnectedCallback() { + super.disconnectedCallback(); + this.unregisterListeners(); + } + + static override styles = [ + styles, + haStyleScrollbar, + css` + .mdc-top-app-bar__row { + height: var(--header-height); + border-bottom: var(--app-header-border-bottom); + } + .mdc-top-app-bar--fixed-adjust { + padding-top: var(--header-height); + } + .shadow-container { + position: absolute; + top: calc(-1 * var(--header-height)); + width: 100%; + height: var(--header-height); + z-index: 1; + transition: box-shadow 200ms linear; + } + .scrolled .shadow-container { + box-shadow: var( + --mdc-top-app-bar-fixed-box-shadow, + 0px 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), + 0px 1px 10px 0px rgba(0, 0, 0, 0.12) + ); + } + .mdc-top-app-bar { + --mdc-typography-headline6-font-weight: 400; + color: var(--app-header-text-color, var(--mdc-theme-on-primary, #fff)); + background-color: var( + --app-header-background-color, + var(--mdc-theme-primary) + ); + } + .mdc-top-app-bar--pane.mdc-top-app-bar--fixed-scrolled { + box-shadow: none; + } + #title { + border-right: 1px solid rgba(255, 255, 255, 0.12); + flex: 0 0 var(--sidepane-width, 250px); + box-sizing: border-box; + } + div.mdc-top-app-bar--pane { + display: flex; + height: calc(100vh - var(--header-height)); + } + .pane { + border-right: 1px solid var(--divider-color); + width: var(--sidepane-width, 250px); + box-sizing: border-box; + display: flex; + flex-direction: column; + position: relative; + } + .pane .ha-scrollbar { + flex: 1; + } + .pane .footer { + border-top: 1px solid var(--divider-color); + } + .mdc-top-app-bar--pane .main { + position: relative; + height: 100%; + flex: 1; + } + .mdc-top-app-bar--pane .content { + height: 100%; + overflow: auto; + } + `, + ]; +} diff --git a/src/data/calendar.ts b/src/data/calendar.ts index c0b9d2b072ee..178ef3544ece 100644 --- a/src/data/calendar.ts +++ b/src/data/calendar.ts @@ -143,7 +143,7 @@ export const getCalendars = (hass: HomeAssistant): Calendar[] => ) .sort() .map((eid, idx) => ({ - entity_id: eid, + ...hass.states[eid], name: computeStateName(hass.states[eid]), backgroundColor: getColorByIndex(idx), })); diff --git a/src/panels/calendar/ha-full-calendar.ts b/src/panels/calendar/ha-full-calendar.ts index 61a370c2e484..30b959b736d0 100644 --- a/src/panels/calendar/ha-full-calendar.ts +++ b/src/panels/calendar/ha-full-calendar.ts @@ -439,6 +439,11 @@ export class HAFullCalendar extends LitElement { justify-content: initial; } + .header { + padding-right: var(--calendar-header-padding); + padding-left: var(--calendar-header-padding); + } + .navigation { display: flex; align-items: center; @@ -513,7 +518,11 @@ export class HAFullCalendar extends LitElement { .fc-theme-standard .fc-scrollgrid { border: 1px solid var(--divider-color); - border-radius: var(--mdc-shape-small, 4px); + border-width: var(--calendar-border-width, 1px); + border-radius: var( + --calendar-border-radius, + var(--mdc-shape-small, 4px) + ); } .fc-theme-standard td { diff --git a/src/panels/calendar/ha-panel-calendar.ts b/src/panels/calendar/ha-panel-calendar.ts index c2562d135786..4ddb3e7af53f 100644 --- a/src/panels/calendar/ha-panel-calendar.ts +++ b/src/panels/calendar/ha-panel-calendar.ts @@ -1,22 +1,28 @@ -import "@material/mwc-checkbox"; -import "@material/mwc-formfield"; -import { mdiRefresh } from "@mdi/js"; +import "@material/mwc-list"; +import { mdiChevronDown, mdiRefresh } from "@mdi/js"; import { - css, CSSResultGroup, - html, LitElement, PropertyValues, TemplateResult, + css, + html, + nothing, } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; import { storage } from "../../common/decorators/storage"; import { HASSDomEvent } from "../../common/dom/fire_event"; import { computeStateName } from "../../common/entity/compute_state_name"; +import "../../components/ha-button"; +import "../../components/ha-button-menu"; import "../../components/ha-card"; +import "../../components/ha-check-list-item"; import "../../components/ha-icon-button"; import "../../components/ha-menu-button"; +import "../../components/ha-state-icon"; +import "../../components/ha-svg-icon"; +import "../../components/ha-two-pane-top-app-bar-fixed"; import { Calendar, CalendarEvent, @@ -26,7 +32,6 @@ import { import { haStyle } from "../../resources/styles"; import type { CalendarViewChanged, HomeAssistant } from "../../types"; import "./ha-full-calendar"; -import "../../components/ha-top-app-bar-fixed"; @customElement("ha-panel-calendar") class PanelCalendar extends LitElement { @@ -35,6 +40,8 @@ class PanelCalendar extends LitElement { @property({ type: Boolean, reflect: true }) public narrow!: boolean; + @property({ type: Boolean, reflect: true }) public mobile = false; + @state() private _calendars: Calendar[] = []; @state() private _events: CalendarEvent[] = []; @@ -51,6 +58,33 @@ class PanelCalendar extends LitElement { private _end?: Date; + private _mql?: MediaQueryList; + + private _headerHeight = 56; + + public connectedCallback() { + super.connectedCallback(); + this._mql = window.matchMedia( + "(max-width: 450px), all and (max-height: 500px)" + ); + this._mql.addListener(this._setIsMobile); + this.mobile = this._mql.matches; + const computedStyles = getComputedStyle(this); + this._headerHeight = Number( + computedStyles.getPropertyValue("--header-height").replace("px", "") + ); + } + + public disconnectedCallback() { + super.disconnectedCallback(); + this._mql?.removeListener(this._setIsMobile!); + this._mql = undefined; + } + + private _setIsMobile = (ev: MediaQueryListEvent) => { + this.mobile = ev.matches; + }; + public willUpdate(changedProps: PropertyValues): void { super.willUpdate(changedProps); if (!this.hasUpdated) { @@ -59,54 +93,75 @@ class PanelCalendar extends LitElement { } protected render(): TemplateResult { + const calendarItems = this._calendars.map( + (selCal) => html` + + + ${selCal.name} + + ` + ); + const showPane = !this.narrow; return html` - + -
${this.hass.localize("panel.calendar")}
+ + ${!showPane + ? html` + + ${this.hass.localize("ui.components.calendar.my_calendars")} + + + ${calendarItems} + ` + : html`
+ ${this.hass.localize("ui.components.calendar.my_calendars")} +
`} -
-
-
- ${this.hass.localize("ui.components.calendar.my_calendars")} -
- ${this._calendars.map( - (selCal) => html` -
- - - -
- ` - )} -
- -
-
+ ${showPane + ? html`${calendarItems} ` + : nothing} + + `; } @@ -121,42 +176,34 @@ class PanelCalendar extends LitElement { end: Date, calendars: Calendar[] ): Promise<{ events: CalendarEvent[]; errors: string[] }> { - if (!calendars.length) { + if (!calendars.length || !start || !end) { return { events: [], errors: [] }; } return fetchCalendarEvents(this.hass, start, end, calendars); } - private async _handleToggle(ev): Promise { - const results = this._calendars.map(async (cal) => { - if (ev.target.value !== cal.entity_id) { - return cal; - } - - const checked = ev.target.checked; - - if (checked) { - const result = await this._fetchEvents(this._start!, this._end!, [cal]); - this._events = [...this._events, ...result.events]; - this._handleErrors(result.errors); - this._deSelectedCalendars = this._deSelectedCalendars.filter( - (deCal) => deCal !== cal.entity_id - ); - } else { - this._events = this._events.filter( - (event) => event.calendar !== cal.entity_id - ); - this._deSelectedCalendars = [ - ...this._deSelectedCalendars, - cal.entity_id, - ]; - } - - return cal; - }); + private async _handleSelected(ev): Promise { + const deselectedCalendars: Set = new Set( + this._calendars.map((cal) => cal.entity_id) + ); + for (const index of ev.detail.index) { + deselectedCalendars.delete(this._calendars[index].entity_id); + } + this._deSelectedCalendars = [...deselectedCalendars]; - this._calendars = await Promise.all(results); + if (ev.detail.diff.added.length) { + const cal = this._calendars[ev.detail.diff.added[0]]; + const result = await this._fetchEvents(this._start, this._end, [cal]); + this._events = [...this._events, ...result.events]; + this._handleErrors(result.errors); + } + if (ev.detail.diff.removed.length) { + const cal = this._calendars[ev.detail.diff.removed[0]]; + this._events = this._events.filter( + (event) => event.calendar !== cal.entity_id + ); + } } private async _handleViewChanged( @@ -175,8 +222,8 @@ class PanelCalendar extends LitElement { private async _handleRefresh(): Promise { const result = await this._fetchEvents( - this._start!, - this._end!, + this._start, + this._end, this._selectedCalendars ); this._events = result.events; @@ -204,56 +251,42 @@ class PanelCalendar extends LitElement { return [ haStyle, css` - .content { - padding: 16px; - display: flex; - box-sizing: border-box; - } - - :host(:not([narrow])) .content { - height: calc(100vh - var(--header-height)); - } - - .calendar-list { - padding-right: 16px; - padding-inline-end: 16px; - padding-inline-start: initial; - min-width: 170px; - flex: 0 0 15%; - overflow-x: hidden; - overflow-y: auto; - --mdc-theme-text-primary-on-background: var(--primary-text-color); - direction: var(--direction); - } - - .calendar-list > div { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - } - - .calendar-list-header { - font-size: 16px; - padding: 16px 16px 8px 8px; - } - ha-full-calendar { - flex-grow: 1; + height: 100%; + --calendar-header-padding: 12px; + --calendar-border-radius: 0; + --calendar-border-width: 1px 0; } - :host([narrow]) ha-full-calendar { - height: calc(100vh - 72px); + height: calc(100vh - var(--header-height)); } - - :host([narrow]) .content { - flex-direction: column-reverse; - padding: 8px 0 0 0; + ha-button-menu ha-button { + --mdc-theme-primary: currentColor; + --mdc-typography-button-text-transform: none; + --mdc-typography-button-font-size: var( + --mdc-typography-headline6-font-size, + 1.25rem + ); + --mdc-typography-button-font-weight: var( + --mdc-typography-headline6-font-weight, + 500 + ); + --mdc-typography-button-letter-spacing: var( + --mdc-typography-headline6-letter-spacing, + 0.0125em + ); + --mdc-typography-button-line-height: var( + --mdc-typography-headline6-line-height, + 2rem + ); + --button-height: 40px; } - - :host([narrow]) .calendar-list { - margin-bottom: 24px; - width: 100%; - padding-right: 0; + :host([mobile]) .lists { + --mdc-menu-min-width: 100vw; + } + :host([mobile]) ha-button-menu { + --mdc-shape-medium: 0 0 var(--mdc-shape-medium) + var(--mdc-shape-medium); } `, ]; diff --git a/src/state/logging-mixin.ts b/src/state/logging-mixin.ts index 711514f857a4..fc4d7f77a64b 100644 --- a/src/state/logging-mixin.ts +++ b/src/state/logging-mixin.ts @@ -25,6 +25,7 @@ export const loggingMixin = >( protected hassConnected() { super.hassConnected(); window.addEventListener("error", async (ev) => { + return; if (!this.hass?.connected) { return; } @@ -58,6 +59,7 @@ export const loggingMixin = >( } }); window.addEventListener("unhandledrejection", async (ev) => { + return; if (!this.hass?.connected) { return; } diff --git a/yarn.lock b/yarn.lock index 230bbce0e751..50e2cce78c58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2106,7 +2106,16 @@ __metadata: languageName: node linkType: hard -"@lit-labs/ssr-dom-shim@npm:^1.0.0, @lit-labs/ssr-dom-shim@npm:^1.1.0": +"@lit-labs/observers@npm:2.0.1": + version: 2.0.1 + resolution: "@lit-labs/observers@npm:2.0.1" + dependencies: + "@lit/reactive-element": ^2.0.0 + checksum: 6c4518ee37678d86b263799590edd5c202a9f6800b52b52d2a0fc47e572d029c587fa9722108e449ab6bc7b1b1aee9e780b3e42ca2e91d63a43fc96541a80f98 + languageName: node + linkType: hard + +"@lit-labs/ssr-dom-shim@npm:^1.0.0, @lit-labs/ssr-dom-shim@npm:^1.1.0, @lit-labs/ssr-dom-shim@npm:^1.1.2-pre.0": version: 1.1.2 resolution: "@lit-labs/ssr-dom-shim@npm:1.1.2" checksum: 73fd787893851d4ec4aaa5c775405ed2aae4ca0891b2dd3c973b32c2f4bf70ada5481dd0224e52b786d037aa8a00052186ad1623c44551affd66f6409cca8da6 @@ -2132,6 +2141,15 @@ __metadata: languageName: node linkType: hard +"@lit/reactive-element@npm:^2.0.0": + version: 2.0.0 + resolution: "@lit/reactive-element@npm:2.0.0" + dependencies: + "@lit-labs/ssr-dom-shim": ^1.1.2-pre.0 + checksum: afa12f1cf72e8735cb7eaa51d428610785ee796882ca52108310e75ac54bbf5690da718c8bf85d042060f98c139ff0d5efd54f677a9d3fc4d794ad2e0f7a12c5 + languageName: node + linkType: hard + "@lokalise/node-api@npm:12.0.0": version: 12.0.0 resolution: "@lokalise/node-api@npm:12.0.0" @@ -2497,7 +2515,7 @@ __metadata: languageName: node linkType: hard -"@material/mwc-base@npm:^0.27.0": +"@material/mwc-base@npm:0.27.0, @material/mwc-base@npm:^0.27.0": version: 0.27.0 resolution: "@material/mwc-base@npm:0.27.0" dependencies: @@ -9636,11 +9654,13 @@ __metadata: "@lezer/highlight": 1.1.6 "@lit-labs/context": 0.4.1 "@lit-labs/motion": 1.0.4 + "@lit-labs/observers": 2.0.1 "@lit-labs/virtualizer": 2.0.7 "@lokalise/node-api": 12.0.0 "@lrnwebcomponents/simple-tooltip": 7.0.18 "@material/chips": =14.0.0-canary.53b3cad2f.0 "@material/data-table": =14.0.0-canary.53b3cad2f.0 + "@material/mwc-base": 0.27.0 "@material/mwc-button": 0.27.0 "@material/mwc-checkbox": 0.27.0 "@material/mwc-circular-progress": 0.27.0 From 4e458bf3922f87141e6f6c441c7d7eae36530c6c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 19 Oct 2023 12:16:59 +0200 Subject: [PATCH 2/8] Update logging-mixin.ts --- src/state/logging-mixin.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/state/logging-mixin.ts b/src/state/logging-mixin.ts index fc4d7f77a64b..711514f857a4 100644 --- a/src/state/logging-mixin.ts +++ b/src/state/logging-mixin.ts @@ -25,7 +25,6 @@ export const loggingMixin = >( protected hassConnected() { super.hassConnected(); window.addEventListener("error", async (ev) => { - return; if (!this.hass?.connected) { return; } @@ -59,7 +58,6 @@ export const loggingMixin = >( } }); window.addEventListener("unhandledrejection", async (ev) => { - return; if (!this.hass?.connected) { return; } From 4e7b537ff1d31b29cf561d935bd0d1e1c583f046 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 19 Oct 2023 12:29:02 +0200 Subject: [PATCH 3/8] fix initialization --- src/panels/calendar/ha-panel-calendar.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/panels/calendar/ha-panel-calendar.ts b/src/panels/calendar/ha-panel-calendar.ts index 4ddb3e7af53f..bf343cee558d 100644 --- a/src/panels/calendar/ha-panel-calendar.ts +++ b/src/panels/calendar/ha-panel-calendar.ts @@ -172,8 +172,8 @@ class PanelCalendar extends LitElement { } private async _fetchEvents( - start: Date, - end: Date, + start: Date | undefined, + end: Date | undefined, calendars: Calendar[] ): Promise<{ events: CalendarEvent[]; errors: string[] }> { if (!calendars.length || !start || !end) { @@ -184,6 +184,8 @@ class PanelCalendar extends LitElement { } private async _handleSelected(ev): Promise { + const oldSelected = this._selectedCalendars; + const deselectedCalendars: Set = new Set( this._calendars.map((cal) => cal.entity_id) ); @@ -192,13 +194,15 @@ class PanelCalendar extends LitElement { } this._deSelectedCalendars = [...deselectedCalendars]; - if (ev.detail.diff.added.length) { + if (ev.detail.diff.added.length === 1) { const cal = this._calendars[ev.detail.diff.added[0]]; - const result = await this._fetchEvents(this._start, this._end, [cal]); - this._events = [...this._events, ...result.events]; - this._handleErrors(result.errors); + if (!oldSelected.includes(cal)) { + const result = await this._fetchEvents(this._start, this._end, [cal]); + this._events = [...this._events, ...result.events]; + this._handleErrors(result.errors); + } } - if (ev.detail.diff.removed.length) { + if (ev.detail.diff.removed.length === 1) { const cal = this._calendars[ev.detail.diff.removed[0]]; this._events = this._events.filter( (event) => event.calendar !== cal.entity_id From 5ee6321661d2e8f3a3a30e06ecb3e10e365c03a0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 23 Oct 2023 12:40:48 +0200 Subject: [PATCH 4/8] Update src/components/ha-button-menu.ts Co-authored-by: Paul Bottein --- src/components/ha-button-menu.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ha-button-menu.ts b/src/components/ha-button-menu.ts index a686957bdad4..8fda2deff0da 100644 --- a/src/components/ha-button-menu.ts +++ b/src/components/ha-button-menu.ts @@ -26,7 +26,7 @@ export class HaButtonMenu extends LitElement { @property({ type: Boolean }) public fixed = false; - @property({ type: Boolean }) public noAnchor = false; + @property({ type: Boolean, attribute: "no-anchor" }) public noAnchor = false; @query("mwc-menu", true) private _menu?: Menu; From aa1e886f3c5332b8688415d2805847bdac02ccb4 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 23 Oct 2023 12:49:40 +0200 Subject: [PATCH 5/8] dont shrink pane --- src/components/ha-two-pane-top-app-bar-fixed.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ha-two-pane-top-app-bar-fixed.ts b/src/components/ha-two-pane-top-app-bar-fixed.ts index 9f3b924662d1..5c8a3d4b9c87 100644 --- a/src/components/ha-two-pane-top-app-bar-fixed.ts +++ b/src/components/ha-two-pane-top-app-bar-fixed.ts @@ -280,8 +280,8 @@ export abstract class TopAppBarBaseBase extends BaseElement { } #title { border-right: 1px solid rgba(255, 255, 255, 0.12); - flex: 0 0 var(--sidepane-width, 250px); box-sizing: border-box; + flex: 0 0 var(--sidepane-width, 250px); } div.mdc-top-app-bar--pane { display: flex; @@ -289,9 +289,9 @@ export abstract class TopAppBarBaseBase extends BaseElement { } .pane { border-right: 1px solid var(--divider-color); - width: var(--sidepane-width, 250px); box-sizing: border-box; display: flex; + flex: 0 0 var(--sidepane-width, 250px); flex-direction: column; position: relative; } From e081ccaeec3328734f150d34d0aae07ae797b55d Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 23 Oct 2023 13:22:35 +0200 Subject: [PATCH 6/8] dont close menu when item is selected --- .../ha-two-pane-top-app-bar-fixed.ts | 2 + src/panels/calendar/ha-panel-calendar.ts | 49 ++++++++++--------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/components/ha-two-pane-top-app-bar-fixed.ts b/src/components/ha-two-pane-top-app-bar-fixed.ts index 5c8a3d4b9c87..e141517ba9b6 100644 --- a/src/components/ha-two-pane-top-app-bar-fixed.ts +++ b/src/components/ha-two-pane-top-app-bar-fixed.ts @@ -282,6 +282,7 @@ export abstract class TopAppBarBaseBase extends BaseElement { border-right: 1px solid rgba(255, 255, 255, 0.12); box-sizing: border-box; flex: 0 0 var(--sidepane-width, 250px); + width: var(--sidepane-width, 250px); } div.mdc-top-app-bar--pane { display: flex; @@ -292,6 +293,7 @@ export abstract class TopAppBarBaseBase extends BaseElement { box-sizing: border-box; display: flex; flex: 0 0 var(--sidepane-width, 250px); + width: var(--sidepane-width, 250px); flex-direction: column; position: relative; } diff --git a/src/panels/calendar/ha-panel-calendar.ts b/src/panels/calendar/ha-panel-calendar.ts index bf343cee558d..bbfc2bc007a7 100644 --- a/src/panels/calendar/ha-panel-calendar.ts +++ b/src/panels/calendar/ha-panel-calendar.ts @@ -11,6 +11,7 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; +import { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item"; import { storage } from "../../common/decorators/storage"; import { HASSDomEvent } from "../../common/dom/fire_event"; import { computeStateName } from "../../common/entity/compute_state_name"; @@ -32,6 +33,7 @@ import { import { haStyle } from "../../resources/styles"; import type { CalendarViewChanged, HomeAssistant } from "../../types"; import "./ha-full-calendar"; +import { HaListItem } from "../../components/ha-list-item"; @customElement("ha-panel-calendar") class PanelCalendar extends LitElement { @@ -96,6 +98,7 @@ class PanelCalendar extends LitElement { const calendarItems = this._calendars.map( (selCal) => html` ${this.hass.localize("ui.components.calendar.my_calendars")} @@ -183,29 +185,32 @@ class PanelCalendar extends LitElement { return fetchCalendarEvents(this.hass, start, end, calendars); } - private async _handleSelected(ev): Promise { - const oldSelected = this._selectedCalendars; - - const deselectedCalendars: Set = new Set( - this._calendars.map((cal) => cal.entity_id) - ); - for (const index of ev.detail.index) { - deselectedCalendars.delete(this._calendars[index].entity_id); - } - this._deSelectedCalendars = [...deselectedCalendars]; - - if (ev.detail.diff.added.length === 1) { - const cal = this._calendars[ev.detail.diff.added[0]]; - if (!oldSelected.includes(cal)) { - const result = await this._fetchEvents(this._start, this._end, [cal]); - this._events = [...this._events, ...result.events]; - this._handleErrors(result.errors); + private async _requestSelected(ev: CustomEvent) { + ev.stopPropagation(); + const entityId = (ev.target as HaListItem).value; + if (ev.detail.selected) { + this._deSelectedCalendars = this._deSelectedCalendars.filter( + (cal) => cal !== entityId + ); + if (ev.detail.source === "interaction") { + // prevent adding the same calendar twice, an interaction event will be followed by a property event + return; } - } - if (ev.detail.diff.removed.length === 1) { - const cal = this._calendars[ev.detail.diff.removed[0]]; + const calendar = this._calendars.find( + (cal) => cal.entity_id === entityId + ); + if (!calendar) { + return; + } + const result = await this._fetchEvents(this._start, this._end, [ + calendar, + ]); + this._events = [...this._events, ...result.events]; + this._handleErrors(result.errors); + } else { + this._deSelectedCalendars = [...this._deSelectedCalendars, entityId]; this._events = this._events.filter( - (event) => event.calendar !== cal.entity_id + (event) => event.calendar !== entityId ); } } From 0328b75e2a7f4ce69aed3505c16df4a20d1dc39d Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 23 Oct 2023 13:33:35 +0200 Subject: [PATCH 7/8] Add resize controller --- .../ha-two-pane-top-app-bar-fixed.ts | 5 +++- src/panels/calendar/ha-panel-calendar.ts | 24 +++++++++++-------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/components/ha-two-pane-top-app-bar-fixed.ts b/src/components/ha-two-pane-top-app-bar-fixed.ts index e141517ba9b6..ec64e4c2f9b4 100644 --- a/src/components/ha-two-pane-top-app-bar-fixed.ts +++ b/src/components/ha-two-pane-top-app-bar-fixed.ts @@ -303,10 +303,13 @@ export abstract class TopAppBarBaseBase extends BaseElement { .pane .footer { border-top: 1px solid var(--divider-color); } + .main { + min-height: 100%; + } .mdc-top-app-bar--pane .main { position: relative; - height: 100%; flex: 1; + height: 100%; } .mdc-top-app-bar--pane .content { height: 100%; diff --git a/src/panels/calendar/ha-panel-calendar.ts b/src/panels/calendar/ha-panel-calendar.ts index bbfc2bc007a7..9001634a12ab 100644 --- a/src/panels/calendar/ha-panel-calendar.ts +++ b/src/panels/calendar/ha-panel-calendar.ts @@ -1,4 +1,6 @@ import "@material/mwc-list"; +import { ResizeController } from "@lit-labs/observers/resize-controller"; +import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item"; import { mdiChevronDown, mdiRefresh } from "@mdi/js"; import { CSSResultGroup, @@ -11,7 +13,6 @@ import { } from "lit"; import { customElement, property, state } from "lit/decorators"; import { styleMap } from "lit/directives/style-map"; -import { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item"; import { storage } from "../../common/decorators/storage"; import { HASSDomEvent } from "../../common/dom/fire_event"; import { computeStateName } from "../../common/entity/compute_state_name"; @@ -20,6 +21,7 @@ import "../../components/ha-button-menu"; import "../../components/ha-card"; import "../../components/ha-check-list-item"; import "../../components/ha-icon-button"; +import type { HaListItem } from "../../components/ha-list-item"; import "../../components/ha-menu-button"; import "../../components/ha-state-icon"; import "../../components/ha-svg-icon"; @@ -33,7 +35,6 @@ import { import { haStyle } from "../../resources/styles"; import type { CalendarViewChanged, HomeAssistant } from "../../types"; import "./ha-full-calendar"; -import { HaListItem } from "../../components/ha-list-item"; @customElement("ha-panel-calendar") class PanelCalendar extends LitElement { @@ -60,6 +61,11 @@ class PanelCalendar extends LitElement { private _end?: Date; + private _showPaneController = new ResizeController(this, { + callback: (entries: ResizeObserverEntry[]) => + entries[0]?.contentRect.width > 750, + }); + private _mql?: MediaQueryList; private _headerHeight = 56; @@ -111,7 +117,7 @@ class PanelCalendar extends LitElement { ` ); - const showPane = !this.narrow; + const showPane = this._showPaneController.value ?? !this.narrow; return html` ${showPane - ? html`${calendarItems} ` + ? html`${calendarItems}` : nothing} Date: Mon, 23 Oct 2023 13:34:39 +0200 Subject: [PATCH 8/8] Update ha-panel-calendar.ts --- src/panels/calendar/ha-panel-calendar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/calendar/ha-panel-calendar.ts b/src/panels/calendar/ha-panel-calendar.ts index 9001634a12ab..f775d8b02ed3 100644 --- a/src/panels/calendar/ha-panel-calendar.ts +++ b/src/panels/calendar/ha-panel-calendar.ts @@ -1,5 +1,5 @@ -import "@material/mwc-list"; import { ResizeController } from "@lit-labs/observers/resize-controller"; +import "@material/mwc-list"; import type { RequestSelectedDetail } from "@material/mwc-list/mwc-list-item"; import { mdiChevronDown, mdiRefresh } from "@mdi/js"; import {