mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-19 07:16:39 +00:00
Add two pane view to calendar panel (#18286)
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
This commit is contained in:
parent
6cae11f0a6
commit
463a3244cf
@ -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: {
|
||||
|
@ -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",
|
||||
|
@ -26,6 +26,8 @@ export class HaButtonMenu extends LitElement {
|
||||
|
||||
@property({ type: Boolean }) public fixed = false;
|
||||
|
||||
@property({ type: Boolean, attribute: "no-anchor" }) 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();
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,9 @@ export class HaButton extends Button {
|
||||
.mdc-button {
|
||||
height: var(--button-height, 36px);
|
||||
}
|
||||
.trailing-icon {
|
||||
display: flex;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
320
src/components/ha-two-pane-top-app-bar-fixed.ts
Normal file
320
src/components/ha-two-pane-top-app-bar-fixed.ts
Normal file
@ -0,0 +1,320 @@
|
||||
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`<span class="mdc-top-app-bar__title"
|
||||
><slot name="title"></slot
|
||||
></span>`;
|
||||
return html`
|
||||
<header class="mdc-top-app-bar ${classMap(this.barClasses())}">
|
||||
<div class="mdc-top-app-bar__row">
|
||||
${this.pane
|
||||
? html`<section
|
||||
class="mdc-top-app-bar__section mdc-top-app-bar__section--align-start"
|
||||
id="title"
|
||||
>
|
||||
<slot
|
||||
name="navigationIcon"
|
||||
@click=${this.handleNavigationClick}
|
||||
></slot>
|
||||
${title}
|
||||
</section>`
|
||||
: nothing}
|
||||
<section class="mdc-top-app-bar__section" id="navigation">
|
||||
${this.pane
|
||||
? nothing
|
||||
: html`<slot
|
||||
name="navigationIcon"
|
||||
@click=${this.handleNavigationClick}
|
||||
></slot
|
||||
>${title}`}
|
||||
</section>
|
||||
<section
|
||||
class="mdc-top-app-bar__section mdc-top-app-bar__section--align-end"
|
||||
id="actions"
|
||||
role="toolbar"
|
||||
>
|
||||
<slot name="actionItems"></slot>
|
||||
</section>
|
||||
</div>
|
||||
</header>
|
||||
<div class=${classMap(this.contentClasses())}>
|
||||
${this.pane
|
||||
? html`<div class="pane">
|
||||
<div class="shadow-container"></div>
|
||||
<div class="ha-scrollbar">
|
||||
<slot name="pane"></slot>
|
||||
</div>
|
||||
${this.footer
|
||||
? html`<div class="footer">
|
||||
<slot name="pane-footer"></slot>
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>`
|
||||
: nothing}
|
||||
<div class="main">
|
||||
${this.pane ? html`<div class="shadow-container"></div>` : nothing}
|
||||
<div class="content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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);
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 var(--sidepane-width, 250px);
|
||||
width: var(--sidepane-width, 250px);
|
||||
}
|
||||
div.mdc-top-app-bar--pane {
|
||||
display: flex;
|
||||
height: calc(100vh - var(--header-height));
|
||||
}
|
||||
.pane {
|
||||
border-right: 1px solid var(--divider-color);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 0 0 var(--sidepane-width, 250px);
|
||||
width: var(--sidepane-width, 250px);
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
}
|
||||
.pane .ha-scrollbar {
|
||||
flex: 1;
|
||||
}
|
||||
.pane .footer {
|
||||
border-top: 1px solid var(--divider-color);
|
||||
}
|
||||
.main {
|
||||
min-height: 100%;
|
||||
}
|
||||
.mdc-top-app-bar--pane .main {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
.mdc-top-app-bar--pane .content {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
@ -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),
|
||||
}));
|
||||
|
@ -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 {
|
||||
|
@ -1,22 +1,31 @@
|
||||
import "@material/mwc-checkbox";
|
||||
import "@material/mwc-formfield";
|
||||
import { mdiRefresh } from "@mdi/js";
|
||||
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 {
|
||||
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 type { HaListItem } from "../../components/ha-list-item";
|
||||
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 +35,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 +43,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 +61,38 @@ 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;
|
||||
|
||||
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 +101,73 @@ class PanelCalendar extends LitElement {
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const calendarItems = this._calendars.map(
|
||||
(selCal) => html`
|
||||
<ha-check-list-item
|
||||
@request-selected=${this._requestSelected}
|
||||
graphic="icon"
|
||||
style=${styleMap({
|
||||
"--mdc-theme-secondary": selCal.backgroundColor!,
|
||||
})}
|
||||
.value=${selCal.entity_id}
|
||||
.selected=${!this._deSelectedCalendars.includes(selCal.entity_id)}
|
||||
>
|
||||
<ha-state-icon slot="graphic" .state=${selCal}></ha-state-icon>
|
||||
${selCal.name}
|
||||
</ha-check-list-item>
|
||||
`
|
||||
);
|
||||
const showPane = this._showPaneController.value ?? !this.narrow;
|
||||
return html`
|
||||
<ha-top-app-bar-fixed>
|
||||
<ha-two-pane-top-app-bar-fixed .pane=${showPane}>
|
||||
<ha-menu-button
|
||||
slot="navigationIcon"
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
<div slot="title">${this.hass.localize("panel.calendar")}</div>
|
||||
|
||||
${!showPane
|
||||
? html`<ha-button-menu
|
||||
slot="title"
|
||||
class="lists"
|
||||
multi
|
||||
fixed
|
||||
.noAnchor=${this.mobile}
|
||||
.y=${this.mobile
|
||||
? this._headerHeight / 2
|
||||
: this._headerHeight / 4}
|
||||
.x=${this.mobile ? 0 : undefined}
|
||||
>
|
||||
<ha-button slot="trigger">
|
||||
${this.hass.localize("ui.components.calendar.my_calendars")}
|
||||
<ha-svg-icon
|
||||
slot="trailingIcon"
|
||||
.path=${mdiChevronDown}
|
||||
></ha-svg-icon>
|
||||
</ha-button>
|
||||
${calendarItems}
|
||||
</ha-button-menu>`
|
||||
: html`<div slot="title">
|
||||
${this.hass.localize("ui.components.calendar.my_calendars")}
|
||||
</div>`}
|
||||
<ha-icon-button
|
||||
slot="actionItems"
|
||||
.path=${mdiRefresh}
|
||||
.label=${this.hass.localize("ui.common.refresh")}
|
||||
@click=${this._handleRefresh}
|
||||
></ha-icon-button>
|
||||
<div class="content">
|
||||
<div class="calendar-list">
|
||||
<div class="calendar-list-header">
|
||||
${this.hass.localize("ui.components.calendar.my_calendars")}
|
||||
</div>
|
||||
${this._calendars.map(
|
||||
(selCal) => html`
|
||||
<div>
|
||||
<mwc-formfield .label=${selCal.name}>
|
||||
<mwc-checkbox
|
||||
style=${styleMap({
|
||||
"--mdc-theme-secondary": selCal.backgroundColor!,
|
||||
})}
|
||||
.value=${selCal.entity_id}
|
||||
.checked=${!this._deSelectedCalendars.includes(
|
||||
selCal.entity_id
|
||||
)}
|
||||
@change=${this._handleToggle}
|
||||
></mwc-checkbox>
|
||||
</mwc-formfield>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<ha-full-calendar
|
||||
.events=${this._events}
|
||||
.calendars=${this._calendars}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.error=${this._error}
|
||||
@view-changed=${this._handleViewChanged}
|
||||
></ha-full-calendar>
|
||||
</div>
|
||||
</ha-top-app-bar-fixed>
|
||||
${showPane
|
||||
? html`<mwc-list slot="pane" multi}>${calendarItems}</mwc-list>`
|
||||
: nothing}
|
||||
<ha-full-calendar
|
||||
.events=${this._events}
|
||||
.calendars=${this._calendars}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
.error=${this._error}
|
||||
@view-changed=${this._handleViewChanged}
|
||||
></ha-full-calendar>
|
||||
</ha-two-pane-top-app-bar-fixed>
|
||||
`;
|
||||
}
|
||||
|
||||
@ -117,46 +178,45 @@ 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) {
|
||||
if (!calendars.length || !start || !end) {
|
||||
return { events: [], errors: [] };
|
||||
}
|
||||
|
||||
return fetchCalendarEvents(this.hass, start, end, calendars);
|
||||
}
|
||||
|
||||
private async _handleToggle(ev): Promise<void> {
|
||||
const results = this._calendars.map(async (cal) => {
|
||||
if (ev.target.value !== cal.entity_id) {
|
||||
return cal;
|
||||
private async _requestSelected(ev: CustomEvent<RequestSelectedDetail>) {
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
];
|
||||
const calendar = this._calendars.find(
|
||||
(cal) => cal.entity_id === entityId
|
||||
);
|
||||
if (!calendar) {
|
||||
return;
|
||||
}
|
||||
|
||||
return cal;
|
||||
});
|
||||
|
||||
this._calendars = await Promise.all(results);
|
||||
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 !== entityId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async _handleViewChanged(
|
||||
@ -175,8 +235,8 @@ class PanelCalendar extends LitElement {
|
||||
|
||||
private async _handleRefresh(): Promise<void> {
|
||||
const result = await this._fetchEvents(
|
||||
this._start!,
|
||||
this._end!,
|
||||
this._start,
|
||||
this._end,
|
||||
this._selectedCalendars
|
||||
);
|
||||
this._events = result.events;
|
||||
@ -204,56 +264,42 @@ class PanelCalendar extends LitElement {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.content {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
|
||||
: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: calc(100vh - var(--header-height));
|
||||
--calendar-header-padding: 12px;
|
||||
--calendar-border-radius: 0;
|
||||
--calendar-border-width: 1px 0;
|
||||
}
|
||||
|
||||
:host([narrow]) ha-full-calendar {
|
||||
height: calc(100vh - 72px);
|
||||
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]) .content {
|
||||
flex-direction: column-reverse;
|
||||
padding: 8px 0 0 0;
|
||||
:host([mobile]) .lists {
|
||||
--mdc-menu-min-width: 100vw;
|
||||
}
|
||||
|
||||
:host([narrow]) .calendar-list {
|
||||
margin-bottom: 24px;
|
||||
width: 100%;
|
||||
padding-right: 0;
|
||||
:host([mobile]) ha-button-menu {
|
||||
--mdc-shape-medium: 0 0 var(--mdc-shape-medium)
|
||||
var(--mdc-shape-medium);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
24
yarn.lock
24
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user