mirror of
https://github.com/home-assistant/frontend.git
synced 2025-07-13 20:36:35 +00:00
Calendar Panel: FullCalendar (#5742)
* WIP * remove big calendar * remove file * Convert to lit * More * Ready for the public to see? prob not * Fix types and imports * Remove dependencies * ignore the typing that hasnt been finished in Beta * Convert paper to MWC * Styling * View list as name of view | MWC components version * Updates action directive for ripple. MWC 14.1.0 * Update * Updates * Update height styling * Toggle Button Group * Adds Toggle group transition * style updates * Few fixes * Fix Yarn lock from merge | height of celndar as parent * Update package Json and yarn | remove unneeded pkg * Remove mwc-list * Search hass.states for calendars instead of api * Move function to file in data | event fetch logic * compute state name * add ha button menu | refresh * Remove Event ffetch logic * copy pasta * Types * Fix for toggling * Translations * Update ha-button-toggle-group * Update ha-button-toggle-group.ts * Update ha-button-toggle-group.ts * Change mobile view * Locale in fullcalendar * Comments * ha-button-menu trigger slot * Comments * icon-x * Update src/panels/calendar/ha-panel-calendar.ts Co-authored-by: Bram Kragten <mail@bramkragten.nl> * Update src/panels/calendar/ha-panel-calendar.ts Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
parent
9630a58ea7
commit
71492d0467
@ -18,10 +18,6 @@
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "h",
|
||||
"version": "15.0"
|
||||
},
|
||||
"import/resolver": {
|
||||
"webpack": {
|
||||
"config": "./webpack.config.js"
|
||||
@ -88,13 +84,6 @@
|
||||
"@typescript-eslint/no-unused-vars": 0,
|
||||
"@typescript-eslint/explicit-function-return-type": 0
|
||||
},
|
||||
"plugins": [
|
||||
"disable",
|
||||
"import",
|
||||
"react",
|
||||
"lit",
|
||||
"prettier",
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"plugins": ["disable", "import", "lit", "prettier", "@typescript-eslint"],
|
||||
"processor": "disable/disable"
|
||||
}
|
||||
|
@ -27,12 +27,6 @@ module.exports.babelLoaderConfig = ({ latestBuild }) => {
|
||||
],
|
||||
// Only support the syntax, Webpack will handle it.
|
||||
"@babel/syntax-dynamic-import",
|
||||
[
|
||||
"@babel/transform-react-jsx",
|
||||
{
|
||||
pragma: "h",
|
||||
},
|
||||
],
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
[
|
||||
|
@ -80,7 +80,6 @@ gulp.task("copy-translations", (done) => {
|
||||
|
||||
gulp.task("copy-static", (done) => {
|
||||
const staticDir = paths.static;
|
||||
const staticPath = genStaticPath(paths.static);
|
||||
// Basic static files
|
||||
fs.copySync(polyPath("public"), paths.root);
|
||||
|
||||
@ -90,10 +89,6 @@ gulp.task("copy-static", (done) => {
|
||||
copyMdiIcons(staticDir);
|
||||
|
||||
// Panel assets
|
||||
copyFileDir(
|
||||
npmPath("react-big-calendar/lib/css/react-big-calendar.css"),
|
||||
staticPath("panels/calendar/")
|
||||
);
|
||||
copyMapPanel(staticDir);
|
||||
done();
|
||||
});
|
||||
|
@ -100,14 +100,6 @@ const createWebpackConfig = ({
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
extensions: [".ts", ".js", ".json"],
|
||||
alias: {
|
||||
react: "preact-compat",
|
||||
"react-dom": "preact-compat",
|
||||
// Not necessary unless you consume a module using `createClass`
|
||||
"create-react-class": "preact-compat/lib/create-react-class",
|
||||
// Not necessary unless you consume a module requiring `react-dom-factories`
|
||||
"react-dom-factories": "preact-compat/lib/react-dom-factories",
|
||||
},
|
||||
},
|
||||
output: {
|
||||
filename: ({ chunk }) => {
|
||||
|
50
package.json
50
package.json
@ -24,14 +24,19 @@
|
||||
"author": "Paulus Schoutsen <Paulus@PaulusSchoutsen.nl> (http://paulusschoutsen.nl)",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@material/chips": "^5.0.0",
|
||||
"@material/mwc-button": "^0.13.0",
|
||||
"@material/mwc-checkbox": "^0.13.0",
|
||||
"@material/mwc-dialog": "^0.13.0",
|
||||
"@material/mwc-fab": "^0.13.0",
|
||||
"@material/mwc-icon-button": "^0.13.0",
|
||||
"@material/mwc-ripple": "^0.13.0",
|
||||
"@material/mwc-switch": "^0.13.0",
|
||||
"@fullcalendar/core": "5.0.0-beta.2",
|
||||
"@fullcalendar/daygrid": "5.0.0-beta.2",
|
||||
"@material/chips": "^6.0.0-canary.35a32aaea.0",
|
||||
"@material/mwc-button": "0.14.1",
|
||||
"@material/mwc-checkbox": "0.14.1",
|
||||
"@material/mwc-dialog": "0.14.1",
|
||||
"@material/mwc-fab": "0.14.1",
|
||||
"@material/mwc-formfield": "0.14.1",
|
||||
"@material/mwc-icon-button": "0.14.1",
|
||||
"@material/mwc-list": "0.14.1",
|
||||
"@material/mwc-menu": "0.14.1",
|
||||
"@material/mwc-ripple": "0.14.1",
|
||||
"@material/mwc-switch": "0.14.1",
|
||||
"@mdi/js": "4.9.95",
|
||||
"@mdi/svg": "4.9.95",
|
||||
"@polymer/app-layout": "^3.0.2",
|
||||
@ -102,9 +107,6 @@
|
||||
"memoize-one": "^5.0.2",
|
||||
"moment": "^2.24.0",
|
||||
"node-vibrant": "^3.1.5",
|
||||
"preact": "^8.4.2",
|
||||
"preact-compat": "^3.18.4",
|
||||
"react-big-calendar": "^0.20.4",
|
||||
"regenerator-runtime": "^0.13.2",
|
||||
"resize-observer": "^1.0.0",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
@ -123,7 +125,6 @@
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.9.5",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
|
||||
"@babel/plugin-transform-react-jsx": "^7.9.4",
|
||||
"@babel/preset-env": "^7.9.5",
|
||||
"@babel/preset-typescript": "^7.9.0",
|
||||
"@types/chai": "^4.1.7",
|
||||
@ -151,7 +152,6 @@
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-lit": "^1.2.0",
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"eslint-plugin-wc": "^1.2.0",
|
||||
"fs-extra": "^7.0.1",
|
||||
"gulp": "^4.0.0",
|
||||
@ -198,17 +198,19 @@
|
||||
"@webcomponents/webcomponentsjs": "^2.2.10",
|
||||
"@polymer/polymer": "3.1.0",
|
||||
"lit-html": "^1.1.2",
|
||||
"@material/button": "^5.0.0",
|
||||
"@material/checkbox": "^5.0.0",
|
||||
"@material/density": "^5.0.0",
|
||||
"@material/dialog": "^5.0.0",
|
||||
"@material/fab": "^5.0.0",
|
||||
"@material/feature-targeting": "^5.0.0",
|
||||
"@material/switch": "^5.0.0",
|
||||
"@material/ripple": "^5.0.0",
|
||||
"@material/dom": "^5.0.0",
|
||||
"@material/touch-target": "^5.0.0",
|
||||
"@material/theme": "^5.0.0"
|
||||
"@material/animation": "6.0.0",
|
||||
"@material/base": "6.0.0",
|
||||
"@material/checkbox": "6.0.0",
|
||||
"@material/density": "6.0.0",
|
||||
"@material/dom": "6.0.0",
|
||||
"@material/elevation": "6.0.0",
|
||||
"@material/feature-targeting": "6.0.0",
|
||||
"@material/ripple": "6.0.0",
|
||||
"@material/rtl": "6.0.0",
|
||||
"@material/shape": "6.0.0",
|
||||
"@material/theme": "6.0.0",
|
||||
"@material/touch-target": "6.0.0",
|
||||
"@material/typography": "6.0.0"
|
||||
},
|
||||
"main": "src/home-assistant.js",
|
||||
"husky": {
|
||||
|
@ -87,3 +87,66 @@ export const UNIT_F = "°F";
|
||||
|
||||
/** Entity ID of the default view. */
|
||||
export const DEFAULT_VIEW_ENTITY_ID = "group.default_view";
|
||||
|
||||
/** HA Color Pallete. */
|
||||
export const HA_COLOR_PALETTE = [
|
||||
"ff0029",
|
||||
"66a61e",
|
||||
"377eb8",
|
||||
"984ea3",
|
||||
"00d2d5",
|
||||
"ff7f00",
|
||||
"af8d00",
|
||||
"7f80cd",
|
||||
"b3e900",
|
||||
"c42e60",
|
||||
"a65628",
|
||||
"f781bf",
|
||||
"8dd3c7",
|
||||
"bebada",
|
||||
"fb8072",
|
||||
"80b1d3",
|
||||
"fdb462",
|
||||
"fccde5",
|
||||
"bc80bd",
|
||||
"ffed6f",
|
||||
"c4eaff",
|
||||
"cf8c00",
|
||||
"1b9e77",
|
||||
"d95f02",
|
||||
"e7298a",
|
||||
"e6ab02",
|
||||
"a6761d",
|
||||
"0097ff",
|
||||
"00d067",
|
||||
"f43600",
|
||||
"4ba93b",
|
||||
"5779bb",
|
||||
"927acc",
|
||||
"97ee3f",
|
||||
"bf3947",
|
||||
"9f5b00",
|
||||
"f48758",
|
||||
"8caed6",
|
||||
"f2b94f",
|
||||
"eff26e",
|
||||
"e43872",
|
||||
"d9b100",
|
||||
"9d7a00",
|
||||
"698cff",
|
||||
"d9d9d9",
|
||||
"00d27e",
|
||||
"d06800",
|
||||
"009f82",
|
||||
"c49200",
|
||||
"cbe8ff",
|
||||
"fecddf",
|
||||
"c27eb6",
|
||||
"8cd2ce",
|
||||
"c4b8d9",
|
||||
"f883b0",
|
||||
"a49100",
|
||||
"f48800",
|
||||
"27d0df",
|
||||
"a04a9b",
|
||||
];
|
||||
|
55
src/components/ha-button-menu.ts
Normal file
55
src/components/ha-button-menu.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
TemplateResult,
|
||||
LitElement,
|
||||
CSSResult,
|
||||
css,
|
||||
query,
|
||||
} from "lit-element";
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-menu";
|
||||
import "@material/mwc-list/mwc-list-item";
|
||||
import type { Menu } from "@material/mwc-menu";
|
||||
|
||||
import { haStyle } from "../resources/styles";
|
||||
|
||||
import "./ha-icon-button";
|
||||
|
||||
@customElement("ha-button-menu")
|
||||
export class HaButtonMenu extends LitElement {
|
||||
@query("mwc-menu") private _menu?: Menu;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div @click=${this._handleClick}>
|
||||
<slot name="trigger"></slot>
|
||||
</div>
|
||||
<mwc-menu>
|
||||
<slot></slot>
|
||||
</mwc-menu>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleClick(): void {
|
||||
this._menu!.anchor = this;
|
||||
this._menu!.show();
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-button-menu": HaButtonMenu;
|
||||
}
|
||||
}
|
86
src/components/ha-button-toggle-group.ts
Normal file
86
src/components/ha-button-toggle-group.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import {
|
||||
customElement,
|
||||
html,
|
||||
TemplateResult,
|
||||
property,
|
||||
LitElement,
|
||||
CSSResult,
|
||||
css,
|
||||
} from "lit-element";
|
||||
|
||||
import "./ha-icon-button";
|
||||
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { ToggleButton } from "../types";
|
||||
|
||||
@customElement("ha-button-toggle-group")
|
||||
export class HaButtonToggleGroup extends LitElement {
|
||||
@property() public buttons!: ToggleButton[];
|
||||
|
||||
@property() public active?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<div>
|
||||
${this.buttons.map(
|
||||
(button) => html` <ha-icon-button
|
||||
.label=${button.label}
|
||||
.icon=${button.icon}
|
||||
.value=${button.value}
|
||||
?active=${this.active === button.value}
|
||||
@click=${this._handleClick}
|
||||
>
|
||||
</ha-icon-button>`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleClick(ev): void {
|
||||
this.active = ev.target.value;
|
||||
fireEvent(this, "value-changed", { value: this.active });
|
||||
}
|
||||
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
div {
|
||||
display: flex;
|
||||
--mdc-icon-button-size: var(--button-toggle-size, 36px);
|
||||
--mdc-icon-size: var(--button-toggle-icon-size, 20px);
|
||||
}
|
||||
ha-icon-button {
|
||||
border: 1px solid var(--primary-color);
|
||||
border-right-width: 0px;
|
||||
position: relative;
|
||||
}
|
||||
ha-icon-button::before {
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
background-color: currentColor;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
transition: opacity 15ms linear, background-color 15ms linear;
|
||||
}
|
||||
ha-icon-button[active]::before {
|
||||
opacity: var(--mdc-icon-button-ripple-opacity, 0.12);
|
||||
}
|
||||
ha-icon-button:first-child {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
ha-icon-button:last-child {
|
||||
border-radius: 0 4px 4px 0;
|
||||
border-right-width: 1px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-button-toggle-button": HaButtonToggleGroup;
|
||||
}
|
||||
}
|
@ -50,8 +50,7 @@ export class HaIconButton extends LitElement {
|
||||
--mdc-theme-on-primary: currentColor;
|
||||
}
|
||||
ha-icon {
|
||||
display: inline-flex;
|
||||
vertical-align: initial;
|
||||
--ha-icon-display: inline;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
@ -127,11 +127,6 @@ export class HaIcon extends LitElement {
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
fill: currentcolor;
|
||||
}
|
||||
`;
|
||||
|
@ -27,7 +27,7 @@ export class HaSvgIcon extends LitElement {
|
||||
static get styles(): CSSResult {
|
||||
return css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
display: var(--ha-icon-display, inline-flex);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
80
src/data/calendar.ts
Normal file
80
src/data/calendar.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import type { HomeAssistant, Calendar, CalendarEvent } from "../types";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { HA_COLOR_PALETTE } from "../common/const";
|
||||
import { computeStateName } from "../common/entity/compute_state_name";
|
||||
|
||||
export const fetchCalendarEvents = async (
|
||||
hass: HomeAssistant,
|
||||
start: Date,
|
||||
end: Date,
|
||||
calendars: Calendar[]
|
||||
): Promise<CalendarEvent[]> => {
|
||||
const params = encodeURI(
|
||||
`?start=${start.toISOString()}&end=${end.toISOString()}`
|
||||
);
|
||||
|
||||
const calEvents: CalendarEvent[] = [];
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
calendars.forEach((cal) => {
|
||||
promises.push(
|
||||
hass.callApi<CalendarEvent[]>(
|
||||
"GET",
|
||||
`calendars/${cal.entity_id}${params}`
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
const results = await Promise.all(promises);
|
||||
|
||||
results.forEach((result, idx) => {
|
||||
const cal = calendars[idx];
|
||||
result.forEach((ev) => {
|
||||
const eventStart = getCalendarDate(ev.start);
|
||||
if (!eventStart) {
|
||||
return;
|
||||
}
|
||||
const eventEnd = getCalendarDate(ev.end);
|
||||
const event: CalendarEvent = {
|
||||
start: eventStart,
|
||||
end: eventEnd,
|
||||
title: ev.summary,
|
||||
summary: ev.summary,
|
||||
backgroundColor: cal.backgroundColor,
|
||||
borderColor: cal.backgroundColor,
|
||||
calendar: cal.entity_id,
|
||||
};
|
||||
|
||||
calEvents.push(event);
|
||||
});
|
||||
});
|
||||
|
||||
return calEvents;
|
||||
};
|
||||
|
||||
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[] => {
|
||||
return Object.keys(hass.states)
|
||||
.filter((eid) => computeDomain(eid) === "calendar")
|
||||
.sort()
|
||||
.map((eid, idx) => ({
|
||||
entity_id: eid,
|
||||
name: computeStateName(hass.states[eid]),
|
||||
backgroundColor: `#${HA_COLOR_PALETTE[idx % HA_COLOR_PALETTE.length]}`,
|
||||
}));
|
||||
};
|
@ -1,69 +0,0 @@
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import moment from "moment";
|
||||
// eslint-disable-next-line import/no-duplicates,import/no-extraneous-dependencies
|
||||
import React from "react";
|
||||
import BigCalendar from "react-big-calendar";
|
||||
// eslint-disable-next-line import/no-duplicates,import/no-extraneous-dependencies
|
||||
import { render } from "react-dom";
|
||||
import { EventsMixin } from "../../mixins/events-mixin";
|
||||
import "../../resources/ha-style";
|
||||
|
||||
BigCalendar.setLocalizer(BigCalendar.momentLocalizer(moment));
|
||||
|
||||
const DEFAULT_VIEW = "month";
|
||||
|
||||
class HaBigCalendar extends EventsMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/static/panels/calendar/react-big-calendar.css"
|
||||
/>
|
||||
<style>
|
||||
div#root {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<div id="root"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
events: {
|
||||
type: Array,
|
||||
observer: "_update",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
_update(events) {
|
||||
const allViews = BigCalendar.Views.values;
|
||||
|
||||
const BCElement = React.createElement(BigCalendar, {
|
||||
events: events,
|
||||
views: allViews,
|
||||
popup: true,
|
||||
onNavigate: (date, viewName) => this.fire("navigate", { date, viewName }),
|
||||
onView: (viewName) => this.fire("view-changed", { viewName }),
|
||||
eventPropGetter: this._setEventStyle,
|
||||
defaultView: DEFAULT_VIEW,
|
||||
defaultDate: new Date(),
|
||||
});
|
||||
render(BCElement, this.$.root);
|
||||
}
|
||||
|
||||
_setEventStyle(event) {
|
||||
// https://stackoverflow.com/questions/34587067/change-color-of-react-big-calendar-events
|
||||
const newStyle = {};
|
||||
if (event.color) {
|
||||
newStyle.backgroundColor = event.color;
|
||||
}
|
||||
return { style: newStyle };
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-big-calendar", HaBigCalendar);
|
331
src/panels/calendar/ha-full-calendar.ts
Normal file
331
src/panels/calendar/ha-full-calendar.ts
Normal file
@ -0,0 +1,331 @@
|
||||
import {
|
||||
property,
|
||||
PropertyValues,
|
||||
LitElement,
|
||||
CSSResult,
|
||||
html,
|
||||
css,
|
||||
unsafeCSS,
|
||||
TemplateResult,
|
||||
} from "lit-element";
|
||||
import { Calendar } from "@fullcalendar/core";
|
||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||
// @ts-ignore
|
||||
import fullcalendarStyle from "@fullcalendar/core/main.css";
|
||||
// @ts-ignore
|
||||
import daygridStyle from "@fullcalendar/daygrid/main.css";
|
||||
import "@material/mwc-button";
|
||||
|
||||
import "../../components/ha-icon-button";
|
||||
import "../../components/ha-button-toggle-group";
|
||||
|
||||
import type {
|
||||
CalendarViewChanged,
|
||||
CalendarEvent,
|
||||
ToggleButton,
|
||||
HomeAssistant,
|
||||
} from "../../types";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"view-changed": CalendarViewChanged;
|
||||
}
|
||||
}
|
||||
|
||||
const fullCalendarConfig = {
|
||||
headerToolbar: false,
|
||||
plugins: [dayGridPlugin],
|
||||
initialView: "dayGridMonth",
|
||||
dayMaxEventRows: true,
|
||||
height: "parent",
|
||||
};
|
||||
|
||||
const viewButtons: ToggleButton[] = [
|
||||
{ label: "Month View", value: "dayGridMonth", icon: "hass:view-module" },
|
||||
{ label: "Week View", value: "dayGridWeek", icon: "hass:view-week" },
|
||||
{ label: "Day View", value: "dayGridDay", icon: "hass:view-day" },
|
||||
];
|
||||
|
||||
class HAFullCalendar extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property() public events: CalendarEvent[] = [];
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public narrow!: boolean;
|
||||
|
||||
@property() private calendar?: Calendar;
|
||||
|
||||
@property() private _activeView = "dayGridMonth";
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.calendar
|
||||
? html`
|
||||
<div class="header">
|
||||
${!this.narrow
|
||||
? html`
|
||||
<div class="navigation">
|
||||
<mwc-button
|
||||
outlined
|
||||
class="today"
|
||||
@click=${this._handleToday}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.calendar.today"
|
||||
)}</mwc-button
|
||||
>
|
||||
<ha-icon-button
|
||||
label=${this.hass.localize("ui.common.previous")}
|
||||
icon="hass:chevron-left"
|
||||
class="prev"
|
||||
@click=${this._handlePrev}
|
||||
>
|
||||
</ha-icon-button>
|
||||
<ha-icon-button
|
||||
label=${this.hass.localize("ui.common.next")}
|
||||
icon="hass:chevron-right"
|
||||
class="next"
|
||||
@click=${this._handleNext}
|
||||
>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
<h1>
|
||||
${this.calendar.view.title}
|
||||
</h1>
|
||||
<ha-button-toggle-group
|
||||
.buttons=${viewButtons}
|
||||
.active=${this._activeView}
|
||||
@value-changed=${this._handleView}
|
||||
></ha-button-toggle-group>
|
||||
`
|
||||
: html`
|
||||
<div class="controls">
|
||||
<mwc-button
|
||||
outlined
|
||||
class="today"
|
||||
@click=${this._handleToday}
|
||||
>${this.hass.localize(
|
||||
"ui.panel.calendar.today"
|
||||
)}</mwc-button
|
||||
>
|
||||
<ha-button-toggle-group
|
||||
.buttons=${viewButtons}
|
||||
.active=${this._activeView}
|
||||
@value-changed=${this._handleView}
|
||||
></ha-button-toggle-group>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<h1>
|
||||
${this.calendar.view.title}
|
||||
</h1>
|
||||
<div>
|
||||
<ha-icon-button
|
||||
label=${this.hass.localize("ui.common.previous")}
|
||||
icon="hass:chevron-left"
|
||||
class="prev"
|
||||
@click=${this._handlePrev}
|
||||
>
|
||||
</ha-icon-button>
|
||||
<ha-icon-button
|
||||
label=${this.hass.localize("ui.common.next")}
|
||||
icon="hass:chevron-right"
|
||||
class="next"
|
||||
@click=${this._handleNext}
|
||||
>
|
||||
</ha-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
<div id="calendar"></div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues): void {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (!this.calendar) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (changedProps.has("events")) {
|
||||
this.calendar.removeAllEventSources();
|
||||
this.calendar.addEventSource(this.events);
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
const config = { ...fullCalendarConfig, locale: this.hass.language };
|
||||
|
||||
this.calendar = new Calendar(
|
||||
this.shadowRoot!.getElementById("calendar")!,
|
||||
// @ts-ignore
|
||||
config
|
||||
);
|
||||
|
||||
this.calendar!.render();
|
||||
this._fireViewChanged();
|
||||
}
|
||||
|
||||
private _handleNext(): void {
|
||||
this.calendar!.next();
|
||||
this._fireViewChanged();
|
||||
}
|
||||
|
||||
private _handlePrev(): void {
|
||||
this.calendar!.prev();
|
||||
this._fireViewChanged();
|
||||
}
|
||||
|
||||
private _handleToday(): void {
|
||||
this.calendar!.today();
|
||||
this._fireViewChanged();
|
||||
}
|
||||
|
||||
private _handleView(ev): void {
|
||||
this._activeView = ev.detail.value;
|
||||
this.calendar!.changeView(this._activeView);
|
||||
this._fireViewChanged();
|
||||
}
|
||||
|
||||
private _fireViewChanged(): void {
|
||||
fireEvent(this, "view-changed", {
|
||||
start: this.calendar!.view.activeStart,
|
||||
end: this.calendar!.view.activeEnd,
|
||||
view: this.calendar!.view.type,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
${unsafeCSS(fullcalendarStyle)}
|
||||
${unsafeCSS(daygridStyle)}
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
:host([narrow]) .header {
|
||||
padding-right: 8px;
|
||||
padding-left: 8px;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: initial;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.today {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.prev,
|
||||
.next {
|
||||
--mdc-icon-button-size: 32px;
|
||||
}
|
||||
|
||||
ha-button-toggle-group {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
#calendar {
|
||||
flex-grow: 1;
|
||||
background-color: var(--card-background-color);
|
||||
}
|
||||
|
||||
.fc-scrollgrid-section-header td {
|
||||
border: none;
|
||||
}
|
||||
|
||||
th.fc-col-header-cell.fc-day {
|
||||
color: #70757a;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.fc-daygrid-day-top {
|
||||
text-align: center;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
table.fc-scrollgrid-sync-table
|
||||
tbody
|
||||
tr:first-child
|
||||
.fc-daygrid-day-top {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
a.fc-daygrid-day-number {
|
||||
float: none !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
td.fc-day-today {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
td.fc-day-today .fc-daygrid-day-number {
|
||||
height: 24px;
|
||||
color: #fff;
|
||||
background-color: #1a73e8;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
width: max-content;
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
.fc-daygrid-day-events {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.fc-event {
|
||||
border-radius: 4px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.fc-daygrid-block-event .fc-event-main {
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
.fc-day-past .fc-daygrid-day-events {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.fc-icon-x:before {
|
||||
font-family: var(--material-font-family);
|
||||
content: "X";
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define("ha-full-calendar", HAFullCalendar);
|
@ -1,220 +0,0 @@
|
||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@polymer/paper-checkbox/paper-checkbox";
|
||||
import "@polymer/paper-item/paper-item";
|
||||
import "@polymer/paper-listbox/paper-listbox";
|
||||
import { html } from "@polymer/polymer/lib/utils/html-tag";
|
||||
/* eslint-plugin-disable lit */
|
||||
import { PolymerElement } from "@polymer/polymer/polymer-element";
|
||||
import moment from "moment";
|
||||
import dates from "react-big-calendar/lib/utils/dates";
|
||||
import "../../components/ha-card";
|
||||
import "../../components/ha-menu-button";
|
||||
import LocalizeMixin from "../../mixins/localize-mixin";
|
||||
import "../../resources/ha-style";
|
||||
import "./ha-big-calendar";
|
||||
|
||||
const DEFAULT_VIEW = "month";
|
||||
|
||||
/*
|
||||
* @appliesMixin LocalizeMixin
|
||||
*/
|
||||
class HaPanelCalendar extends LocalizeMixin(PolymerElement) {
|
||||
static get template() {
|
||||
return html`
|
||||
<style include="iron-flex ha-style">
|
||||
.content {
|
||||
padding: 16px;
|
||||
@apply --layout-horizontal;
|
||||
}
|
||||
|
||||
ha-big-calendar {
|
||||
min-height: 500px;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
#calendars {
|
||||
padding-right: 16px;
|
||||
width: 15%;
|
||||
min-width: 170px;
|
||||
}
|
||||
|
||||
paper-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div.all_calendars {
|
||||
height: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.iron-selected {
|
||||
background-color: #e5e5e5;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
:host([narrow]) .content {
|
||||
flex-direction: column;
|
||||
}
|
||||
:host([narrow]) #calendars {
|
||||
margin-bottom: 24px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
hass="[[hass]]"
|
||||
narrow="[[narrow]]"
|
||||
></ha-menu-button>
|
||||
<div main-title>[[localize('panel.calendar')]]</div>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
|
||||
<div class="flex content">
|
||||
<div id="calendars" class="layout vertical wrap">
|
||||
<ha-card header="Calendars">
|
||||
<paper-listbox
|
||||
id="calendar_list"
|
||||
multi
|
||||
on-selected-items-changed="_fetchData"
|
||||
selected-values="{{selectedCalendars}}"
|
||||
attr-for-selected="item-name"
|
||||
>
|
||||
<template is="dom-repeat" items="[[calendars]]">
|
||||
<paper-item item-name="[[item.entity_id]]">
|
||||
<span
|
||||
class="calendar_color"
|
||||
style$="background-color: [[item.color]]"
|
||||
></span>
|
||||
<span class="calendar_color_spacer"></span> [[item.name]]
|
||||
</paper-item>
|
||||
</template>
|
||||
</paper-listbox>
|
||||
</ha-card>
|
||||
</div>
|
||||
<div class="flex layout horizontal wrap">
|
||||
<ha-big-calendar
|
||||
default-date="[[currentDate]]"
|
||||
default-view="[[currentView]]"
|
||||
on-navigate="_handleNavigate"
|
||||
on-view="_handleViewChanged"
|
||||
events="[[events]]"
|
||||
>
|
||||
</ha-big-calendar>
|
||||
</div>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
static get properties() {
|
||||
return {
|
||||
hass: Object,
|
||||
|
||||
currentView: {
|
||||
type: String,
|
||||
value: DEFAULT_VIEW,
|
||||
},
|
||||
|
||||
currentDate: {
|
||||
type: Object,
|
||||
value: new Date(),
|
||||
},
|
||||
|
||||
events: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
|
||||
calendars: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
|
||||
selectedCalendars: {
|
||||
type: Array,
|
||||
value: [],
|
||||
},
|
||||
|
||||
narrow: {
|
||||
type: Boolean,
|
||||
reflectToAttribute: true,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._fetchCalendars();
|
||||
}
|
||||
|
||||
_fetchCalendars() {
|
||||
this.hass.callApi("get", "calendars").then((result) => {
|
||||
this.calendars = result;
|
||||
this.selectedCalendars = result.map((cal) => cal.entity_id);
|
||||
});
|
||||
}
|
||||
|
||||
_fetchData() {
|
||||
const start = dates.firstVisibleDay(this.currentDate).toISOString();
|
||||
const end = dates.lastVisibleDay(this.currentDate).toISOString();
|
||||
const params = encodeURI(`?start=${start}&end=${end}`);
|
||||
const calls = this.selectedCalendars.map((cal) =>
|
||||
this.hass.callApi("get", `calendars/${cal}${params}`)
|
||||
);
|
||||
Promise.all(calls).then((results) => {
|
||||
const tmpEvents = [];
|
||||
|
||||
results.forEach((res) => {
|
||||
res.forEach((ev) => {
|
||||
ev.start = new Date(ev.start);
|
||||
if (ev.end) {
|
||||
ev.end = new Date(ev.end);
|
||||
} else {
|
||||
ev.end = null;
|
||||
}
|
||||
tmpEvents.push(ev);
|
||||
});
|
||||
});
|
||||
this.events = tmpEvents;
|
||||
});
|
||||
}
|
||||
|
||||
_getDateRange() {
|
||||
let startDate;
|
||||
let endDate;
|
||||
if (this.currentView === "day") {
|
||||
startDate = moment(this.currentDate).startOf("day");
|
||||
endDate = moment(this.currentDate).startOf("day");
|
||||
} else if (this.currentView === "week") {
|
||||
startDate = moment(this.currentDate).startOf("isoWeek");
|
||||
endDate = moment(this.currentDate).endOf("isoWeek");
|
||||
} else if (this.currentView === "month") {
|
||||
startDate = moment(this.currentDate).startOf("month").subtract(7, "days");
|
||||
endDate = moment(this.currentDate).endOf("month").add(7, "days");
|
||||
} else if (this.currentView === "agenda") {
|
||||
startDate = moment(this.currentDate).startOf("day");
|
||||
endDate = moment(this.currentDate).endOf("day").add(1, "month");
|
||||
}
|
||||
return [startDate.toISOString(), endDate.toISOString()];
|
||||
}
|
||||
|
||||
_handleViewChanged(ev) {
|
||||
// Calendar view changed
|
||||
this.currentView = ev.detail.viewName;
|
||||
this._fetchData();
|
||||
}
|
||||
|
||||
_handleNavigate(ev) {
|
||||
// Calendar date range changed
|
||||
this.currentDate = ev.detail.date;
|
||||
this.currentView = ev.detail.viewName;
|
||||
this._fetchData();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("ha-panel-calendar", HaPanelCalendar);
|
236
src/panels/calendar/ha-panel-calendar.ts
Normal file
236
src/panels/calendar/ha-panel-calendar.ts
Normal file
@ -0,0 +1,236 @@
|
||||
import {
|
||||
customElement,
|
||||
LitElement,
|
||||
property,
|
||||
CSSResultArray,
|
||||
css,
|
||||
TemplateResult,
|
||||
html,
|
||||
PropertyValues,
|
||||
} from "lit-element";
|
||||
import { styleMap } from "lit-html/directives/style-map";
|
||||
|
||||
import "@polymer/app-layout/app-header-layout/app-header-layout";
|
||||
import "@polymer/app-layout/app-header/app-header";
|
||||
import "@polymer/app-layout/app-toolbar/app-toolbar";
|
||||
import "@material/mwc-checkbox";
|
||||
import "@material/mwc-formfield";
|
||||
|
||||
import "../../components/ha-menu-button";
|
||||
import "../../components/ha-card";
|
||||
import "./ha-full-calendar";
|
||||
|
||||
import type {
|
||||
HomeAssistant,
|
||||
SelectedCalendar,
|
||||
CalendarEvent,
|
||||
CalendarViewChanged,
|
||||
Calendar,
|
||||
} from "../../types";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import { HASSDomEvent } from "../../common/dom/fire_event";
|
||||
import { getCalendars, fetchCalendarEvents } from "../../data/calendar";
|
||||
|
||||
@customElement("ha-panel-calendar")
|
||||
class PanelCalendar extends LitElement {
|
||||
@property() public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
public narrow!: boolean;
|
||||
|
||||
@property() private _calendars: SelectedCalendar[] = [];
|
||||
|
||||
@property() private _events: CalendarEvent[] = [];
|
||||
|
||||
private _start?: Date;
|
||||
|
||||
private _end?: Date;
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues): void {
|
||||
super.firstUpdated(changedProps);
|
||||
this._calendars = getCalendars(this.hass).map((calendar) => ({
|
||||
selected: true,
|
||||
calendar,
|
||||
}));
|
||||
|
||||
if (!this._start || !this._end) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._fetchEvents(this._start, this._end, this._selectedCalendars);
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<app-header-layout has-scrolling-region>
|
||||
<app-header fixed slot="header">
|
||||
<app-toolbar>
|
||||
<ha-menu-button
|
||||
.hass=${this.hass}
|
||||
.narrow=${this.narrow}
|
||||
></ha-menu-button>
|
||||
<div main-title>${this.hass.localize("panel.calendar")}</div>
|
||||
<ha-icon-button
|
||||
icon="hass:refresh"
|
||||
@click=${this._handleRefresh}
|
||||
></ha-icon-button>
|
||||
</app-toolbar>
|
||||
</app-header>
|
||||
<div class="content">
|
||||
<div class="calendar-list">
|
||||
<div class="calendar-list-header">
|
||||
${this.hass.localize("ui.panel.calendar.my_calendars")}
|
||||
</div>
|
||||
${this._calendars.map(
|
||||
(selCal) =>
|
||||
html`<div>
|
||||
<mwc-formfield .label=${selCal.calendar.name}>
|
||||
<mwc-checkbox
|
||||
style=${styleMap({
|
||||
"--mdc-theme-secondary":
|
||||
selCal.calendar.backgroundColor,
|
||||
})}
|
||||
.value=${selCal.calendar.entity_id}
|
||||
.checked=${selCal.selected}
|
||||
@change=${this._handleToggle}
|
||||
></mwc-checkbox>
|
||||
</mwc-formfield>
|
||||
</div>`
|
||||
)}
|
||||
</div>
|
||||
<ha-full-calendar
|
||||
.events=${this._events}
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
@view-changed=${this._handleViewChanged}
|
||||
></ha-full-calendar>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
`;
|
||||
}
|
||||
|
||||
private get _selectedCalendars(): Calendar[] {
|
||||
return this._calendars
|
||||
.filter((selCal) => selCal.selected)
|
||||
.map((cal) => cal.calendar);
|
||||
}
|
||||
|
||||
private async _fetchEvents(
|
||||
start: Date,
|
||||
end: Date,
|
||||
calendars: Calendar[]
|
||||
): Promise<CalendarEvent[]> {
|
||||
if (!calendars.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
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.calendar.entity_id) {
|
||||
return cal;
|
||||
}
|
||||
|
||||
const checked = ev.target.checked;
|
||||
|
||||
if (checked) {
|
||||
const events = await this._fetchEvents(this._start!, this._end!, [
|
||||
cal.calendar,
|
||||
]);
|
||||
this._events = [...this._events, ...events];
|
||||
} else {
|
||||
this._events = this._events.filter(
|
||||
(event) => event.calendar !== cal.calendar.entity_id
|
||||
);
|
||||
}
|
||||
|
||||
cal.selected = checked;
|
||||
return cal;
|
||||
});
|
||||
|
||||
this._calendars = await Promise.all(results);
|
||||
}
|
||||
|
||||
private async _handleViewChanged(
|
||||
ev: HASSDomEvent<CalendarViewChanged>
|
||||
): Promise<void> {
|
||||
this._start = ev.detail.start;
|
||||
this._end = ev.detail.end;
|
||||
this._events = await this._fetchEvents(
|
||||
this._start,
|
||||
this._end,
|
||||
this._selectedCalendars
|
||||
);
|
||||
}
|
||||
|
||||
private async _handleRefresh(): Promise<void> {
|
||||
this._events = await this._fetchEvents(
|
||||
this._start!,
|
||||
this._end!,
|
||||
this._selectedCalendars
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
haStyle,
|
||||
css`
|
||||
.content {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host(:not([narrow])) .content {
|
||||
height: calc(100vh - 64px);
|
||||
}
|
||||
|
||||
.calendar-list {
|
||||
padding-right: 16px;
|
||||
min-width: 170px;
|
||||
flex: 0 0 15%;
|
||||
overflow: hidden;
|
||||
--mdc-theme-text-primary-on-background: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
:host([narrow]) ha-full-calendar {
|
||||
height: calc(100vh - 72px);
|
||||
}
|
||||
|
||||
:host([narrow]) .content {
|
||||
flex-direction: column-reverse;
|
||||
padding: 8px 0 0 0;
|
||||
}
|
||||
|
||||
:host([narrow]) .calendar-list {
|
||||
margin-bottom: 24px;
|
||||
width: 100%;
|
||||
padding-right: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-panel-calendar": PanelCalendar;
|
||||
}
|
||||
}
|
@ -170,12 +170,12 @@ class ActionHandler extends HTMLElement implements ActionHandler {
|
||||
display: null,
|
||||
});
|
||||
this.ripple.disabled = false;
|
||||
this.ripple.active = true;
|
||||
this.ripple.activate();
|
||||
this.ripple.unbounded = true;
|
||||
}
|
||||
|
||||
private stopAnimation() {
|
||||
this.ripple.active = false;
|
||||
this.ripple.deactivate();
|
||||
this.ripple.disabled = true;
|
||||
this.style.display = "none";
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ export const derivedStyles = {
|
||||
"mdc-theme-on-primary": "var(--text-primary-color)",
|
||||
"mdc-theme-on-secondary": "var(--text-primary-color)",
|
||||
"mdc-theme-on-surface": "var(--primary-text-color)",
|
||||
"mdc-theme-text-primary-on-background": "var(--primary-text-color)",
|
||||
"app-header-text-color": "var(--text-primary-color)",
|
||||
"app-header-background-color": "var(--primary-color)",
|
||||
"material-body-text-color": "var(--primary-text-color)",
|
||||
|
@ -231,10 +231,13 @@
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"previous": "Previous",
|
||||
"loading": "Loading",
|
||||
"refresh": "Refresh",
|
||||
"cancel": "Cancel",
|
||||
"delete": "Delete",
|
||||
"close": "Close",
|
||||
"next": "Next",
|
||||
"undo": "Undo",
|
||||
"save": "Save",
|
||||
"yes": "Yes",
|
||||
@ -499,6 +502,7 @@
|
||||
"sidebar_toggle": "Sidebar Toggle"
|
||||
},
|
||||
"panel": {
|
||||
"calendar": { "my_calendars": "My Calendars", "today": "Today" },
|
||||
"config": {
|
||||
"header": "Configure Home Assistant",
|
||||
"introduction": "Here it is possible to configure your components and Home Assistant. Not everything is possible to configure from the UI yet, but we're working on it.",
|
||||
|
34
src/types.ts
34
src/types.ts
@ -96,6 +96,40 @@ export interface Panels {
|
||||
[name: string]: PanelInfo;
|
||||
}
|
||||
|
||||
export interface Calendar {
|
||||
entity_id: string;
|
||||
name: string;
|
||||
backgroundColor: string;
|
||||
}
|
||||
|
||||
export interface SelectedCalendar {
|
||||
selected: boolean;
|
||||
calendar: Calendar;
|
||||
}
|
||||
|
||||
export interface CalendarEvent {
|
||||
summary: string;
|
||||
title: string;
|
||||
start: string;
|
||||
end?: string;
|
||||
backgroundColor?: string;
|
||||
borderColor?: string;
|
||||
calendar: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface CalendarViewChanged {
|
||||
end: Date;
|
||||
start: Date;
|
||||
view: string;
|
||||
}
|
||||
|
||||
export interface ToggleButton {
|
||||
label?: string;
|
||||
icon: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface Translation {
|
||||
nativeName: string;
|
||||
isRTL: boolean;
|
||||
|
Loading…
x
Reference in New Issue
Block a user