diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..e2da2147 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,15 @@ +FROM node:latest + +RUN apt update + +RUN apt upgrade -y + +RUN apt install zsh -y + +RUN wget https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh -O - | zsh || true + +COPY package*.json ./ + +RUN npm install + +CMD ["zsh"] diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..f53447b9 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,21 @@ +{ + "name": "Atomic Calendar Revive", + "dockerFile": "Dockerfile", + "context": "..", + "workspaceFolder": "/workspace", + "workspaceMount": "type=bind,source=/home/${env:USERNAME}/repos/atomic-calendar-revive,target=/workspace", + "extensions": [ + "github.vscode-pull-request-github", + "esbenp.prettier-vscode", + "yzhang.markdown-all-in-one" + ], + "settings": { + "files.eol": "\n", + "editor.tabSize": 2, + "terminal.integrated.shell.linux": "/bin/zsh", + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "files.trimTrailingWhitespace": true + } +} diff --git a/README.md b/README.md index 2b1dfe4d..ba379d4f 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Atomic Calendar Revive v1.2.0 +# Atomic Calendar Revive v1.3.0 An advanced calendar card for Home Assistant with Lovelace. (fixed to work with HA v106+) **If you already have Atomic Calendar Revive installed, please check the Releases Change Log before upgrading, there have been some breaking changes since I took on this development.** @@ -14,37 +14,50 @@ An advanced calendar card for Home Assistant with Lovelace. (fixed to work with [![Discord][discord-shield]][discord] -## Support +## Documentation -Hey dude! Help me out for a couple of :beers: or a :coffee:! +All documentation is now maintained [here](https://marksie1988.github.io/atomic-calendar-revive) + +## Contributions & Support + +Contributions & Support is welcomed. [![coffee](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/marksie1988) ## About -Atomic Calendar Revive is an updated version of the card originally created by atomic7777. +Atomic Calendar Revive is an updated version of the card originally created by atomic7777. -This calendar card includes advanced settings that allow much more flexibility than other cards. +This calendar card includes advanced settings that allow much more flexibility than other cards. It loads calendar events from Home Assistant - Google calendar component. It contains two types of views: `Events mode` and `Calendar mode`. You can switch or select the default one. +New features that are only in Revive: +- Added more appealing UI Look +- Added the ability to display calendar names +- Added keyword whitelists +- Added linkTarget option to allow selecting specific action when clicking link +- Added ability to disable Event and Location Links +- Added first version of Lovelace UI Editor (not all options are in yet) +- Merged PR that: Hides finished events, sorts events by start time, shows currently running events. allows limiting total number of events +- Resolved issue with 106 showing error: `Cannot assign to read only property '0' of object '[object Array]'` +- Multiple Bug Fixes The most important features: -- NEW - Added first version of Lovelace UI Editor (not all options are in yet) - No need to load external libraries (everything is included) - Custom colors and settings for different calendars, custom font sizes, colors of every text and line -- All translations included, few of the words can be configured in settings +- All translations included, few of the words can be configured in settings - Compatible with all day and multiple day events - Fast switch between both modes, or make one of them default * Event mode: - Shows nearest events, one by one, day by day, time of events in a different way (dates, hours) - Moves today's completed events up and dim them -- Highlights the next event, or show a progress bar -- Shows event location link +- Highlights the next event, or show a progress bar +- Shows event location link - Clicking on the event title will open a new window with Google Calendar (can be disabled) - Clicking on Location will open a window with this location on Google Maps (can be disabled) @@ -55,272 +68,13 @@ The most important features: If you have any suggestions about design or functionality, please let me know by opening an issue -![Preview](https://user-images.githubusercontent.com/11677097/52933554-08d5a780-3354-11e9-87d8-d5d15c4a7c7a.png) -![Preview](https://user-images.githubusercontent.com/11677097/52933319-3ff78900-3353-11e9-8c9b-09a315b840a0.png) -![Preview](https://user-images.githubusercontent.com/11677097/53302875-b6205200-3863-11e9-8ab2-5ec95b0799d0.png) - -## Options - -### Main Options - -| Name | Type | Default | Since | Description | -|------|:----:|:-------:|:-----:|-------------| -| type | string | **required** | v0.3.0 | `custom:atomic-calendar` -| entities | list | **required** | v0.3.0 | One or more calendars, configured in HA [Google Calendar component](https://www.home-assistant.io/components/calendar.google/) -| name | string | optional | v0.12.0 | Card name. -| showColors | boolean | optional | v0.3.0 | `true` Show colors in events, configured in entities list -| maxDaysToShow | integer | optional | v0.3.0 | `7` Maximum number of days to show; if set to zero will only display currently running events -| maxEventCount | integer | optional | v0.9.0 | `0` Maximum number of events to show; zero removes any limitation -| showLocation | boolean | optional | v0.3.0 | `true` Show location link (right side) -| showMonth | boolean | optional | v0.3.0 | `false` Show month under day (left side) -| 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 card name -| 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. -| showDescription | boolean | optional | v0.8.4 | `false` Shows long description of event from Google Calendar. -| showNoEventsForToday | boolean | optional | v0.8.6 | `false` Shows `No events for today` if no events, instead of omit the entry. -| sortByStartTime | boolean | optional | v0.9.0 | `false` Sort events by start time first instead of grouping them by calendar. -| disableEventLink | boolean | optional | v0.10.0 | `false` disables links in event title. -| disableLocationLink | boolean | optional | v0.10.0 | `false` disables links in event location. -| linkTarget | string | optional | v0.11.0 | `_blank` Allows custom target for links, default will open new tab. - -### Event Options - -| Name | Type | Since | Description | -|------|:----:|:-----:|-------------| -| showCurrentEventLine | boolean | v0.3.0 | `false` Show line before next event. Don't enable when showProgressBar is true - will look bad -| eventBarColor | string | v0.3.0 | `default color` Color of line showing next event -| showProgressBar | boolean | optional | v0.5.5 | `true` Show event progress with moving icon. Don't enable when showCurrentEventLine - will look bad -| progressBarColor | string | v0.5.5 | `default color` Color of progress bar - -### Entity options (configurations for each calendar) - -| Name | Type | Since | Description | -|------|:----:|:-----:|-------------| -| type | string | optional | v0.5.5 | `null` Type of calendar (in calendar mode) Icon1, Icon2, Icon3, Birthday. Explained below. -| blacklist | string | v0.7.9 | `null` List of comma separated blacklisted keywords. Events containing any of them will not be shown. -| whitelist | string | v1.1.0 | `null` List of comma separated whitelisted keywords. Only events containing any of them will be shown. -| eventCalName | string | v1.2.0 | `null` Add a calendar name to be shown with event. - -### Calendar Mode settings -| Name | Type | Since | Description | -|------|:----:|:-----:|-------------| -| enableModeChange | boolean | v0.7.0 | `false` Set true to enable mode change (Calendar/Events) must have "name" set to toggle -| defaultMode | integer | v0.7.0 | `1` Set `1` to make Events default mode, set `2` to make Calendar mode default -| firstDayOfWeek | integer | v0.7.0 | `1` First day of week, default 1 for Monday -| CalEventHolidayColor | string | v0.7.0 | `red` Color of day for `type: holiday` calendar -| CalEventIcon1 | string | v0.7.0 | `mdi:gift` Icon for `type: icon1` calendar -| CalEventIcon1Color | string | v0.7.0 | `default` Color of icon for `type: icon1` calendar -| CalEventIcon1Filter | string | v0.7.0 | `null` List of comma separated keywords -| CalEventIcon2 | string | v0.7.0 | `mdi:home` Icon for `type: icon2` calendar -| CalEventIcon2Color | string | v0.7.0 | `default` Color of icon for `type: icon2` calendar -| CalEventIcon2Filter | string | v0.7.0 | `null` List of comma separated keywords -| CalEventIcon3 | string | v0.7.0 | `mdi:star` Icon for `type: icon3` calendar -| CalEventIcon3Color | string | v0.7.0 | `default` Color of icon for `type: icon3` calendar -| CalEventIcon3Filter | string | v0.7.0 | `null` List of comma separated keywords -| showLastCalendarWeek | boolean | v0.7.5 | `true` If true (default) it will always show 6 lines. If false, the 6th line won't be displayed if not needed. - -### Text colors and fonts - -If you don't set colors, default theme colors will be used. If you use automatic night/day modes, don't use manual color settings. - -| Name | Type | Since | Description | -|------|:----:|:-----:|-------------| -| dateColor | string | v0.3.0 | `default text color` Color of date (left side) -| dateSize | integer | v0.3.0 | `90` Date text size (percent of default font) -| timeColor | string | v0.3.0 | `default color` Color of time (under the event title) -| timeSize | integer | v0.3.0 | `90` Time text size (percent of default font) -| titleColor | string | v0.3.0 | `default text color` Color of event title -| titleSize | integer | v0.3.0 | `100` Event title text size (percent of default font) -| locationLinkColor | string | v0.3.0 | `default text color` Color of location link (right side) -| locationTextSize | integer | v0.3.0 | `90` Location text size (percent of default font) -| locationIconColor | string | v0.3.0 | `rgb(230, 124, 115)` Color of location icon -| hideFinishedEvents | boolean | v0.9.0 | `false` Don't display finished events -| dimFinishedEvents | boolean | v0.3.0 | `true` Apply filters to finished events (configured below) -| finishedEventOpacity | float | v0.3.0 | `0.6` Opacity level of finished events -| finishedEventOpacity | string | v0.3.0 | `grayscale(100%)` additional css filter to of finished events (default - greyscale) -| dayWrapperLineColor | string | v0.3.0 | `default text color` Color of line - days separate -| descColor | string | v0.8.4 | `default text color` Description of date (left side) -| descSize | integer | v0.8.4 | `80` Description text size (percent of default font) -| eventCalNameColor | string | v1.2.0 | `default text color` color of `eventCalName` if set -| eventCalNameSize | integer | v1.2.0 | `90` text size of `eventCalName` if set (percent of default font) - -### Language Options - -Week / month names are translated automatically - -| Name | Type | Default | Since | Description | -|------|:----:|:-------:|:-----:|-------------| -| language | string | optional | v0.8.4 | `default` Force language change. For example `pt-br`. If not set, default HA language is used. -| untilText | string | optional | v0.3.0 | `Until` Custom translation of `Until` text -| fullDayEventText | string | optional | v0.3.0 | `All day` Custom translation of `All day` text -| noEventsForNextDaysText | string | optional | v0.8.6 | `No events in the next days` Custom translation of `No events in the next days` text -| noEventsForTodayText | string | optional | v0.8.6 | `No events for today` Custom translation of `No events for today` text, only if `showNoEventsForToday` is true -| 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 - 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 - -## Installation - -### Manual -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-revive.js` file from the `dist` directory to `/www/community/atomic_calendar_revive/atomic-calendar-revive.js` directory in your Home Assistant - [latest release](https://github.com/marksie1988/atomic_calendar_revive/releases/latest) -3. Add this reference to your `ui-lovelace.yaml` file: - ```yaml - resources: - - url: /community_plugin/atomic_calendar_revive/atomic-calendar-revive.js - type: module - ``` -4. If you use Lovelace and want to use the editor, download the atomic-calendar-revive-editor.js to /www/community/atomic_calendar/. (or the folder you used above) -5. Add the card to the UI using the GUI editor or to `ui-lovelace.yaml` with options, examples below -6. If you are upgrading, try to reload your browser cache by pressing ctrl-shift-r or shift-F5. -7. If you want to use `Calendar mode` follow the guide [here](#more-than-5), because by default HA only gets the 5 nearest events from Google Calendar. - - -### HACS (recommended) -Install using `HACS` component: -1. You need HACS installed and configured -2. Go to Community tab, Settings -3. Paste this line into `Add custom repository` field: -``` -marksie1988/atomic_calendar -``` -4. Choose type: Plugin -5. The atomic calendar revive component will be available to install under the Plugins tab. -6. Add the below to the raw configuration editor or to the `ui-lovelace.yaml` file : -```yaml -resources: - - url: /community_plugin/atomic_calendar_revive/atomic-calendar-revive.js - type: module -``` - -## How to show more than 5 events {: #more-than-5 } -In order to increase the amount of events that are shown you have to add `max_results` setting to `google_calendars.yaml` file - -For calendar mode we recommend that this is set to at least 30 -```yaml -- cal_id: xxxxxxxxxxxxxxxxxxxx@group.calendar.google.com - entities: - - device_id: calendar_id - name: Calendar_name - max_results: 30 -``` - -## Configuration - -### Simple configuration: -```yaml -- type: "custom:atomic-calendar-revive" - entities: - - entity: calendar.YOUR_CALENDAR_HERE - titleColor: red - whitelist: 'word1,word2' - - entity: calendar.YOUR_CALENDAR1_HERE - blacklist: 'word1, word2' -``` - -### Advanced config with all options, colors changed and progress bar enabled: -```yaml -- type: "custom:atomic-calendar-revive" - name: "Calendar" - entities: - - entity: calendar.YOUR_CALENDARS_HERE - fullDayEventText: 'All day' - untilText: 'Until' - showColors: true - maxDaysToShow: 7 - showLocation: true - showMonth: false - showCurrentEventLine: false - dateColor: black - dateSize: 90 - timeColor: blue - timeSize: 90 - titleColor: black - titleSize: 100 - locationIconColor: 'rgb(230, 124, 115)' - locationLinkColor: black - locationTextSize: 90 - dimFinishedEvents: true - finishedEventOpacity: 0.6 - finishedEventFilter: 'grayscale(100%)' - dayWrapperLineColor: black - eventBarColor: blue - showProgressBar: true - progressBarColor: blue -``` - -### Simple configuration, both Events mode and Calendar mode, calendar is default: -```yaml -- type: "custom:atomic-calendar-revive" - name: "Calendar" - enableModeChange: true - defaultMode: 2 - CalEventIcon1Filter: birthday - CalEventIcon2Filter: waste,bills - entities: - - entity: calendar.YOUR_CALENDAR_HERE - type: icon2 - - entity: calendar.YOUR_CALENDAR1_HERE - type: icon1,icon2 - - entity: calendar.YOUR_CALENDAR2_HERE - type: holiday - -``` - -### Calendar Mode -The second mode of view - calendar mode - is to show full month calendar with simple events icons or colors, for most important, infrequent events, like holiday or birthday. -You can change mode by clicking the card name, or even make it the default view. -To make it work correctly you need to get more events than default 5 - you need to follow instruction [here](#more-than-5) for this, and setup it for 20-30 events at least. - -There are four configurable possibilities for showing events occurring any day: -- day number color - for example "14" will be red for Valentine's Day -- Icon1 - will show any mdi icon under date, like birthday (default: gift icon) -- Icon2 - like above, just any other type of event (default: home icon) -- Icon3 - like above (default: star icon) - -If you want to use any calendar's events, you have to add one or more of types: - -```yaml -CalEventIcon1Filter: bills,waste # only events with those words will be shown -CalEventIcon2Filter: cleaning # only events with those words will be shown -entities: -- entity: calendar.YOUR_CALENDAR_HERE # no type, it won't be shown in calendar mode -- type: holiday # events from this calendar will be red - entity: calendar.YOUR_CALENDAR1_HERE -- type: icon1,icon2 # will show icon1 and icon2, but with filters configured above - entity: calendar.home_events -- type: icon3 # icon1 has no filters, show all events from this calendar - entity: calendar.birthday -``` -```yaml -entities: -- entity: calendar.YOUR_CALENDAR_HERE - type: holiday # events from this calendar will be red -- entity: calendar.YOUR_CALENDAR1_HERE - type: icon2,icon3 # will show icon2 and icon3, but with filters configured below -- entity: calendar.birthday - type: icon1 # Icon1 has no filters, show all events from this calendar -- entity: calendar.YOUR_CALENDAR2_HERE # no type, it won't be shown in calendar mode -CalEventIcon1Filter: bills,waste # only events with those words will be shown -CalEventIcon2Filter: cleaning # only events with those words will be shown -``` - -If you set filters (keywords) for any type, it will show an icon only when event summary contains one of keywords. If you don't set any filter, it will show icons for all days with any events. - -## Development - -I welcome anyone to improve on this module, any amendments should be made to the files inside the `src` directory and then rollup should be run. - -If you do not have rollup please note this in your pull request and I will happily do this prior to merging the request. - - - -[commits-shield]: https://img.shields.io/github/commit-activity/y/marksie1988/atomic_calendar_revive?color=32cd32&style=for-the-badge -[commits]: https://github.com/marksie1988/atomic_calendar_revive/commits/master +[commits-shield]: https://img.shields.io/github/commit-activity/y/marksie1988/atomic-calendar-revive?color=32cd32&style=for-the-badge +[commits]: https://github.com/marksie1988/atomic-calendar-revive/commits/master [discord]: https://discord.gg/8JYbyCQ [discord-shield]: https://img.shields.io/discord/250606775361994754?color=32cd32&style=for-the-badge -[license-shield]: https://img.shields.io/github/license/marksie1988/atomic_calendar_revive?color=32cd32&style=for-the-badge +[license-shield]: https://img.shields.io/github/license/marksie1988/atomic-calendar-revive?color=32cd32&style=for-the-badge [maintenance-shield]: https://img.shields.io/maintenance/yes/2020?color=32cd32&style=for-the-badge -[releases-shield]: https://img.shields.io/github/v/release/marksie1988/atomic_calendar_revive?color=32cd32&style=for-the-badge -[releases]: https://github.com/marksie1988/atomic_calendar_revive/releases -[issues-shield]: https://img.shields.io/github/issues/marksie1988/atomic_calendar_revive?color=32cd32&style=for-the-badge -[issues]: https://github.com/marksie1988/atomic_calendar_revive/issues +[releases-shield]: https://img.shields.io/github/v/release/marksie1988/atomic-calendar-revive?color=32cd32&style=for-the-badge +[releases]: https://github.com/marksie1988/atomic-calendar-revive/releases +[issues-shield]: https://img.shields.io/github/issues/marksie1988/atomic-calendar-revive?color=32cd32&style=for-the-badge +[issues]: https://github.com/marksie1988/atomic-calendar-revive/issues diff --git a/dist/atomic-calendar-revive.js b/dist/atomic-calendar-revive.js index cb093794..02009885 100644 --- a/dist/atomic-calendar-revive.js +++ b/dist/atomic-calendar-revive.js @@ -1,4 +1,4 @@ -function _templateObject39(){const data=_taggedTemplateLiteral(["\n\t\t\t
{this.showLoader=!0,this.isUpdating=!0;try{this.events=await this.getEvents()}catch(e){console.log(e),this.errorMessage="The calendar can't be loaded from Home Assistant component",this.showLoader=!1}this.lastEventsUpdateTime=Q(),this.updateEventsHTML(this.events),this.isUpdating=!1,this.showLoader=!1})(),1==this.modeToggle?this.updateEventsHTML(this.events):this.updateCalendarHTML(),P(_templateObject3(),this.setStyle(),this._config.name?P(_templateObject4(),()=>this.handleToggle(),this._config.name):"",this.showLoader&&this._config.showLoader?P(_templateObject5()):"",this._config.showDate?this.getDate():null,this.content)}firstTimeConfig(){}handleToggle(){this._config.enableModeChange&&(this.modeToggle=1==this.modeToggle?2:1,this.requestUpdate())}setStyle(){return P(_templateObject6(),this._config.eventBarColor,this._config.eventCalNameColor,this._config.eventCalNameSize,this._config.progressBarColor,this._config.progressBarColor)}getDate(){const e=Q().format(this._config.dateFormat);return P(_templateObject7(),e)}setConfig(e){if(!(e=JSON.parse(JSON.stringify(e))).entities)throw new Error("Please define atomic-calendar-revive card entity");this._config={fullDayEventText:"All day",untilText:"Until",language:"",showColors:!0,maxDaysToShow:7,maxEventCount:0,showLoader:!0,showLocation:!0,showMonth:!1,fullTextTime:!0,showCurrentEventLine:!1,showDate:!1,dateFormat:"LL",hoursFormat:"default",startDaysAhead:0,showLastCalendarWeek:!0,showCalNameInEvent:!1,sortByStartTime:!1,disableEventLink:!1,disableLocationLink:!1,linkTarget:"_blank",dateColor:"var(--primary-text-color)",dateSize:90,descColor:"var(--primary-text-color)",descSize:80,showNoEventsForToday:!1,noEventsForTodayText:"No events for today",noEventsForNextDaysText:"No events in the next days",timeColor:"var(--primary-color)",timeSize:90,showHours:!0,titleColor:"var(--primary-text-color)",titleSize:100,locationIconColor:"rgb(230, 124, 115)",locationLinkColor:"var(--primary-text-color)",locationTextSize:90,hideFinishedEvents:!1,dimFinishedEvents:!0,finishedEventOpacity:.6,finishedEventFilter:"grayscale(100%)",dayWrapperLineColor:"var(--primary-text-color)",eventBarColor:"var(--primary-color)",eventCalNameColor:"var(--primary-text-color)",eventCalNameSize:90,showProgressBar:!0,progressBarColor:"var(--primary-color)",enableModeChange:!1,defaultMode:1,CalEventBackgroundColor:"#ededed",CalEventBackgroundFilter:null,CalEventHolidayColor:"red",CalEventHolidayFilter:null,CalEventIcon1:"mdi:gift",CalEventIcon1Color:"var(--primary-text-color)",CalEventIcon1Filter:null,CalEventIcon2:"mdi:home",CalEventIcon2Color:"var(--primary-text-color)",CalEventIcon2Filter:null,CalEventIcon3:"mdi:star",CalEventIcon3Color:"var(--primary-text-color)",CalEventIcon3Filter:null,firstDayOfWeek:1,blacklist:null,whitelist:null,...e},this.modeToggle=this._config.defaultMode,"string"==typeof this._config.entities&&(this._config.entities=[{entity:e.entities}]),this._config.entities.forEach((e,t)=>{"string"==typeof e&&(this._config.entities[t]={entity:e})})}shouldUpdate(e){return function(e,t){if(t.has("_config"))return!0;const a=t.get("hass");return!a||a.states[e._config.entity]!==e.hass.states[e._config.entity]}(this,e)}getCardSize(){return this._config.entities.length+1}_toggle(e){this.hass.callService("homeassistant","toggle",{entity_id:e.entity_id})}getTitleHTML(e){const t=this._config.showCalNameInEvent?e.eventClass.organizer.displayName+": "+e.title:e.title,a=this._config.showColors&&void 0!==e._config.titleColor?e._config.titleColor:this._config.titleColor;return this._config.disableEventLink||null===e.link?P(_templateObject8(),this._config.titleSize,a,t):P(_templateObject9(),e.link,this._config.linkTarget,this._config.titleSize,a,t)}getHoursHTML(e){const t=Q();return e.isEmpty?P(_templateObject10()):e.isFullOneDayEvent||e.isFullMoreDaysEvent&&Q(e.endTime).isSame(t,"day")?P(_templateObject11(),this._config.fullDayEventText):e.isFullMoreDaysEvent||e.isFullMoreDaysEvent&&(Q(e.startTime).isBefore(t,"day")||Q(e.endTime).isAfter(t,"day"))?P(_templateObject12(),this._config.fullDayEventText,this._config.untilText.toLowerCase(),this.getCurrDayAndMonth(Q(e.endTime))):Q(e.startTime).isBefore(t,"day")&&Q(e.endTime).isAfter(t,"day")?P(_templateObject13(),this._config.untilText,this.getCurrDayAndMonth(Q(e.endTime))):Q(e.startTime).isBefore(t,"day")&&Q(e.endTime).isSame(t,"day")?P(_templateObject14(),this._config.untilText,e.endTime.format("LT")):!Q(e.startTime).isBefore(t,"day")&&Q(e.endTime).isAfter(e.startTime,"day")?P(_templateObject15(),e.startTime.format("LT"),this._config.untilText.toLowerCase(),this.getCurrDayAndMonth(Q(e.endTime))):P(_templateObject16(),e.startTime.format("LT"),e.endTime.format("LT"))}getLocationHTML(e){return e.location&&this._config.showLocation?this._config.disableLocationLink?P(_templateObject17(),this._config.locationIconColor,e.address):P(_templateObject18(),e.location,this._config.linkTarget,this._config.locationLinkColor,this._config.locationTextSize,this._config.locationIconColor,e.address):P(_templateObject19())}updateEventsHTML(e){var t;if(!e)this.content=P(_templateObject27(),this.errorMessage);else if(0!=e.length){if(Q(e[0][0]).isSame(Q(),"day")&&1e.map((e,a,s)=>{const n=0==a&&0 !!RegExp("(?:^|\\s)"+t.trim(),"i").test(e))}async getEvents(){let e=-Q().utcOffset(),t=Q().add(this._config.startDaysAhead,"days").startOf("day").add(e,"minutes").format("YYYY-MM-DDTHH:mm:ss"),a=Q().add(this._config.maxDaysToShow+this._config.startDaysAhead,"days").endOf("day").add(e,"minutes").format("YYYY-MM-DDTHH:mm:ss"),s=[];this._config.entities.map(e=>{s.push(["calendars/".concat(e.entity,"?start=").concat(t,"Z&end=").concat(a,"Z")])});try{return await Promise.all(s.map(e=>this.hass.callApi("get",e[0]))).then(e=>{let t=[],a=0;e.map((e,s)=>{e.map(e=>{let n=void 0===this._config.entities[s].blacklist?"":this._config.entities[s].blacklist,i=void 0===this._config.entities[s].whitelist?"":this._config.entities[s].whitelist,r=(void 0!==this._config.entities[s].eventCalName&&this._config.entities[s].eventCalName,new ee(e,this._config.entities[s]));!(0===this._config.maxEventCount||a {void 0!==e.type&&d.push(["calendars/".concat(e.entity,"?start=").concat(i,"Z&end=").concat(r,"Z"),e.type,void 0===e.blacklist?"":e.blacklist,void 0===e.whitelist?"":e.whitelist])}),Promise.all(d.map(e=>this.hass.callApi("get",e[0]))).then(e=>{a==this.monthToGet&&e.map((e,t)=>(this.month.map(a=>{const s=d[t][1],n=d[t][0],i=void 0===d[t][2]?"":d[t][2];void 0!==d[t][3]&&d[t][3],e.map(e=>{const t=e.start.dateTime?Q(e.start.dateTime):Q(e.start.date).startOf("day"),r=e.end.dateTime?Q(e.end.dateTime):Q(e.end.date).subtract(1,"days").endOf("day");if(!Q(t).isAfter(a.date,"day")&&!Q(r).isBefore(a.date,"day")&&s&&!this.checkFilter(e.summary,i))try{this.checkFilter("icon1",s)&&(this._config.CalEventIcon1Filter&&!this.checkFilter(e.summary,this._config.CalEventIcon1Filter)||a.icon1.push(e.summary)),this.checkFilter("icon2",s)&&(this._config.CalEventIcon2Filter&&!this.checkFilter(e.summary,this._config.CalEventIcon2Filter)||a.icon2.push(e.summary)),this.checkFilter("icon3",s)&&(this._config.CalEventIcon3Filter&&!this.checkFilter(e.summary,this._config.CalEventIcon3Filter)||a.icon3.push(e.summary)),this.checkFilter("holiday",s)&&a.holiday.push(e.summary)}catch(e){console.log("error: ",e,n)}})}),s)),a==this.monthToGet&&(this.showLoader=!1),this.refreshCalEvents=!1,this.requestUpdate()}).catch(e=>{this.refreshCalEvents=!1,console.log("error: ",e),this.showLoader=!1})}buildCalendar(e){const t=Q(e).startOf("month"),a=t.day();this.month=[];let s=0;s=0<=a-this._config.firstDayOfWeek?0:7;for(var n=this._config.firstDayOfWeek-a-s;n<42-a+this._config.firstDayOfWeek-s;n++)this.month.push(new X(Q(t).add(n,"days"),n))}handleMonthChange(e){this.selectedMonth=Q(this.selectedMonth).add(e,"months"),this.monthToGet=this.selectedMonth.format("M"),this.eventSummary=P(_templateObject28()),this.refreshCalEvents=!0}handleEventSummary(e){let t=[","].concat.apply([],[e.holiday,e.daybackground,e.icon1,e.icon2,e.icon3]).join(", ");""==t&&(t=P(_templateObject29())),this.eventSummary=P(_templateObject30(),t),this.requestUpdate()}getCalendarHeaderHTML(){return P(_templateObject31(),()=>this.handleMonthChange(-1),Q(this.selectedMonth).format("YYYY"),Q(this.selectedMonth).format("MM"),this._config.titleColor,this._config.linkTarget,Q(this.selectedMonth).locale(this.language).format("MMMM"),Q(this.selectedMonth).format("YYYY"),()=>this.handleMonthChange(1))}getCalendarDaysHTML(e){var t=!0;return this._config.showLastCalendarWeek||Q(e[35].date).isSame(this.selectedMonth,"month")||(t=!1),e.map((e,a)=>{const s=Q(e.date).isSame(Q(this.selectedMonth),"month")?"":"opacity: .35;",n=Q(e.date).isSame(Q(),"day")?"border: 1px solid grey;":"border: 1px solid grey; border-color: transparent;",i=e.holiday&&0 a||t)return P(_templateObject35(),0==a%7?P(_templateObject36()):"",()=>this.handleEventSummary(e),this._config.titleColor,s,n,i,r,e.dayNumber.replace(/^0|[^/]0./,""),d,o,_,a&&0==a%6?P(_templateObject37()):"")})}updateCalendarHTML(){(0==this.month.length||this.refreshCalEvents||120 P(_templateObject38(),this._config.titleColor,e));this.content=P(_templateObject39(),this.getCalendarHeaderHTML(),this._config.titleColor,t,this.getCalendarDaysHTML(e),this.eventSummary)}});class X{constructor(e,t){this.calendarDay=e,this._lp=t,this.ymd=Q(e).format("YYYY-MM-DD"),this._holiday=[],this._icon1=[],this._icon2=[],this._icon3=[],this._daybackground=[]}get date(){return Q(this.calendarDay)}get dayNumber(){return Q(this.calendarDay).format("DD")}get monthNumber(){return Q(this.calendarDay).month()}set holiday(e){this._holiday=e}get holiday(){return this._holiday}set icon1(e){this._icon1=e}get icon1(){return this._icon1}set icon2(e){this._icon2=e}get icon2(){return this._icon2}set icon3(e){this._icon3=e}get icon3(){return this._icon3}set daybackground(e){this._daybackground=e}get daybackground(){return this._daybackground}}class ee{constructor(e,t){this.eventClass=e,this._config=t,this._startTime=this.eventClass.start.dateTime?Q(this.eventClass.start.dateTime):Q(this.eventClass.start.date).startOf("day"),this._endTime=this.eventClass.end.dateTime?Q(this.eventClass.end.dateTime):Q(this.eventClass.end.date).subtract(1,"days").endOf("day"),this.isFinished=!1,this.isEmpty=!1}get titleColor(){return this._config.titleColor?this._config.titleColor:"var(--primary-text-color)"}get title(){return this.eventClass.summary}get description(){return this.eventClass.description}get startTime(){return this._startTime}get startTimeToShow(){var e=this.eventClass.start.dateTime?Q(this.eventClass.start.dateTime):Q(this.eventClass.start.date).startOf("day");return Q(e).isBefore(Q().startOf("day"))?Q().startOf("day"):e}get endTime(){return this._endTime}get isFullDayEvent(){return!this.eventClass.start.dateTime&&!this.eventClass.end.dateTime}get isFullOneDayEvent(){return!!(!this.eventClass.start.dateTime&&!this.eventClass.end.dateTime&&Q(this.eventClass.start.date).isSame(Q(this.eventClass.end.date).subtract(1,"days"),"day")||Q(this.eventClass.start.dateTime).isSame(Q(this.eventClass.start.dateTime).startOf("day"))&&Q(this.eventClass.end.dateTime).isSame(Q(this.eventClass.end.dateTime).startOf("day"))&&Q(this.eventClass.start.dateTime).isSame(Q(this.eventClass.end.dateTime).subtract(1,"days"),"day"))}get isFullMoreDaysEvent(){return!((this.eventClass.start.dateTime||this.eventClass.end.dateTime||Q(this.eventClass.start.date).isSame(Q(this.eventClass.end.date).subtract(1,"days"),"day"))&&!(Q(this.eventClass.start.dateTime).isSame(Q(this.eventClass.start.dateTime).startOf("day"))&&Q(this.eventClass.end.dateTime).isSame(Q(this.eventClass.end.dateTime).startOf("day"))&&Q(this.eventClass.end.dateTime).isAfter(Q(this.eventClass.start.dateTime).subtract(1,"days"),"day")))}get daysToSort(){return Q(this.startTimeToShow).format("YYYYMMDD")}get isEventRunning(){return Q(this.startTime).isBefore(Q())&&Q(this.endTime).isAfter(Q())}get isEventFinished(){return Q(this.endTime).isBefore(Q())}get location(){return this.eventClass.location?this.eventClass.location.split(" ").join("+"):""}get address(){return this.eventClass.location?this.eventClass.location.split(",")[0]:""}get link(){return this.eventClass.htmlLink}} +e.defineLocale("zh-tw",{months:"\u4E00\u6708_\u4E8C\u6708_\u4E09\u6708_\u56DB\u6708_\u4E94\u6708_\u516D\u6708_\u4E03\u6708_\u516B\u6708_\u4E5D\u6708_\u5341\u6708_\u5341\u4E00\u6708_\u5341\u4E8C\u6708".split("_"),monthsShort:"1\u6708_2\u6708_3\u6708_4\u6708_5\u6708_6\u6708_7\u6708_8\u6708_9\u6708_10\u6708_11\u6708_12\u6708".split("_"),weekdays:"\u661F\u671F\u65E5_\u661F\u671F\u4E00_\u661F\u671F\u4E8C_\u661F\u671F\u4E09_\u661F\u671F\u56DB_\u661F\u671F\u4E94_\u661F\u671F\u516D".split("_"),weekdaysShort:"\u9031\u65E5_\u9031\u4E00_\u9031\u4E8C_\u9031\u4E09_\u9031\u56DB_\u9031\u4E94_\u9031\u516D".split("_"),weekdaysMin:"\u65E5_\u4E00_\u4E8C_\u4E09_\u56DB_\u4E94_\u516D".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY\u5E74M\u6708D\u65E5",LLL:"YYYY\u5E74M\u6708D\u65E5 HH:mm",LLLL:"YYYY\u5E74M\u6708D\u65E5dddd HH:mm",l:"YYYY/M/D",ll:"YYYY\u5E74M\u6708D\u65E5",lll:"YYYY\u5E74M\u6708D\u65E5 HH:mm",llll:"YYYY\u5E74M\u6708D\u65E5dddd HH:mm"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(e,t){return 12===e&&(e=0),"\u51CC\u6668"===t||"\u65E9\u4E0A"===t||"\u4E0A\u5348"===t?e:"\u4E2D\u5348"===t?11<=e?e:e+12:"\u4E0B\u5348"===t||"\u665A\u4E0A"===t?e+12:void 0},meridiem:function(e,t){var s=100*e+t;return 600>s?"\u51CC\u6668":900>s?"\u65E9\u4E0A":1130>s?"\u4E0A\u5348":1230>s?"\u4E2D\u5348":1800>s?"\u4E0B\u5348":"\u665A\u4E0A"},calendar:{sameDay:"[\u4ECA\u5929] LT",nextDay:"[\u660E\u5929] LT",nextWeek:"[\u4E0B]dddd LT",lastDay:"[\u6628\u5929] LT",lastWeek:"[\u4E0A]dddd LT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(日|月|週)/,ordinal:function(e,t){return"d"===t||"D"===t||"DDD"===t?e+"\u65E5":"M"===t?e+"\u6708":"w"===t||"W"===t?e+"\u9031":e},relativeTime:{future:"%s\u5167",past:"%s\u524D",s:"\u5E7E\u79D2",ss:"%d \u79D2",m:"1 \u5206\u9418",mm:"%d \u5206\u9418",h:"1 \u5C0F\u6642",hh:"%d \u5C0F\u6642",d:"1 \u5929",dd:"%d \u5929",M:"1 \u500B\u6708",MM:"%d \u500B\u6708",y:"1 \u5E74",yy:"%d \u5E74"}}),e.locale("en")})(K)}),customElements.define("atomic-calendar-revive",class extends B{constructor(){super(),this.lastCalendarUpdateTime,this.lastEventsUpdateTime,this.lastHTMLUpdateTime,this.events,this.content=W(_templateObject()),this.shouldUpdateHtml=!0,this.errorMessage="",this.modeToggle=0,this.selectedMonth=K(),this.refreshCalEvents=null,this.monthToGet=K().format("MM"),this.month=[],this.showLoader=!1,this.eventSummary=W(_templateObject2()),this.firstrun=!0,this.language=""}static get properties(){return{hass:{},_config:{},content:{},selectedMonth:{}}}static async getConfigElement(){return await import("./atomic-calendar-revive-editor.js"),document.createElement("atomic-calendar-revive-editor")}updated(){}render(){this.firstrun&&console.info("%c atomic-calendar-revive %c Version: 1.3.0-dev3 ","color: white; background: #484848; font-weight: 700;","color: white; background: #cc5500; font-weight: 700;"),this.language=""==this._config.language?this.hass.language.toLowerCase():this._config.language;let e=K.localeData(this.language).longDateFormat("LT");return"12h"==this._config.hoursFormat?e="h:mm A":"24h"==this._config.hoursFormat?e="H:mm":"default"!=this._config.hoursFormat&&(e=this._config.hoursFormat),K.updateLocale(this.language,{week:{dow:this._config.firstDayOfWeek},longDateFormat:{LT:e}}),this.firstrun=!1,this.isUpdating||1!=this.modeToggle||(!this.lastEventsUpdateTime||15{this.showLoader=!0,this.isUpdating=!0;try{this.events=await this.getEvents()}catch(e){console.log(e),this.errorMessage="The calendar can't be loaded from Home Assistant component",this.showLoader=!1}this.lastEventsUpdateTime=K(),this.updateEventsHTML(this.events),this.isUpdating=!1,this.showLoader=!1})(),1==this.modeToggle?this.updateEventsHTML(this.events):this.updateCalendarHTML(),W(_templateObject3(),this.setStyle(),this._config.name?W(_templateObject4(),()=>this.handleToggle(),this._config.name):"",this.showLoader&&this._config.showLoader?W(_templateObject5()):"",this._config.showDate?this.getDate():null,this.content)}firstTimeConfig(){}handleToggle(){this._config.enableModeChange&&(this.modeToggle=1==this.modeToggle?2:1,this.requestUpdate())}setStyle(){return W(_templateObject6(),this._config.eventBarColor,this._config.eventCalNameColor,this._config.eventCalNameSize,this._config.progressBarColor,this._config.progressBarColor,this._config.CalGridColor)}getDate(){const e=K().format(this._config.dateFormat);return W(_templateObject7(),e)}setConfig(e){if(!(e=JSON.parse(JSON.stringify(e))).entities)throw new Error("Please define atomic-calendar-revive card entity");this._config={fullDayEventText:"All day",untilText:"Until",language:"",showColors:!0,maxDaysToShow:7,maxEventCount:0,showLoader:!0,showLocation:!0,showMonth:!1,showWeekDay:!1,fullTextTime:!0,showCurrentEventLine:!1,showDate:!1,dateFormat:"LL",hoursFormat:"default",startDaysAhead:0,showLastCalendarWeek:!1,showCalNameInEvent:!1,sortByStartTime:!1,disableEventLink:!1,disableLocationLink:!1,linkTarget:"_blank",dateColor:"var(--primary-text-color)",dateSize:90,descColor:"var(--primary-text-color)",descSize:80,showNoEventsForToday:!1,noEventsForTodayText:"No events for today",noEventsForNextDaysText:"No events in the next days",timeColor:"var(--primary-color)",timeSize:90,showHours:!0,titleColor:"var(--primary-text-color)",titleSize:100,locationIconColor:"rgb(230, 124, 115)",locationLinkColor:"var(--primary-text-color)",locationTextSize:90,hideFinishedEvents:!1,dimFinishedEvents:!0,finishedEventOpacity:.6,finishedEventFilter:"grayscale(100%)",dayWrapperLineColor:"var(--primary-text-color)",eventBarColor:"var(--primary-color)",eventCalNameColor:"var(--primary-text-color)",eventCalNameSize:90,showProgressBar:!0,progressBarColor:"var(--primary-color)",enableModeChange:!1,defaultMode:1,CalGridColor:"rgba(86, 86, 86, .35)",calEventBulletColor:"#cc5500",CalEventBackgroundColor:"rgba(86, 100, 86, .35)",CalEventBackgroundFilter:null,CalEventWeekendColor:"rgba(86, 86, 86, .60)",CalEventHolidayColor:"red",CalEventHolidayFilter:null,CalEventIcon1:"mdi:gift",CalEventIcon1Color:"var(--primary-text-color)",CalEventIcon1Filter:null,CalEventIcon2:"mdi:home",CalEventIcon2Color:"var(--primary-text-color)",CalEventIcon2Filter:null,CalEventIcon3:"mdi:star",CalEventIcon3Color:"var(--primary-text-color)",CalEventIcon3Filter:null,firstDayOfWeek:1,blacklist:null,whitelist:null,...e},this.modeToggle=this._config.defaultMode,"string"==typeof this._config.entities&&(this._config.entities=[{entity:e.entities}]),this._config.entities.forEach((e,t)=>{"string"==typeof e&&(this._config.entities[t]={entity:e})})}shouldUpdate(e){return function(e,t){if(t.has("_config"))return!0;const a=t.get("hass");return!a||a.states[e._config.entity]!==e.hass.states[e._config.entity]}(this,e)}getCardSize(){return this._config.entities.length+1}_toggle(e){this.hass.callService("homeassistant","toggle",{entity_id:e.entity_id})}getTitleHTML(e){const t=this._config.showCalNameInEvent?e.eventClass.organizer.displayName+": "+e.title:e.title,a=this._config.showColors&&void 0!==e._config.titleColor?e._config.titleColor:this._config.titleColor;return this._config.disableEventLink||null===e.link?W(_templateObject8(),this._config.titleSize,a,t):W(_templateObject9(),e.link,this._config.linkTarget,this._config.titleSize,a,t)}getHoursHTML(e){const t=K();return e.isEmpty?W(_templateObject10()):e.isFullOneDayEvent||e.isFullMoreDaysEvent&&K(e.endTime).isSame(t,"day")?W(_templateObject11(),this._config.fullDayEventText):e.isFullMoreDaysEvent||e.isFullMoreDaysEvent&&(K(e.startTime).isBefore(t,"day")||K(e.endTime).isAfter(t,"day"))?W(_templateObject12(),this._config.fullDayEventText,this._config.untilText.toLowerCase(),this.getCurrDayAndMonth(K(e.endTime))):K(e.startTime).isBefore(t,"day")&&K(e.endTime).isAfter(t,"day")?W(_templateObject13(),this._config.untilText,this.getCurrDayAndMonth(K(e.endTime))):K(e.startTime).isBefore(t,"day")&&K(e.endTime).isSame(t,"day")?W(_templateObject14(),this._config.untilText,e.endTime.format("LT")):!K(e.startTime).isBefore(t,"day")&&K(e.endTime).isAfter(e.startTime,"day")?W(_templateObject15(),e.startTime.format("LT"),this._config.untilText.toLowerCase(),this.getCurrDayAndMonth(K(e.endTime))):W(_templateObject16(),e.startTime.format("LT"),e.endTime.format("LT"))}getLocationHTML(e){return e.location&&this._config.showLocation?this._config.disableLocationLink?W(_templateObject17(),this._config.locationIconColor,e.address):W(_templateObject18(),e.location,this._config.linkTarget,this._config.locationLinkColor,this._config.locationTextSize,this._config.locationIconColor,e.address):W(_templateObject19())}updateEventsHTML(e){var t;if(!e)this.content=W(_templateObject27(),this.errorMessage);else if(0!=e.length){if(K(e[0][0]).isSame(K(),"day")&&1 e.map((e,a,s)=>{const n=0==a&&0 !!RegExp("(?:^|\\s)"+t.trim(),"i").test(e))}async getEvents(){let e=-K().utcOffset(),t=K().add(this._config.startDaysAhead,"days").startOf("day").add(e,"minutes").format("YYYY-MM-DDTHH:mm:ss"),a=K().add(this._config.maxDaysToShow+this._config.startDaysAhead,"days").endOf("day").add(e,"minutes").format("YYYY-MM-DDTHH:mm:ss"),s=[];this._config.entities.map(e=>{s.push(["calendars/".concat(e.entity,"?start=").concat(t,"Z&end=").concat(a,"Z")])});try{return await Promise.all(s.map(e=>this.hass.callApi("get",e[0]))).then(e=>{let t=[],a=0;e.map((e,s)=>{e.map(e=>{let n=void 0===this._config.entities[s].blacklist?"":this._config.entities[s].blacklist,i=void 0===this._config.entities[s].whitelist?"":this._config.entities[s].whitelist,r=(void 0!==this._config.entities[s].eventCalName&&this._config.entities[s].eventCalName,new X(e,this._config.entities[s]));!(0===this._config.maxEventCount||a {void 0!==e.type&&d.push(["calendars/".concat(e.entity,"?start=").concat(i,"Z&end=").concat(r,"Z"),e.type,void 0===e.blacklist?"":e.blacklist,void 0===e.whitelist?"":e.whitelist])}),Promise.all(d.map(e=>this.hass.callApi("get",e[0]))).then(e=>{a==this.monthToGet&&e.map((e,t)=>(this.month.map(a=>{const s=d[t][1],n=d[t][0],i=void 0===d[t][2]?"":d[t][2];void 0!==d[t][3]&&d[t][3],e.map(e=>{const t=e.start.dateTime?K(e.start.dateTime):K(e.start.date).startOf("day"),r=e.end.dateTime?K(e.end.dateTime):K(e.end.date).subtract(1,"days").endOf("day");if(!K(t).isAfter(a.date,"day")&&!K(r).isBefore(a.date,"day")&&s&&!this.checkFilter(e.summary,i))try{this.checkFilter("icon1",s)&&(this._config.CalEventIcon1Filter&&!this.checkFilter(e.summary,this._config.CalEventIcon1Filter)||a.icon1.push(e.summary)),this.checkFilter("icon2",s)&&(this._config.CalEventIcon2Filter&&!this.checkFilter(e.summary,this._config.CalEventIcon2Filter)||a.icon2.push(e.summary)),this.checkFilter("icon3",s)&&(this._config.CalEventIcon3Filter&&!this.checkFilter(e.summary,this._config.CalEventIcon3Filter)||a.icon3.push(e.summary)),this.checkFilter("holiday",s)&&a.holiday.push(e.summary)}catch(e){console.log("error: ",e,n)}})}),s)),a==this.monthToGet&&(this.showLoader=!1),this.refreshCalEvents=!1,this.requestUpdate()}).catch(e=>{this.refreshCalEvents=!1,console.log("error: ",e),this.showLoader=!1})}buildCalendar(e){const t=K(e).startOf("month"),a=t.day();this.month=[];let s=0;s=0<=a-this._config.firstDayOfWeek?0:7;for(var n=this._config.firstDayOfWeek-a-s;n<42-a+this._config.firstDayOfWeek-s;n++)this.month.push(new Q(K(t).add(n,"days"),n))}handleMonthChange(e){this.selectedMonth=K(this.selectedMonth).add(e,"months"),this.monthToGet=this.selectedMonth.format("M"),this.eventSummary=W(_templateObject28()),this.refreshCalEvents=!0}handleEventSummary(e){let t=[","].concat.apply([],[e.holiday,e.daybackground,e.icon1,e.icon2,e.icon3]);this.eventSummary=t.map(e=>W(_templateObject29(),this._config.calEventBulletColor,e)),this.requestUpdate()}getCalendarHeaderHTML(){return W(_templateObject30(),()=>this.handleMonthChange(-1),K(this.selectedMonth).format("YYYY"),K(this.selectedMonth).format("MM"),this._config.titleColor,this._config.linkTarget,K(this.selectedMonth).locale(this.language).format("MMMM"),K(this.selectedMonth).format("YYYY"),()=>this.handleMonthChange(1))}getCalendarDaysHTML(e){var t=!0;return this._config.showLastCalendarWeek||K(e[35].date).isSame(this.selectedMonth,"month")||(t=!1),e.map((e,a)=>{const s=K(e.date).isSame(K(this.selectedMonth),"month")?"":"opacity: .35;",n=K(e.date).isSame(K(),"day")?"background-color: ".concat(this._config.CalEventBackgroundColor,";"):"",i=e.holiday&&0 a||t)return W(_templateObject34(),0==a%7?W(_templateObject35()):"",()=>this.handleEventSummary(e),this._config.titleColor,s,n,i,d,r,e.dayNumber.replace(/^0|[^/]0./,""),o,_,m,a&&0==a%6?W(_templateObject36()):"")})}updateCalendarHTML(){(0==this.month.length||this.refreshCalEvents||120 W(_templateObject37(),this._config.titleColor,e));this.content=W(_templateObject38(),this.getCalendarHeaderHTML(),this._config.titleColor,t,this.getCalendarDaysHTML(e),this.eventSummary)}});class Q{constructor(e,t){this.calendarDay=e,this._lp=t,this.ymd=K(e).format("YYYY-MM-DD"),this._holiday=[],this._icon1=[],this._icon2=[],this._icon3=[],this._daybackground=[]}get date(){return K(this.calendarDay)}get dayNumber(){return K(this.calendarDay).format("DD")}get monthNumber(){return K(this.calendarDay).month()}set holiday(e){this._holiday=e}get holiday(){return this._holiday}set icon1(e){this._icon1=e}get icon1(){return this._icon1}set icon2(e){this._icon2=e}get icon2(){return this._icon2}set icon3(e){this._icon3=e}get icon3(){return this._icon3}set daybackground(e){this._daybackground=e}get daybackground(){return this._daybackground}}class X{constructor(e,t){this.eventClass=e,this._config=t,this._startTime=this.eventClass.start.dateTime?K(this.eventClass.start.dateTime):K(this.eventClass.start.date).startOf("day"),this._endTime=this.eventClass.end.dateTime?K(this.eventClass.end.dateTime):K(this.eventClass.end.date).subtract(1,"days").endOf("day"),this.isFinished=!1,this.isEmpty=!1}get titleColor(){return this._config.titleColor?this._config.titleColor:"var(--primary-text-color)"}get title(){return this.eventClass.summary}get description(){return this.eventClass.description}get startTime(){return this._startTime}get startTimeToShow(){var e=this.eventClass.start.dateTime?K(this.eventClass.start.dateTime):K(this.eventClass.start.date).startOf("day");return K(e).isBefore(K().startOf("day"))?K().startOf("day"):e}get endTime(){return this._endTime}get isFullDayEvent(){return!this.eventClass.start.dateTime&&!this.eventClass.end.dateTime}get isFullOneDayEvent(){return!!(!this.eventClass.start.dateTime&&!this.eventClass.end.dateTime&&K(this.eventClass.start.date).isSame(K(this.eventClass.end.date).subtract(1,"days"),"day")||K(this.eventClass.start.dateTime).isSame(K(this.eventClass.start.dateTime).startOf("day"))&&K(this.eventClass.end.dateTime).isSame(K(this.eventClass.end.dateTime).startOf("day"))&&K(this.eventClass.start.dateTime).isSame(K(this.eventClass.end.dateTime).subtract(1,"days"),"day"))}get isFullMoreDaysEvent(){return!((this.eventClass.start.dateTime||this.eventClass.end.dateTime||K(this.eventClass.start.date).isSame(K(this.eventClass.end.date).subtract(1,"days"),"day"))&&!(K(this.eventClass.start.dateTime).isSame(K(this.eventClass.start.dateTime).startOf("day"))&&K(this.eventClass.end.dateTime).isSame(K(this.eventClass.end.dateTime).startOf("day"))&&K(this.eventClass.end.dateTime).isAfter(K(this.eventClass.start.dateTime).subtract(1,"days"),"day")))}get daysToSort(){return K(this.startTimeToShow).format("YYYYMMDD")}get isEventRunning(){return K(this.startTime).isBefore(K())&&K(this.endTime).isAfter(K())}get isEventFinished(){return K(this.endTime).isBefore(K())}get location(){return this.eventClass.location?this.eventClass.location.split(" ").join("+"):""}get address(){return this.eventClass.location?this.eventClass.location.split(",")[0]:""}get link(){return this.eventClass.htmlLink}} diff --git a/docs/404.md b/docs/404.md new file mode 100644 index 00000000..b992d66e --- /dev/null +++ b/docs/404.md @@ -0,0 +1,11 @@ +--- +layout: default +title: Page not found +permalink: /404.html +nav_exclude: true +search_exclude: true +--- + + 404
+ +Page not found
diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 00000000..4dd7d4b0 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,16 @@ +remote_theme: pmarsceill/just-the-docs + +title: Atomic Calendar Revive +description: A Home Assistant Plugin to add a nice calendar view + +# Specific to this theme +search_enabled: true +search_tokenizer_separator: /[\s/]+/ +ga_tracking: UA-149869416-2 +heading_anchors: true +color_scheme: "dark" +footer_content: "Copyright © 2017-2020 Steven Marks. Distributed by an MIT license." +# Aux links for the upper right navigation +aux_links: + "Atomic Calendar Revive on GitHub": + - "//github.com/marksie1988/atomic-calendar-revive" diff --git a/docs/about.md b/docs/about.md new file mode 100644 index 00000000..21c1997b --- /dev/null +++ b/docs/about.md @@ -0,0 +1,46 @@ +--- +layout: default +title: About +nav_order: 4 +--- + +Atomic Calendar Revive is an updated version of the card originally created by atomic7777. + +This calendar card includes advanced settings that allow much more flexibility than other cards. + +It loads calendar events from Home Assistant - Google calendar component. + +It contains two types of views: `Events mode` and `Calendar mode`. You can switch or select the default one. + +New features that are only in Revive: +- Added more appealing UI Look +- Added the ability to display calendar names +- Added keyword whitelists +- Added linkTarget option to allow selecting specific action when clicking link +- Added ability to disable Event and Location Links +- Added first version of Lovelace UI Editor (not all options are in yet) +- Merged PR that: Hides finished events, sorts events by start time, shows currently running events. allows limiting total number of events +- Resolved issue with 106 showing error: `Cannot assign to read only property '0' of object '[object Array]'` +- Multiple Bug Fixes + +The most important features: +- No need to load external libraries (everything is included) +- Custom colors and settings for different calendars, custom font sizes, colors of every text and line +- All translations included, few of the words can be configured in settings +- Compatible with all day and multiple day events +- Fast switch between both modes, or make one of them default + +* Event mode: +- Shows nearest events, one by one, day by day, time of events in a different way (dates, hours) +- Moves today's completed events up and dim them +- Highlights the next event, or show a progress bar +- Shows event location link +- Clicking on the event title will open a new window with Google Calendar (can be disabled) +- Clicking on Location will open a window with this location on Google Maps (can be disabled) + +* Calendar mode: +- Show a traditional calendar (a table with 42 days) with configurable events icons like holiday, birthday +- Quick overview of the following months +- You can set keywords to show only important things, like birthday + +If you have any suggestions about design or functionality, please let me know by opening an issue diff --git a/docs/assets/img/screenshots/basic_config_calendar_example.png b/docs/assets/img/screenshots/basic_config_calendar_example.png new file mode 100644 index 00000000..77d9e35a Binary files /dev/null and b/docs/assets/img/screenshots/basic_config_calendar_example.png differ diff --git a/docs/assets/img/screenshots/basic_config_example.png b/docs/assets/img/screenshots/basic_config_example.png new file mode 100644 index 00000000..d2a032ee Binary files /dev/null and b/docs/assets/img/screenshots/basic_config_example.png differ diff --git a/docs/assets/img/screenshots/basic_config_name_example.png b/docs/assets/img/screenshots/basic_config_name_example.png new file mode 100644 index 00000000..a6649af0 Binary files /dev/null and b/docs/assets/img/screenshots/basic_config_name_example.png differ diff --git a/docs/assets/search-data.json b/docs/assets/search-data.json new file mode 100644 index 00000000..0532f7c6 --- /dev/null +++ b/docs/assets/search-data.json @@ -0,0 +1,12 @@ +--- +--- +{ + {% assign comma = false %} + {% for page in site.html_pages %}{% if page.search_exclude != true %}{% if comma == true%},{% endif %}"{{ forloop.index0 }}": { + "title": "{{ page.title | replace: '&', '&' }}", + "content": "{{ page.content | markdownify | replace: ' 15) (async() => { @@ -110,14 +106,14 @@ class AtomicCalendarRevive extends LitElement { this.updateCalendarHTML(); return html ` - + ${this.setStyle()} ${this._config.name ? html` --this.handleToggle()}'> +this.handleToggle()}'> ${this._config.name}` @@ -130,17 +126,15 @@ class AtomicCalendarRevive extends LitElement { ${(this._config.showDate) ? this.getDate() : null}+${this.content}` } - + firstTimeConfig() { - - + } - handleToggle() { if (this._config.enableModeChange) { @@ -149,7 +143,6 @@ class AtomicCalendarRevive extends LitElement { } } - setStyle() { return html ` - ` } - + getDate() { const date=moment().format(this._config.dateFormat) return html`${date}` @@ -429,13 +412,14 @@ class AtomicCalendarRevive extends LitElement { showLocation: true, // show location (right side) showMonth: false, // show month under day (left side) + showWeekDay: false, // show day name under day (left side) fullTextTime: true, // show advanced time messages, like: All day, until Friday 12 showCurrentEventLine: false, // show a line between last and next event 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. - showLastCalendarWeek: true, // always shows last line/week in calendar mode, even if it's not the current month + showLastCalendarWeek: false, // always shows last line/week in calendar mode, even if it's not the current month showCalNameInEvent: false, sortByStartTime: false, // sort first by calendar, then by time disableEventLink: false, // disables links to event calendar @@ -469,7 +453,7 @@ class AtomicCalendarRevive extends LitElement { hideFinishedEvents: false, // show finished events dimFinishedEvents: true, // make finished events greyed out or set opacity finishedEventOpacity: 0.6, // opacity level - finishedEventFilter: 'grayscale(100%)', // css filter + finishedEventFilter: 'grayscale(100%)', // css filter // days separating dayWrapperLineColor: 'var(--primary-text-color)', // days separating line color @@ -484,9 +468,15 @@ class AtomicCalendarRevive extends LitElement { enableModeChange: false, defaultMode: 1, - CalEventBackgroundColor: '#ededed', + CalGridColor: 'rgba(86, 86, 86, .35)', + + calEventBulletColor: '#cc5500', + + CalEventBackgroundColor: 'rgba(86, 100, 86, .35)', CalEventBackgroundFilter: null, + CalEventWeekendColor: 'rgba(86, 86, 86, .60)', + CalEventHolidayColor: 'red', CalEventHolidayFilter: null, @@ -521,7 +511,7 @@ class AtomicCalendarRevive extends LitElement { entity: entity }; }); - + } shouldUpdate(changedProps) { @@ -542,7 +532,7 @@ class AtomicCalendarRevive extends LitElement { /** * generate Event Title (summary) HTML - * + * */ getTitleHTML(event) { const titletext = (this._config.showCalNameInEvent) ? event.eventClass.organizer.displayName+": " + event.title : event.title @@ -556,9 +546,9 @@ class AtomicCalendarRevive extends LitElement { ` } - /** + /** * generate Hours HTML - * + * */ getHoursHTML(event) { const today = moment() @@ -590,14 +580,14 @@ class AtomicCalendarRevive extends LitElement { /** * generate Event Location link HTML - * + * */ getLocationHTML(event) { if (!event.location || !this._config.showLocation) return html `` else if (this._config.disableLocationLink) return html `- ` + ` else return html ` ` @@ -605,7 +595,7 @@ class AtomicCalendarRevive extends LitElement { /** * update Events main HTML - * + * */ updateEventsHTML(days) { var htmlDays = '' @@ -613,14 +603,14 @@ class AtomicCalendarRevive extends LitElement { if (!days) { // TODO some more tests end error message this.content = html `${this.errorMessage}` return - } - + } + // TODO write something if no events if (days.length == 0) { this.content = this._config.noEventsForNextDaysText return } - + // move today's finished events up if (moment(days[0][0]).isSame(moment(), "day") && days[0].length > 1) { @@ -633,7 +623,7 @@ class AtomicCalendarRevive extends LitElement { i++ } } - + // check if no events for today and push a "no events" fake event if (this._config.showNoEventsForToday && moment(days[0][0].startTime).isAfter(moment(), "day") && days[0].length > 0) { var emptyEv = { @@ -647,11 +637,11 @@ class AtomicCalendarRevive extends LitElement { }; var emptyEvent = new EventClass(emptyEv , '') emptyEvent.isEmpty = true - var d = [] + var d = [] d[0]=emptyEvent days.unshift(d) } - + //loop through days htmlDays = days.map((day, di) => { @@ -683,12 +673,12 @@ class AtomicCalendarRevive extends LitElement { const lastEventStyle = i == arr.length - 1 ? 'padding-bottom: 8px;' : '' return html ` - +${event.address} ${(i===0 && this._config.showMonth) ? event.startTimeToShow.format('MMM') : ''}${i===0 ? event.startTimeToShow.format('DD') : ''}-${i===0 ? event.startTimeToShow.format('ddd') : ''}+${(i===0 && this._config.showWeekDay) ? event.startTimeToShow.format('ddd') : ''}@@ -716,7 +706,7 @@ class AtomicCalendarRevive extends LitElement { }) return htmlEvents - }) + }) this.content = html ` ${currentEventLine}@@ -708,7 +698,7 @@ class AtomicCalendarRevive extends LitElement {${eventCalName}- + ${progressBar}${htmlDays}
` } @@ -761,14 +751,14 @@ class AtomicCalendarRevive extends LitElement { /** * gets events from HA Calendar to Events mode - * + * */ async getEvents() { let timeOffset = -moment().utcOffset() - let start = moment().add(this._config.startDaysAhead, 'days').startOf('day').add(timeOffset,'minutes').format('YYYY-MM-DDTHH:mm:ss'); + 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 =>{ calendarUrlList.push([`calendars/${entity.entity}?start=${start}Z&end=${end}Z`]) @@ -790,7 +780,7 @@ class AtomicCalendarRevive extends LitElement { } }) }) - + if (this._config.sortByStartTime) { singleEvents.sort(function(a,b) { return moment(a.startTime).diff(moment(b.startTime)); @@ -820,7 +810,7 @@ class AtomicCalendarRevive extends LitElement { /** * gets events from HA to Calendar mode - * + * */ getCalendarEvents(startDay, endDay, monthToGet, month) { this.refreshCalEvents = false @@ -835,7 +825,7 @@ class AtomicCalendarRevive extends LitElement { typeof entity.blacklist!= 'undefined' ? entity.blacklist : '', typeof entity.whitelist!= 'undefined' ? entity.whitelist : '' ]) - } + } }) Promise.all(calendarUrlList.map(url => @@ -909,7 +899,7 @@ class AtomicCalendarRevive extends LitElement { /** * change month in calendar mode - * + * */ handleMonthChange(i) { this.selectedMonth = moment(this.selectedMonth).add(i, 'months'); @@ -920,27 +910,35 @@ class AtomicCalendarRevive extends LitElement { /** * show events summary under the calendar - * + * */ handleEventSummary(day) { - let events = ([','].concat.apply([], [day.holiday, day.daybackground, day.icon1, day.icon2, day.icon3])).join(', ') - if (events == '') events = html ` ` - this.eventSummary = html `${events}` + let events = ([','].concat.apply([], [day.holiday, day.daybackground, day.icon1, day.icon2, day.icon3])) + var eventsHtm = ''; + + this.eventSummary = events.map((eventItem, i, arr)=> { + return html ` ++ + ${eventItem} +` + }) + this.requestUpdate() } /** * create html calendar header - * + * */ getCalendarHeaderHTML() { return html`this.handleMonthChange(-1)}' title="left"> -+this.handleMonthChange(1)}' title="right"> @@ -950,7 +948,7 @@ class AtomicCalendarRevive extends LitElement { /** * create html cells for all days of calendar - * + * */ getCalendarDaysHTML(month) { var showLastRow = true @@ -958,36 +956,36 @@ class AtomicCalendarRevive extends LitElement { return month.map((day, i) => { const dayStyleOtherMonth = moment(day.date).isSame(moment(this.selectedMonth), 'month') ? '' : `opacity: .35;` - const dayStyleToday = moment(day.date).isSame(moment(), 'day') ? `border: 1px solid grey;` : `border: 1px solid grey; border-color: transparent;` + const dayStyleToday = moment(day.date).isSame(moment(), 'day') ? `background-color: ${this._config.CalEventBackgroundColor};` : `` const dayHolidayStyle = (day.holiday && day.holiday.length > 0) ? `color: ${this._config.CalEventHolidayColor}; ` : '' const dayBackgroundStyle = (day.daybackground && day.daybackground.length > 0) ? `background-color: ${this._config.CalEventBackgroundColor}; ` : '' - const dayIcon1 = (day.icon1 && day.icon1.length > 0) ? html `` : '' + const dayStyleWeekend = (moment(day.date).isoWeekday() == 6 || moment(day.date).isoWeekday() == 7) ? `background-color: ${this._config.CalEventWeekendColor}; ` : '' + const dayIcon1 = (day.icon1 && day.icon1.length > 0) ? html` ` : '' const dayIcon2 = (day.icon2 && day.icon2.length > 0) ? html ` ` : '' const dayIcon3 = (day.icon3 && day.icon3.length > 0) ? html ` ` : '' + console.log(moment(day.date).isoWeekday()) if(i<35 || showLastRow) - return html ` - ${i % 7 === 0 ? html` ` :''} - - this.handleEventSummary(day)}' class="calDay" style=" color: ${this._config.titleColor}; ${dayStyleOtherMonth} ${dayStyleToday} ${dayHolidayStyle} ${dayBackgroundStyle}"> -+ return html ` + ${i % 7 === 0 ? html`` : ''} + ` :''} ` - + }) } /** * update Calendar mode HTML - * + * */ updateCalendarHTML() { if (this.month.length == 0 || this.refreshCalEvents || moment().diff(this.lastCalendarUpdateTime, 'minutes') > 120) { @@ -999,6 +997,7 @@ class AtomicCalendarRevive extends LitElement { } const month = this.month var weekDays = moment.weekdaysMin(true) + const htmlDayNames = weekDays.map((day) => html `this.handleEventSummary(day)}' class="cal" style="color: ${this._config.titleColor};${dayStyleOtherMonth}${dayStyleToday}${dayHolidayStyle}${dayStyleWeekend}${dayBackgroundStyle}"> + ${i && (i % 6 === 0) ? html`+- +${(day.dayNumber).replace(/^0|[^/]0./, '')} --- ${dayIcon1} ${dayIcon2} ${dayIcon3} -+ ${dayIcon1} ${dayIcon2} ${dayIcon3} +${day} `) @@ -1017,11 +1016,11 @@ class AtomicCalendarRevive extends LitElement {- ${this.eventSummary} + ${this.eventSummary}` } - + } customElements.define('atomic-calendar-revive', AtomicCalendarRevive); @@ -1029,7 +1028,7 @@ customElements.define('atomic-calendar-revive', AtomicCalendarRevive); /** * class for 42 calendar days - * + * */ class CalendarDay { constructor(calendarDay, d) { @@ -1099,7 +1098,7 @@ class CalendarDay { /** * class for Events in events mode - * + * */ class EventClass { diff --git a/tracker.json b/tracker.json index 1b8db5cc..0a0172ed 100755 --- a/tracker.json +++ b/tracker.json @@ -1,9 +1,9 @@ { "atomic-calendar-revive": { "updated_at": "2020-04-07", - "version": "1.1.1", - "remote_location": "https://github.com/marksie1988/atomic_calendar_revive/releases/latest/dist/atomic-calendar-revive.js", - "visit_repo": "https://github.com/marksie1988/atomic_calendar_revive", - "changelog": "https://github.com/marksie1988/atomic_calendar_revive/releases/latest" + "version": "1.3.0", + "remote_location": "https://github.com/marksie1988/atomic-calendar-revive/releases/latest/dist/atomic-calendar-revive.js", + "visit_repo": "https://github.com/marksie1988/atomic-calendar-revive", + "changelog": "https://github.com/marksie1988/atomic-calendar-revive/releases/latest" } }