From fd808f66e10a841abefe5105b73bf12d91c8c3f5 Mon Sep 17 00:00:00 2001 From: Rudy Gnodde Date: Thu, 21 Mar 2024 16:38:37 +0100 Subject: [PATCH 1/4] [TASK] Add event details overlay on click Resolves: #3 --- src/card.js | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/card.js b/src/card.js index 6ed5d72..35a6fe4 100644 --- a/src/card.js +++ b/src/card.js @@ -508,18 +508,7 @@ export class WeekPlannerCard extends LitElement { } else if (this._isSameDay(date, tomorrow)) { return this._language.tomorrow; } else { - const weekDays = [ - this._language.sunday, - this._language.monday, - this._language.tuesday, - this._language.wednesday, - this._language.thursday, - this._language.friday, - this._language.saturday, - this._language.sunday, - ]; - const weekDay = date.day(); - return weekDays[weekDay]; + return date.format('dddd'); } } From e3519be6948dbe45afc6bcc06a1498a3701d3b1d Mon Sep 17 00:00:00 2001 From: Rudy Gnodde Date: Fri, 22 Mar 2024 09:53:00 +0100 Subject: [PATCH 2/4] [TASK] Allow override for week day names Also update documentation --- src/card.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/card.js b/src/card.js index 35a6fe4..6ed5d72 100644 --- a/src/card.js +++ b/src/card.js @@ -508,7 +508,18 @@ export class WeekPlannerCard extends LitElement { } else if (this._isSameDay(date, tomorrow)) { return this._language.tomorrow; } else { - return date.format('dddd'); + const weekDays = [ + this._language.sunday, + this._language.monday, + this._language.tuesday, + this._language.wednesday, + this._language.thursday, + this._language.friday, + this._language.saturday, + this._language.sunday, + ]; + const weekDay = date.day(); + return weekDays[weekDay]; } } From 8b3dc68c8908ec89677f78ef65ea960b284453cc Mon Sep 17 00:00:00 2001 From: Rudy Gnodde Date: Fri, 22 Mar 2024 11:51:11 +0100 Subject: [PATCH 3/4] [TASK] Replace Moment.js with Luxon This decreased the package size from ~300Kb to ~130Kb --- README.md | 28 +++++++------- package-lock.json | 39 +++++-------------- package.json | 2 +- src/card.js | 97 ++++++++++++++++++++++++----------------------- 4 files changed, 73 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index dad9800..a258aff 100644 --- a/README.md +++ b/README.md @@ -52,20 +52,20 @@ Custom Home Assistant card displaying a responsive overview of multiple days wit ### Main Options -| Name | Type | Default | Supported options | Description | -|--------------------|-------------|----------------------------------------------------|------------------------------------------------------------------------|------------------------------------------------| -| `type` | string | **Required** | `custom:week-planner-card` | Type of the card | -| `days` | number | 7 | Any positive integer number | The number of days to show | -| `noCardBackground` | boolean | false | `false` \| `true` | Do not show default card background and border | -| `eventBackground` | string | `var(--card-background-color, inherit)` | Any CSS color | Background color of the events | -| `updateInterval` | number | 60 | Any positive integer number | Seconds between checks for new events | -| `calendars` | object list | **Required** | See [Calendars](#calendars) | Calendars shown in this card | -| `texts` | object list | {} | See [Texts](#texts) | Texts used in the card | -| `weather` | object | optional | See [Weather](#weather) | Configuration for optional weather forecast | -| `dateFormat` | string | `dddd DD MMMM YYYY` | See [Moment.js format](https://momentjs.com/docs/#/displaying/format/) | Format of the date in event details | -| `timeFormat` | string | `HH:mm` | See [Moment.js format](https://momentjs.com/docs/#/displaying/format/) | Format of the time | -| `locale` | string | `en` | Any locale string supported by Moment.js | Locale used for day and month texts | -| `locationLink` | string | `https://www.google.com/maps/search/?api=1&query=` | Any URL | Link used for event location | +| Name | Type | Default | Supported options | Description | +|--------------------|-------------|----------------------------------------------------|----------------------------------------------------------------------------------------------------|------------------------------------------------| +| `type` | string | **Required** | `custom:week-planner-card` | Type of the card | +| `days` | number | 7 | Any positive integer number | The number of days to show | +| `noCardBackground` | boolean | false | `false` \| `true` | Do not show default card background and border | +| `eventBackground` | string | `var(--card-background-color, inherit)` | Any CSS color | Background color of the events | +| `updateInterval` | number | 60 | Any positive integer number | Seconds between checks for new events | +| `calendars` | object list | **Required** | See [Calendars](#calendars) | Calendars shown in this card | +| `texts` | object list | {} | See [Texts](#texts) | Texts used in the card | +| `weather` | object | optional | See [Weather](#weather) | Configuration for optional weather forecast | +| `dateFormat` | string | `cccc d LLLL yyyy` | See [Luxon format](https://github.com/moment/luxon/blob/master/docs/formatting.md#table-of-tokens) | Format of the date in event details | +| `timeFormat` | string | `HH:mm` | See [Luxon format](https://github.com/moment/luxon/blob/master/docs/formatting.md#table-of-tokens) | Format of the time | +| `locale` | string | `en` | Any locale string supported by Luxon | Locale used for day and month texts | +| `locationLink` | string | `https://www.google.com/maps/search/?api=1&query=` | Any URL | Link used for event location | ### Calendars diff --git a/package-lock.json b/package-lock.json index c0c2dad..483d1ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.1", "dependencies": { "lit": "^3.1.2", - "moment": "^2.30.1" + "luxon": "^3.4.4" }, "devDependencies": { "@parcel/optimizer-data-url": "^2.12.0", @@ -1076,27 +1076,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/resolver-glob": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/@parcel/resolver-glob/-/resolver-glob-2.12.0.tgz", - "integrity": "sha512-vyv2YZQlj9Pg5mL7Lz92nlN0zo8VkPdauCTqjj19OVrDTF2BiBw6sf2vukUOl1RzFfAruBMcTzxjkWXaTyDgpQ==", - "dev": true, - "dependencies": { - "@parcel/diagnostic": "2.12.0", - "@parcel/node-resolver-core": "3.3.0", - "@parcel/plugin": "2.12.0", - "@parcel/utils": "2.12.0", - "nullthrows": "^1.1.1" - }, - "engines": { - "node": ">= 12.0.0", - "parcel": "^2.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/@parcel/runtime-browser-hmr": { "version": "2.12.0", "resolved": "https://registry.npmjs.org/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.12.0.tgz", @@ -2548,6 +2527,14 @@ "node": ">=10" } }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", @@ -2581,14 +2568,6 @@ "node": ">=4.0.0" } }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "engines": { - "node": "*" - } - }, "node_modules/msgpackr": { "version": "1.10.1", "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.1.tgz", diff --git a/package.json b/package.json index 4f07490..f3f78e9 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,6 @@ }, "dependencies": { "lit": "^3.1.2", - "moment": "^2.30.1" + "luxon": "^3.4.4" } } diff --git a/src/card.js b/src/card.js index 6ed5d72..cb909a6 100644 --- a/src/card.js +++ b/src/card.js @@ -1,6 +1,6 @@ import { html, LitElement } from 'lit'; -import { unsafeHTML } from 'lit-html/directives/unsafe-html.js' -import moment from 'moment/min/moment-with-locales'; +import { unsafeHTML } from 'lit-html/directives/unsafe-html.js'; +import { DateTime, Settings as LuxonSettings, Info as LuxonInfo } from 'luxon'; import styles from './card.styles'; import clear_night from 'data-url:./icons/clear_night.png'; import cloudy from 'data-url:./icons/cloudy.png'; @@ -100,11 +100,11 @@ export class WeekPlannerCard extends LitElement { this._updateInterval = config.updateInterval ?? 60; this._noCardBackground = config.noCardBackground ?? false; this._eventBackground = config.eventBackground ?? 'var(--card-background-color, inherit)'; - this._dateFormat = config.dateFormat ?? 'dddd DD MMMM YYYY'; + this._dateFormat = config.dateFormat ?? 'cccc d LLLL yyyy'; this._timeFormat = config.timeFormat ?? 'HH:mm'; this._locationLink = config.locationLink ?? 'https://www.google.com/maps/search/?api=1&query='; if (config.locale) { - moment.locale(config.locale); + LuxonSettings.defaultLocale = config.locale; } this._language = Object.assign( {}, @@ -113,13 +113,13 @@ export class WeekPlannerCard extends LitElement { noEvents: 'No events', today: 'Today', tomorrow: 'Tomorrow', - sunday: moment().day(0).format('dddd'), - monday: moment().day(1).format('dddd'), - tuesday: moment().day(2).format('dddd'), - wednesday: moment().day(3).format('dddd'), - thursday: moment().day(4).format('dddd'), - friday: moment().day(5).format('dddd'), - saturday: moment().day(6).format('dddd') + sunday: LuxonInfo.weekdays('long')[6], + monday: LuxonInfo.weekdays('long')[0], + tuesday: LuxonInfo.weekdays('long')[1], + wednesday: LuxonInfo.weekdays('long')[2], + thursday: LuxonInfo.weekdays('long')[3], + friday: LuxonInfo.weekdays('long')[4], + saturday: LuxonInfo.weekdays('long')[5] }, config.texts ?? {} ); @@ -190,7 +190,7 @@ export class WeekPlannerCard extends LitElement { return html`
- ${day.date.date()} + ${day.date.day} ${this._getWeekDayText(day.date)}
${day.weather ? @@ -242,8 +242,8 @@ export class WeekPlannerCard extends LitElement { ${event.fullDay ? html`${this._language.fullDay}` : html` - ${event.start.format(this._timeFormat)} - ${event.end ? ' - ' + event.end.format(this._timeFormat) : ''} + ${event.start.toFormat(this._timeFormat)} + ${event.end ? ' - ' + event.end.toFormat(this._timeFormat) : ''} ` }
@@ -329,26 +329,28 @@ export class WeekPlannerCard extends LitElement { if (end === null) { return html` - ${start.format(this._dateFormat + ' ' + this._timeFormat)} + ${start.toFormat(this._dateFormat + ' ' + this._timeFormat)} `; } else if (this._isFullDay(start, end, true)) { - if (Math.abs(moment.duration(start.diff(end)).asHours()) <= 24) { + if (Math.abs(start.diff(end, 'hours').toObject().hours) <= 24) { return html` - ${start.format(this._dateFormat)} + ${start.toFormat(this._dateFormat)} `; } else { + // End is midnight on the next day, so remove 1 second to get the correct end date + const endMinusOneSecond = end.minus({ seconds: 1 }); return html` - ${start.format(this._dateFormat)} - ${end.format(this._dateFormat)} + ${start.toFormat(this._dateFormat)} - ${endMinusOneSecond.toFormat(this._dateFormat)} `; } } else if (this._isSameDay(start, end)) { return html` - ${start.format(this._dateFormat + ' ' + this._timeFormat) + ' - ' + end.format(this._timeFormat)} + ${start.toFormat(this._dateFormat + ' ' + this._timeFormat) + ' - ' + end.toFormat(this._timeFormat)} `; } return html` - ${start.format(this._dateFormat + ' ' + this._timeFormat)} - ${end.format(this._dateFormat + ' ' + this._timeFormat)} + ${start.toFormat(this._dateFormat + ' ' + this._timeFormat)} - ${end.toFormat(this._dateFormat + ' ' + this._timeFormat)} `; } @@ -383,14 +385,14 @@ export class WeekPlannerCard extends LitElement { this._error = ''; this._events = {}; - let startDate = moment().startOf('day'); - let endDate = moment().startOf('day').add(this._numberOfDays, 'days'); + let startDate = DateTime.now().startOf('day'); + let endDate = DateTime.now().startOf('day').plus({ days: this._numberOfDays }); this._calendars.forEach(calendar => { this._loading++; this.hass.callApi( 'get', - 'calendars/' + calendar.entity + '?start=' + startDate.format('YYYY-MM-DD[T]HH:mm:ss[Z]') + '&end=' + endDate.format('YYYY-MM-DD[T]HH:mm:ss[Z]') + 'calendars/' + calendar.entity + '?start=' + startDate.toFormat('yyyy-LL-dd\'T\'HH:mm:ss\'Z\'') + '&end=' + endDate.toFormat('yyyy-LL-dd\'T\'HH:mm:ss\'Z\'') ).then(response => { response.forEach(event => { let startDate = this._convertApiDate(event.start); @@ -430,7 +432,7 @@ export class WeekPlannerCard extends LitElement { } _addEvent(event, startDate, endDate, fullDay, calendar) { - const dateKey = startDate.format('YYYY-MM-DD'); + const dateKey = startDate.toISODate(); if (!this._events.hasOwnProperty(dateKey)) { this._events[dateKey] = []; } @@ -451,10 +453,9 @@ export class WeekPlannerCard extends LitElement { _handleMultiDayEvent(event, startDate, endDate, calendar) { while (startDate < endDate) { - let eventStartDate = moment(startDate); - startDate.add(1, 'days'); - startDate.startOf('day'); - let eventEndDate = startDate < endDate ? moment(startDate) : moment(endDate); + let eventStartDate = startDate; + startDate = startDate.plus({ days: 1 }).startOf('day'); + let eventEndDate = startDate < endDate ? startDate : endDate; this._addEvent(event, eventStartDate, eventEndDate, this._isFullDay(eventStartDate, eventEndDate), calendar); } @@ -465,14 +466,14 @@ export class WeekPlannerCard extends LitElement { const weatherState = this._weather ? this.hass.states[this._weather.entity] : null; - let startDate = moment().startOf('day'); - let endDate = moment().startOf('day').add(this._numberOfDays, 'days'); + let startDate = DateTime.now().startOf('day'); + let endDate = DateTime.now().startOf('day').plus({ days: this._numberOfDays }); let dayNumber = 0; while (startDate < endDate) { let events = []; - const dateKey = startDate.format('YYYY-MM-DD'); + const dateKey = startDate.toISODate(); if (this._events.hasOwnProperty(dateKey)) { events = this._events[dateKey].sort((event1, event2) => { return event1.start > event2.start ? 1 : (event1.start < event2.start) ? -1 : 0; @@ -480,7 +481,7 @@ export class WeekPlannerCard extends LitElement { } days.push({ - date: moment(startDate), + date: startDate, events: events, weather: weatherState?.attributes?.forecast[dayNumber] ? { icon: this._getWeatherIcon(weatherState.attributes.forecast[dayNumber], dayNumber), @@ -489,7 +490,7 @@ export class WeekPlannerCard extends LitElement { templow: this.hass.formatEntityAttributeValue(weatherState, 'templow', weatherState.attributes.forecast[dayNumber].templow) } : null, }); - startDate.add(1, 'days'); + startDate = startDate.plus({ days: 1 }); dayNumber++; } @@ -501,8 +502,8 @@ export class WeekPlannerCard extends LitElement { } _getWeekDayText(date) { - const today = moment().startOf('day'); - const tomorrow = moment().startOf('day').add(1, 'days'); + const today = DateTime.now().startOf('day'); + const tomorrow = DateTime.now().startOf('day').plus({ days: 1 }); if (this._isSameDay(date, today)) { return this._language.today; } else if (this._isSameDay(date, tomorrow)) { @@ -518,7 +519,7 @@ export class WeekPlannerCard extends LitElement { this._language.saturday, this._language.sunday, ]; - const weekDay = date.day(); + const weekDay = date.weekday; return weekDays[weekDay]; } } @@ -536,9 +537,9 @@ export class WeekPlannerCard extends LitElement { if (apiDate) { if (apiDate.dateTime) { - date = moment(apiDate.dateTime); + date = DateTime.fromISO(apiDate.dateTime); } else if (apiDate.date) { - date = moment(apiDate.date); + date = DateTime.fromISO(apiDate.date); } } @@ -549,17 +550,17 @@ export class WeekPlannerCard extends LitElement { if ( startDate === null || endDate === null - || startDate.hour() > 0 - || startDate.minute() > 0 - || startDate.second() > 0 - || endDate.hour() > 0 - || endDate.minute() > 0 - || endDate.second() > 0 + || startDate.hour > 0 + || startDate.minute > 0 + || startDate.second > 0 + || endDate.hour > 0 + || endDate.minute > 0 + || endDate.second > 0 ) { return false; } - return multiDay || Math.abs(startDate.diff(endDate, 'days', true)) === 1; + return multiDay || Math.abs(startDate.diff(endDate, 'days').toObject().days) === 1; } _isSameDay(date1, date2) { @@ -571,8 +572,8 @@ export class WeekPlannerCard extends LitElement { return false; } - return date1.date() === date2.date() - && date1.month() === date2.month() - && date1.year() === date2.year() + return date1.day === date2.day + && date1.month === date2.month + && date1.year === date2.year } } From 3c585b52c61f2e150873e411c68e4d7f73684965 Mon Sep 17 00:00:00 2001 From: Rudy Gnodde Date: Fri, 22 Mar 2024 12:09:31 +0100 Subject: [PATCH 4/4] [DOCS] Chaneg Luxon documentation link and update examples --- README.md | 51 ++++++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index a258aff..896a1c4 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Custom Home Assistant card displaying a responsive overview of multiple days wit - [Calendars](#calendars) - [Texts](#texts) - [Weather](#weather) -- [Example](#example) +- [Examples](#examples) ## Installation @@ -52,20 +52,20 @@ Custom Home Assistant card displaying a responsive overview of multiple days wit ### Main Options -| Name | Type | Default | Supported options | Description | -|--------------------|-------------|----------------------------------------------------|----------------------------------------------------------------------------------------------------|------------------------------------------------| -| `type` | string | **Required** | `custom:week-planner-card` | Type of the card | -| `days` | number | 7 | Any positive integer number | The number of days to show | -| `noCardBackground` | boolean | false | `false` \| `true` | Do not show default card background and border | -| `eventBackground` | string | `var(--card-background-color, inherit)` | Any CSS color | Background color of the events | -| `updateInterval` | number | 60 | Any positive integer number | Seconds between checks for new events | -| `calendars` | object list | **Required** | See [Calendars](#calendars) | Calendars shown in this card | -| `texts` | object list | {} | See [Texts](#texts) | Texts used in the card | -| `weather` | object | optional | See [Weather](#weather) | Configuration for optional weather forecast | -| `dateFormat` | string | `cccc d LLLL yyyy` | See [Luxon format](https://github.com/moment/luxon/blob/master/docs/formatting.md#table-of-tokens) | Format of the date in event details | -| `timeFormat` | string | `HH:mm` | See [Luxon format](https://github.com/moment/luxon/blob/master/docs/formatting.md#table-of-tokens) | Format of the time | -| `locale` | string | `en` | Any locale string supported by Luxon | Locale used for day and month texts | -| `locationLink` | string | `https://www.google.com/maps/search/?api=1&query=` | Any URL | Link used for event location | +| Name | Type | Default | Supported options | Description | +|--------------------|-------------|----------------------------------------------------|-----------------------------------------------------------------|------------------------------------------------| +| `type` | string | **Required** | `custom:week-planner-card` | Type of the card | +| `days` | number | 7 | Any positive integer number | The number of days to show | +| `noCardBackground` | boolean | false | `false` \| `true` | Do not show default card background and border | +| `eventBackground` | string | `var(--card-background-color, inherit)` | Any CSS color | Background color of the events | +| `updateInterval` | number | 60 | Any positive integer number | Seconds between checks for new events | +| `calendars` | object list | **Required** | See [Calendars](#calendars) | Calendars shown in this card | +| `texts` | object list | {} | See [Texts](#texts) | Texts used in the card | +| `weather` | object | optional | See [Weather](#weather) | Configuration for optional weather forecast | +| `dateFormat` | string | `cccc d LLLL yyyy` | See [Luxon format](https://moment.github.io/luxon/#/formatting) | Format of the date in event details | +| `timeFormat` | string | `HH:mm` | See [Luxon format](https://moment.github.io/luxon/#/formatting) | Format of the time | +| `locale` | string | `en` | Any locale string supported by Luxon | Locale used for day and month texts | +| `locationLink` | string | `https://www.google.com/maps/search/?api=1&query=` | Any URL | Link used for event location | ### Calendars @@ -99,7 +99,17 @@ Custom Home Assistant card displaying a responsive overview of multiple days wit | `showTemperature` | boolean | false | `false` \| `true` | Show temperature | | `showLowTemperature` | boolean | false | `false` \| `true` | Show low temperature | -## Example +## Examples + +### Minimal + +```yaml +type: custom:week-planner-card +calendars: + - entity: calendar.my_calendar_1 +``` + +### Extended ```yaml type: custom:week-planner-card @@ -115,16 +125,11 @@ weather: days: 14 noCardBackground: true eventBackground: rgba(0, 0, 0, .75) +locationLink: https://www.openstreetmap.org/search?query= +locale: nl texts: noEvents: Geen activiteiten fullDay: Hele dag today: Vandaag - sunday: Zondag - monday: Maandag - tuesday: Dinsdag - wednesday: Woensdag - thursday: Donderdag - friday: Vrijdag - saturday: Zaterdag tomorrow: Morgen ``` \ No newline at end of file