diff --git a/README.md b/README.md index ecae5e7..24baa4f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# atomic calendar card v0.7.2 +# atomic calendar card v0.7.3 Advanced calendar card for Home Assistant with Lovelace Calendar card with advanced settings. It loads calendar events from Home Assistant - Google calendar component. @@ -35,7 +35,7 @@ If you have any suggestions about design or functionality, please let me know, o ## 1. Installation 1. You need to have the ![Google calendar](https://www.home-assistant.io/components/calendar.google/) component configured in Home Assistant. -2. Download `atomic-calendar.js` file to `/www` directory in your Home Assistant - [latest release](https://github.com/atomic7777/atomic_calendar/releases/download/v0.7.2/atomic-calendar.js) - link not working (in development) +2. Download `atomic-calendar.js` file to `/www` directory in your Home Assistant - [latest release](https://github.com/atomic7777/atomic_calendar/releases/download/v0.7.3/atomic-calendar.js) - link not working (in development) 3. Add this reference to your `ui-lovelace.yaml` file: ```yaml resources: @@ -62,8 +62,8 @@ If you have any suggestions about design or functionality, please let me know, o | showLoader | boolean | optional | v0.7.0 | `true` Show animation, when events are being loaded from Google Calendar. | showDate | boolean | optional | v0.7.2 | `false` Show the date on the right side of the title | dateFormat | string | optional | v0.7.2 | `LL` Custom date format - see https://devhints.io/moment for examples -| hoursFormat | string | optional | v0.7.3 | `default` Custom hours format - `12h` or `24h` or `default` (default for local HA language settings) - +| hoursFormat | string | optional | v0.7.3 | `default` Custom hours format - you can set `12h` or `24h` or `default` (default for local HA language settings) or even provide your custom, like `HH:mm` or `h:mm A` - see https://devhints.io/moment for examples +| startDaysAhead | integer | optional | v0.7.3 | `0` If you set more than 0, events will be loaded starting `x` days from today. For example `1` - the component will show events starting from tomorrow. ### Text colors and fonts | Name | Type | Since | Description | diff --git a/app.js b/app.js index 0f68ca5..51179b8 100644 --- a/app.js +++ b/app.js @@ -10,7 +10,8 @@ class AtomicCalendar extends LitElement { let timeFormat = moment.localeData(this._hass.language).longDateFormat('LT') if (this.config.hoursFormat=='12h') timeFormat = 'h:mm A' - else if (this.config.hoursFormat=='24h') timeFormat = 'hh:mm' + else if (this.config.hoursFormat=='24h') timeFormat = 'H:mm' + else if(this.config.hoursFormat!='default') timeFormat = this.config.hoursFormat moment.updateLocale(this._hass.language, { week: { dow: this.config.firstDayOfWeek @@ -61,6 +62,7 @@ class AtomicCalendar extends LitElement { try { this.events = await this.getEvents() } catch (error) { + console.log(error) this.errorMessage = 'The calendar can\'t be loaded from Home Assistant component' } @@ -405,7 +407,7 @@ class AtomicCalendar extends LitElement { showDate: false, dateFormat: 'LL', hoursFormat: 'default', // 12h / 24h / default time format. Default is HA language setting. - + startDaysAhead: 0, // shows the events starting on x days from today. Default 0. // color and font settings dateColor: 'var(--primary-text-color)', // Date text color (left side) @@ -673,8 +675,11 @@ class AtomicCalendar extends LitElement { * */ async getEvents() { - let start = moment().startOf('day').format('YYYY-MM-DDTHH:mm:ss'); - let end = moment().add(this.config.maxDaysToShow, 'days').format('YYYY-MM-DDTHH:mm:ss'); + + + let timeOffset = new Date().getTimezoneOffset() + let start = moment().add(this.config.startDaysAhead, 'days').startOf('day').add(timeOffset,'minutes').format('YYYY-MM-DDTHH:mm:ss'); + let end = moment().add((this.config.maxDaysToShow + this.config.startDaysAhead), 'days').endOf('day').add(timeOffset,'minutes').format('YYYY-MM-DDTHH:mm:ss'); let calendarUrlList = this.config.entities.map(entity => `calendars/${entity.entity}?start=${start}Z&end=${end}Z`) try { @@ -713,8 +718,9 @@ class AtomicCalendar extends LitElement { */ getCalendarEvents(startDay, endDay, monthToGet, month) { this.refreshCalEvents = false - let start = moment(startDay).startOf('day').format('YYYY-MM-DDTHH:mm:ss'); - let end = moment(endDay).endOf('day').format('YYYY-MM-DDTHH:mm:ss'); + let timeOffset = new Date().getTimezoneOffset() + let start = moment(startDay).startOf('day').add(timeOffset,'minutes').format('YYYY-MM-DDTHH:mm:ss'); + let end = moment(endDay).endOf('day').add(timeOffset,'minutes').format('YYYY-MM-DDTHH:mm:ss'); // calendarUrlList[url, type of event configured for this callendar,filters] let calendarUrlList = [] this.config.entities.map(entity => { diff --git a/atomic-calendar.js b/atomic-calendar.js index b7a860d..7798765 100644 --- a/atomic-calendar.js +++ b/atomic-calendar.js @@ -1,18026 +1 @@ -(function () { - 'use strict'; - - /** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ - const directives = new WeakMap(); - const isDirective = (o) => { - return typeof o === 'function' && directives.has(o); - }; - //# sourceMappingURL=directive.js.map - - /** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ - /** - * True if the custom elements polyfill is in use. - */ - const isCEPolyfill = window.customElements !== undefined && - window.customElements.polyfillWrapFlushCallback !== - undefined; - /** - * Removes nodes, starting from `startNode` (inclusive) to `endNode` - * (exclusive), from `container`. - */ - const removeNodes = (container, startNode, endNode = null) => { - let node = startNode; - while (node !== endNode) { - const n = node.nextSibling; - container.removeChild(node); - node = n; - } - }; - //# sourceMappingURL=dom.js.map - - /** - * @license - * Copyright (c) 2018 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ - /** - * A sentinel value that signals that a value was handled by a directive and - * should not be written to the DOM. - */ - const noChange = {}; - /** - * A sentinel value that signals a NodePart to fully clear its content. - */ - const nothing = {}; - //# sourceMappingURL=part.js.map - - /** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ - /** - * An expression marker with embedded unique key to avoid collision with - * possible text in templates. - */ - const marker = `{{lit-${String(Math.random()).slice(2)}}}`; - /** - * An expression marker used text-positions, multi-binding attributes, and - * attributes with markup-like text values. - */ - const nodeMarker = ``; - const markerRegex = new RegExp(`${marker}|${nodeMarker}`); - /** - * Suffix appended to all bound attribute names. - */ - const boundAttributeSuffix = '$lit$'; - /** - * An updateable Template that tracks the location of dynamic parts. - */ - class Template { - constructor(result, element) { - this.parts = []; - this.element = element; - let index = -1; - let partIndex = 0; - const nodesToRemove = []; - const _prepareTemplate = (template) => { - const content = template.content; - // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be - // null - const walker = document.createTreeWalker(content, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, null, false); - // Keeps track of the last index associated with a part. We try to delete - // unnecessary nodes, but we never want to associate two different parts - // to the same index. They must have a constant node between. - let lastPartIndex = 0; - while (walker.nextNode()) { - index++; - const node = walker.currentNode; - if (node.nodeType === 1 /* Node.ELEMENT_NODE */) { - if (node.hasAttributes()) { - const attributes = node.attributes; - // Per - // https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap, - // attributes are not guaranteed to be returned in document order. - // In particular, Edge/IE can return them out of order, so we cannot - // assume a correspondance between part index and attribute index. - let count = 0; - for (let i = 0; i < attributes.length; i++) { - if (attributes[i].value.indexOf(marker) >= 0) { - count++; - } - } - while (count-- > 0) { - // Get the template literal section leading up to the first - // expression in this attribute - const stringForPart = result.strings[partIndex]; - // Find the attribute name - const name = lastAttributeNameRegex.exec(stringForPart)[2]; - // Find the corresponding attribute - // All bound attributes have had a suffix added in - // TemplateResult#getHTML to opt out of special attribute - // handling. To look up the attribute value we also need to add - // the suffix. - const attributeLookupName = name.toLowerCase() + boundAttributeSuffix; - const attributeValue = node.getAttribute(attributeLookupName); - const strings = attributeValue.split(markerRegex); - this.parts.push({ type: 'attribute', index, name, strings }); - node.removeAttribute(attributeLookupName); - partIndex += strings.length - 1; - } - } - if (node.tagName === 'TEMPLATE') { - _prepareTemplate(node); - } - } - else if (node.nodeType === 3 /* Node.TEXT_NODE */) { - const data = node.data; - if (data.indexOf(marker) >= 0) { - const parent = node.parentNode; - const strings = data.split(markerRegex); - const lastIndex = strings.length - 1; - // Generate a new text node for each literal section - // These nodes are also used as the markers for node parts - for (let i = 0; i < lastIndex; i++) { - parent.insertBefore((strings[i] === '') ? createMarker() : - document.createTextNode(strings[i]), node); - this.parts.push({ type: 'node', index: ++index }); - } - // If there's no text, we must insert a comment to mark our place. - // Else, we can trust it will stick around after cloning. - if (strings[lastIndex] === '') { - parent.insertBefore(createMarker(), node); - nodesToRemove.push(node); - } - else { - node.data = strings[lastIndex]; - } - // We have a part for each match found - partIndex += lastIndex; - } - } - else if (node.nodeType === 8 /* Node.COMMENT_NODE */) { - if (node.data === marker) { - const parent = node.parentNode; - // Add a new marker node to be the startNode of the Part if any of - // the following are true: - // * We don't have a previousSibling - // * The previousSibling is already the start of a previous part - if (node.previousSibling === null || index === lastPartIndex) { - index++; - parent.insertBefore(createMarker(), node); - } - lastPartIndex = index; - this.parts.push({ type: 'node', index }); - // If we don't have a nextSibling, keep this node so we have an end. - // Else, we can remove it to save future costs. - if (node.nextSibling === null) { - node.data = ''; - } - else { - nodesToRemove.push(node); - index--; - } - partIndex++; - } - else { - let i = -1; - while ((i = node.data.indexOf(marker, i + 1)) !== - -1) { - // Comment node has a binding marker inside, make an inactive part - // The binding won't work, but subsequent bindings will - // TODO (justinfagnani): consider whether it's even worth it to - // make bindings in comments work - this.parts.push({ type: 'node', index: -1 }); - } - } - } - } - }; - _prepareTemplate(element); - // Remove text binding nodes after the walk to not disturb the TreeWalker - for (const n of nodesToRemove) { - n.parentNode.removeChild(n); - } - } - } - const isTemplatePartActive = (part) => part.index !== -1; - // Allows `document.createComment('')` to be renamed for a - // small manual size-savings. - const createMarker = () => document.createComment(''); - /** - * This regex extracts the attribute name preceding an attribute-position - * expression. It does this by matching the syntax allowed for attributes - * against the string literal directly preceding the expression, assuming that - * the expression is in an attribute-value position. - * - * See attributes in the HTML spec: - * https://www.w3.org/TR/html5/syntax.html#attributes-0 - * - * "\0-\x1F\x7F-\x9F" are Unicode control characters - * - * " \x09\x0a\x0c\x0d" are HTML space characters: - * https://www.w3.org/TR/html5/infrastructure.html#space-character - * - * So an attribute is: - * * The name: any character except a control character, space character, ('), - * ("), ">", "=", or "/" - * * Followed by zero or more space characters - * * Followed by "=" - * * Followed by zero or more space characters - * * Followed by: - * * Any character except space, ('), ("), "<", ">", "=", (`), or - * * (") then any non-("), or - * * (') then any non-(') - */ - const lastAttributeNameRegex = /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F \x09\x0a\x0c\x0d"'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/; - //# sourceMappingURL=template.js.map - - /** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ - /** - * An instance of a `Template` that can be attached to the DOM and updated - * with new values. - */ - class TemplateInstance { - constructor(template, processor, options) { - this._parts = []; - this.template = template; - this.processor = processor; - this.options = options; - } - update(values) { - let i = 0; - for (const part of this._parts) { - if (part !== undefined) { - part.setValue(values[i]); - } - i++; - } - for (const part of this._parts) { - if (part !== undefined) { - part.commit(); - } - } - } - _clone() { - // When using the Custom Elements polyfill, clone the node, rather than - // importing it, to keep the fragment in the template's document. This - // leaves the fragment inert so custom elements won't upgrade and - // potentially modify their contents by creating a polyfilled ShadowRoot - // while we traverse the tree. - const fragment = isCEPolyfill ? - this.template.element.content.cloneNode(true) : - document.importNode(this.template.element.content, true); - const parts = this.template.parts; - let partIndex = 0; - let nodeIndex = 0; - const _prepareInstance = (fragment) => { - // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be - // null - const walker = document.createTreeWalker(fragment, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, null, false); - let node = walker.nextNode(); - // Loop through all the nodes and parts of a template - while (partIndex < parts.length && node !== null) { - const part = parts[partIndex]; - // Consecutive Parts may have the same node index, in the case of - // multiple bound attributes on an element. So each iteration we either - // increment the nodeIndex, if we aren't on a node with a part, or the - // partIndex if we are. By not incrementing the nodeIndex when we find a - // part, we allow for the next part to be associated with the current - // node if neccessasry. - if (!isTemplatePartActive(part)) { - this._parts.push(undefined); - partIndex++; - } - else if (nodeIndex === part.index) { - if (part.type === 'node') { - const part = this.processor.handleTextExpression(this.options); - part.insertAfterNode(node.previousSibling); - this._parts.push(part); - } - else { - this._parts.push(...this.processor.handleAttributeExpressions(node, part.name, part.strings, this.options)); - } - partIndex++; - } - else { - nodeIndex++; - if (node.nodeName === 'TEMPLATE') { - _prepareInstance(node.content); - } - node = walker.nextNode(); - } - } - }; - _prepareInstance(fragment); - if (isCEPolyfill) { - document.adoptNode(fragment); - customElements.upgrade(fragment); - } - return fragment; - } - } - //# sourceMappingURL=template-instance.js.map - - /** - * @license - * Copyright (c) 2017 The Polymer Project Authors. All rights reserved. - * This code may only be used under the BSD style license found at - * http://polymer.github.io/LICENSE.txt - * The complete set of authors may be found at - * http://polymer.github.io/AUTHORS.txt - * The complete set of contributors may be found at - * http://polymer.github.io/CONTRIBUTORS.txt - * Code distributed by Google as part of the polymer project is also - * subject to an additional IP rights grant found at - * http://polymer.github.io/PATENTS.txt - */ - /** - * The return type of `html`, which holds a Template and the values from - * interpolated expressions. - */ - class TemplateResult { - constructor(strings, values, type, processor) { - this.strings = strings; - this.values = values; - this.type = type; - this.processor = processor; - } - /** - * Returns a string of HTML used to create a `