From cc376a8be1ab770cc37b0bb90638b21cc13e4e67 Mon Sep 17 00:00:00 2001 From: Dermot Duffy Date: Thu, 6 Feb 2025 19:32:32 -0800 Subject: [PATCH] feat: Rename the card to `advanced-camera-card` (#1873) Whilst the Frigate support in this card is the best among camera engines, the name incorrectly suggests that Frigate is a requirement. Instead, to broaden the appeal, change to more camera agnostic name. This does not suggest any change in priority, role or support for Frigate. This change is likely to be bug prone, due to the size of the rename -- the code contains 1500+ references to "Frigate" most of which make sense to rename, some which do not, all of which needed human assessment. - Closes #1298 BREAKING CHANGE: References to `frigate-card` in all kinds of configuration need to be updated to `advanced-camera-card`. An automated config upgrade should take care of the majority of usecases (click `Edit -> Upgrade -> Save`), though may not be perfect. --- .devcontainer/preconfig/.storage/lovelace | 6 +- .../preconfig/.storage/lovelace_resources | 2 +- .github/ISSUE_TEMPLATE/bug_report.md | 4 +- .github/workflows/build.yml | 4 +- .github/workflows/manual-release.yml | 4 +- .github/workflows/semantic-release.yml | 2 +- README.md | 19 +- docs/README.md | 10 +- docs/_coverpage.md | 10 +- docs/advanced-installation.md | 10 +- docs/common/rename-hacs.md | 1 + docs/configuration/README.md | 2 +- docs/configuration/actions/README.md | 10 +- docs/configuration/actions/custom/README.md | 548 ++++---- docs/configuration/automations.md | 8 +- docs/configuration/cameras/live-provider.md | 16 +- docs/configuration/elements/README.md | 2 +- docs/configuration/elements/custom/README.md | 56 +- docs/configuration/elements/stock/README.md | 2 +- docs/configuration/image.md | 16 +- docs/configuration/menu.md | 38 +- docs/configuration/profiles.md | 4 +- docs/configuration/view.md | 40 +- docs/developing.md | 14 +- docs/examples.md | 241 ++-- docs/images/advanced-camera-card.png | Bin 0 -> 252956 bytes docs/index.html | 17 +- docs/rolling-back.md | 2 + docs/support.md | 4 +- docs/troubleshooting.md | 12 +- docs/usage/2-way-audio.md | 2 +- docs/usage/casting.md | 12 +- docs/usage/url-actions.md | 24 +- hacs.json | 4 +- package.json | 15 +- rollup.config.js | 42 +- src/action-handler-directive.ts | 22 +- src/camera-manager/error.ts | 6 +- src/camera-manager/frigate/requests.ts | 8 +- src/camera-manager/manager.ts | 8 +- src/camera-manager/reolink/engine-reolink.ts | 2 +- src/camera-manager/store.ts | 2 +- src/camera-manager/types.ts | 4 +- .../actions/actions-manager.ts | 10 +- src/card-controller/actions/actions/base.ts | 6 +- .../actions/actions/camera-select.ts | 4 +- .../actions/actions/camera-ui.ts | 4 +- .../actions/actions/default.ts | 4 +- .../actions/actions/display-mode-select.ts | 4 +- .../actions/actions/download.ts | 4 +- src/card-controller/actions/actions/expand.ts | 4 +- .../actions/actions/fullscreen.ts | 4 +- .../actions/actions/generic.ts | 2 +- src/card-controller/actions/actions/log.ts | 4 +- .../actions/actions/media-player.ts | 4 +- .../actions/actions/menu-toggle.ts | 4 +- .../actions/actions/microphone-connect.ts | 4 +- .../actions/actions/microphone-disconnect.ts | 4 +- .../actions/actions/microphone-mute.ts | 4 +- .../actions/actions/microphone-unmute.ts | 4 +- src/card-controller/actions/actions/mute.ts | 4 +- src/card-controller/actions/actions/pause.ts | 4 +- src/card-controller/actions/actions/play.ts | 4 +- .../actions/actions/ptz-controls.ts | 4 +- .../actions/actions/ptz-digital.ts | 4 +- .../actions/actions/ptz-multi.ts | 4 +- src/card-controller/actions/actions/ptz.ts | 4 +- .../actions/actions/screenshot.ts | 4 +- src/card-controller/actions/actions/sleep.ts | 4 +- .../actions/actions/status-bar.ts | 4 +- .../actions/actions/substream-off.ts | 4 +- .../actions/actions/substream-on.ts | 4 +- .../actions/actions/substream-select.ts | 4 +- src/card-controller/actions/actions/unmute.ts | 4 +- src/card-controller/actions/actions/view.ts | 6 +- src/card-controller/actions/factory.ts | 70 +- .../actions/utils/execution-request.ts | 8 +- src/card-controller/automations-manager.ts | 2 +- src/card-controller/card-element-manager.ts | 6 +- src/card-controller/conditions-manager.ts | 35 +- src/card-controller/config/config-manager.ts | 30 +- src/card-controller/controller.ts | 8 +- src/card-controller/default-manager.ts | 4 +- .../fullscreen/webkit/index.ts | 2 +- src/card-controller/initialization-manager.ts | 6 +- src/card-controller/media-info-manager.ts | 2 +- src/card-controller/media-player-manager.ts | 8 +- src/card-controller/message-manager.ts | 4 +- src/card-controller/microphone-manager.ts | 2 +- src/card-controller/query-string-manager.ts | 26 +- .../status-bar-item-manager.ts | 10 +- src/card-controller/style-manager.ts | 22 +- src/card-controller/view/factory.ts | 26 +- src/card-controller/view/types.ts | 10 +- src/card-controller/view/view-manager.ts | 13 +- .../view/view-query-executor.ts | 2 +- src/card.ts | 121 +- src/components-lib/live/live-controller.ts | 18 +- .../live/utils/dispatch-live-error.ts | 4 +- .../media-actions-controller.ts | 12 +- src/components-lib/media-grid-controller.ts | 18 +- src/components-lib/menu-button-controller.ts | 185 +-- src/components-lib/menu-controller.ts | 9 +- src/components-lib/message/dispatch.ts | 26 +- src/components-lib/status-bar-controller.ts | 12 +- src/components-lib/timeline-source.ts | 24 +- src/components-lib/zoom/zoom-controller.ts | 18 +- src/components/carousel.ts | 6 +- src/components/date-picker.ts | 14 +- src/components/diagnostics.ts | 10 +- src/components/drawer.ts | 12 +- src/components/elements.ts | 146 ++- src/components/gallery.ts | 42 +- src/components/icon.ts | 6 +- src/components/image-base.ts | 17 +- src/components/image.ts | 23 +- src/components/key-assigner.ts | 12 +- src/components/live/carousel.ts | 56 +- src/components/live/grid.ts | 19 +- src/components/live/index.ts | 10 +- src/components/live/provider.ts | 76 +- .../live/providers/go2rtc/README.md | 2 +- src/components/live/providers/go2rtc/index.ts | 15 +- .../live/providers/go2rtc/video-rtc.d.ts | 2 +- .../live/providers/go2rtc/video-rtc.js | 10 +- src/components/live/providers/ha.ts | 17 +- src/components/live/providers/image.ts | 17 +- src/components/live/providers/jsmpeg.ts | 11 +- src/components/live/providers/webrtc-card.ts | 24 +- src/components/loading.ts | 10 +- src/components/media-filter.ts | 86 +- src/components/media-grid.ts | 6 +- src/components/menu.ts | 46 +- src/components/message.ts | 14 +- src/components/next-prev-control.ts | 16 +- src/components/overlay.ts | 6 +- src/components/progress-indicator.ts | 20 +- src/components/ptz.ts | 16 +- src/components/select.ts | 8 +- src/components/status-bar.ts | 32 +- src/components/submenu.ts | 43 +- src/components/surround-basic.ts | 38 +- src/components/surround.ts | 32 +- src/components/thumbnail-carousel.ts | 27 +- src/components/thumbnail.ts | 110 +- src/components/timeline-core.ts | 82 +- src/components/timeline.ts | 10 +- src/components/viewer/carousel.ts | 50 +- src/components/viewer/grid.ts | 19 +- src/components/viewer/index.ts | 10 +- src/components/viewer/provider.ts | 69 +- src/components/views.ts | 50 +- src/components/zoomer.ts | 14 +- src/config/management.ts | 292 +++-- src/config/profiles/index.ts | 16 +- src/config/profiles/low-performance.ts | 6 +- src/config/types.ts | 203 +-- src/const.ts | 16 +- src/editor.ts | 84 +- src/images/README.md | 15 +- src/images/frigate-bird-in-sky.jpg | Bin 18003 -> 0 bytes src/images/iris-screensaver.jpg | Bin 0 -> 30949 bytes src/localize/languages/ca.json | 9 +- src/localize/languages/en.json | 13 +- src/localize/languages/fr.json | 9 +- src/localize/languages/it.json | 9 +- src/localize/languages/pt-BR.json | 9 +- src/localize/languages/pt-PT.json | 9 +- src/localize/localize.ts | 6 +- src/patches/ha-camera-stream.ts | 28 +- src/patches/ha-hls-player.ts | 10 +- src/patches/ha-web-rtc-player.ts | 10 +- src/scss/button.scss | 4 +- src/scss/card.scss | 54 +- src/scss/const.scss | 6 +- src/scss/date-picker.scss | 2 +- src/scss/drawer-inject.scss | 2 +- src/scss/drawer.scss | 14 +- src/scss/editor.scss | 2 +- src/scss/gallery-core.scss | 24 +- src/scss/gallery.scss | 2 +- src/scss/key-assigner.scss | 4 +- src/scss/live-carousel.scss | 2 +- src/scss/live-grid.scss | 12 +- src/scss/live-provider.scss | 2 +- src/scss/loading.scss | 6 +- src/scss/media-filter.scss | 12 +- src/scss/media-grid.scss | 16 +- src/scss/media-layout.scss | 14 +- src/scss/menu.scss | 16 +- src/scss/message.scss | 14 +- src/scss/next-previous-control.scss | 44 +- src/scss/ptz.scss | 20 +- src/scss/status.scss | 6 +- src/scss/submenu.scss | 10 +- src/scss/themes/base.scss | 138 +- src/scss/themes/traditional.scss | 52 +- src/scss/thumbnail-carousel.scss | 16 +- src/scss/thumbnail-details.scss | 2 +- src/scss/thumbnail-feature-text.scss | 8 +- src/scss/thumbnail-feature-thumbnail.scss | 8 +- src/scss/thumbnail.scss | 26 +- src/scss/timeline-core.scss | 56 +- src/scss/viewer-carousel.scss | 4 +- src/scss/viewer-provider.scss | 4 +- src/scss/viewer.scss | 2 +- src/types.ts | 6 +- src/utils/action.ts | 59 +- src/utils/basic.ts | 14 +- src/utils/camera.ts | 4 +- src/utils/diagnostics.ts | 8 +- src/utils/download.ts | 6 +- src/utils/embla/carousel-controller.ts | 16 +- .../auto-media-loaded-info.ts | 34 +- .../embla/plugins/auto-size/auto-size.ts | 12 +- src/utils/ha/registry/entity/types.ts | 2 +- src/utils/ha/ws-request.ts | 12 +- src/utils/media-info.ts | 42 +- src/utils/media-layout.ts | 19 +- src/utils/media.ts | 20 +- src/utils/scroll.ts | 2 +- src/view/view-to-cameras.ts | 4 +- src/view/view.ts | 10 +- .../frigate/engine-frigate.test.ts | 19 +- .../generic/engine-generic.test.ts | 6 +- tests/camera-manager/manager.test.ts | 6 +- .../actions/actions-manager.test.ts | 8 +- .../actions/actions/camera-select.test.ts | 12 +- .../actions/actions/camera-ui.test.ts | 2 +- .../actions/actions/default.test.ts | 2 +- .../actions/display-mode-select.test.ts | 2 +- .../actions/actions/download.test.ts | 2 +- .../actions/actions/expand.test.ts | 2 +- .../actions/actions/fullscreen.test.ts | 2 +- .../actions/actions/log.test.ts | 2 +- .../actions/actions/media-player.test.ts | 8 +- .../actions/actions/menu-toggle.test.ts | 2 +- .../actions/microphone-connect.test.ts | 2 +- .../actions/microphone-disconnect.test.ts | 2 +- .../actions/actions/microphone-mute.test.ts | 2 +- .../actions/actions/microphone-unmute.test.ts | 2 +- .../actions/actions/mute.test.ts | 6 +- .../actions/actions/pause.test.ts | 6 +- .../actions/actions/play.test.ts | 6 +- .../actions/actions/ptz-controls.test.ts | 2 +- .../actions/actions/ptz-digital.test.ts | 16 +- .../actions/actions/ptz-multi.test.ts | 8 +- .../actions/actions/ptz.test.ts | 28 +- .../actions/actions/screenshot.test.ts | 2 +- .../actions/actions/sleep.test.ts | 2 +- .../actions/actions/status-bar.test.ts | 10 +- .../actions/actions/substream-off.test.ts | 2 +- .../actions/actions/substream-on.test.ts | 2 +- .../actions/actions/substream-select.test.ts | 2 +- .../actions/actions/unmute.test.ts | 6 +- .../actions/actions/view.test.ts | 2 +- tests/card-controller/actions/factory.test.ts | 114 +- .../automations-manager.test.ts | 2 +- .../conditions-manager.test.ts | 18 +- .../config/config-manager.test.ts | 42 +- .../config/load-automations.test.ts | 2 +- .../config/load-keyboard-shortcuts.test.ts | 6 +- tests/card-controller/controller.test.ts | 6 +- tests/card-controller/default-manager.test.ts | 2 +- .../card-controller/download-manager.test.ts | 6 +- .../fullscreen/webkit/index.test.ts | 13 +- tests/card-controller/message-manager.test.ts | 8 +- .../query-string-manager.test.ts | 55 +- .../status-bar-item-manager.test.ts | 14 +- tests/card-controller/style-manager.test.ts | 88 +- .../card-controller/triggers-manager.test.ts | 4 +- tests/card-controller/view/factory.test.ts | 4 +- .../view/modifiers/substream-on.test.ts | 6 +- tests/card-controller/view/test-utils.ts | 6 +- .../card-controller/view/view-manager.test.ts | 22 +- .../view/view-query-executor.test.ts | 9 +- .../live/live-controller.test.ts | 4 +- .../live/utils/dispatch-live-error.test.ts | 2 +- .../media-actions-controller.test.ts | 18 +- .../media-grid-controller.test.ts | 41 +- .../menu-button-controller.test.ts | 327 +++-- tests/components-lib/menu-controller.test.ts | 100 +- tests/components-lib/message/dispatch.test.ts | 19 +- .../components-lib/ptz/ptz-controller.test.ts | 12 +- .../status-bar-controller.test.ts | 44 +- .../zoom/zoom-controller.test.ts | 14 +- .../zoom/zoom-view-context.test.ts | 2 +- tests/config/management.test.ts | 1154 ++++++++++------- tests/config/profiles/casting.test.ts | 8 +- tests/config/profiles/index.test.ts | 2 +- tests/config/profiles/low-performance.test.ts | 8 +- tests/config/profiles/scrubbing.test.ts | 8 +- tests/config/types.test.ts | 42 +- tests/test-utils.ts | 40 +- tests/utils/action.test.ts | 52 +- tests/utils/basic.test.ts | 18 +- tests/utils/diagnostics.test.ts | 18 +- tests/utils/embla/carousel-controller.test.ts | 14 +- .../auto-media-loaded-info.test.ts | 8 +- tests/utils/media-info.test.ts | 20 +- tests/utils/media-layout.test.ts | 64 +- tests/utils/media.test.ts | 20 +- tests/utils/ptz.test.ts | 4 +- tests/view/view-to-cameras.test.ts | 6 +- yarn.lock | 164 +-- 305 files changed, 4270 insertions(+), 3686 deletions(-) create mode 100644 docs/common/rename-hacs.md create mode 100644 docs/images/advanced-camera-card.png delete mode 100644 src/images/frigate-bird-in-sky.jpg create mode 100644 src/images/iris-screensaver.jpg diff --git a/.devcontainer/preconfig/.storage/lovelace b/.devcontainer/preconfig/.storage/lovelace index bda880d3..3b8ef284 100644 --- a/.devcontainer/preconfig/.storage/lovelace +++ b/.devcontainer/preconfig/.storage/lovelace @@ -4,7 +4,7 @@ "key": "lovelace", "data": { "config": { - "title": "Frigate Card", + "title": "Advanced Camera Card", "views": [ { "theme": "Backend-selected", @@ -13,8 +13,8 @@ "badges": [], "cards": [ { - "type": "custom:frigate-card", - "name": "Frigate Card with Frigate Camera", + "type": "custom:advanced-camera-card", + "name": "Advanced Camera Card with Frigate Camera", "cameras": [ { "camera_entity": "camera.big_buck_bunny", diff --git a/.devcontainer/preconfig/.storage/lovelace_resources b/.devcontainer/preconfig/.storage/lovelace_resources index 5f9c118e..3c045c82 100644 --- a/.devcontainer/preconfig/.storage/lovelace_resources +++ b/.devcontainer/preconfig/.storage/lovelace_resources @@ -5,7 +5,7 @@ "data": { "items": [ { - "url": "http://localhost:10001/frigate-hass-card.js", + "url": "http://localhost:10001/advanced-camera-card.js", "type": "module", "id": "00c459541bac4290af01c51bf9c06ba1" } diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index fc028421..d43853ed 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -14,13 +14,13 @@ assignees: '' **Checklist:** -- Please try updating to the [latest available version](https://github.com/dermotduffy/frigate-hass-card/releases). +- Please try updating to the [latest available version](https://github.com/dermotduffy/advanced-camera-card/releases). - Please try clearing your browser cache. **[REQUIRED] Card diagnostic information:** ```yaml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a73dc71a..6d4748f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,7 +49,7 @@ jobs: - name: Upload javascript uses: actions/upload-artifact@v4 with: - name: frigate-hass-card + name: advanced-camera-card path: dist/*.js # When this issue is fixed, it would be useful to upload visualizations: @@ -57,5 +57,5 @@ jobs: # - name: Upload visualizations # uses: actions/upload-artifact@v4 # with: - # name: frigate-hass-card + # name: advanced-camera-card # path: visualizations/*.html diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 5489feb2..0649b06d 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -26,7 +26,7 @@ jobs: with: type: zip path: dist - filename: frigate-hass-card.zip + filename: advanced-camera-card.zip - name: Upload JS files to release uses: svenstaro/upload-release-action@2.9.0 @@ -43,7 +43,7 @@ jobs: with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: frigate-hass-card.zip + file: advanced-camera-card.zip tag: ${{ github.ref }} overwrite: true diff --git a/.github/workflows/semantic-release.yml b/.github/workflows/semantic-release.yml index 02248080..3f9d36e1 100644 --- a/.github/workflows/semantic-release.yml +++ b/.github/workflows/semantic-release.yml @@ -50,7 +50,7 @@ jobs: with: type: zip path: dist - filename: frigate-hass-card.zip + filename: advanced-camera-card.zip - run: npx semantic-release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 1a162a66..3fb8b201 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,17 @@ -Frigate icon -[![GitHub Release](https://img.shields.io/github/release/dermotduffy/frigate-hass-card.svg?style=flat-square)](https://github.com/dermotduffy/frigate-hass-card/releases) -[![Build Status](https://img.shields.io/github/actions/workflow/status/dermotduffy/frigate-hass-card/build.yml?style=flat-square)](https://github.com/dermotduffy/frigate-hass-card/actions/workflows/build.yml) -[![License](https://img.shields.io/github/license/dermotduffy/frigate-hass-card.svg?style=flat-square)](LICENSE) +[![GitHub Release](https://img.shields.io/github/release/dermotduffy/advanced-camera-card.svg?style=flat-square)](https://github.com/dermotduffy/advanced-camera-card/releases) +[![Build Status](https://img.shields.io/github/actions/workflow/status/dermotduffy/advanced-camera-card/build.yml?style=flat-square)](https://github.com/dermotduffy/advanced-camera-card/actions/workflows/build.yml) +[![License](https://img.shields.io/github/license/dermotduffy/advanced-camera-card.svg?style=flat-square)](LICENSE) [![HACS](https://img.shields.io/badge/HACS-default-orange.svg?style=flat-square)](https://hacs.xyz) [![Sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86&style=flat-square)](https://github.com/sponsors/dermotduffy) -Frigate Card +Advanced Camera Card -# Frigate Card +# Advanced Camera Card + +Formerly known as: `Frigate Card` A comprehensive camera card for Home Assistant. @@ -31,4 +28,4 @@ A comprehensive camera card for Home Assistant. - Full [Picture Elements](https://www.home-assistant.io/lovelace/picture-elements/) support. - Theme friendly. -See [full documentation](https://dermotduffy.github.io/frigate-hass-card). +See [full documentation](https://dermotduffy.github.io/advanced-camera-card). diff --git a/docs/README.md b/docs/README.md index 532d1e0c..9e544b25 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,6 +10,8 @@ Home Assistant > HACS > Frontend > "Explore & Add Integrations" > Frigate Card ``` +[](./common/rename-hacs.md ':include') + - Click `Download this repository with HACS`. See [Advanced Installation](advanced-installation.md) for other installation resources, or [Rolling Back](./rolling-back.md) to rollback to prior versions. @@ -23,14 +25,14 @@ See [Advanced Installation](advanced-installation.md) for other installation res ``` - Click `+ Add Card` shown on the bottom of the screen -- Choose `Custom: Frigate card` from the list +- Choose `Custom: Advanced Camera Card` from the list ## Initial configuration ### Minimal configuration ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office ``` @@ -38,7 +40,7 @@ cameras: ### Video scrubbing configuration ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office profiles: @@ -48,7 +50,7 @@ profiles: ### Multi-camera grid configuration ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office - camera_entity: camera.kitchen diff --git a/docs/_coverpage.md b/docs/_coverpage.md index c1cb5d64..7c0ab1ce 100644 --- a/docs/_coverpage.md +++ b/docs/_coverpage.md @@ -1,8 +1,10 @@ -![logo](images/icons/frigate.svg) +![logo](images/icons/iris.svg ':size=48px') -# Frigate Card 6.0.0 +# Advanced Camera Card -> A comprehensive camera card for Home Assistant. +> A comprehensive camera card for Home Assistant -[GitHub](https://github.com/dermotduffy/frigate-hass-card/) +FKA: Frigate Card + +[GitHub](https://github.com/dermotduffy/advanced-camera-card/) [Documentation](README.md) diff --git a/docs/advanced-installation.md b/docs/advanced-installation.md index d1946899..e6b91f8b 100644 --- a/docs/advanced-installation.md +++ b/docs/advanced-installation.md @@ -14,7 +14,7 @@ For most users, HACS should automatically add the necessary resources. Should th Three dots menu -> "Edit Dashboard" -> Three dots menu -> "Manage resources" -> "Add Resource" ``` -- URL: `/hacsfiles/frigate-hass-card/frigate-hass-card.js` +- URL: `/hacsfiles/advanced-camera-card/advanced-camera-card.js` - Resource type: `JavaScript Module` #### Lovelace in "YAML mode" (rare) @@ -26,7 +26,7 @@ You would see`mode: yaml` under `lovelace:` in your `configuration.yaml` if this ```yaml lovelace: resources: - - url: /hacsfiles/frigate-hass-card/frigate-hass-card.js + - url: /hacsfiles/advanced-camera-card/advanced-camera-card.js type: module ``` @@ -34,15 +34,15 @@ lovelace: ### Manual installation -- Download the `frigate-hass-card.zip` attachment of the desired [release](https://github.com/dermotduffy/frigate-hass-card/releases) to a location accessible by Home Assistant. Note that the release will have a series of `.js` files (for HACS users) **and** a `frigate-hass-card.zip` for the convenience of manual installers. -- Unzip the file and move the contents of the `dist/` folder to any subfolder name you'd like, e.g. `frigate-card` is used in the below example. +- Download the `advanced-camera-card.zip` attachment of the desired [release](https://github.com/dermotduffy/advanced-camera-card/releases) to a location accessible by Home Assistant. Note that the release will have a series of `.js` files (for HACS users) **and** a `advanced-camera-card.zip` for the convenience of manual installers. +- Unzip the file and move the contents of the `dist/` folder to any subfolder name you'd like, e.g. `advanced-camera-card` is used in the below example. - Add the location as a Lovelace resource via the UI, or via [YAML configuration](https://www.home-assistant.io/lovelace/dashboards/#resources) such as: ```yaml lovelace: mode: yaml resources: - - url: /local/frigate-card/frigate-hass-card.js + - url: /local/advanced-camera-card/advanced-camera-card.js type: module ``` diff --git a/docs/common/rename-hacs.md b/docs/common/rename-hacs.md new file mode 100644 index 00000000..b24c43b3 --- /dev/null +++ b/docs/common/rename-hacs.md @@ -0,0 +1 @@ +?> This card was previously called `Frigate Card`. Until the rename is processed by HACS, use `Frigate Card` as the search term, thereafter it will be called `Advanced Camera Card`. diff --git a/docs/configuration/README.md b/docs/configuration/README.md index b19be1b1..847f96dc 100644 --- a/docs/configuration/README.md +++ b/docs/configuration/README.md @@ -5,7 +5,7 @@ The card supports a myriad of configuration options for simple or complex setups ### Minimal configuration ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office ``` diff --git a/docs/configuration/actions/README.md b/docs/configuration/actions/README.md index 22114ff2..d3d52617 100644 --- a/docs/configuration/actions/README.md +++ b/docs/configuration/actions/README.md @@ -10,15 +10,15 @@ variety of circumstances (e.g. tapping on a menu icon, double tapping on an [element](../elements/README.md) or holding the mouse/tap down on a particular [view](../view.md?id=supported-views)). -### Differences in actions between Frigate Card and Home Assistant +### Differences in actions between Advanced Camera Card and Home Assistant -Both the Home Assistant frontend and the Frigate card cooperate to provide -action functionality. In general, the Frigate Card functionality is a superset +Both the Home Assistant frontend and the Advanced Camera Card cooperate to provide +action functionality. In general, the Advanced Camera Card functionality is a superset of that offered by stock Home Assistant. Stock action functionality is used for Stock [Home Assistant picture elements](https://www.home-assistant.io/lovelace/picture-elements/). Extended -Frigate card behavior covers all other interactions on the Frigate card (e.g. +Advanced Camera Card behavior covers all other interactions on the Advanced Camera Card (e.g. menu icon elements, submenus and actions on the card or views). #### Custom action types: `start_tap` and `end_tap` @@ -32,7 +32,7 @@ actually occurring. #### Multiple actions -Extended Frigate card behavior supports a list of actions instead of a single +Extended Advanced Camera Card behavior supports a list of actions instead of a single action, all of which will be handled. See [an example of multiple actions](../../examples.md?id=multiple-actions). diff --git a/docs/configuration/actions/custom/README.md b/docs/configuration/actions/custom/README.md index ed45590a..4a3232c2 100644 --- a/docs/configuration/actions/custom/README.md +++ b/docs/configuration/actions/custom/README.md @@ -1,33 +1,33 @@ -# `custom:frigate-card-action` +# `custom:advanced-camera-card-action` -Execute a Frigate Card action. +Execute an Advanced Camera Card action. ```yaml -action: custom:frigate-card-action +action: custom:advanced-camera-card-action # [...] ``` -| Parameter | Description | -| --------------------- | ------------------------------------------- | -| `action` | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | A supported Frigate Card action. See below. | +| Parameter | Description | +| ----------------------------- | --------------------------------------------------- | +| `action` | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | A supported Advanced Camera Card action. See below. | ## `camera_select` Select a given camera. ```yaml -action: custom:frigate-card-action -frigate_card_action: camera_select +action: custom:advanced-camera-card-action +advanced_camera_card_action: camera_select # [...] ``` -| Parameter | Description | -| --------------------- | --------------------------------------------------------------------------------------------------- | -| `action` | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | Must be `camera_select`. | -| `camera` | The [camera ID](../../cameras/README.md?id=cameras) of the camera to select. | -| `triggered` | If `true` instead of `camera` being specified then a triggered camera (if any) is selected instead. | +| Parameter | Description | +| ----------------------------- | --------------------------------------------------------------------------------------------------- | +| `action` | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | Must be `camera_select`. | +| `camera` | The [camera ID](../../cameras/README.md?id=cameras) of the camera to select. | +| `triggered` | If `true` instead of `camera` being specified then a triggered camera (if any) is selected instead. | This action will respect the value of the `view.camera_select` to choose the appropriate view on the new camera. See [`view` configuration](../../view.md). @@ -36,8 +36,8 @@ This action will respect the value of the `view.camera_select` to choose the app Download the displayed media. ```yaml -action: custom:frigate-card-action -frigate_card_action: camera_ui +action: custom:advanced-camera-card-action +advanced_camera_card_action: camera_ui ``` Open the UI for the selected camera engine (e.g. the Frigate UI). @@ -47,8 +47,8 @@ Open the UI for the selected camera engine (e.g. the Frigate UI). Change to the `clip` view. ```yaml -action: custom:frigate-card-action -frigate_card_action: clip +action: custom:advanced-camera-card-action +advanced_camera_card_action: clip ``` ## `clips` @@ -56,8 +56,8 @@ frigate_card_action: clip Change to the `clips` view. ```yaml -action: custom:frigate-card-action -frigate_card_action: clips +action: custom:advanced-camera-card-action +advanced_camera_card_action: clips ``` ## `default` @@ -65,8 +65,8 @@ frigate_card_action: clips Change to the default view. ```yaml -action: custom:frigate-card-action -frigate_card_action: default +action: custom:advanced-camera-card-action +advanced_camera_card_action: default ``` ## `diagnostics` @@ -74,8 +74,8 @@ frigate_card_action: default Show the card diagnostics. ```yaml -action: custom:frigate-card-action -frigate_card_action: diagnostics +action: custom:advanced-camera-card-action +advanced_camera_card_action: diagnostics ``` ## `display_mode_select` @@ -83,24 +83,24 @@ frigate_card_action: diagnostics Select a display mode (e.g. view a `single` camera or a `grid` of cameras). ```yaml -action: custom:frigate-card-action -frigate_card_action: display_mode_select +action: custom:advanced-camera-card-action +advanced_camera_card_action: display_mode_select # [...] ``` -| Parameter | Description | -| --------------------- | ---------------------------------------------------------------------------------------------- | -| `action` | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | Must be `display_mode_select`. | -| `display_mode` | `single` to show a single camera at a time in a carousel, or `grid` to show a grid of cameras. | +| Parameter | Description | +| ----------------------------- | ---------------------------------------------------------------------------------------------- | +| `action` | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | Must be `display_mode_select`. | +| `display_mode` | `single` to show a single camera at a time in a carousel, or `grid` to show a grid of cameras. | ## `download` Download the displayed media. ```yaml -action: custom:frigate-card-action -frigate_card_action: download +action: custom:advanced-camera-card-action +advanced_camera_card_action: download ``` ## `expand` @@ -108,8 +108,8 @@ frigate_card_action: download Expand the card into a dialog/popup. ```yaml -action: custom:frigate-card-action -frigate_card_action: expand +action: custom:advanced-camera-card-action +advanced_camera_card_action: expand ``` ## `fullscreen` @@ -117,8 +117,8 @@ frigate_card_action: expand Toggle fullscreen. ```yaml -action: custom:frigate-card-action -frigate_card_action: fullscreen +action: custom:advanced-camera-card-action +advanced_camera_card_action: fullscreen ``` ## `image` @@ -126,8 +126,8 @@ frigate_card_action: fullscreen Change to the `image` view. ```yaml -action: custom:frigate-card-action -frigate_card_action: image +action: custom:advanced-camera-card-action +advanced_camera_card_action: image ``` ## `live` @@ -135,8 +135,8 @@ frigate_card_action: image Change to the `live` view. ```yaml -action: custom:frigate-card-action -frigate_card_action: live +action: custom:advanced-camera-card-action +advanced_camera_card_action: live ``` ## `live_substream_off` @@ -144,86 +144,86 @@ frigate_card_action: live Turn off the substream (if on). ```yaml -action: custom:frigate-card-action -frigate_card_action: live_substream_on +action: custom:advanced-camera-card-action +advanced_camera_card_action: live_substream_on ``` -| Parameter | Description | -| --------------------- | ------------------------------------- | -| `action` | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | Must be `live_substream_on`. | +| Parameter | Description | +| ----------------------------- | --------------------------------------------- | +| `action` | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | Must be `live_substream_on`. | ## `live_substream_on` Turn on the first available substream. Use [Camera dependency configuration](../../cameras/README.md?id=dependencies) to configure substreams. ```yaml -action: custom:frigate-card-action -frigate_card_action: live_substream_on +action: custom:advanced-camera-card-action +advanced_camera_card_action: live_substream_on ``` -| Parameter | Description | -| --------------------- | ------------------------------------- | -| `action` | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | Must be `live_substream_on`. | +| Parameter | Description | +| ----------------------------- | --------------------------------------------- | +| `action` | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | Must be `live_substream_on`. | ## `live_substream_select` Select a substream. Use [Camera dependency configuration](../../cameras/README.md?id=dependencies) to configure substreams. ```yaml -action: custom:frigate-card-action -frigate_card_action: live_substream_select +action: custom:advanced-camera-card-action +advanced_camera_card_action: live_substream_select # [...] ``` -| Parameter | Description | -| --------------------- | ------------------------------------------------------------------------------- | -| `action` | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | Must be `live_substream_select`. | -| `camera` | The [camera ID](../../cameras/README.md?id=cameras) of the substream to select. | +| Parameter | Description | +| ----------------------------- | ------------------------------------------------------------------------------- | +| `action` | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | Must be `live_substream_select`. | +| `camera` | The [camera ID](../../cameras/README.md?id=cameras) of the substream to select. | ## `log` Log a message to the Javascript console. ```yaml -action: custom:frigate-card-action -frigate_card_action: log +action: custom:advanced-camera-card-action +advanced_camera_card_action: log # [...] ``` -| Parameter | Default | Description | -| --------------------- | ------- | ------------------------------------------------------------------------------ | -| `action` | | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | | Must be `log`. | -| `message` | | The message to log. | -| `level` | `info` | The console logging level to use. One of `['debug', 'info', 'warn', 'error']`. | +| Parameter | Default | Description | +| ----------------------------- | ------- | ------------------------------------------------------------------------------ | +| `action` | | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | | Must be `log`. | +| `message` | | The message to log. | +| `level` | `info` | The console logging level to use. One of `['debug', 'info', 'warn', 'error']`. | ## `media_player` Perform a media player action. ```yaml -action: custom:frigate-card-action -frigate_card_action: media_player +action: custom:advanced-camera-card-action +advanced_camera_card_action: media_player # [...] ``` -| Parameter | Description | -| --------------------- | ----------------------------------------------------------------- | -| `action` | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | Must be `media_player`. | -| `media_player` | The entity ID of the media_player on which to perform the action. | -| `media_player_action` | Either `play` or `stop` to play or stop the media in question. | +| Parameter | Description | +| ----------------------------- | ----------------------------------------------------------------- | +| `action` | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | Must be `media_player`. | +| `media_player` | The entity ID of the media_player on which to perform the action. | +| `media_player_action` | Either `play` or `stop` to play or stop the media in question. | ## `menu_toggle` Show/hide the menu (for the `hidden` mode style). ```yaml -action: custom:frigate-card-action -frigate_card_action: menu_toggle +action: custom:advanced-camera-card-action +advanced_camera_card_action: menu_toggle ``` ## `microphone_connect` @@ -231,8 +231,8 @@ frigate_card_action: menu_toggle Connect the microphone for [2-way audio](../../../usage/2-way-audio.md). ```yaml -action: custom:frigate-card-action -frigate_card_action: microphone_connect +action: custom:advanced-camera-card-action +advanced_camera_card_action: microphone_connect ``` ## `microphone_disconnect` @@ -240,8 +240,8 @@ frigate_card_action: microphone_connect Disconnect the microphone during [2-way audio](../../../usage/2-way-audio.md). ```yaml -action: custom:frigate-card-action -frigate_card_action: microphone_disconnect +action: custom:advanced-camera-card-action +advanced_camera_card_action: microphone_disconnect ``` ## `microphone_mute` @@ -249,8 +249,8 @@ frigate_card_action: microphone_disconnect Mute the microphone during [2-way audio](../../../usage/2-way-audio.md). ```yaml -action: custom:frigate-card-action -frigate_card_action: microphone_mute +action: custom:advanced-camera-card-action +advanced_camera_card_action: microphone_mute ``` ## `microphone_unmute` @@ -258,8 +258,8 @@ frigate_card_action: microphone_mute Unmute the microphone during [2-way audio](../../../usage/2-way-audio.md). ```yaml -action: custom:frigate-card-action -frigate_card_action: microphone_unmute +action: custom:advanced-camera-card-action +advanced_camera_card_action: microphone_unmute ``` ## `mute` @@ -267,8 +267,8 @@ frigate_card_action: microphone_unmute Mute the selected media. ```yaml -action: custom:frigate-card-action -frigate_card_action: mute +action: custom:advanced-camera-card-action +advanced_camera_card_action: mute ``` ## `pause` @@ -276,8 +276,8 @@ frigate_card_action: mute Pause the selected media. ```yaml -action: custom:frigate-card-action -frigate_card_action: pause +action: custom:advanced-camera-card-action +advanced_camera_card_action: pause ``` ## `play` @@ -285,8 +285,8 @@ frigate_card_action: pause Play the selected media. ```yaml -action: custom:frigate-card-action -frigate_card_action: play +action: custom:advanced-camera-card-action +advanced_camera_card_action: play ``` ## `ptz` @@ -294,19 +294,19 @@ frigate_card_action: play Execute a real PTZ action, whether configured manually (see [Camera PTZ configuration](../../cameras/README.md?id=ptz)) or auto-detected. ```yaml -action: custom:frigate-card-action -frigate_card_action: ptz +action: custom:advanced-camera-card-action +advanced_camera_card_action: ptz # [...] ``` -| Parameter | | Description | -| --------------------- | ------------------------- | --------------------------------------------------------------------------------------------- | -| `action` | | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | | Must be `ptz`. | -| `camera` | Currently selected camera | An optional camera ID to execute the action on. | -| `ptz_action` | | Optional action that is of `left`, `right`, `up`, `down`, `zoom_in`, `zoom_out` or `preset`. | -| `ptz_phase` | | Optional parameter that is one of `start` or `stop` to start or stop the movement separately. | -| `ptz_preset` | | Optional preset to execute when the `ptz_action` is `preset`. | +| Parameter | | Description | +| ----------------------------- | ------------------------- | --------------------------------------------------------------------------------------------- | +| `action` | | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | | Must be `ptz`. | +| `camera` | Currently selected camera | An optional camera ID to execute the action on. | +| `ptz_action` | | Optional action that is of `left`, `right`, `up`, `down`, `zoom_in`, `zoom_out` or `preset`. | +| `ptz_phase` | | Optional parameter that is one of `start` or `stop` to start or stop the movement separately. | +| `ptz_preset` | | Optional preset to execute when the `ptz_action` is `preset`. | ?> If no `ptz_action` is specified, the camera returns to its "home" position. For a real PTZ camera, the "home" position is the first available preset. If there are no presets, there is no home position. @@ -315,35 +315,35 @@ frigate_card_action: ptz Show or hide the PTZ controls. ```yaml -action: custom:frigate-card-action -frigate_card_action: ptz_controls +action: custom:advanced-camera-card-action +advanced_camera_card_action: ptz_controls # [...] ``` -| Parameter | Description | -| --------------------- | -------------------------------------------------------- | -| `action` | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | Must be `ptz_controls`. | -| `show` | If `true` shows the PTZ controls, if `false` hides them. | +| Parameter | Description | +| ----------------------------- | -------------------------------------------------------- | +| `action` | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | Must be `ptz_controls`. | +| `show` | If `true` shows the PTZ controls, if `false` hides them. | ## `ptz_digital` Execute a digital PTZ action. ```yaml -action: custom:frigate-card-action -frigate_card_action: ptz_digital +action: custom:advanced-camera-card-action +advanced_camera_card_action: ptz_digital # [...] ``` -| Parameter | Default | Description | -| --------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `action` | | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | | Must be `ptz_digital`. | -| `target_id` | The currently selected camera or media | The target (camera or media) to execute a digital PTZ action on. Can be a camera ID, or another media ID (e.g. for Frigate, can specify a media/event ID). | -| `ptz_action` | Optional action that is one of `left`, `right`, `up`, `down`, `zoom_in` or `zoom_out`. | -| `ptz_phase` | Optional parameter that is one of `start` or `stop` to start or stop the movement separately. | -| `absolute` | Optional parameter to specify exact absolute pan and zoom settings. See below. | +| Parameter | Default | Description | +| ----------------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `action` | | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | | Must be `ptz_digital`. | +| `target_id` | The currently selected camera or media | The target (camera or media) to execute a digital PTZ action on. Can be a camera ID, or another media ID (e.g. for Frigate, can specify a media/event ID). | +| `ptz_action` | Optional action that is one of `left`, `right`, `up`, `down`, `zoom_in` or `zoom_out`. | +| `ptz_phase` | Optional parameter that is one of `start` or `stop` to start or stop the movement separately. | +| `absolute` | Optional parameter to specify exact absolute pan and zoom settings. See below. | ?> If no `ptz_action` is specified and no `absolute` value is specified, the camera returns to its "home" position. See [Camera layout configuration](../../cameras/README.md?id=layout-configuration) to configure the default "home" position for digital PTZ. @@ -369,18 +369,18 @@ action), otherwise a digital PTZ action will be run (equivalent to using the ?> If the camera supports _any_ real PTZ action, _all_ actions will attempt to make real PTZ calls. ```yaml -action: custom:frigate-card-action -frigate_card_action: ptz_multi +action: custom:advanced-camera-card-action +advanced_camera_card_action: ptz_multi # [...] ``` -| Parameter | Description | -| --------------------- | --------------------------------------------------------------------------------------------- | -| `action` | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | Must be `ptz_multi`. | -| `ptz_action` | Optional action that is one of `left`, `right`, `up`, `down`, `zoom_in` or `zoom_out`. | -| `ptz_phase` | Optional parameter that is one of `start` or `stop` to start or stop the movement separately. | -| `ptz_preset` | Optional preset to execute when the `ptz_action` is `preset`. | +| Parameter | Description | +| ----------------------------- | --------------------------------------------------------------------------------------------- | +| `action` | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | Must be `ptz_multi`. | +| `ptz_action` | Optional action that is one of `left`, `right`, `up`, `down`, `zoom_in` or `zoom_out`. | +| `ptz_phase` | Optional parameter that is one of `start` or `stop` to start or stop the movement separately. | +| `ptz_preset` | Optional preset to execute when the `ptz_action` is `preset`. | ?> If no `ptz_action` is specified, the camera returns to its "home" position. @@ -389,8 +389,8 @@ frigate_card_action: ptz_multi Change to the `recording` view. ```yaml -action: custom:frigate-card-action -frigate_card_action: recording +action: custom:advanced-camera-card-action +advanced_camera_card_action: recording ``` ## `recordings` @@ -398,8 +398,8 @@ frigate_card_action: recording Change to the `recordings` view. ```yaml -action: custom:frigate-card-action -frigate_card_action: recordings +action: custom:advanced-camera-card-action +advanced_camera_card_action: recordings ``` ## `screenshot` @@ -407,8 +407,8 @@ frigate_card_action: recordings Take a screenshot of the selected media (e.g. a still from a video). ```yaml -action: custom:frigate-card-action -frigate_card_action: screenshot +action: custom:advanced-camera-card-action +advanced_camera_card_action: screenshot ``` ## `sleep` @@ -416,15 +416,15 @@ frigate_card_action: screenshot Take no action for a given duration. Useful to pause between multiple other actions. ```yaml -action: custom:frigate-card-action -frigate_card_action: sleep +action: custom:advanced-camera-card-action +advanced_camera_card_action: sleep ``` -| Parameter | Description | -| --------------------- | ------------------------------------- | -| `action` | Must be `custom:frigate-card-action`. | -| `frigate_card_action` | Must be `sleep`. | -| `duration` | A duration object. See below. | +| Parameter | Description | +| ----------------------------- | --------------------------------------------- | +| `action` | Must be `custom:advanced-camera-card-action`. | +| `advanced_camera_card_action` | Must be `sleep`. | +| `duration` | A duration object. See below. | ### `duration` @@ -444,8 +444,8 @@ The `duration` block configures how long the `sleep` should last. Change to the `snapshot` view. ```yaml -action: custom:frigate-card-action -frigate_card_action: snapshot +action: custom:advanced-camera-card-action +advanced_camera_card_action: snapshot ``` ## `status_bar` @@ -453,8 +453,8 @@ frigate_card_action: snapshot Add or remove items from the status bar. ```yaml -action: custom:frigate-card-action -frigate_card_action: status_bar +action: custom:advanced-camera-card-action +advanced_camera_card_action: status_bar # [...] ``` @@ -465,15 +465,15 @@ frigate_card_action: status_bar ### `items` -The items parameter is a list of items to `add` or `remove`. See [`custom:frigate-card-status-bar-icon`](../../elements/custom/README.md?id=status-bar-icon), [`custom:frigate-card-status-bar-image`](../../elements/custom/README.md?id=status-bar-image), [`custom:frigate-card-status-bar-string`](../../elements/custom/README.md?id=status-bar-string) for the allowable items and their parameters. See the [fully expanded reference](./README.md?fully-expanded-reference) below for a complete example. +The items parameter is a list of items to `add` or `remove`. See [`custom:advanced-camera-card-status-bar-icon`](../../elements/custom/README.md?id=status-bar-icon), [`custom:advanced-camera-card-status-bar-image`](../../elements/custom/README.md?id=status-bar-image), [`custom:advanced-camera-card-status-bar-string`](../../elements/custom/README.md?id=status-bar-string) for the allowable items and their parameters. See the [fully expanded reference](./README.md?fully-expanded-reference) below for a complete example. ## `timeline` Change to the `timeline` view. ```yaml -action: custom:frigate-card-action -frigate_card_action: timeline +action: custom:advanced-camera-card-action +advanced_camera_card_action: timeline ``` ## `unmute` @@ -481,8 +481,8 @@ frigate_card_action: timeline Unmute the selected media. ```yaml -action: custom:frigate-card-action -frigate_card_action: unmute +action: custom:advanced-camera-card-action +advanced_camera_card_action: unmute ``` ## Fully expanded reference @@ -491,308 +491,308 @@ frigate_card_action: unmute ```yaml elements: - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-a-circle title: Select Front Door tap_action: - action: custom:frigate-card-action - frigate_card_action: camera_select + action: custom:advanced-camera-card-action + advanced_camera_card_action: camera_select camera: camera.front_door - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-b-circle title: Open Camera UI tap_action: - action: custom:frigate-card-action - frigate_card_action: camera_ui - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: camera_ui + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-d-circle title: Show most recent clip tap_action: - action: custom:frigate-card-action - frigate_card_action: clip - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: clip + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-e-circle title: Show clips tap_action: - action: custom:frigate-card-action - frigate_card_action: clips - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: clips + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-f-circle title: Show default view tap_action: - action: custom:frigate-card-action - frigate_card_action: default - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: default + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-g-circle title: Show diagnostics tap_action: - action: custom:frigate-card-action - frigate_card_action: diagnostics - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: diagnostics + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-h-circle title: Show grid tap_action: - action: custom:frigate-card-action - frigate_card_action: display_mode_select + action: custom:advanced-camera-card-action + advanced_camera_card_action: display_mode_select display_mode: grid - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-i-circle title: Download media tap_action: - action: custom:frigate-card-action - frigate_card_action: download - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: download + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-j-circle title: Expand tap_action: - action: custom:frigate-card-action - frigate_card_action: expand - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: expand + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-k-circle title: Fullscreen tap_action: - action: custom:frigate-card-action - frigate_card_action: fullscreen - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: fullscreen + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-l-circle title: Show image view tap_action: - action: custom:frigate-card-action - frigate_card_action: image - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: image + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-m-circle title: Show live view tap_action: - action: custom:frigate-card-action - frigate_card_action: live - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: live + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-n-circle title: Turn off substream tap_action: - action: custom:frigate-card-action - frigate_card_action: live_substream_off - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: live_substream_off + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-o-circle title: Turn on substream tap_action: - action: custom:frigate-card-action - frigate_card_action: live_substream_on - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: live_substream_on + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-n-circle title: Select HD substream tap_action: - action: custom:frigate-card-action - frigate_card_action: live_substream_select + action: custom:advanced-camera-card-action + advanced_camera_card_action: live_substream_select camera: camera.front_door_hd - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-o-circle title: Log to console tap_action: - action: custom:frigate-card-action - frigate_card_action: log + action: custom:advanced-camera-card-action + advanced_camera_card_action: log message: "Hello, world!" level: debug - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-m-circle title: Media player play tap_action: - action: custom:frigate-card-action - frigate_card_action: media_player + action: custom:advanced-camera-card-action + advanced_camera_card_action: media_player media_player: media_player.nesthub50be media_player_action: play - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-n-circle title: Media player stop tap_action: - action: custom:frigate-card-action - frigate_card_action: media_player + action: custom:advanced-camera-card-action + advanced_camera_card_action: media_player media_player: media_player.nesthub media_player_action: stop - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-o-circle title: Toggle hidden menu tap_action: - action: custom:frigate-card-action - frigate_card_action: menu_toggle - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: menu_toggle + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-p-circle title: Microphone mute tap_action: - action: custom:frigate-card-action - frigate_card_action: microphone_mute - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: microphone_mute + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-q-circle title: Microphone unmute tap_action: - action: custom:frigate-card-action - frigate_card_action: microphone_unmute - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: microphone_unmute + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-r-circle title: Mute tap_action: - action: custom:frigate-card-action - frigate_card_action: mute - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: mute + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-s-circle title: Pause tap_action: - action: custom:frigate-card-action - frigate_card_action: pause - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: pause + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-t-circle title: Play tap_action: - action: custom:frigate-card-action - frigate_card_action: play - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: play + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-u-circle title: Real PTZ Preset tap_action: - action: custom:frigate-card-action - frigate_card_action: ptz + action: custom:advanced-camera-card-action + advanced_camera_card_action: ptz ptz_action: preset ptz_preset: doorway - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-v-circle title: Show PTZ Controls tap_action: - action: custom:frigate-card-action - frigate_card_action: ptz_controls + action: custom:advanced-camera-card-action + advanced_camera_card_action: ptz_controls enabled: true - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-w-circle title: Go to precise digital location tap_action: - action: custom:frigate-card-action - frigate_card_action: ptz_digital + action: custom:advanced-camera-card-action + advanced_camera_card_action: ptz_digital absolute: zoom: 5 pan: x: 58 y: 14 - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-x-circle title: Smart select between real and digital PTZ tap_action: - action: custom:frigate-card-action - frigate_card_action: ptz_multi + action: custom:advanced-camera-card-action + advanced_camera_card_action: ptz_multi ptz_action: left - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-y-circle title: Show most recent recording tap_action: - action: custom:frigate-card-action - frigate_card_action: recording - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: recording + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-a-circle-outline title: Show recordings tap_action: - action: custom:frigate-card-action - frigate_card_action: recordings - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: recordings + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-b-circle-outline title: Screenshot tap_action: - action: custom:frigate-card-action - frigate_card_action: screenshot - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: screenshot + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-c-circle-outline title: Sleep tap_action: - action: custom:frigate-card-action - frigate_card_action: sleep + action: custom:advanced-camera-card-action + advanced_camera_card_action: sleep duration: h: 1 m: 20 s: 56 ms: 422 - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-d-circle-outline title: Show most recent snapshot tap_action: - action: custom:frigate-card-action - frigate_card_action: snapshot - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: snapshot + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-e-circle-outline title: Show snapshots tap_action: - action: custom:frigate-card-action - frigate_card_action: snapshots - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: snapshots + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-f-circle-outline title: Show timeline tap_action: - action: custom:frigate-card-action - frigate_card_action: timeline - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: timeline + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-g-circle-outline title: Unmute tap_action: - action: custom:frigate-card-action - frigate_card_action: unmute - - type: custom:frigate-card-menu-icon + action: custom:advanced-camera-card-action + advanced_camera_card_action: unmute + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-h-circle-outline title: Add status bar contents tap_action: - - action: custom:frigate-card-action - frigate_card_action: status_bar + - action: custom:advanced-camera-card-action + advanced_camera_card_action: status_bar status_bar_action: add items: - - type: custom:frigate-card-status-bar-string + - type: custom:advanced-camera-card-status-bar-string enabled: true exclusive: false expand: false string: 'Intruder alert!' priority: 50 sufficient: false - - type: custom:frigate-card-status-bar-icon + - type: custom:advanced-camera-card-status-bar-icon enabled: true exclusive: false expand: false icon: 'mdi:cow' priority: 50 sufficient: false - - type: custom:frigate-card-status-bar-image + - type: custom:advanced-camera-card-status-bar-image enabled: true exclusive: false expand: false image: https://my.site.com/status.png priority: 50 sufficient: false - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-i-circle-outline title: Remove status bar contents tap_action: - - action: custom:frigate-card-action - frigate_card_action: status_bar + - action: custom:advanced-camera-card-action + advanced_camera_card_action: status_bar status_bar_action: remove items: - - type: custom:frigate-card-status-bar-string + - type: custom:advanced-camera-card-status-bar-string enabled: true exclusive: false expand: false string: 'Intruder alert!' priority: 50 sufficient: false - - type: custom:frigate-card-status-bar-icon + - type: custom:advanced-camera-card-status-bar-icon enabled: true exclusive: false expand: false icon: 'mdi:cow' priority: 50 sufficient: false - - type: custom:frigate-card-status-bar-image + - type: custom:advanced-camera-card-status-bar-image enabled: true exclusive: false expand: false image: https://my.site.com/status.png priority: 50 sufficient: false - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alpha-i-circle-outline title: Reset status bar contents tap_action: - - action: custom:frigate-card-action - frigate_card_action: status_bar + - action: custom:advanced-camera-card-action + advanced_camera_card_action: status_bar status_bar_action: reset ``` diff --git a/docs/configuration/automations.md b/docs/configuration/automations.md index 49697bb6..5c69b2dd 100644 --- a/docs/configuration/automations.md +++ b/docs/configuration/automations.md @@ -30,9 +30,9 @@ automations: - condition: fullscreen fullscreen: true actions: - - action: custom:frigate-card-action - frigate_card_action: live_substream_on + - action: custom:advanced-camera-card-action + advanced_camera_card_action: live_substream_on actions_not: - - action: custom:frigate-card-action - frigate_card_action: live_substream_off + - action: custom:advanced-camera-card-action + advanced_camera_card_action: live_substream_off ``` diff --git a/docs/configuration/cameras/live-provider.md b/docs/configuration/cameras/live-provider.md index 6fc7cff7..727d2cf1 100644 --- a/docs/configuration/cameras/live-provider.md +++ b/docs/configuration/cameras/live-provider.md @@ -7,7 +7,7 @@ The `live_provider` parameter determines what provides the live stream for a cam | Live Provider | Latency | Frame Rate | Loading Time | Installation | Description | | ---------------------------------- | ------- | ---------- | ------------ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `go2rtc` | Best | High | Better | Builtin | Uses [go2rtc](https://github.com/AlexxIT/go2rtc) to stream live feeds. This is supported by Frigate >= `0.12`. | -| `ha` (default HA configuration) | Poor | High | Better | Builtin | Use the built-in Home Assistant camera stream. The camera doesn't even need to be a Frigate camera! | +| `ha` (default HA configuration) | Poor | High | Better | Builtin | Use the built-in Home Assistant camera stream. | | `ha` (Native WebRTC) | Best | High | Better | Builtin | Use the built-in Home Assistant camera streams -- can be configured to use [native WebRTC](https://www.home-assistant.io/integrations/rtsp_to_webrtc/) offering a very low-latency feed direct to your browser. | | `ha` (when configured with LL-HLS) | Better | High | Better | Builtin | Use the built-in Home Assistant camera streams -- can be configured to use an [LL-HLS](https://www.home-assistant.io/integrations/stream/#ll-hls) feed for lower latency. | | `image` | Poor | Poor | Best | Builtin | Use refreshing snapshots of the built-in Home Assistant camera streams. | @@ -44,13 +44,13 @@ cameras: # [...] ``` -| Option | Default | Description | -| ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `entity` | | The entity to use when `mode` is set to `entity`. This entity is expected to have an `entity_picture` attribute that specifies the image URL. | -| `entity_parameters` | | Optional URL parameters to add to the URL generated for entity-based modes (i.e. when `mode` is `camera` or `entity`). | -| `mode` | `auto` | Value must be one of `url` (to fetch an arbitrary image URL), `camera` (to show a still of the currently selected camera entity using either `camera_entity` or `webrtc_card.entity` in that order of precedence), `entity` to show an image associated with a named entity (see the `entity` parameter below), or `screensaver` (to show an [embedded stock Frigate card logo](https://github.com/dermotduffy/frigate-hass-card/blob/main/src/images/frigate-bird-in-sky.jpg)). If `auto`, the mode is chosen automatically based on whether `url` or `entity` parameters have been specified. | -| `refresh_seconds` | 1 | The image will be refreshed at least every `refresh_seconds` (it may refresh more frequently, e.g. whenever Home Assistant updates its camera security token). `0` implies no refreshing. | -| `url` | | A static image URL to be used when the `mode` is set to `url` or when a temporary image is required (e.g. may appear momentarily prior to load of a camera snapshot in the `camera` mode). Note that a `_t=[timestsamp]` query parameter will be automatically added to all URLs such that the image will not be cached by the browser. | +| Option | Default | Description | +| ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `entity` | | The entity to use when `mode` is set to `entity`. This entity is expected to have an `entity_picture` attribute that specifies the image URL. | +| `entity_parameters` | | Optional URL parameters to add to the URL generated for entity-based modes (i.e. when `mode` is `camera` or `entity`). | +| `mode` | `auto` | Value must be one of `url` (to fetch an arbitrary image URL), `camera` (to show a still of the currently selected camera entity using either `camera_entity` or `webrtc_card.entity` in that order of precedence), `entity` to show an image associated with a named entity (see the `entity` parameter below), or `screensaver` (to show an [embedded image](https://github.com/dermotduffy/advanced-camera-card/blob/main/src/images/iris-screensaver.jpg)). If `auto`, the mode is chosen automatically based on whether `url` or `entity` parameters have been specified. | +| `refresh_seconds` | 1 | The image will be refreshed at least every `refresh_seconds` (it may refresh more frequently, e.g. whenever Home Assistant updates its camera security token). `0` implies no refreshing. | +| `url` | | A static image URL to be used when the `mode` is set to `url` or when a temporary image is required (e.g. may appear momentarily prior to load of a camera snapshot in the `camera` mode). Note that a `_t=[timestsamp]` query parameter will be automatically added to all URLs such that the image will not be cached by the browser. | ## `jsmpeg` diff --git a/docs/configuration/elements/README.md b/docs/configuration/elements/README.md index 5591fb39..834d6d62 100644 --- a/docs/configuration/elements/README.md +++ b/docs/configuration/elements/README.md @@ -12,7 +12,7 @@ elements: - [element_2] ``` -?> The Frigate Card allows either a single [action](../actions/README.md) (as in stock Home +?> The Advanced Camera Card allows either a single [action](../actions/README.md) (as in stock Home Assistant) or list of [actions](../actions/README.md) to be defined for each class of user interaction (e.g. `tap`, `double_tap`, `hold`, etc). See [an example of multiple actions](../../examples.md?id=multiple-actions). diff --git a/docs/configuration/elements/custom/README.md b/docs/configuration/elements/custom/README.md index d9ef932f..e7d3e677 100644 --- a/docs/configuration/elements/custom/README.md +++ b/docs/configuration/elements/custom/README.md @@ -2,19 +2,19 @@ ## `conditional` -Restrict a set of elements to only render when the card is matches a set of [conditions](../../conditions.md). This is analogous to the stock [`conditional`](../stock/README.md?id=conditional) element except supporting a rich set of Frigate Card [conditions](../../conditions.md). +Restrict a set of elements to only render when the card is matches a set of [conditions](../../conditions.md). This is analogous to the stock [`conditional`](../stock/README.md?id=conditional) element except supporting a rich set of Advanced Camera Card [conditions](../../conditions.md). ```yaml elements: - - type: custom:frigate-card-conditional + - type: custom:advanced-camera-card-conditional # [...] ``` -Parameters for the `custom:frigate-card-conditional` element: +Parameters for the `custom:advanced-camera-card-conditional` element: | Parameter | Description | | ------------ | ---------------------------------------------------------------------------------------------------------------- | -| `type` | Must be `custom:frigate-card-conditional`. | +| `type` | Must be `custom:advanced-camera-card-conditional`. | | `conditions` | A list of [conditions](../../conditions.md) that must evaluate to true in order for the elements to be rendered. | | `elements` | The elements to render. Can be any supported element. | @@ -22,11 +22,11 @@ See the [conditional elements example](../../../examples.md?id=conditional-eleme ## `menu-icon` -Add an arbitrary icon to the Frigate Card menu. +Add an arbitrary icon to the Advanced Camera Card menu. ```yaml elements: - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon # [...] ``` @@ -34,7 +34,7 @@ Configuration is similar to a stock [Picture Elements Icon](https://www.home-ass | Parameter | Default | Description | | ------------------------------------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `type` | | Must be `custom:frigate-card-menu-icon`. | +| `type` | | Must be `custom:advanced-camera-card-menu-icon`. | | `alignment` | `matching` | Whether this menu item should have an alignment that is `matching` the menu alignment or `opposing` the menu alignment. Can be used to create two separate groups of buttons on the menu. The `priority` option orders buttons within a given `alignment`. | | `enabled` | `true` | Whether or not to show this menu item. | | `entity` | | An optional Home Assistant entity for use with actions. | @@ -51,13 +51,13 @@ Add a configurable submenu dropdown. ```yaml elements: - - type: custom:frigate-card-menu-submenu + - type: custom:advanced-camera-card-menu-submenu # [...] ``` | Parameter | Default | Description | | ------------------------------------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `type` | | Must be `custom:frigate-card-menu-submenu`. | +| `type` | | Must be `custom:advanced-camera-card-menu-submenu`. | | `alignment` | `matching` | Whether this menu item should have an alignment that is `matching` the menu alignment or `opposing` the menu alignment. Can be used to create two separate groups of buttons on the menu. The `priority` option orders buttons within a given `alignment`. | | `enabled` | `true` | Whether or not to show this menu item. | | `entity` | | An optional Home Assistant entity for use with actions. | @@ -88,13 +88,13 @@ Add a submenu based on a `select` or `input_select`. This element allows you to ```yaml elements: - - type: custom:frigate-card-menu-submenu-select + - type: custom:advanced-camera-card-menu-submenu-select # [...] ``` | Parameter | Default | Description | | ------------------------------------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `type` | | Must be `custom:frigate-card-menu-submenu-select`. | +| `type` | | Must be `custom:advanced-camera-card-menu-submenu-select`. | | `alignment` | `matching` | Whether this menu item should have an alignment that is `matching` the menu alignment or `opposing` the menu alignment. Can be used to create two separate groups of buttons on the menu. The `priority` option orders buttons within a given `alignment`. | | `enabled` | `true` | Whether or not to show this menu item. | | `entity` | | An optional Home Assistant entity for use with actions. | @@ -110,11 +110,11 @@ See the `select` [submenu example](../../../examples.md?id=select-submenu). ## `menu-state-icon` -Add a state icon to the Frigate Card menu that represents the state of a Home Assistant entity. +Add a state icon to the Advanced Camera Card menu that represents the state of a Home Assistant entity. ```yaml elements: - - type: custom:frigate-card-menu-state-icon + - type: custom:advanced-camera-card-menu-state-icon # [...] ``` @@ -122,7 +122,7 @@ Configuration is similar to a stock [Picture Elements State Icon](https://www.ho | Parameter | Default | Description | | ------------------------------------------------------------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `type` | | Must be `custom:frigate-card-menu-state-icon`. | +| `type` | | Must be `custom:advanced-camera-card-menu-state-icon`. | | `alignment` | `matching` | Whether this menu item should have an alignment that is `matching` the menu alignment or `opposing` the menu alignment. Can be used to create two separate groups of buttons on the menu. The `priority` option orders buttons within a given `alignment`. | | `enabled` | `true` | Whether or not to show this menu item. | | `entity` | | An optional Home Assistant entity for use with actions. | @@ -140,13 +140,13 @@ Add an arbitrary icon to the status bar. ```yaml elements: - - type: custom:frigate-card-status-bar-icon + - type: custom:advanced-camera-card-status-bar-icon # [...] ``` | Parameter | Default | Description | | ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `type` | | Must be `custom:frigate-card-status-bar-icon`. | +| `type` | | Must be `custom:advanced-camera-card-status-bar-icon`. | | `actions` | | Actions to performs when the status bar item is interacted with. See [actions](../../actions/README.md). | | `enabled` | `true` | `true` to enable this status bar item, `false` to disable. | | `exclusive` | `false` | Whether or not this item should evict non-exclusive items from the status bar. | @@ -161,13 +161,13 @@ Add an arbitrary image to the status bar. ```yaml elements: - - type: custom:frigate-card-status-bar-image + - type: custom:advanced-camera-card-status-bar-image # [...] ``` | Parameter | Default | Description | | ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `type` | | Must be `custom:frigate-card-status-bar-image`. | +| `type` | | Must be `custom:advanced-camera-card-status-bar-image`. | | `actions` | | Actions to performs when the status bar item is interacted with. See [actions](../../actions/README.md). | | `enabled` | `true` | `true` to enable this status bar item, `false` to disable. | | `exclusive` | `false` | Whether or not this item should evict non-exclusive items from the status bar. | @@ -182,13 +182,13 @@ Add an arbitrary string to the status bar. ```yaml elements: - - type: custom:frigate-card-status-bar-string + - type: custom:advanced-camera-card-status-bar-string # [...] ``` | Parameter | Default | Description | | ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `type` | | Must be `custom:frigate-card-status-bar-string`. | +| `type` | | Must be `custom:advanced-camera-card-status-bar-string`. | | `actions` | | Actions to performs when the status bar item is interacted with. See [actions](../../actions/README.md). | | `enabled` | `true` | `true` to enable this status bar item, `false` to disable. | | `exclusive` | `false` | Whether or not this item should evict non-exclusive items from the status bar. | @@ -205,7 +205,7 @@ elements: ```yaml elements: - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon alignment: matching enabled: true entity: light.office_main_lights @@ -215,7 +215,7 @@ elements: style: color: white title: Vroom - - type: custom:frigate-card-menu-state-icon + - type: custom:advanced-camera-card-menu-state-icon alignment: matching enabled: true entity: light.office_main_lights @@ -226,7 +226,7 @@ elements: style: color: white title: Office lights - - type: custom:frigate-card-menu-submenu + - type: custom:advanced-camera-card-menu-submenu alignment: matching enabled: true entity: light.office_main_lights @@ -256,7 +256,7 @@ elements: style: color: white title: Office lights - - type: custom:frigate-card-menu-submenu-select + - type: custom:advanced-camera-card-menu-submenu-select alignment: matching enabled: true entity: input_select.kitchen_scene @@ -280,7 +280,7 @@ elements: color: white title: 'Kitchen Scene' # Show a pig icon if a variety of conditions are met. - - type: custom:frigate-card-conditional + - type: custom:advanced-camera-card-conditional elements: - type: icon icon: mdi:pig @@ -321,21 +321,21 @@ elements: - condition: user users: - 581fca7fdc014b8b894519cc531f9a04 - - type: custom:frigate-card-status-bar-string + - type: custom:advanced-camera-card-status-bar-string enabled: true exclusive: false expand: false string: 'Intruder alert!' priority: 50 sufficient: false - - type: custom:frigate-card-status-bar-icon + - type: custom:advanced-camera-card-status-bar-icon enabled: true exclusive: false expand: false icon: 'mdi:cow' priority: 50 sufficient: false - - type: custom:frigate-card-status-bar-image + - type: custom:advanced-camera-card-status-bar-image enabled: true exclusive: false expand: false diff --git a/docs/configuration/elements/stock/README.md b/docs/configuration/elements/stock/README.md index 620ce56c..606495a0 100644 --- a/docs/configuration/elements/stock/README.md +++ b/docs/configuration/elements/stock/README.md @@ -20,7 +20,7 @@ elements: # [...] ``` -?> See [Frigate Card custom elements](../custom//README.md) for the custom elements offered by _this_ card. +?> See [Advanced Camera Card custom elements](../custom//README.md) for the custom elements offered by _this_ card. ## `icon` diff --git a/docs/configuration/image.md b/docs/configuration/image.md index 2727e289..0e09f08a 100644 --- a/docs/configuration/image.md +++ b/docs/configuration/image.md @@ -7,14 +7,14 @@ image: # [...] ``` -| Option | Default | Description | -| ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `actions` | | [Actions](actions/README.md) to use for the `image` view. | -| `entity` | | The entity to use when `mode` is set to `entity`. This entity is expected to have an `entity_picture` attribute that specifies the image URL. | -| `entity_parameters` | | Optional URL parameters to add to the URL generated for entity-based modes (i.e. when `mode` is `camera` or `entity`). | -| `mode` | `auto` | Value must be one of `url` (to fetch an arbitrary image URL), `camera` (to show a still of the currently selected camera entity using either `camera_entity` or `webrtc_card.entity` in that order of precedence), `entity` to show an image associated with a named entity (see the `entity` parameter below), or `screensaver` (to show an [embedded stock Frigate card logo](https://github.com/dermotduffy/frigate-hass-card/blob/main/src/images/frigate-bird-in-sky.jpg)). If `auto`, the mode is chosen automatically based on whether `url` or `entity` parameters have been specified. | -| `refresh_seconds` | 1 | The image will be refreshed at least every `refresh_seconds` (it may refresh more frequently, e.g. whenever Home Assistant updates its camera security token). `0` implies no refreshing. | -| `url` | | A static image URL to be used when the `mode` is set to `url` or when a temporary image is required (e.g. may appear momentarily prior to load of a camera snapshot in the `camera` mode). Note that a `_t=[timestsamp]` query parameter will be automatically added to all URLs such that the image will not be cached by the browser. | +| Option | Default | Description | +| ------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `actions` | | [Actions](actions/README.md) to use for the `image` view. | +| `entity` | | The entity to use when `mode` is set to `entity`. This entity is expected to have an `entity_picture` attribute that specifies the image URL. | +| `entity_parameters` | | Optional URL parameters to add to the URL generated for entity-based modes (i.e. when `mode` is `camera` or `entity`). | +| `mode` | `auto` | Value must be one of `url` (to fetch an arbitrary image URL), `camera` (to show a still of the currently selected camera entity using either `camera_entity` or `webrtc_card.entity` in that order of precedence), `entity` to show an image associated with a named entity (see the `entity` parameter below), or `screensaver` (to show an [embedded image](https://github.com/dermotduffy/advanced-camera-card/blob/main/src/images/iris-screensaver.jpg)). If `auto`, the mode is chosen automatically based on whether `url` or `entity` parameters have been specified. | +| `refresh_seconds` | 1 | The image will be refreshed at least every `refresh_seconds` (it may refresh more frequently, e.g. whenever Home Assistant updates its camera security token). `0` implies no refreshing. | +| `url` | | A static image URL to be used when the `mode` is set to `url` or when a temporary image is required (e.g. may appear momentarily prior to load of a camera snapshot in the `camera` mode). Note that a `_t=[timestsamp]` query parameter will be automatically added to all URLs such that the image will not be cached by the browser. | ?> When `mode` is set to `camera` this is effectively providing the same image as the `image` [live provider](cameras/live-provider.md) would show in the live camera carousel. diff --git a/docs/configuration/menu.md b/docs/configuration/menu.md index 43e615d9..bf74de25 100644 --- a/docs/configuration/menu.md +++ b/docs/configuration/menu.md @@ -36,9 +36,9 @@ menu: | `display_mode` | The `display_mode` button allows changing between single and grid views. | | `download` | The `download` menu button: allow direct download of the media being displayed. | | `expand` | The `expand` menu button: expand the card into a popup/dialog. | -| `frigate` | The `Frigate` menu button: brings the user to the default configured view (`view.default`), or collapses/expands the menu if the `menu.style` is `hidden` . | | `fullscreen` | The `fullscreen` menu button: expand the card to consume the fullscreen. Please note that fullscreen behavior on iPhone is limited, see [troubleshooting](../troubleshooting.md?id=fullscreen-doesn39t-work-on-iphone). | | `image` | The `image` view menu button: brings the user to the static `image` view. | +| `iris` | The main Advanced Camera Card `iris` menu button: brings the user to the default configured view (`view.default`), or collapses/expands the menu if the `menu.style` is `hidden` . | | `live` | The `live` view menu button: brings the user to the `live` view. | | `media_player` | The `media_player` menu button: sends the visible media to a remote media player. Supports Frigate clips, snapshots and live camera (only for cameras that specify a `camera_entity` and only using the default HA stream (equivalent to the `ha` live provider)). `jsmpeg` or `webrtc-card` are not supported, although live can still be played as long as `camera_entity` is specified. In the player list, a `tap` will send the media to the player, a `hold` will stop the media on the player. | | `microphone` | The `microphone` button allows usage of 2-way audio in certain configurations. See [Using 2-way audio](../usage/2-way-audio.md). | @@ -51,26 +51,26 @@ menu: ### Options for each button -| Option | Default | Description | -| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `alignment` | `matching` | Whether this menu item should have an alignment that is `matching` the menu alignment or `opposing` the menu alignment. Can be used to create two separate groups of buttons on the menu. The `priority` option orders buttons within a given `alignment`. | -| `enabled` | `true` for `frigate`, `cameras`, `substreams`, `live`, `clips`, `snapshots`, `timeline`, `download`, `camera_ui`, `fullscreen`, `media_player`, `display_mode` and `ptz_home`. `false` for `image`, `expand`, `microphone`, `mute`, `play`, `recordings`, `screenshot`, `ptz_controls` | Whether or not to show the button. | -| `icon` | | An icon to overriding the default for that button, e.g. `mdi:camera-front`. See also [custom icons](../usage/custom-icons.md). | -| `permanent` | `false` | If `false` the menu item is hidden when the menu has the `hidden` style and the menu is closed, otherwise it is shown (and sorted to the front). | -| `priority` | `50` | The menu item priority. Higher priority items are ordered closer to the start of the menu alignment (i.e. a button with priority `70` will order further to the left than a button with priority `60`). Priority applies separately to `matching` and `opposing` groups (see `alignment` above). Minimum `0`, maximum `100`. | +| Option | Default | Description | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `alignment` | `matching` | Whether this menu item should have an alignment that is `matching` the menu alignment or `opposing` the menu alignment. Can be used to create two separate groups of buttons on the menu. The `priority` option orders buttons within a given `alignment`. | +| `enabled` | `true` for `iris`, `cameras`, `substreams`, `live`, `clips`, `snapshots`, `timeline`, `download`, `camera_ui`, `fullscreen`, `media_player`, `display_mode` and `ptz_home`. `false` for `image`, `expand`, `microphone`, `mute`, `play`, `recordings`, `screenshot`, `ptz_controls` | Whether or not to show the button. | +| `icon` | | An icon to overriding the default for that button, e.g. `mdi:camera-front`. See also [custom icons](../usage/custom-icons.md). | +| `permanent` | `false` | If `false` the menu item is hidden when the menu has the `hidden` style and the menu is closed, otherwise it is shown (and sorted to the front). | +| `priority` | `50` | The menu item priority. Higher priority items are ordered closer to the start of the menu alignment (i.e. a button with priority `70` will order further to the left than a button with priority `60`). Priority applies separately to `matching` and `opposing` groups (see `alignment` above). Minimum `0`, maximum `100`. | ## `style` This card supports several menu styles. -| Key | Description | Screenshot | -| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | -| `hidden` | Hide the menu by default. It may be toggled open as needed. | ![](../images/menu-mode-hidden.png 'Menu hidden :size=400') | -| `hover-card` | Overlay the menu over the card contents when the mouse is over the **card**, otherwise it is not shown. The Frigate button shows the default view. | ![](../images/menu-mode-overlay.png 'Menu hover-card :size=400') | -| `hover` | Overlay the menu over the card contents when the mouse is over the **menu**, otherwise it is not shown. The Frigate button shows the default view. | ![](../images/menu-mode-overlay.png 'Menu hover :size=400') | -| `none` | No menu is shown. | ![](../images/menu-mode-none.png 'No menu :size=400') | -| `outside` | Render the menu outside the card (i.e. above it if `position` is `top`, or below it if `position` is `bottom`). The Frigate button shows the default view. | ![](../images/menu-mode-above.png 'Menu outside :size=400') | -| `overlay` | Overlay the menu over the card contents. The Frigate button shows the default view. | ![](../images/menu-mode-overlay.png 'Menu hidden :size=400') | +| Key | Description | Screenshot | +| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | +| `hidden` | Hide the menu by default. It may be toggled open as needed with the `iris` button. | ![](../images/menu-mode-hidden.png 'Menu hidden :size=400') | +| `hover-card` | Overlay the menu over the card contents when the mouse is over the **card**, otherwise it is not shown. The `iris` button shows the default view. | ![](../images/menu-mode-overlay.png 'Menu hover-card :size=400') | +| `hover` | Overlay the menu over the card contents when the mouse is over the **menu**, otherwise it is not shown. The `iris` button shows the default view. | ![](../images/menu-mode-overlay.png 'Menu hover :size=400') | +| `none` | No menu is shown. | ![](../images/menu-mode-none.png 'No menu :size=400') | +| `outside` | Render the menu outside the card (i.e. above it if `position` is `top`, or below it if `position` is `bottom`). The `iris` button shows the default view. | ![](../images/menu-mode-above.png 'Menu outside :size=400') | +| `overlay` | Overlay the menu over the card contents. The `iris` button shows the default view. | ![](../images/menu-mode-overlay.png 'Menu hidden :size=400') | ## Fully expanded reference @@ -80,13 +80,11 @@ This card supports several menu styles. menu: alignment: left buttons: - frigate: + iris: priority: 50 enabled: true alignment: matching - # Default icon is an internal coded Frigate icon. Note - # absence of 'mdi' here (mdi has no Frigate icon). - icon: frigate + icon: iris cameras: priority: 50 enabled: true diff --git a/docs/configuration/profiles.md b/docs/configuration/profiles.md index 4baa2cd7..aca95c29 100644 --- a/docs/configuration/profiles.md +++ b/docs/configuration/profiles.md @@ -39,13 +39,13 @@ Principles used in the selection of options set by `low-performance` profile mod - Get 'out of the box' performance similar to the basic "Home Assistant Picture Glance" card. - Do not break the visual aesthetic of the card. -See the [source code](https://github.com/dermotduffy/frigate-hass-card/blob/main/src/config/profiles/low-performance.ts) for an exhaustive list of defaults set by this profile. +See the [source code](https://github.com/dermotduffy/advanced-camera-card/blob/main/src/config/profiles/low-performance.ts) for an exhaustive list of defaults set by this profile. ## `scrubbing` Configures the `live` view and media viewer to allow media "scrubbing" as the timeline is dragged back and forth. -See the [source code](https://github.com/dermotduffy/frigate-hass-card/blob/main/src/config/profiles/scrubbing.ts) for an exhaustive list of options set by this profile. +See the [source code](https://github.com/dermotduffy/advanced-camera-card/blob/main/src/config/profiles/scrubbing.ts) for an exhaustive list of options set by this profile. ## Fully expanded reference diff --git a/docs/configuration/view.md b/docs/configuration/view.md index 645b7b10..6ef695ef 100644 --- a/docs/configuration/view.md +++ b/docs/configuration/view.md @@ -7,20 +7,20 @@ view: # [...] ``` -| Option | Default | Description | -| ------------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `actions` | | [Actions](actions/README.md) to use for all views, individual actions may be overriden by view-specific actions. | -| `camera_select` | `current` | The [view](view.md?id=supported-views) to show when a new camera is selected (e.g. in the camera menu). If `current` the view is unchanged when a new camera is selected. | -| `dim` | `false` | Whether or not to 'dim' the brightness of the card (by 25%) if the card `interaction_seconds` has expired (i.e. card has been left unattended for that period of time). | -| `default` | `live` | The view to show in the card by default. The default camera is the first one listed. See [Supported Views](view.md?id=supported-views) below. | -| `default_reset` | | The circumstances and behavior that cause the card to reset to the default view. See below. | -| `interaction_seconds` | `300` | After a mouse/touch interaction with the card, it will be considered "interacted with" until this number of seconds elapses without further interaction. May be used as part of an [interaction condition](conditions.md?id=interaction) or with `reset_after_interaction` to reset the view after the interaction is complete. | -| `keyboard_shortcuts` | See [usage](../usage/keyboard-shortcuts.md) for defaults. | Configure keyboard shortcuts. See below. | -| `render_entities` | | **YAML only**: A list of entity ids that should cause the card to re-render 'in-place'. The view/camera is not changed. This should **very** rarely be needed, but could be useful if the card is both setting and changing HA state of the same object as could be the case for some complex `card_mod` scenarios ([example](https://github.com/dermotduffy/frigate-hass-card/issues/343)). | -| `reset_after_interaction` | `true` | If `true` the card will reset to the default configured view (i.e. 'screensaver' functionality) after `interaction_seconds` has elapsed after user interaction. | -| `theme` | | How the card is themed. See below. | -| `triggers` | | How to react when a camera is [triggered](cameras/README.md?id=triggers). | -| `default_cycle_camera` | `false` | When set to `true` the selected camera is cycled on each default view change. | +| Option | Default | Description | +| ------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `actions` | | [Actions](actions/README.md) to use for all views, individual actions may be overriden by view-specific actions. | +| `camera_select` | `current` | The [view](view.md?id=supported-views) to show when a new camera is selected (e.g. in the camera menu). If `current` the view is unchanged when a new camera is selected. | +| `dim` | `false` | Whether or not to 'dim' the brightness of the card (by 25%) if the card `interaction_seconds` has expired (i.e. card has been left unattended for that period of time). | +| `default` | `live` | The view to show in the card by default. The default camera is the first one listed. See [Supported Views](view.md?id=supported-views) below. | +| `default_reset` | | The circumstances and behavior that cause the card to reset to the default view. See below. | +| `interaction_seconds` | `300` | After a mouse/touch interaction with the card, it will be considered "interacted with" until this number of seconds elapses without further interaction. May be used as part of an [interaction condition](conditions.md?id=interaction) or with `reset_after_interaction` to reset the view after the interaction is complete. | +| `keyboard_shortcuts` | See [usage](../usage/keyboard-shortcuts.md) for defaults. | Configure keyboard shortcuts. See below. | +| `render_entities` | | **YAML only**: A list of entity ids that should cause the card to re-render 'in-place'. The view/camera is not changed. This should **very** rarely be needed, but could be useful if the card is both setting and changing HA state of the same object as could be the case for some complex `card_mod` scenarios ([example](https://github.com/dermotduffy/advanced-camera-card/issues/343)). | +| `reset_after_interaction` | `true` | If `true` the card will reset to the default configured view (i.e. 'screensaver' functionality) after `interaction_seconds` has elapsed after user interaction. | +| `theme` | | How the card is themed. See below. | +| `triggers` | | How to react when a camera is [triggered](cameras/README.md?id=triggers). | +| `default_cycle_camera` | `false` | When set to `true` the selected camera is cycled on each default view change. | ## `default_reset` @@ -87,15 +87,15 @@ Configure the theming/colors applied to the card. | `dark` | Use a dark theme that is identical to the HA dark theme. | | `ha` | Uses HA-prescribed theming. Respects HA choice of dark or light colors. | | `light` | Use a light theme that is similar to the HA light theme (there are some differences if you do not use the standard choices of primary color). | -| `traditional` | A theme based on the default Frigate card theme before full theming support was added. Respects HA color theme choices. | +| `traditional` | A theme based on the default Advanced Camera Card theme before full theming support was added. Respects HA color theme choices. | ### `overrides` Allows overriding of any CSS value, can be used to tweak theming parameters. -| Option | Description | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | -| Any CSS key. Overriding the [Frigate Card](https://github.com/dermotduffy/frigate-hass-card/tree/main/src/scss/themes/base.scss) CSS variables allows changing individual theming paramters, e.g. `--frigate-card-menu-override-background` | Any CSS value, e.g. `red` or `rgba(10, 11, 12, 0.64)`. | +| Option | Description | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | +| Any CSS key. Overriding the [Advanced Camera Card](https://github.com/dermotduffy/advanced-camera-card/tree/main/src/scss/themes/base.scss) CSS variables allows changing individual theming paramters, e.g. `--advanced-camera-card-menu-override-background` | Any CSS value, e.g. `red` or `rgba(10, 11, 12, 0.64)`. | ## `triggers` @@ -199,8 +199,8 @@ view: themes: - ha overrides: - '--frigate-card-menu-button-active-color': red - '--frigate-card-menu-position-left-style-overlay-alignment-left-background': pink + '--advanced-camera-card-menu-button-active-color': red + '--advanced-camera-card-menu-position-left-style-overlay-alignment-left-background': pink actions: entity: light.office_main_lights tap_action: diff --git a/docs/developing.md b/docs/developing.md index 9702f1a6..097525eb 100644 --- a/docs/developing.md +++ b/docs/developing.md @@ -1,6 +1,6 @@ # Developing -?> Want to contribute? Development help [**welcome**](https://github.com/dermotduffy/frigate-hass-card/issues/1248)! +?> Want to contribute? Development help [**welcome**](https://github.com/dermotduffy/advanced-camera-card/issues/1248)! ## Building @@ -13,13 +13,13 @@ However, if you are not using Volta, you can check the `volta` key in the `package.json` to see which version of Node and Yarn should be used. ```sh -$ git clone https://github.com/dermotduffy/frigate-hass-card -$ cd frigate-hass-card +$ git clone https://github.com/dermotduffy/advanced-camera-card +$ cd advanced-camera-card $ yarn install $ yarn run build ``` -Resultant build entry file will be in `dist/frigate-hass-card.js`. This could be +Resultant build entry file will be in `dist/advanced-camera-card.js`. This could be installed via the [manual installation instructions](advanced-installation.md?id=manual-installation). @@ -38,7 +38,7 @@ Releases follow [Semantic Versioning](https://semver.org/) with the following de ### Manual Releases 1. Merge a PR that contains only a `package.json` version number bump. -1. Go to the [releases page](https://github.com/dermotduffy/frigate-hass-card/releases). +1. Go to the [releases page](https://github.com/dermotduffy/advanced-camera-card/releases). 1. A release draft will automatically have been created, click 'Edit'. 1. Use the same version number for the release title and tag. 1. Choose 'This is a pre-release' for a beta version. @@ -46,14 +46,14 @@ Releases follow [Semantic Versioning](https://semver.org/) with the following de ## Translations -[![translation badge](https://badge.inlang.com/?url=github.com/dermotduffy/frigate-hass-card)](https://fink.inlang.com/github.com/dermotduffy/frigate-hass-card?ref=badge) +[![translation badge](https://badge.inlang.com/?url=github.com/dermotduffy/advanced-camera-card)](https://fink.inlang.com/github.com/dermotduffy/advanced-camera-card?ref=badge) To add translations, you can manually edit the JSON translation files in `src/localize/languages` or use the [inlang](https://inlang.com/) online editor. ## Using a dev container -[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/dermotduffy/frigate-hass-card) +[![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/dermotduffy/advanced-camera-card) You can use the [VS Code Dev Containers](https://code.visualstudio.com/docs/remote/containers) extension to speed up the development environment creation. Simply: diff --git a/docs/examples.md b/docs/examples.md index 12f4c748..377dc77c 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -9,14 +9,14 @@ into fullscreen mode, **except** when the view is `live` in which case the office lights are toggled. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office view: actions: double_tap_action: - action: custom:frigate-card-action - frigate_card_action: fullscreen + action: custom:advanced-camera-card-action + advanced_camera_card_action: fullscreen live: actions: entity: light.office_main_lights @@ -27,7 +27,7 @@ live: ## Aspect ratios ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office dimensions: @@ -44,7 +44,7 @@ card is put in fullscreen mode, and turn off the substream when exiting fullscreen mode. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: image @@ -63,11 +63,11 @@ automations: - condition: fullscreen fullscreen: true actions: - - action: custom:frigate-card-action - frigate_card_action: live_substream_on + - action: custom:advanced-camera-card-action + advanced_camera_card_action: live_substream_on actions_not: - - action: custom:frigate-card-action - frigate_card_action: live_substream_off + - action: custom:advanced-camera-card-action + advanced_camera_card_action: live_substream_off ``` ### Responding to key input @@ -85,14 +85,14 @@ automations: key: z alt: true actions: - - action: custom:frigate-card-action - frigate_card_action: live - - action: custom:frigate-card-action - frigate_card_action: sleep + - action: custom:advanced-camera-card-action + advanced_camera_card_action: live + - action: custom:advanced-camera-card-action + advanced_camera_card_action: sleep duration: s: 5 - - action: custom:frigate-card-action - frigate_card_action: clips + - action: custom:advanced-camera-card-action + advanced_camera_card_action: clips ``` #### Change to `live` while key _held_ down @@ -106,11 +106,11 @@ automations: key: z alt: true actions: - - action: custom:frigate-card-action - frigate_card_action: live + - action: custom:advanced-camera-card-action + advanced_camera_card_action: live actions_not: - - action: custom:frigate-card-action - frigate_card_action: clips + - action: custom:advanced-camera-card-action + advanced_camera_card_action: clips ``` ## Cameras @@ -120,7 +120,7 @@ automations: In this example, there is no Home Assistant entity linked to the camera, just a `frigate` camera name: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - frigate: camera_name: office @@ -132,7 +132,7 @@ cameras: In this example, there is no Home Assistant entity linked to the camera, just a `go2rtc` stream. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - live_provider: go2rtc go2rtc: @@ -158,12 +158,12 @@ Elements state label](https://www.home-assistant.io/lovelace/picture-elements/#state-label). ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office card_mod: style: - frigate-card-elements $: + advanced-camera-card-elements $: hui-state-label-element $: | div { padding: 0px !important; @@ -173,12 +173,12 @@ card_mod: ## Cast a `dashboard` -This example will configure a Frigate card that can cast a dashboard view to a media player, which has a second Frigate card in panel mode with a low-latency live provider. +This example will configure an Advanced Camera Card that can cast a dashboard view to a media player, which has a second Advanced Camera Card in panel mode with a low-latency live provider. ### Source card ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: image @@ -194,7 +194,7 @@ cameras: ?> This dashboard is configured at the path `/cast/` (path referred to in `dashboard_path` above). ```yaml -title: Frigate Card Casting +title: Advanced Camera Card Casting views: - title: Casting # This path is referred to in `view_path` above. @@ -202,7 +202,7 @@ views: # Ensure the video is "maximized" / dashboard in "panel" mode. type: panel cards: - - type: custom:frigate-card + - type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: go2rtc @@ -212,15 +212,15 @@ views: You can restrict elements to only show for certain [views](configuration/view.md?id=supported-views) using a -`custom:frigate-card-conditional` element. This example shows a car icon that +`custom:advanced-camera-card-conditional` element. This example shows a car icon that calls a service but only in the `live` view. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: - - type: custom:frigate-card-conditional + - type: custom:advanced-camera-card-conditional conditions: - condition: view views: @@ -248,7 +248,7 @@ You can have icons conditionally added to the menu based on entity state. ### Show a menu icon based on state ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: @@ -258,7 +258,7 @@ elements: entity: light.office_main_lights state: on elements: - - type: custom:frigate-card-menu-state-icon + - type: custom:advanced-camera-card-menu-state-icon entity: light.office tap_action: action: toggle @@ -269,13 +269,13 @@ elements: This example adds a menu button to optionally activate a siren when the camera is triggered. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: - - type: custom:frigate-card-conditional + - type: custom:advanced-camera-card-conditional elements: - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:alarm-bell title: Activate alarm style: @@ -308,7 +308,7 @@ events with the `birdseye` camera (since it will not have events of its own). This example shows events for two other cameras when `birdseye` is selected. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office - camera_entity: camera.kitchen @@ -326,7 +326,7 @@ This example shows events for _all_ other cameras when `birdseye` is selected. This is just a shortcut for naming all other cameras. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.kitchen - camera_entity: camera.sitting_room @@ -342,7 +342,7 @@ This example will automatically use a HD live substream when the mouse cursor interacts with the card. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: image @@ -356,11 +356,11 @@ cameras: - substream automations: - actions: - - action: custom:frigate-card-action - frigate_card_action: live_substream_on + - action: custom:advanced-camera-card-action + advanced_camera_card_action: live_substream_on actions_not: - - action: custom:frigate-card-action - frigate_card_action: live_substream_off + - action: custom:advanced-camera-card-action + advanced_camera_card_action: live_substream_off conditions: - condition: interaction interaction: true @@ -373,7 +373,7 @@ These examples change how the media fits and is positioned within the card dimen ### Stretch a camera into a 4:4 square ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.landing dimensions: @@ -387,7 +387,7 @@ cameras: Take the left-hand side (position with x == `0`) and use that as the basis of a `9:16` (i.e. portrait) live view. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office dimensions: @@ -404,7 +404,7 @@ This example moves the fullscreen button into its own group aligned to the `left`, enables the `image` button and orders it furthest to the `right`. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office menu: @@ -423,15 +423,15 @@ You can add custom icons to the menu with arbitrary actions. This example adds an icon that navigates the browser to the releases page for this card: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:book tap_action: action: url - url_path: https://github.com/dermotduffy/frigate-hass-card/releases + url_path: https://github.com/dermotduffy/advanced-camera-card/releases ``` ## Menu state icons @@ -442,11 +442,11 @@ of the `light.office_main_lights` entity, that toggles the light on double click. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: - - type: custom:frigate-card-menu-state-icon + - type: custom:advanced-camera-card-menu-state-icon entity: light.office_main_lights tap_action: action: toggle @@ -454,21 +454,21 @@ elements: ## Multiple actions -This example shows how to configure multiple actions for a single Frigate card user interaction, in this case both selecting a different camera and changing the view on `tap`. Note that multiple actions are not supported on stock Picture Elements, see [actions](configuration/actions/README.md) for more information. +This example shows how to configure multiple actions for a single Advanced Camera Card user interaction, in this case both selecting a different camera and changing the view on `tap`. Note that multiple actions are not supported on stock Picture Elements, see [actions](configuration/actions/README.md) for more information. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: - - type: custom:frigate-card-menu-icon + - type: custom:advanced-camera-card-menu-icon icon: mdi:chair-rolling tap_action: - - action: custom:frigate-card-action - frigate_card_action: camera_select + - action: custom:advanced-camera-card-action + advanced_camera_card_action: camera_select camera: camera.office - - action: custom:frigate-card-action - frigate_card_action: live + - action: custom:advanced-camera-card-action + advanced_camera_card_action: live ``` ## Multiple providers @@ -478,7 +478,7 @@ to provide a separate unambiguous way of referring to that camera, since the `camera_entity` is shared between the two cameras). ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: jsmpeg @@ -496,7 +496,7 @@ You can override card configuration when certain [conditions](configuration/cond ### Change menu position based on HA state ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office overrides: @@ -519,7 +519,7 @@ ask the card to trigger a card update based on that entity -- which causes it to use the new overriden default immediately. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office view: @@ -543,7 +543,7 @@ This example will always render 5 columns in fullscreen mode in both the live and media viewer views, and will not enlarge the selected item. The [normal auto-layout behavior](configuration/grid-layout-algorithm.md) will be used outside of fullscreen mode. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office overrides: @@ -569,7 +569,7 @@ This example changes the menu style to `overlay` in expanded mode in order to take advantage of the extra horizontal space of the dialog/popup. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office menu: @@ -589,14 +589,14 @@ This example disables the menu unless the card is in fullscreen mode, and uses a card-wide action to enable fullscreen mode on `double_tap`. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office view: actions: double_tap_action: - action: custom:frigate-card-action - frigate_card_action: fullscreen + action: custom:advanced-camera-card-action + advanced_camera_card_action: fullscreen overrides: - conditions: - condition: fullscreen @@ -611,7 +611,7 @@ overrides: This example removes a camera from the card when an entity is disabled (e.g. a switch controlling power to the camera). ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office - camera_entity: camera.kitchen @@ -629,7 +629,7 @@ overrides: This example disables the PTZ controls when the card is viewed on the Companion app: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office overrides: @@ -644,10 +644,11 @@ overrides: The card supports using PTZ controls to conveniently control pan, tilt and zoom for cameras. If you're using a Frigate camera, and Frigate itself shows PTZ -controls, this should work straight out of the box without any extra configuration: +controls, this should work straight out of the box without any extra +configuration: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office ``` @@ -657,7 +658,7 @@ controls on your camera but Home Assistant does, you can still manually configure actions for the card to perform for each PTZ control: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office ptz: @@ -677,7 +678,7 @@ These examples show altering the card configuration based on device or viewport ### Change menu position when orientation changes ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office - camera_entity: camera.kitchen @@ -695,7 +696,7 @@ overrides: ### Hide menu & controls when viewport width <= 300 (e.g. PIP mode) ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office - camera_entity: camera.kitchen @@ -720,7 +721,7 @@ You can add a state badge to the card showing arbitrary entity states. This example adds a state badge showing the temperature and hides the label text: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: @@ -740,7 +741,7 @@ elements: This example fetches a static image every 10 seconds (in this case the latest image saved on the Frigate server for a given camera). ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office view: @@ -757,7 +758,7 @@ image: This example disables the status bar. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office status_bar: @@ -771,7 +772,7 @@ This example shows an icon and a message on the status bar when a camera is trig ![Dynamic Status Messages](images/dynamic-status.gif 'Dynamic Status Messages :size=400') ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office automations: @@ -780,21 +781,21 @@ automations: triggered: - camera.office actions: - - action: custom:frigate-card-action - frigate_card_action: status_bar + - action: custom:advanced-camera-card-action + advanced_camera_card_action: status_bar status_bar_action: add items: - - type: custom:frigate-card-status-bar-icon + - type: custom:advanced-camera-card-status-bar-icon icon: mdi:alarm-light exclusive: true - - type: custom:frigate-card-status-bar-string + - type: custom:advanced-camera-card-status-bar-string string: Intruder detected! expand: true exclusive: true sufficient: true actions_not: - - action: custom:frigate-card-action - frigate_card_action: status_bar + - action: custom:advanced-camera-card-action + advanced_camera_card_action: status_bar status_bar_action: reset ``` @@ -805,11 +806,11 @@ You can add submenus to the menu -- buttons that when pressed reveal a dropdown ### Basic submenu ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: - - type: custom:frigate-card-menu-submenu + - type: custom:advanced-camera-card-menu-submenu icon: mdi:menu items: - title: Lights @@ -825,8 +826,8 @@ elements: - title: Fullscreen icon: mdi:fullscreen tap_action: - action: custom:frigate-card-action - frigate_card_action: fullscreen + action: custom:advanced-camera-card-action + advanced_camera_card_action: fullscreen ``` ### Conditional submenu @@ -834,17 +835,17 @@ elements: This example shows submenus conditional on the camera selected. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: - - type: custom:frigate-card-conditional + - type: custom:advanced-camera-card-conditional conditions: - condition: camera cameras: - camera.office elements: - - type: custom:frigate-card-menu-submenu + - type: custom:advanced-camera-card-menu-submenu icon: mdi:door items: - title: Office Lights @@ -852,13 +853,13 @@ elements: entity: light.office_main_lights tap_action: action: toggle - - type: custom:frigate-card-conditional + - type: custom:advanced-camera-card-conditional conditions: - condition: camera cameras: - camera.kitchen elements: - - type: custom:frigate-card-menu-submenu + - type: custom:advanced-camera-card-menu-submenu icon: mdi:sofa items: - title: Kitchen Lights @@ -890,22 +891,22 @@ input_select: The following will convert this entity into a submenu: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: - - type: custom:frigate-card-menu-submenu-select + - type: custom:advanced-camera-card-menu-submenu-select entity: input_select.office_scene ``` To override 1 or more individual options (e.g. to set custom icons and titles) ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: - - type: custom:frigate-card-menu-submenu-select + - type: custom:advanced-camera-card-menu-submenu-select icon: mdi:lamps entity: input_select.office_scene options: @@ -925,7 +926,7 @@ camera through the use of [camera dependencies](configuration/cameras/README.md? This example shows two substreams for a single live camera, and uses the 'HD' icon. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: image @@ -948,11 +949,11 @@ menu: ## Trigger actions -You can control the card itself with the `custom:frigate-card-action` action. +You can control the card itself with the `custom:advanced-camera-card-action` action. This example shows an icon that toggles the card fullscreen mode. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office elements: @@ -962,8 +963,8 @@ elements: left: 40px top: 40px tap_action: - action: custom:frigate-card-action - frigate_card_action: fullscreen + action: custom:advanced-camera-card-action + advanced_camera_card_action: fullscreen ``` ## Trigger fullscreen @@ -999,7 +1000,7 @@ action: size: wide timeout: 15000 content: - type: custom:frigate-card + type: custom:advanced-camera-card aspect_ratio: 55% cameras: - camera_entity: camera.frontdoor @@ -1023,7 +1024,7 @@ trigger conditions per camera. It will change back to the `default` view when untriggered. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: # This is a Frigate camera which will automatically # be triggered when events occur. @@ -1051,7 +1052,7 @@ view: Disable the stock video controls and add menu button equivalents. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live: @@ -1076,27 +1077,27 @@ The card can respond to actions in the query string. See [URL Actions](usage/url ### Choosing `clips` view on a named card -This example assumes that one card (of potentially multiple Frigate Cards on the dashboard) is configured with a `card_id` parameter: +This example assumes that one card (of potentially multiple Advanced Camera Cards on the dashboard) is configured with a `card_id` parameter: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office card_id: main ``` ```text -https://ha.mydomain.org/lovelace-test/0?frigate-card-action.main.clips +https://ha.mydomain.org/lovelace-test/0?advanced_camera_card_action-action.main.clips ``` ### Choosing the camera from a separate picture elements card In this example, the card will select a given camera when the user navigates from a _separate_ Picture Elements card: -Frigate Card configuration: +Advanced Camera Card configuration: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office - camera_entity: camera.kitchen @@ -1115,7 +1116,7 @@ elements: left: 30% tap_action: action: navigate - navigation_path: /lovelace-test/0?frigate-card-action.camera_select=camera.office + navigation_path: /lovelace-test/0?advanced_camera_card_action-action.camera_select=camera.office - type: icon icon: mdi:cctv style: @@ -1123,7 +1124,7 @@ elements: left: 42% tap_action: action: navigate - navigation_path: /lovelace-test/0?frigate-card-action.camera_select=camera.kitchen + navigation_path: /lovelace-test/0?advanced_camera_card_action-action.camera_select=camera.kitchen ``` ![Taking card actions via the URL](images/navigate-picture-elements.gif 'Taking card actions via the URL :size=400') @@ -1131,13 +1132,13 @@ elements: ### Selecting a camera in expanded mode via query string ```text -https://ha.mydomain.org/lovelace-test/0?frigate-card-action.camera_select=kitchen&frigate-card-action.expand +https://ha.mydomain.org/lovelace-test/0?advanced_camera_card_action-action.camera_select=kitchen&advanced_camera_card_action-action.expand ``` ## WebRTC Card configuration ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: webrtc-card @@ -1152,7 +1153,7 @@ cameras: This example changes the default [zoom/pan settings for a camera](./configuration/cameras/README.md?id=layout-configuration) to always zoom in on a given area: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office dimensions: @@ -1168,7 +1169,7 @@ cameras: This example prevents zooming on the media viewer but keeps it on in other views (e.g. `live` view): ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office dimensions: @@ -1186,7 +1187,7 @@ media_viewer: This example uses different settings for the media viewer and `live` view, by overriding the camera configuration: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office dimensions: @@ -1210,7 +1211,7 @@ overrides: This example automatically zooms in and out based on the state of an entity: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.living_room live_provider: go2rtc @@ -1222,8 +1223,8 @@ automations: entity: binary_sensor.door_contact state: 'on' actions: - - action: custom:frigate-card-action - frigate_card_action: ptz_digital + - action: custom:advanced-camera-card-action + advanced_camera_card_action: ptz_digital target_id: camera.living_room absolute: zoom: 4 @@ -1231,8 +1232,8 @@ automations: x: 38 y: 20 actions_not: - - action: custom:frigate-card-action - frigate_card_action: ptz_digital + - action: custom:advanced-camera-card-action + advanced_camera_card_action: ptz_digital target_id: camera.living_room ``` diff --git a/docs/images/advanced-camera-card.png b/docs/images/advanced-camera-card.png new file mode 100644 index 0000000000000000000000000000000000000000..7c234da4fe8c8a131dd35d1aed2a98c1bb5f4923 GIT binary patch literal 252956 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 JPDHLkV1kcYblm^| literal 0 HcmV?d00001 diff --git a/docs/index.html b/docs/index.html index bc9cfc82..0f9bce14 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,9 +2,9 @@ - Frigate Card + Advanced Camera Card - + :root { --content-max-width: 95vw; + --cover-heading-font-size-max: 48; } .svg-icon { background-color: rgb(255, 255, 255); @@ -34,6 +35,9 @@ background: repeating-conic-gradient(#80808091 0% 25%, transparent 0% 50%) 50% / 4px 4px; } + #__sidebar img { + max-width: 100px; + } @@ -43,12 +47,13 @@ // Full reference: https://docsify.js.org/#/configuration // The repo to link to. - repo: 'dermotduffy/frigate-hass-card', + repo: 'dermotduffy/advanced-camera-card', // The overall documentation name. - name: 'Frigate Card', + name: 'Advanced Camera Card', - logo: 'images/icons/frigate.svg', + // The logo to show on the top of the sidebar. + logo: 'images/icons/iris.svg', // Automatically go to the top of a page on route change. auto2top: true, @@ -59,7 +64,7 @@ // Automatically use headings up to H2 for sider. subMaxLevel: 2, - // Show the Frigate Card cover page. + // Show the Advanced Camera Card cover page. coverpage: true, // Use relative links (this way both vscode and docsify work on clicking diff --git a/docs/rolling-back.md b/docs/rolling-back.md index 13014f89..ce4ebb64 100644 --- a/docs/rolling-back.md +++ b/docs/rolling-back.md @@ -13,6 +13,8 @@ There may be many versions of this card (see [release philosophy](./developing.m - Click "Download" - Reload your browser +[](./common/rename-hacs.md ':include') + ### Rolling Back Manually If you didn't use `HACS` to install the card, you're on your own to roll it diff --git a/docs/support.md b/docs/support.md index e32eeaf3..6de4863f 100644 --- a/docs/support.md +++ b/docs/support.md @@ -19,7 +19,7 @@ incrementally improve, and I think time is better spent working on reproducible issues or new features, than guessing what versions/options you are using. The new issue template asks for the minimum possible information to support you: -- **Diagnostics**: On the card, hold down the Default/Frigate menu button for a +- **Diagnostics**: On the card, hold down the Iris (default) menu button for a few seconds then paste the diagnostics into the issue between the backticks. - **Description of the problem**: I need to understand the issue in sufficient detail. @@ -44,7 +44,7 @@ fixes should be provided, or new features developed, is misunderstanding the fundamental dynamics of open source. I am sorry you feel this way, but open source software (including this card) is typically provided "as-is". Take it or leave it. If you want things to go faster / differently, [please -contribute](https://github.com/dermotduffy/frigate-hass-card/issues/1248). If +contribute](https://github.com/dermotduffy/advanced-camera-card/issues/1248). If you don't want to contribute to the shared codebase, fork it and make your own. Or pay for a (different) Enterprise grade solution to your problem. This is certainly not that -- a card, developed in hobbiest-level time, with diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index fab54452..fc4881dd 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -36,7 +36,7 @@ Debugging broken streams: ensure [you have configured Frigate accordingly](https://docs.frigate.video/configuration/live/#webrtc-extra-configuration). 1. Search for your symptoms on the [card issues - page](https://github.com/dermotduffy/frigate-hass-card/issues) and see if + page](https://github.com/dermotduffy/advanced-camera-card/issues) and see if you find any prior relevant discussions. If you're happy with just using an image stream but want the small circle to go @@ -81,7 +81,7 @@ live: disableGl: true ``` -[This bug](https://github.com/dermotduffy/frigate-hass-card/issues/191) has some +[This bug](https://github.com/dermotduffy/advanced-camera-card/issues/191) has some more discussion on this topic. New ideas to address this underlying limitation most welcome! @@ -97,7 +97,7 @@ by the card. Other browsers (e.g. Firefox, Safari) do not exhibit this behavior. For some slowly loading cameras, for which [Home Assistant stream preloading](https://www.home-assistant.io/integrations/camera/) is not enabled, Home Assistant may return a blank white image when asked for a still. These -stills are used during initial Frigate card load of the `live` view if the +stills are used during initial Advanced Camera Card load of the `live` view if the `live.show_image_during_load` option is enabled. Disabling this option should show the default media loading controls (e.g. a spinner or empty video player) instead of the blank white image. @@ -130,7 +130,7 @@ The Android video player swallows `double_tap` interactions in order to rewind or fast-forward. Workarounds: - Use `hold` instead of `double_tap` for your card-wide action. -- Use a [Frigate Card Element](configuration/elements/README.md) or menu icon to +- Use an [Advanced Camera Card Element](configuration/elements/README.md) or menu icon to trigger the action instead. ### Dragging in carousels broken in Firefox @@ -252,10 +252,10 @@ status_bar: [AlexxIT's WebRTC Card](https://github.com/AlexxIT/WebRTC) which is embedded by the `webrtc_card` live provider internally disconnects the stream when the -browser tab is changed (regardless of any Frigate card configuration settings, +browser tab is changed (regardless of any Advanced Camera Card configuration settings, e.g. `lazy_unload`). To allow the stream to continue running in the background, pass the `background` argument to the `webrtc_card` live provider as shown -below. This effectively allows the Frigate card to decide whether or not to +below. This effectively allows the Advanced Camera Card to decide whether or not to unload the stream. ```yaml diff --git a/docs/usage/2-way-audio.md b/docs/usage/2-way-audio.md index f0877132..ed26cd3c 100644 --- a/docs/usage/2-way-audio.md +++ b/docs/usage/2-way-audio.md @@ -23,7 +23,7 @@ challenging. ## Example configuration ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: go2rtc diff --git a/docs/usage/casting.md b/docs/usage/casting.md index 8a92b5d3..75404b93 100644 --- a/docs/usage/casting.md +++ b/docs/usage/casting.md @@ -3,7 +3,7 @@ This card allows casting a camera stream to a Google Cast device. You can enable the cast button with: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: go2rtc @@ -21,7 +21,7 @@ There are two different casting methods for cameras: `standard` and `dashboard` This is the default casting method. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: go2rtc @@ -41,7 +41,7 @@ The main disadvantage of this method is that the stream will be played through H This method is powered by [Home Assistant Cast](https://cast.home-assistant.io) and requires additional configuration, but allows **low latency streaming** with go2rtc. ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: go2rtc @@ -68,7 +68,7 @@ For the example above to work, a dashboard with `dashboard-cast` URL must exist: ![image](https://github.com/user-attachments/assets/67f0e145-df05-412a-8c6d-897feb5439d2) -Within that dashboard, create an `office` view and finally within that view you can place the Frigate card configured for that camera for a proper full-screen casting experience: +Within that dashboard, create an `office` view and finally within that view you can place the Advanced Camera Card configured for that camera for a proper full-screen casting experience: ```yaml views: @@ -76,7 +76,7 @@ views: type: panel path: office cards: - - type: custom:frigate-card + - type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: go2rtc @@ -92,7 +92,7 @@ The `casting` profile pre-configures the card to be casted to a 16:9 screen (suc When casting to a Google Nest Hub, the following configuration can be used: ```yaml -type: custom:frigate-card +type: custom:advanced-camera-card cameras: - camera_entity: camera.office live_provider: go2rtc diff --git a/docs/usage/url-actions.md b/docs/usage/url-actions.md index 291a41da..bc683039 100644 --- a/docs/usage/url-actions.md +++ b/docs/usage/url-actions.md @@ -1,12 +1,12 @@ # URL Actions -It is possible to pass the Frigate card one or more +It is possible to pass the Advanced Camera Card one or more [actions](../configuration/actions/README.md) from the URL (e.g. select a particular camera, open the live view in expanded mode, etc). ### When actions are executed -The Frigate card will execute these actions in the following circumstances: +The Advanced Camera Card will execute these actions in the following circumstances: - On initial card load. - On 'tab' change in a dashboard. @@ -17,28 +17,30 @@ The Frigate card will execute these actions in the following circumstances: ## Instructions -To send an action to _all_ Frigate Cards on a dashboard: +To send an action to _all_ Advanced Camera Cards on a dashboard: ``` -[PATH_TO_YOUR_HA_DASHBOARD]?frigate-card-action.[ACTION]=[VALUE] +[PATH_TO_YOUR_HA_DASHBOARD]?advanced-camera-card-action.[ACTION]=[VALUE] ``` -To send an action to a specific named Frigate Card: +To send an action to a specific named Advanced Camera Card: ``` -[PATH_TO_YOUR_HA_DASHBOARD]?frigate-card-action.[CARD_ID].[ACTION]=[VALUE] +[PATH_TO_YOUR_HA_DASHBOARD]?advanced-camera-card-action.[CARD_ID].[ACTION]=[VALUE] ``` +?> For backwards compatibility, the card will also respond to `frigate-card-action` as a query-parameter with the same syntax. + | Parameter | Description | | --------- | ------------------------------------------------------------------------------------------------- | -| `ACTION` | One of the supported Frigate Card custom actions. See below. | +| `ACTION` | One of the supported Advanced Camera Card custom actions. See below. | | `CARD_ID` | When specified only cards that have a [`card_id`](../configuration/README.md) parameter will act. | | `VALUE` | An optional value to use with the `camera_select` and `live_substream_select` actions. | ?> Both `.` and `:` may be used as the delimiter. If you use `:` some browsers may require it be escaped to `%3A`. -!> If a dashboard has multiple Frigate cards on it, even if they are on +!> If a dashboard has multiple Advanced Camera Cards on it, even if they are on different 'tabs' within that dashboard, they will all respond to the actions unless the action is targeted with a `CARD_ID` as shown above. @@ -59,16 +61,16 @@ Only a subset of all [actions](../configuration/actions/README.md) are supported | `image` | :white_check_mark: | | | `live_substream_select` | :white_check_mark: | | | `live` | :white_check_mark: | | -| `media_player` | :heavy_multiplication_x: | Please [request](https://github.com/dermotduffy/frigate-hass-card/issues) if you need this. | +| `media_player` | :heavy_multiplication_x: | Please [request](https://github.com/dermotduffy/advanced-camera-card/issues) if you need this. | | `menu_toggle` | :white_check_mark: | | | `microphone_connect`, `microphone_disconnect`, `microphone_mute`, `microphone_unmute` | :heavy_multiplication_x: | | | `mute`, `unmute` | :heavy_multiplication_x: | | | `play`, `pause` | :heavy_multiplication_x: | | -| `ptz` | :heavy_multiplication_x: | Please [request](https://github.com/dermotduffy/frigate-hass-card/issues) if you need this. | +| `ptz` | :heavy_multiplication_x: | Please [request](https://github.com/dermotduffy/advanced-camera-card/issues) if you need this. | | `recording` | :white_check_mark: | | | `recordings` | :white_check_mark: | | | `screenshot` | :heavy_multiplication_x: | Latest media information is not available on initial render. | -| `ptz_controls` | :heavy_multiplication_x: | Please [request](https://github.com/dermotduffy/frigate-hass-card/issues) if you need this. | +| `ptz_controls` | :heavy_multiplication_x: | Please [request](https://github.com/dermotduffy/advanced-camera-card/issues) if you need this. | | `snapshot` | :white_check_mark: | | | `snapshots` | :white_check_mark: | | diff --git a/hacs.json b/hacs.json index 34428673..ccbb4f5b 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,6 @@ { - "name": "Frigate Card", + "name": "Advanced Camera Card", "render_readme": true, - "filename": "frigate-hass-card.js", + "filename": "advanced-camera-card.js", "homeassistant": "2022.3.0" } diff --git a/package.json b/package.json index be046ced..1befd109 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,11 @@ { - "name": "frigate-hass-card", + "name": "advanced-camera-card", "version": "0.0.0-dev", - "description": "Frigate Lovelace Card for Home Assistant", + "description": "Advanced Camera Card for Home Assistant", "keywords": [ + "advanced", + "camera", + "card", "frigate", "home-assistant", "homeassistant", @@ -10,8 +13,8 @@ "lovelace" ], "type": "module", - "module": "frigate-hass-card.js", - "repository": "github:dermotduffy/frigate-hass-card", + "module": "advanced-camera-card.js", + "repository": "github:dermotduffy/advanced-camera-card", "author": "Dermot Duffy ", "license": "MIT", "dependencies": { @@ -149,7 +152,7 @@ { "assets": [ { - "path": "frigate-hass-card.zip", + "path": "advanced-camera-card.zip", "label": "Zip distribution" }, { @@ -163,7 +166,7 @@ "assignees": [ "dermotduffy" ], - "successComment": ":tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nSee: [GitHub release](https://github.com/dermotduffy/frigate-hass-card/releases/tag/${nextRelease.gitTag})", + "successComment": ":tada: This issue has been resolved in version ${nextRelease.version} :tada:\n\nSee: [GitHub release](https://github.com/dermotduffy/advanced-camera-card/releases/tag/${nextRelease.gitTag})", "releaseNameTemplate": "v${nextRelease.version}" } ] diff --git a/rollup.config.js b/rollup.config.js index ede62169..134d04df 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -60,7 +60,7 @@ const plugins = [ preventAssignment: true, values: { 'process.env.NODE_ENV': JSON.stringify(dev ? 'development' : 'production'), - __FRIGATE_CARD_RELEASE_VERSION__: + __ADVANCED_CAMERA_CARD_RELEASE_VERSION__: process.env.RELEASE_VERSION ?? (dev ? 'dev' : 'pkg'), }, }), @@ -72,6 +72,23 @@ const plugins = [ }), ]; +const outputEntryTemplate = { + entryFileNames: 'advanced-camera-card.js', + dir: 'dist', + chunkFileNames: (chunk) => { + // Add "lang-" to the front of the language chunk names for readability. + if ( + chunk.facadeModuleId && + chunk.facadeModuleId.match(/localize\/languages\/.*\.json/) + ) { + return 'lang-[name]-[hash].js'; + } + return '[name]-[hash].js'; + }, + format: 'es', + sourcemap: dev, +}; + /** * @type {import('rollup').RollupOptions} */ @@ -83,22 +100,15 @@ const config = { // the hacstag, causing a re-download of the same content and functionality // problems. preserveEntrySignatures: 'strict', - output: { - entryFileNames: 'frigate-hass-card.js', - dir: 'dist', - chunkFileNames: (chunk) => { - // Add "lang-" to the front of the language chunk names for readability. - if ( - chunk.facadeModuleId && - chunk.facadeModuleId.match(/localize\/languages\/.*\.json/) - ) { - return 'lang-[name]-[hash].js'; - } - return '[name]-[hash].js'; + output: [ + outputEntryTemplate, + + // Continue to include the old file name for backwards compatibility. + { + ...outputEntryTemplate, + entryFileNames: 'frigate-hass-card.js', }, - format: 'es', - sourcemap: dev, - }, + ], plugins: plugins, // These files use this at the toplevel, which causes rollup warning // spam on build: `this` has been rewritten to `undefined`. diff --git a/src/action-handler-directive.ts b/src/action-handler-directive.ts index 387654c2..dcd6744f 100644 --- a/src/action-handler-directive.ts +++ b/src/action-handler-directive.ts @@ -1,8 +1,8 @@ -import { fireEvent } from '@dermotduffy/custom-card-helpers'; import type { ActionHandlerDetail, ActionHandlerOptions, } from '@dermotduffy/custom-card-helpers'; +import { fireEvent } from '@dermotduffy/custom-card-helpers'; import { noChange } from 'lit'; import { AttributePart, @@ -18,10 +18,10 @@ interface ActionHandlerInterface extends HTMLElement { bind(element: Element, options): void; } interface ActionHandlerElement extends HTMLElement { - actionHandlerOptions?: FrigateCardActionHandlerOptions; + actionHandlerOptions?: AdvancedCameraCardActionHandlerOptions; } -interface FrigateCardActionHandlerOptions extends ActionHandlerOptions { +interface AdvancedCameraCardActionHandlerOptions extends ActionHandlerOptions { allowPropagation?: boolean; } @@ -56,7 +56,7 @@ class ActionHandler extends HTMLElement implements ActionHandlerInterface { public bind( element: ActionHandlerElement, - options?: FrigateCardActionHandlerOptions, + options?: AdvancedCameraCardActionHandlerOptions, ): void { if (element.actionHandlerOptions) { // Reset the options on an existing actionHandler. @@ -160,15 +160,15 @@ class ActionHandler extends HTMLElement implements ActionHandlerInterface { } } -customElements.define('action-handler-frigate-card', ActionHandler); +customElements.define('action-handler-advanced-camera-card', ActionHandler); const getActionHandler = (): ActionHandler => { const body = document.body; - if (body.querySelector('action-handler-frigate-card')) { - return body.querySelector('action-handler-frigate-card') as ActionHandler; + if (body.querySelector('action-handler-advanced-camera-card')) { + return body.querySelector('action-handler-advanced-camera-card') as ActionHandler; } - const actionhandler = document.createElement('action-handler-frigate-card'); + const actionhandler = document.createElement('action-handler-advanced-camera-card'); body.appendChild(actionhandler); return actionhandler as ActionHandler; @@ -176,7 +176,7 @@ const getActionHandler = (): ActionHandler => { const actionHandlerBind = ( element: ActionHandlerElement, - options?: FrigateCardActionHandlerOptions, + options?: AdvancedCameraCardActionHandlerOptions, ): void => { const actionhandler: ActionHandler = getActionHandler(); if (!actionhandler) { @@ -193,7 +193,7 @@ export const actionHandler = directive( } // eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars - render(_options?: FrigateCardActionHandlerOptions) {} + render(_options?: AdvancedCameraCardActionHandlerOptions) {} }, ); @@ -222,7 +222,7 @@ export interface ActionEventTarget extends EventTarget { declare global { interface HTMLElementTagNameMap { - 'action-handler-frigate-card': ActionHandler; + 'action-handler-advanced-camera-card': ActionHandler; } interface HASSDomEvents { action: ActionHandlerDetail; diff --git a/src/camera-manager/error.ts b/src/camera-manager/error.ts index 38cb3561..d026f4af 100644 --- a/src/camera-manager/error.ts +++ b/src/camera-manager/error.ts @@ -1,4 +1,4 @@ -import { FrigateCardError } from '../types.js'; +import { AdvancedCameraCardError } from '../types.js'; -export class CameraInitializationError extends FrigateCardError {} -export class CameraNoIDError extends FrigateCardError {} +export class CameraInitializationError extends AdvancedCameraCardError {} +export class CameraNoIDError extends AdvancedCameraCardError {} diff --git a/src/camera-manager/frigate/requests.ts b/src/camera-manager/frigate/requests.ts index 57703d98..95939d2a 100644 --- a/src/camera-manager/frigate/requests.ts +++ b/src/camera-manager/frigate/requests.ts @@ -1,6 +1,6 @@ import { HomeAssistant } from '@dermotduffy/custom-card-helpers'; import { localize } from '../../localize/localize'; -import { FrigateCardError } from '../../types'; +import { AdvancedCameraCardError } from '../../types'; import { homeAssistantWSRequest } from '../../utils/ha/ws-request'; import { RecordingSegment } from '../types'; import { @@ -38,7 +38,7 @@ export const getRecordingsSummary = async ( camera: camera_name, // Ask for the summary relative to HA timezone - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1267 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1267 timezone: hass.config.time_zone, }, true, @@ -100,7 +100,7 @@ export async function retainEvent( true, ); if (!response.success) { - throw new FrigateCardError(localize('error.failed_retain'), { + throw new AdvancedCameraCardError(localize('error.failed_retain'), { request: retainRequest, response: response, }); @@ -153,7 +153,7 @@ export const getEventSummary = async ( instance_id: clientID, // Ask for the summary relative to HA timezone - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1267 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1267 timezone: hass.config.time_zone, }, true, diff --git a/src/camera-manager/manager.ts b/src/camera-manager/manager.ts index ae06d8e4..32a852fa 100644 --- a/src/camera-manager/manager.ts +++ b/src/camera-manager/manager.ts @@ -228,7 +228,7 @@ export class CameraManager { } // Engines are created sequentially, to avoid duplicate creation of the same - // engine. See: https://github.com/dermotduffy/frigate-hass-card/issues/941 + // engine. See: https://github.com/dermotduffy/advanced-camera-card/issues/941 const engineByConfig = await this._getEnginesForCameras(camerasConfig); // Configuration is initialized in parallel. @@ -272,7 +272,7 @@ export class CameraManager { log( this._api.getConfigManager().getCardWideConfig(), - 'Frigate Card CameraManager initialized (Cameras: ', + 'Advanced Camera Card CameraManager initialized (Cameras: ', this._store.getCameras(), `, Duration: ${ (new Date().getTime() - initializationStartTime.getTime()) / 1000 @@ -546,7 +546,7 @@ export class CameraManager { log( this._api.getConfigManager().getCardWideConfig(), - 'Frigate Card CameraManager favorite request (', + 'Advanced Camera Card CameraManager favorite request (', `Duration: ${(new Date().getTime() - queryStartTime.getTime()) / 1000}s,`, 'Media:', media.getID(), @@ -679,7 +679,7 @@ export class CameraManager { log( this._api.getConfigManager().getCardWideConfig(), - 'Frigate Card CameraManager request [Input queries:', + 'Advanced Camera Card CameraManager request [Input queries:', _queries.length, ', Cached output queries:', cachedOutputQueries, diff --git a/src/camera-manager/reolink/engine-reolink.ts b/src/camera-manager/reolink/engine-reolink.ts index 6cd9bea1..78852cda 100644 --- a/src/camera-manager/reolink/engine-reolink.ts +++ b/src/camera-manager/reolink/engine-reolink.ts @@ -181,7 +181,7 @@ export class ReolinkCameraManagerEngine extends BrowseMediaCameraManagerEngine { // First fetch all the Reolink cameras that show up under the media root, // that match the expected camera. Some Reolink cameras will not show up // here causing errors. - // https://github.com/dermotduffy/frigate-hass-card/issues/1723 + // https://github.com/dermotduffy/advanced-camera-card/issues/1723 const camerasWithMedia = await this._browseMediaManager.walkBrowseMedias( hass, [ diff --git a/src/camera-manager/store.ts b/src/camera-manager/store.ts index 58ef7906..ac662827 100644 --- a/src/camera-manager/store.ts +++ b/src/camera-manager/store.ts @@ -48,7 +48,7 @@ export class CameraManagerStore implements CameraManagerReadOnlyConfigStore { // In setting the store cameras, take great care to replace/add first before // remove. Otherwise, there may be race conditions where the card attempts // to render a view with (momentarily) no camera. - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1533 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1533 // Replace/Add the new cameras. for (const camera of cameras) { diff --git a/src/camera-manager/types.ts b/src/camera-manager/types.ts index e08d1052..76ce8342 100644 --- a/src/camera-manager/types.ts +++ b/src/camera-manager/types.ts @@ -1,5 +1,5 @@ +import { AdvancedCameraCardView, SSLCiphers } from '../config/types'; import { CapabilityKey, Icon } from '../types'; -import { FrigateCardView, SSLCiphers } from '../config/types'; import { ViewMedia } from '../view/media'; // ==== @@ -114,7 +114,7 @@ export interface CameraManagerCameraMetadata { export interface CameraEndpointsContext { media?: ViewMedia; - view?: FrigateCardView; + view?: AdvancedCameraCardView; } export interface CameraEndpoint { diff --git a/src/card-controller/actions/actions-manager.ts b/src/card-controller/actions/actions-manager.ts index 79bea074..65807af7 100644 --- a/src/card-controller/actions/actions-manager.ts +++ b/src/card-controller/actions/actions-manager.ts @@ -67,9 +67,9 @@ export class ActionsManager { if ( config && interaction && - // Don't call frigateCardHandleActionConfig() unless there is explicitly an - // action defined (as it uses a default that is unhelpful for views that - // have default tap/click actions). + // Don't execute unless there is explicitly an action defined (as it uses + // a default that is unhelpful for views that have default tap/click + // actions). actionConfig ) { this.executeActions(actionConfig, config); @@ -91,8 +91,8 @@ export class ActionsManager { }; /** - * This method handles actions requested by components of the Frigate card - * itself (e.g. menu, PTZ controller). + * This method handles actions requested by components of the Advanced Camera + * Card itself (e.g. menu, PTZ controller). */ public handleActionExecutionRequestEvent = async ( ev: CustomEvent, diff --git a/src/card-controller/actions/actions/base.ts b/src/card-controller/actions/actions/base.ts index 12533296..5ba18ce9 100644 --- a/src/card-controller/actions/actions/base.ts +++ b/src/card-controller/actions/actions/base.ts @@ -1,5 +1,5 @@ import { ActionContext } from 'action'; -import { FrigateCardCustomAction } from '../../../config/types'; +import { AdvancedCameraCardCustomAction } from '../../../config/types'; import { CardActionsAPI } from '../../types'; import { Action, AuxillaryActionConfig } from '../types'; @@ -24,6 +24,6 @@ export class BaseAction implements Action { } } -export class FrigateCardAction< - T extends FrigateCardCustomAction, +export class AdvancedCameraCardAction< + T extends AdvancedCameraCardCustomAction, > extends BaseAction {} diff --git a/src/card-controller/actions/actions/camera-select.ts b/src/card-controller/actions/actions/camera-select.ts index b492bf0d..e62a1b81 100644 --- a/src/card-controller/actions/actions/camera-select.ts +++ b/src/card-controller/actions/actions/camera-select.ts @@ -1,8 +1,8 @@ import { CameraSelectActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class CameraSelectAction extends FrigateCardAction { +export class CameraSelectAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { const selectCameraID = this._action.camera ?? diff --git a/src/card-controller/actions/actions/camera-ui.ts b/src/card-controller/actions/actions/camera-ui.ts index 148788c9..2b588856 100644 --- a/src/card-controller/actions/actions/camera-ui.ts +++ b/src/card-controller/actions/actions/camera-ui.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class CameraUIAction extends FrigateCardAction { +export class CameraUIAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { api.getCameraURLManager().openURL(); } diff --git a/src/card-controller/actions/actions/default.ts b/src/card-controller/actions/actions/default.ts index 5289c5ef..069b4b90 100644 --- a/src/card-controller/actions/actions/default.ts +++ b/src/card-controller/actions/actions/default.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class DefaultAction extends FrigateCardAction { +export class DefaultAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await api.getViewManager().setViewDefaultWithNewQuery(); } diff --git a/src/card-controller/actions/actions/display-mode-select.ts b/src/card-controller/actions/actions/display-mode-select.ts index 8f1c13d2..f6a6c4b9 100644 --- a/src/card-controller/actions/actions/display-mode-select.ts +++ b/src/card-controller/actions/actions/display-mode-select.ts @@ -1,8 +1,8 @@ import { DisplayModeActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class DisplayModeSelectAction extends FrigateCardAction { +export class DisplayModeSelectAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await api.getViewManager().setViewByParametersWithNewQuery({ params: { diff --git a/src/card-controller/actions/actions/download.ts b/src/card-controller/actions/actions/download.ts index 5103202e..aa7830b0 100644 --- a/src/card-controller/actions/actions/download.ts +++ b/src/card-controller/actions/actions/download.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class DownloadAction extends FrigateCardAction { +export class DownloadAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await api.getDownloadManager().downloadViewerMedia(); } diff --git a/src/card-controller/actions/actions/expand.ts b/src/card-controller/actions/actions/expand.ts index 94d80d48..3bc92caa 100644 --- a/src/card-controller/actions/actions/expand.ts +++ b/src/card-controller/actions/actions/expand.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class ExpandAction extends FrigateCardAction { +export class ExpandAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { api.getExpandManager().toggleExpanded(); } diff --git a/src/card-controller/actions/actions/fullscreen.ts b/src/card-controller/actions/actions/fullscreen.ts index 411b08d2..cef14dbc 100644 --- a/src/card-controller/actions/actions/fullscreen.ts +++ b/src/card-controller/actions/actions/fullscreen.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class FullscreenAction extends FrigateCardAction { +export class FullscreenAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { api.getFullscreenManager().toggleFullscreen(); } diff --git a/src/card-controller/actions/actions/generic.ts b/src/card-controller/actions/actions/generic.ts index 9b7fda95..514556cd 100644 --- a/src/card-controller/actions/actions/generic.ts +++ b/src/card-controller/actions/actions/generic.ts @@ -3,7 +3,7 @@ import { CardActionsAPI } from '../../types'; import { BaseAction } from './base'; /** - * Handles generic HA (non-Frigate) actions (e.g. 'more-info') + * Handles generic HA actions (e.g. 'more-info') */ export class GenericAction extends BaseAction { public async execute(api: CardActionsAPI): Promise { diff --git a/src/card-controller/actions/actions/log.ts b/src/card-controller/actions/actions/log.ts index 50302f37..e3e4bde7 100644 --- a/src/card-controller/actions/actions/log.ts +++ b/src/card-controller/actions/actions/log.ts @@ -1,8 +1,8 @@ import { LogActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class LogAction extends FrigateCardAction { +export class LogAction extends AdvancedCameraCardAction { // eslint-disable-next-line @typescript-eslint/no-unused-vars public async execute(_api: CardActionsAPI): Promise { console[this._action.level](this._action.message); diff --git a/src/card-controller/actions/actions/media-player.ts b/src/card-controller/actions/actions/media-player.ts index 8ecfc574..bb79ac8c 100644 --- a/src/card-controller/actions/actions/media-player.ts +++ b/src/card-controller/actions/actions/media-player.ts @@ -1,9 +1,9 @@ import { MediaPlayerActionConfig } from '../../../config/types'; import { getStreamCameraID } from '../../../utils/substream'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class MediaPlayerAction extends FrigateCardAction { +export class MediaPlayerAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { const mediaPlayer = this._action.media_player; const mediaPlayerController = api.getMediaPlayerManager(); diff --git a/src/card-controller/actions/actions/menu-toggle.ts b/src/card-controller/actions/actions/menu-toggle.ts index 1cbac07d..1c1903fb 100644 --- a/src/card-controller/actions/actions/menu-toggle.ts +++ b/src/card-controller/actions/actions/menu-toggle.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class MenuToggleAction extends FrigateCardAction { +export class MenuToggleAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { api.getCardElementManager().toggleMenu(); } diff --git a/src/card-controller/actions/actions/microphone-connect.ts b/src/card-controller/actions/actions/microphone-connect.ts index 1be48dd1..b4ab7f90 100644 --- a/src/card-controller/actions/actions/microphone-connect.ts +++ b/src/card-controller/actions/actions/microphone-connect.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class MicrophoneConnectAction extends FrigateCardAction { +export class MicrophoneConnectAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await api.getMicrophoneManager().connect(); } diff --git a/src/card-controller/actions/actions/microphone-disconnect.ts b/src/card-controller/actions/actions/microphone-disconnect.ts index f6771a60..0a7d0bb1 100644 --- a/src/card-controller/actions/actions/microphone-disconnect.ts +++ b/src/card-controller/actions/actions/microphone-disconnect.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class MicrophoneDisconnectAction extends FrigateCardAction { +export class MicrophoneDisconnectAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { api.getMicrophoneManager().disconnect(); } diff --git a/src/card-controller/actions/actions/microphone-mute.ts b/src/card-controller/actions/actions/microphone-mute.ts index c7a6c1bb..11658740 100644 --- a/src/card-controller/actions/actions/microphone-mute.ts +++ b/src/card-controller/actions/actions/microphone-mute.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class MicrophoneMuteAction extends FrigateCardAction { +export class MicrophoneMuteAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { api.getMicrophoneManager().mute(); } diff --git a/src/card-controller/actions/actions/microphone-unmute.ts b/src/card-controller/actions/actions/microphone-unmute.ts index 8757b2a8..92ef1796 100644 --- a/src/card-controller/actions/actions/microphone-unmute.ts +++ b/src/card-controller/actions/actions/microphone-unmute.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class MicrophoneUnmuteAction extends FrigateCardAction { +export class MicrophoneUnmuteAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await api.getMicrophoneManager().unmute(); } diff --git a/src/card-controller/actions/actions/mute.ts b/src/card-controller/actions/actions/mute.ts index 3c43b8d0..f90fa6d0 100644 --- a/src/card-controller/actions/actions/mute.ts +++ b/src/card-controller/actions/actions/mute.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class MuteAction extends FrigateCardAction { +export class MuteAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await api.getMediaLoadedInfoManager().get()?.player?.mute(); } diff --git a/src/card-controller/actions/actions/pause.ts b/src/card-controller/actions/actions/pause.ts index f77d657b..e8a75968 100644 --- a/src/card-controller/actions/actions/pause.ts +++ b/src/card-controller/actions/actions/pause.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class PauseAction extends FrigateCardAction { +export class PauseAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await api.getMediaLoadedInfoManager().get()?.player?.pause(); } diff --git a/src/card-controller/actions/actions/play.ts b/src/card-controller/actions/actions/play.ts index 1d0707fe..8ef82c23 100644 --- a/src/card-controller/actions/actions/play.ts +++ b/src/card-controller/actions/actions/play.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class PlayAction extends FrigateCardAction { +export class PlayAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await api.getMediaLoadedInfoManager().get()?.player?.play(); } diff --git a/src/card-controller/actions/actions/ptz-controls.ts b/src/card-controller/actions/actions/ptz-controls.ts index 6bf0e976..501bc074 100644 --- a/src/card-controller/actions/actions/ptz-controls.ts +++ b/src/card-controller/actions/actions/ptz-controls.ts @@ -1,8 +1,8 @@ import { PTZControlsActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class PTZControlsAction extends FrigateCardAction { +export class PTZControlsAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { api.getViewManager().setViewWithMergedContext({ ptzControls: { enabled: this._action.enabled }, diff --git a/src/card-controller/actions/actions/ptz-digital.ts b/src/card-controller/actions/actions/ptz-digital.ts index a2e1e779..0ddab55b 100644 --- a/src/card-controller/actions/actions/ptz-digital.ts +++ b/src/card-controller/actions/actions/ptz-digital.ts @@ -10,12 +10,12 @@ import { PTZDigitialActionConfig, ZOOM_MAX, ZOOM_MIN } from '../../../config/typ import { getPTZTarget } from '../../../utils/ptz'; import { Timer } from '../../../utils/timer'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; import { TargetedActionContext } from '../types'; import { setInProgressForThisTarget, stopInProgressForThisTarget, } from '../utils/action-state'; +import { AdvancedCameraCardAction } from './base'; const STEP_DELAY_SECONDS = 0.1; const STEP_ZOOM = 0.1; @@ -27,7 +27,7 @@ declare module 'action' { } } -export class PTZDigitalAction extends FrigateCardAction { +export class PTZDigitalAction extends AdvancedCameraCardAction { protected _timer = new Timer(); protected async _stepChange(api: CardActionsAPI, targetID: string): Promise { diff --git a/src/card-controller/actions/actions/ptz-multi.ts b/src/card-controller/actions/actions/ptz-multi.ts index 1207a50c..8a5da678 100644 --- a/src/card-controller/actions/actions/ptz-multi.ts +++ b/src/card-controller/actions/actions/ptz-multi.ts @@ -2,11 +2,11 @@ import { PTZMultiActionConfig } from '../../../config/types'; import { createPTZAction, createPTZDigitalAction } from '../../../utils/action'; import { PTZType, getPTZTarget, hasCameraTruePTZ } from '../../../utils/ptz'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; import { PTZAction } from './ptz'; import { PTZDigitalAction } from './ptz-digital'; -export class PTZMultiAction extends FrigateCardAction { +export class PTZMultiAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { const view = api.getViewManager().getView(); let targetID: string | null = null; diff --git a/src/card-controller/actions/actions/ptz.ts b/src/card-controller/actions/actions/ptz.ts index 8e253869..4814faa0 100644 --- a/src/card-controller/actions/actions/ptz.ts +++ b/src/card-controller/actions/actions/ptz.ts @@ -6,7 +6,7 @@ import { setInProgressForThisTarget, stopInProgressForThisTarget, } from '../utils/action-state'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; interface PTZContext { [cameraID: string]: { @@ -20,7 +20,7 @@ declare module 'action' { } } -export class PTZAction extends FrigateCardAction { +export class PTZAction extends AdvancedCameraCardAction { protected _timer = new Timer(); public async stop(): Promise { diff --git a/src/card-controller/actions/actions/screenshot.ts b/src/card-controller/actions/actions/screenshot.ts index 573b69a6..8838ca75 100644 --- a/src/card-controller/actions/actions/screenshot.ts +++ b/src/card-controller/actions/actions/screenshot.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class ScreenshotAction extends FrigateCardAction { +export class ScreenshotAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await api.getDownloadManager().downloadScreenshot(); } diff --git a/src/card-controller/actions/actions/sleep.ts b/src/card-controller/actions/actions/sleep.ts index 3bf10c68..98cba66a 100644 --- a/src/card-controller/actions/actions/sleep.ts +++ b/src/card-controller/actions/actions/sleep.ts @@ -2,9 +2,9 @@ import { SleepActionConfig } from '../../../config/types'; import { sleep } from '../../../utils/basic'; import { CardActionsAPI } from '../../types'; import { timeDeltaToSeconds } from '../utils/time-delta'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class SleepAction extends FrigateCardAction { +export class SleepAction extends AdvancedCameraCardAction { // eslint-disable-next-line @typescript-eslint/no-unused-vars public async execute(_api: CardActionsAPI): Promise { await sleep(timeDeltaToSeconds(this._action.duration)); diff --git a/src/card-controller/actions/actions/status-bar.ts b/src/card-controller/actions/actions/status-bar.ts index a30e5ad0..5075bc87 100644 --- a/src/card-controller/actions/actions/status-bar.ts +++ b/src/card-controller/actions/actions/status-bar.ts @@ -1,8 +1,8 @@ import { StatusBarActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class StatusBarAction extends FrigateCardAction { +export class StatusBarAction extends AdvancedCameraCardAction { // eslint-disable-next-line @typescript-eslint/no-unused-vars public async execute(api: CardActionsAPI): Promise { switch (this._action.status_bar_action) { diff --git a/src/card-controller/actions/actions/substream-off.ts b/src/card-controller/actions/actions/substream-off.ts index 0a89b7a6..cf643d15 100644 --- a/src/card-controller/actions/actions/substream-off.ts +++ b/src/card-controller/actions/actions/substream-off.ts @@ -1,9 +1,9 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; import { SubstreamOffViewModifier } from '../../view/modifiers/substream-off'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class SubstreamOffAction extends FrigateCardAction { +export class SubstreamOffAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { api.getViewManager().setViewByParameters({ modifiers: [new SubstreamOffViewModifier()], diff --git a/src/card-controller/actions/actions/substream-on.ts b/src/card-controller/actions/actions/substream-on.ts index 78e6976d..ec8e0a49 100644 --- a/src/card-controller/actions/actions/substream-on.ts +++ b/src/card-controller/actions/actions/substream-on.ts @@ -1,9 +1,9 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; import { SubstreamOnViewModifier } from '../../view/modifiers/substream-on'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class SubstreamOnAction extends FrigateCardAction { +export class SubstreamOnAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { api.getViewManager().setViewByParameters({ modifiers: [new SubstreamOnViewModifier(api)], diff --git a/src/card-controller/actions/actions/substream-select.ts b/src/card-controller/actions/actions/substream-select.ts index ec55c5f6..c8131dcb 100644 --- a/src/card-controller/actions/actions/substream-select.ts +++ b/src/card-controller/actions/actions/substream-select.ts @@ -1,9 +1,9 @@ import { SubstreamSelectActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; import { SubstreamSelectViewModifier } from '../../view/modifiers/substream-select'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class SubstreamSelectAction extends FrigateCardAction { +export class SubstreamSelectAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { api.getViewManager().setViewByParameters({ modifiers: [new SubstreamSelectViewModifier(this._action.camera)], diff --git a/src/card-controller/actions/actions/unmute.ts b/src/card-controller/actions/actions/unmute.ts index 81043271..ed0b33bf 100644 --- a/src/card-controller/actions/actions/unmute.ts +++ b/src/card-controller/actions/actions/unmute.ts @@ -1,8 +1,8 @@ import { GeneralActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class UnmuteAction extends FrigateCardAction { +export class UnmuteAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await api.getMediaLoadedInfoManager().get()?.player?.unmute(); } diff --git a/src/card-controller/actions/actions/view.ts b/src/card-controller/actions/actions/view.ts index 88967b38..c04c9cd0 100644 --- a/src/card-controller/actions/actions/view.ts +++ b/src/card-controller/actions/actions/view.ts @@ -1,12 +1,12 @@ import { ViewActionConfig } from '../../../config/types'; import { CardActionsAPI } from '../../types'; -import { FrigateCardAction } from './base'; +import { AdvancedCameraCardAction } from './base'; -export class ViewAction extends FrigateCardAction { +export class ViewAction extends AdvancedCameraCardAction { public async execute(api: CardActionsAPI): Promise { await api.getViewManager().setViewByParametersWithNewQuery({ params: { - view: this._action.frigate_card_action, + view: this._action.advanced_camera_card_action, }, }); } diff --git a/src/card-controller/actions/factory.ts b/src/card-controller/actions/factory.ts index a23794c8..c632fd81 100644 --- a/src/card-controller/actions/factory.ts +++ b/src/card-controller/actions/factory.ts @@ -43,8 +43,8 @@ export class ActionFactory { cardID?: string; }, ): Action | null { - const frigateCardAction = convertActionToCardCustomAction(action); - if (action.action !== 'fire-dom-event' || !frigateCardAction) { + const cardCustomAction = convertActionToCardCustomAction(action); + if (action.action !== 'fire-dom-event' || !cardCustomAction) { // * There is a slight typing (but not functional) difference between // ActionType in this card and ActionConfig in `custom-card-helpers`. See // `ExtendedConfirmationRestrictionConfig` in `types.ts` for the source and @@ -54,15 +54,15 @@ export class ActionFactory { if ( // Command not intended for this card (e.g. query string command). - frigateCardAction.card_id && - frigateCardAction.card_id !== options?.cardID + cardCustomAction.card_id && + cardCustomAction.card_id !== options?.cardID ) { return null; } - switch (frigateCardAction.frigate_card_action) { + switch (cardCustomAction.advanced_camera_card_action) { case 'default': - return new DefaultAction(context, frigateCardAction, options?.config); + return new DefaultAction(context, cardCustomAction, options?.config); case 'clip': case 'clips': case 'image': @@ -73,70 +73,70 @@ export class ActionFactory { case 'snapshots': case 'timeline': case 'diagnostics': - return new ViewAction(context, frigateCardAction, options?.config); + return new ViewAction(context, cardCustomAction, options?.config); case 'sleep': - return new SleepAction(context, frigateCardAction, options?.config); + return new SleepAction(context, cardCustomAction, options?.config); case 'download': - return new DownloadAction(context, frigateCardAction, options?.config); + return new DownloadAction(context, cardCustomAction, options?.config); case 'camera_ui': - return new CameraUIAction(context, frigateCardAction, options?.config); + return new CameraUIAction(context, cardCustomAction, options?.config); case 'expand': - return new ExpandAction(context, frigateCardAction, options?.config); + return new ExpandAction(context, cardCustomAction, options?.config); case 'fullscreen': - return new FullscreenAction(context, frigateCardAction, options?.config); + return new FullscreenAction(context, cardCustomAction, options?.config); case 'menu_toggle': - return new MenuToggleAction(context, frigateCardAction, options?.config); + return new MenuToggleAction(context, cardCustomAction, options?.config); case 'camera_select': - return new CameraSelectAction(context, frigateCardAction, options?.config); + return new CameraSelectAction(context, cardCustomAction, options?.config); case 'live_substream_select': - return new SubstreamSelectAction(context, frigateCardAction, options?.config); + return new SubstreamSelectAction(context, cardCustomAction, options?.config); case 'live_substream_off': - return new SubstreamOffAction(context, frigateCardAction, options?.config); + return new SubstreamOffAction(context, cardCustomAction, options?.config); case 'live_substream_on': - return new SubstreamOnAction(context, frigateCardAction, options?.config); + return new SubstreamOnAction(context, cardCustomAction, options?.config); case 'media_player': - return new MediaPlayerAction(context, frigateCardAction, options?.config); + return new MediaPlayerAction(context, cardCustomAction, options?.config); case 'microphone_connect': - return new MicrophoneConnectAction(context, frigateCardAction, options?.config); + return new MicrophoneConnectAction(context, cardCustomAction, options?.config); case 'microphone_disconnect': return new MicrophoneDisconnectAction( context, - frigateCardAction, + cardCustomAction, options?.config, ); case 'microphone_mute': - return new MicrophoneMuteAction(context, frigateCardAction, options?.config); + return new MicrophoneMuteAction(context, cardCustomAction, options?.config); case 'microphone_unmute': - return new MicrophoneUnmuteAction(context, frigateCardAction, options?.config); + return new MicrophoneUnmuteAction(context, cardCustomAction, options?.config); case 'mute': - return new MuteAction(context, frigateCardAction, options?.config); + return new MuteAction(context, cardCustomAction, options?.config); case 'unmute': - return new UnmuteAction(context, frigateCardAction, options?.config); + return new UnmuteAction(context, cardCustomAction, options?.config); case 'play': - return new PlayAction(context, frigateCardAction, options?.config); + return new PlayAction(context, cardCustomAction, options?.config); case 'pause': - return new PauseAction(context, frigateCardAction, options?.config); + return new PauseAction(context, cardCustomAction, options?.config); case 'screenshot': - return new ScreenshotAction(context, frigateCardAction, options?.config); + return new ScreenshotAction(context, cardCustomAction, options?.config); case 'display_mode_select': - return new DisplayModeSelectAction(context, frigateCardAction, options?.config); + return new DisplayModeSelectAction(context, cardCustomAction, options?.config); case 'ptz': - return new PTZAction(context, frigateCardAction, options?.config); + return new PTZAction(context, cardCustomAction, options?.config); case 'ptz_digital': - return new PTZDigitalAction(context, frigateCardAction, options?.config); + return new PTZDigitalAction(context, cardCustomAction, options?.config); case 'ptz_multi': - return new PTZMultiAction(context, frigateCardAction, options?.config); + return new PTZMultiAction(context, cardCustomAction, options?.config); case 'ptz_controls': - return new PTZControlsAction(context, frigateCardAction, options?.config); + return new PTZControlsAction(context, cardCustomAction, options?.config); case 'log': - return new LogAction(context, frigateCardAction, options?.config); + return new LogAction(context, cardCustomAction, options?.config); case 'status_bar': - return new StatusBarAction(context, frigateCardAction, options?.config); + return new StatusBarAction(context, cardCustomAction, options?.config); } /* istanbul ignore next: this path cannot be reached -- @preserve */ console.warn( - `Frigate card received unknown card action: ${frigateCardAction['frigate_card_action']}`, + `Advanced Camera Card received unknown card action: ${cardCustomAction['advanced_camera_card_action']}`, ); /* istanbul ignore next: this path cannot be reached -- @preserve */ return null; diff --git a/src/card-controller/actions/utils/execution-request.ts b/src/card-controller/actions/utils/execution-request.ts index 39ee47bc..b0ad889b 100644 --- a/src/card-controller/actions/utils/execution-request.ts +++ b/src/card-controller/actions/utils/execution-request.ts @@ -1,16 +1,16 @@ -import { dispatchFrigateCardEvent } from '../../../utils/basic'; +import { dispatchAdvancedCameraCardEvent } from '../../../utils/basic'; import { ActionExecutionRequest } from '../types'; export const dispatchActionExecutionRequest = ( element: HTMLElement, request: ActionExecutionRequest, ) => { - dispatchFrigateCardEvent(element, 'action:execution-request', request); + dispatchAdvancedCameraCardEvent(element, 'action:execution-request', request); }; export interface ActionExecutionRequestEventTarget extends EventTarget { addEventListener( - event: 'frigate-card:action:execution-request', + event: 'advanced-camera-card:action:execution-request', listener: ( this: ActionExecutionRequestEventTarget, ev: CustomEvent, @@ -23,7 +23,7 @@ export interface ActionExecutionRequestEventTarget extends EventTarget { options?: AddEventListenerOptions | boolean, ): void; removeEventListener( - event: 'frigate-card:action:execution-request', + event: 'advanced-camera-card:action:execution-request', listener: ( this: ActionExecutionRequestEventTarget, ev: CustomEvent, diff --git a/src/card-controller/automations-manager.ts b/src/card-controller/automations-manager.ts index 367a7f5c..98d840b1 100644 --- a/src/card-controller/automations-manager.ts +++ b/src/card-controller/automations-manager.ts @@ -31,7 +31,7 @@ export class AutomationsManager { !this._api.getHASSManager().hasHASS() || // Never execute automations if the card hasn't finished initializing, as // it could cause a view change when camera loads are not finished. - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1407 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1407 !this._api.getInitializationManager().isInitializedMandatory() || // Never execute automations if there's an error (as our automation loop // avoidance -- which shows as an error -- would not work!). diff --git a/src/card-controller/card-element-manager.ts b/src/card-controller/card-element-manager.ts index c68be81e..b11c6af0 100644 --- a/src/card-controller/card-element-manager.ts +++ b/src/card-controller/card-element-manager.ts @@ -78,7 +78,7 @@ export class CardElementManager { ...(this._api.getConfigManager().getConfig()?.view.render_entities ?? []), // Refresh the card if media player state changes: - // https://github.com/dermotduffy/frigate-hass-card/issues/881 + // https://github.com/dermotduffy/advanced-camera-card/issues/881 ...(this._api.getMediaPlayerManager().getMediaPlayers() ?? []), ]); @@ -109,7 +109,7 @@ export class CardElementManager { this._api.getInteractionManager().reportInteraction, ); this._element.addEventListener( - 'frigate-card:action:execution-request', + 'advanced-camera-card:action:execution-request', this._api.getActionsManager().handleActionExecutionRequestEvent, ); @@ -179,7 +179,7 @@ export class CardElementManager { this._api.getInteractionManager().reportInteraction, ); this._element.removeEventListener( - 'frigate-card:action:execution-request', + 'advanced-camera-card:action:execution-request', this._api.getActionsManager().handleActionExecutionRequestEvent, ); diff --git a/src/card-controller/conditions-manager.ts b/src/card-controller/conditions-manager.ts index 6b9fd23f..35337b46 100644 --- a/src/card-controller/conditions-manager.ts +++ b/src/card-controller/conditions-manager.ts @@ -10,14 +10,14 @@ import { setConfigValue, } from '../config/management'; import { - FrigateCardCondition, - frigateConditionalSchema, + AdvancedCameraCardCondition, + advancedCameraCardConditionalSchema, Overrides, - RawFrigateCardConfig, + RawAdvancedCameraCardConfig, ViewDisplayMode, } from '../config/types'; import { localize } from '../localize/localize'; -import { FrigateCardError, MediaLoadedInfo } from '../types'; +import { AdvancedCameraCardError, MediaLoadedInfo } from '../types'; import { desparsifyArrays } from '../utils/basic'; import { isCompanionApp } from '../utils/companion'; import { CardConditionAPI, KeysState, MicrophoneState } from './types'; @@ -38,25 +38,26 @@ export interface ConditionState { view?: string; } -class OverrideConfigurationError extends FrigateCardError {} +class OverrideConfigurationError extends AdvancedCameraCardError {} export class ConditionsEvaluateRequestEvent extends Event { - public conditions: FrigateCardCondition[]; + public conditions: AdvancedCameraCardCondition[]; public evaluation?: boolean; - constructor(conditions: FrigateCardCondition[], eventInitDict?: EventInit) { - super('frigate-card:conditions:evaluate', eventInitDict); + constructor(conditions: AdvancedCameraCardCondition[], eventInitDict?: EventInit) { + super('advanced-camera-card:conditions:evaluate', eventInitDict); this.conditions = conditions; } } /** - * Evaluate whether a frigateCardCondition is met using an event to evaluate. + * Evaluate whether an AdvancedCameraCardCondition is met using an event to + * evaluate. * @returns A boolean indicating whether the condition is met. */ export function evaluateConditionViaEvent( element: HTMLElement, - conditions?: FrigateCardCondition[], + conditions?: AdvancedCameraCardCondition[], ): boolean { if (!conditions) { return true; @@ -69,7 +70,7 @@ export function evaluateConditionViaEvent( /* Special note on what's going on here: * - * Some parts of the card (e.g. ) may have arbitrary + * Some parts of the card (e.g. ) may have arbitrary * complexity and layers (that this card doesn't control) between that master * element and the element that needs to evaluate the condition. In these * cases there's no clean way to pass state from the rest of card down through @@ -83,7 +84,7 @@ export function evaluateConditionViaEvent( return evaluateEvent.evaluation ?? false; } -export function getOverriddenConfig( +export function getOverriddenConfig( manager: Readonly, config: Readonly, options?: { @@ -198,9 +199,9 @@ export class ConditionsManager { public setConditionsFromConfig(): void { this.removeConditions(); - const getAllConditions = (): FrigateCardCondition[] => { + const getAllConditions = (): AdvancedCameraCardCondition[] => { const config = this._api.getConfigManager().getConfig(); - const conditions: FrigateCardCondition[] = []; + const conditions: AdvancedCameraCardCondition[] = []; config?.overrides?.forEach((override) => conditions.push(...override.conditions)); config?.automations?.forEach((automation) => conditions.push(...automation.conditions), @@ -210,7 +211,7 @@ export class ConditionsManager { // custom elements that this card may not known. Here we recursively parse // down the elements tree, parsing as we go to find valid conditions. const getElementsConditions = (data: unknown): void => { - const parseResult = frigateConditionalSchema.safeParse(data); + const parseResult = advancedCameraCardConditionalSchema.safeParse(data); if (parseResult.success) { conditions.push(...parseResult.data.conditions); parseResult.data.elements?.forEach(getElementsConditions); @@ -265,7 +266,7 @@ export class ConditionsManager { } public evaluateConditions( - conditions: Readonly[], + conditions: Readonly[], stateOverrides?: Partial, ): boolean { return conditions.every((conditionObj) => @@ -274,7 +275,7 @@ export class ConditionsManager { } protected _evaluateCondition( - conditionObj: Readonly, + conditionObj: Readonly, stateOverrides?: Partial, ): boolean { const state = { diff --git a/src/card-controller/config/config-manager.ts b/src/card-controller/config/config-manager.ts index 7ff48c13..9b177dc6 100644 --- a/src/card-controller/config/config-manager.ts +++ b/src/card-controller/config/config-manager.ts @@ -1,13 +1,13 @@ import isEqual from 'lodash-es/isEqual'; import { isConfigUpgradeable } from '../../config/management.js'; +import { setProfiles } from '../../config/profiles/index.js'; import { + AdvancedCameraCardConfig, + advancedCameraCardConfigSchema, CardWideConfig, - FrigateCardConfig, - frigateCardConfigSchema, - RawFrigateCardConfig, + RawAdvancedCameraCardConfig, } from '../../config/types.js'; import { localize } from '../../localize/localize.js'; -import { setProfiles } from '../../config/profiles/index.js'; import { getParseErrorPaths } from '../../utils/zod.js'; import { getOverriddenConfig } from '../conditions-manager.js'; import { InitializationAspect } from '../initialization-manager.js'; @@ -22,9 +22,9 @@ export class ConfigManager { // get the correct configuration (which will return overrides as appropriate). // This variable must be called `_config` or `config` to be compatible with // card-mod. - protected _config: FrigateCardConfig | null = null; - protected _overriddenConfig: FrigateCardConfig | null = null; - protected _rawConfig: RawFrigateCardConfig | null = null; + protected _config: AdvancedCameraCardConfig | null = null; + protected _overriddenConfig: AdvancedCameraCardConfig | null = null; + protected _rawConfig: RawAdvancedCameraCardConfig | null = null; protected _cardWideConfig: CardWideConfig | null = null; constructor(api: CardConfigAPI) { @@ -35,7 +35,7 @@ export class ConfigManager { return !!this.getConfig(); } - public getConfig(): FrigateCardConfig | null { + public getConfig(): AdvancedCameraCardConfig | null { return this._overriddenConfig ?? this._config; } @@ -43,20 +43,20 @@ export class ConfigManager { return this._cardWideConfig; } - public getNonOverriddenConfig(): FrigateCardConfig | null { + public getNonOverriddenConfig(): AdvancedCameraCardConfig | null { return this._config; } - public getRawConfig(): RawFrigateCardConfig | null { + public getRawConfig(): RawAdvancedCameraCardConfig | null { return this._rawConfig; } - public setConfig(inputConfig?: RawFrigateCardConfig): void { + public setConfig(inputConfig?: RawAdvancedCameraCardConfig): void { if (!inputConfig) { throw new Error(localize('error.invalid_configuration')); } - const parseResult = frigateCardConfigSchema.safeParse(inputConfig); + const parseResult = advancedCameraCardConfigSchema.safeParse(inputConfig); if (!parseResult.success) { const configUpgradeable = isConfigUpgradeable(inputConfig); const hint = getParseErrorPaths(parseResult.error); @@ -113,11 +113,11 @@ export class ConfigManager { return; } - let overriddenConfig: FrigateCardConfig | null = null; + let overriddenConfig: AdvancedCameraCardConfig | null = null; try { overriddenConfig = getOverriddenConfig(conditionsManager, this._config, { configOverrides: this._config.overrides, - schema: frigateCardConfigSchema, + schema: advancedCameraCardConfigSchema, }); } catch (ev) { this._api.getMessageManager().setErrorIfHigherPriority(ev); @@ -162,7 +162,7 @@ export class ConfigManager { * card hard requires, use InitializationManager instead. */ protected async _initializeBackground( - previousConfig: FrigateCardConfig | null, + previousConfig: AdvancedCameraCardConfig | null, ): Promise { await this._api.getDefaultManager().initializeIfNecessary(previousConfig); await this._api.getMediaPlayerManager().initializeIfNecessary(previousConfig); diff --git a/src/card-controller/controller.ts b/src/card-controller/controller.ts index 4769c87d..06b893be 100644 --- a/src/card-controller/controller.ts +++ b/src/card-controller/controller.ts @@ -1,7 +1,7 @@ import { LovelaceCardEditor } from '@dermotduffy/custom-card-helpers'; import { ReactiveController } from 'lit'; import { CameraManager } from '../camera-manager/manager'; -import { FrigateCardConfig } from '../config/types'; +import { AdvancedCameraCardConfig } from '../config/types'; import { createDeviceRegistryCache, DeviceRegistryManager, @@ -177,7 +177,7 @@ export class CardController public static async getConfigElement(): Promise { await import('../editor.js'); - return document.createElement('frigate-card-editor'); + return document.createElement('advanced-camera-card-editor'); } public getConfigManager(): ConfigManager { @@ -255,7 +255,7 @@ export class CardController return this._statusBarItemManager; } - public static getStubConfig(entities: string[]): FrigateCardConfig { + public static getStubConfig(entities: string[]): AdvancedCameraCardConfig { const cameraEntity = entities.find((element) => element.startsWith('camera.')); return { cameras: [ @@ -266,7 +266,7 @@ export class CardController // Need to use 'as unknown' to convince Typescript that this really isn't a // mistake, despite the miniscule size of the configuration vs the full type // description. - } as unknown as FrigateCardConfig; + } as unknown as AdvancedCameraCardConfig; } public getStyleManager(): StyleManager { diff --git a/src/card-controller/default-manager.ts b/src/card-controller/default-manager.ts index 72070cce..0d19880a 100644 --- a/src/card-controller/default-manager.ts +++ b/src/card-controller/default-manager.ts @@ -1,5 +1,5 @@ import isEqual from 'lodash-es/isEqual'; -import { FrigateCardConfig } from '../config/types'; +import { AdvancedCameraCardConfig } from '../config/types'; import { createGeneralAction } from '../utils/action'; import { isActionAllowedBasedOnInteractionState } from '../utils/interaction-mode'; import { Timer } from '../utils/timer'; @@ -17,7 +17,7 @@ export class DefaultManager { } public async initializeIfNecessary( - previousConfig: FrigateCardConfig | null, + previousConfig: AdvancedCameraCardConfig | null, ): Promise { if ( !isEqual( diff --git a/src/card-controller/fullscreen/webkit/index.ts b/src/card-controller/fullscreen/webkit/index.ts index 6927f3c1..ccbb78dc 100644 --- a/src/card-controller/fullscreen/webkit/index.ts +++ b/src/card-controller/fullscreen/webkit/index.ts @@ -9,7 +9,7 @@ const WEBKIT_PLAY_SECONDS = 0.5; /** * Fullscreen implementation for webkit based browsers that do not support the * standard Fullscreen API (expected to be exclusively iOS on an iPhone). - * See: https://github.com/dermotduffy/frigate-hass-card/issues/1444 + * See: https://github.com/dermotduffy/advanced-camera-card/issues/1444 */ export class WebkitFullScreenProvider extends FullscreenProviderBase diff --git a/src/card-controller/initialization-manager.ts b/src/card-controller/initialization-manager.ts index 5e2c3cd5..211a10ea 100644 --- a/src/card-controller/initialization-manager.ts +++ b/src/card-controller/initialization-manager.ts @@ -94,8 +94,8 @@ export class InitializationManager { !(await this._initializer.initializeMultipleIfNecessary({ [InitializationAspect.CAMERAS]: async () => { // Recreate the camera manager to guarantee an immediate re-render. - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1811 - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1769 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1811 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1769 this._api.createCameraManager(); return await this._api.getCameraManager().initializeCamerasFromConfig(); }, @@ -103,7 +103,7 @@ export class InitializationManager { // Connecting the microphone (if configured) is considered mandatory to // avoid issues with some cameras that only allow 2-way audio on the // first stream initialized. - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1235 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1235 ...(this._api.getMicrophoneManager().shouldConnectOnInitialization() && { [InitializationAspect.MICROPHONE_CONNECT]: async () => { // Recreate the microphone manager to guarantee an immediate diff --git a/src/card-controller/media-info-manager.ts b/src/card-controller/media-info-manager.ts index d269e325..c9c82359 100644 --- a/src/card-controller/media-info-manager.ts +++ b/src/card-controller/media-info-manager.ts @@ -23,7 +23,7 @@ export class MediaLoadedInfoManager { log( this._api.getConfigManager().getCardWideConfig(), - `Frigate Card media load: `, + `Advanced Camera Card media load: `, mediaLoadedInfo, ); diff --git a/src/card-controller/media-player-manager.ts b/src/card-controller/media-player-manager.ts index fd0d065f..3645f3d8 100644 --- a/src/card-controller/media-player-manager.ts +++ b/src/card-controller/media-player-manager.ts @@ -1,4 +1,4 @@ -import { CameraConfig, FrigateCardConfig } from '../config/types'; +import { AdvancedCameraCardConfig, CameraConfig } from '../config/types'; import { MEDIA_PLAYER_SUPPORT_BROWSE_MEDIA, MEDIA_PLAYER_SUPPORT_STOP, @@ -6,8 +6,8 @@ import { } from '../const'; import { localize } from '../localize/localize'; import { errorToConsole } from '../utils/basic'; -import { Entity } from '../utils/ha/registry/entity/types'; import { supportsFeature } from '../utils/ha'; +import { Entity } from '../utils/ha/registry/entity/types'; import { ViewMedia } from '../view/media'; import { ViewMediaClassifier } from '../view/media-classifier'; import { CardMediaPlayerAPI } from './types'; @@ -30,7 +30,7 @@ export class MediaPlayerManager { } public async initializeIfNecessary( - previousConfig: FrigateCardConfig | null, + previousConfig: AdvancedCameraCardConfig | null, ): Promise { if ( previousConfig?.menu.buttons.media_player.enabled !== @@ -80,7 +80,7 @@ export class MediaPlayerManager { // available in the HA state, only in the registry). this._mediaPlayers = mediaPlayers.filter((entityID) => { // Specifically allow for media players that are not found in the entity registry: - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1016 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1016 const entity = mediaPlayerEntities?.get(entityID); return !entity || !entity.hidden_by; }); diff --git a/src/card-controller/message-manager.ts b/src/card-controller/message-manager.ts index b754c7b1..b9fbf9f3 100644 --- a/src/card-controller/message-manager.ts +++ b/src/card-controller/message-manager.ts @@ -1,4 +1,4 @@ -import { FrigateCardError, Message, MessageType } from '../types'; +import { AdvancedCameraCardError, Message, MessageType } from '../types'; import { errorToConsole } from '../utils/basic'; import { CardMessageAPI } from './types'; @@ -60,7 +60,7 @@ export class MessageManager { this.setMessageIfHigherPriority({ message: prefix ? `${prefix}: ${error.message}` : String(error.message), type: 'error', - ...(error instanceof FrigateCardError && { context: error.context }), + ...(error instanceof AdvancedCameraCardError && { context: error.context }), }); } diff --git a/src/card-controller/microphone-manager.ts b/src/card-controller/microphone-manager.ts index 0cd56624..28557cbd 100644 --- a/src/card-controller/microphone-manager.ts +++ b/src/card-controller/microphone-manager.ts @@ -42,7 +42,7 @@ export class MicrophoneManager { public isSupported(): boolean { // Some browsers will have mediaDevices/getUserMedia as undefined if // accessed over http. - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1543 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1543 return !!navigator.mediaDevices?.getUserMedia; } diff --git a/src/card-controller/query-string-manager.ts b/src/card-controller/query-string-manager.ts index ce105c8a..d4398426 100644 --- a/src/card-controller/query-string-manager.ts +++ b/src/card-controller/query-string-manager.ts @@ -1,4 +1,4 @@ -import { FrigateCardCustomAction, ViewActionConfig } from '../config/types'; +import { AdvancedCameraCardCustomAction, ViewActionConfig } from '../config/types'; import { createCameraAction, createGeneralAction } from '../utils/action.js'; import { ViewParameters } from '../view/view'; import { CardQueryStringAPI } from './types'; @@ -9,7 +9,7 @@ interface QueryStringViewIntent { default?: boolean; substream?: string; }; - other?: FrigateCardCustomAction[]; + other?: AdvancedCameraCardCustomAction[]; } export class QueryStringManager { @@ -72,14 +72,14 @@ export class QueryStringManager { const result: QueryStringViewIntent = {}; for (const action of this._getActions()) { if (this._isViewAction(action)) { - (result.view ??= {}).view = action.frigate_card_action; + (result.view ??= {}).view = action.advanced_camera_card_action; (result.view ??= {}).default = undefined; - } else if (action.frigate_card_action === 'default') { + } else if (action.advanced_camera_card_action === 'default') { (result.view ??= {}).default = true; (result.view ??= {}).view = undefined; - } else if (action.frigate_card_action === 'camera_select') { + } else if (action.advanced_camera_card_action === 'camera_select') { (result.view ??= {}).camera = action.camera; - } else if (action.frigate_card_action === 'live_substream_select') { + } else if (action.advanced_camera_card_action === 'live_substream_select') { (result.view ??= {}).substream = action.camera; } else { (result.other ??= []).push(action); @@ -88,11 +88,11 @@ export class QueryStringManager { return result; } - protected _getActions(): FrigateCardCustomAction[] { + protected _getActions(): AdvancedCameraCardCustomAction[] { const params = new URLSearchParams(window.location.search); - const actions: FrigateCardCustomAction[] = []; + const actions: AdvancedCameraCardCustomAction[] = []; const actionRE = new RegExp( - /^frigate-card-action([.:](?\w+))?[.:](?\w+)/, + /^(advanced-camera-card|frigate-card)-action([.:](?\w+))?[.:](?\w+)/, ); for (const [key, value] of params.entries()) { const match = key.match(actionRE); @@ -102,7 +102,7 @@ export class QueryStringManager { const cardID: string | undefined = match.groups['cardID']; const action = match.groups['action']; - let customAction: FrigateCardCustomAction | null = null; + let customAction: AdvancedCameraCardCustomAction | null = null; switch (action) { case 'camera_select': case 'live_substream_select': @@ -133,7 +133,7 @@ export class QueryStringManager { break; default: console.warn( - `Frigate card received unknown card action in query string: ${action}`, + `Advanced Camera Card received unknown card action in query string: ${action}`, ); } if (customAction) { @@ -144,9 +144,9 @@ export class QueryStringManager { } protected _isViewAction = ( - action: FrigateCardCustomAction, + action: AdvancedCameraCardCustomAction, ): action is ViewActionConfig => { - switch (action.frigate_card_action) { + switch (action.advanced_camera_card_action) { case 'clip': case 'clips': case 'diagnostics': diff --git a/src/card-controller/status-bar-item-manager.ts b/src/card-controller/status-bar-item-manager.ts index 11efce02..d2c9a0bd 100644 --- a/src/card-controller/status-bar-item-manager.ts +++ b/src/card-controller/status-bar-item-manager.ts @@ -62,7 +62,7 @@ export class StatusBarItemManager { ...(title ? [ { - type: 'custom:frigate-card-status-bar-string' as const, + type: 'custom:advanced-camera-card-status-bar-string' as const, string: title, expand: true, sufficient: true, @@ -74,7 +74,7 @@ export class StatusBarItemManager { ...(resolution ? [ { - type: 'custom:frigate-card-status-bar-string' as const, + type: 'custom:advanced-camera-card-status-bar-string' as const, string: resolution, ...options?.statusConfig?.items.resolution, }, @@ -84,7 +84,7 @@ export class StatusBarItemManager { ...(technology && technology === 'webrtc' ? [ { - type: 'custom:frigate-card-status-bar-icon' as const, + type: 'custom:advanced-camera-card-status-bar-icon' as const, icon: 'mdi:webrtc', ...options?.statusConfig?.items.technology, }, @@ -92,7 +92,7 @@ export class StatusBarItemManager { : !!technology ? [ { - type: 'custom:frigate-card-status-bar-string' as const, + type: 'custom:advanced-camera-card-status-bar-string' as const, string: technology.toUpperCase(), ...options?.statusConfig?.items.technology, }, @@ -102,7 +102,7 @@ export class StatusBarItemManager { ...(engineIcon ? [ { - type: 'custom:frigate-card-status-bar-icon' as const, + type: 'custom:advanced-camera-card-status-bar-icon' as const, icon: engineIcon, ...options?.statusConfig?.items.engine, }, diff --git a/src/card-controller/style-manager.ts b/src/card-controller/style-manager.ts index 894673a1..fcba0e3c 100644 --- a/src/card-controller/style-manager.ts +++ b/src/card-controller/style-manager.ts @@ -1,7 +1,7 @@ import { StyleInfo } from 'lit/directives/style-map'; import { - FrigateCardConfig, - frigateCardConfigDefaults, + AdvancedCameraCardConfig, + configDefaults, ThemeConfig, ThemeName, } from '../config/types'; @@ -25,7 +25,7 @@ export class StyleManager { // as dimension configuration does not apply in fullscreen or expanded mode. const lastKnown = this._api.getMediaLoadedInfoManager().getLastKnown(); card.style.setProperty( - '--frigate-card-expand-aspect-ratio', + '--advanced-camera-card-expand-aspect-ratio', view?.isAnyMediaView() && lastKnown ? `${lastKnown.width} / ${lastKnown.height}` : 'unset', @@ -35,16 +35,16 @@ export class StyleManager { // space. const isGrid = view?.isGrid(); card.style.setProperty( - '--frigate-card-expand-width', + '--advanced-camera-card-expand-width', !isGrid && view?.isAnyMediaView() ? 'none' - : 'var(--frigate-card-expand-max-width)', + : 'var(--advanced-camera-card-expand-max-width)', ); card.style.setProperty( - '--frigate-card-expand-height', + '--advanced-camera-card-expand-height', !isGrid && view?.isAnyMediaView() ? 'none' - : 'var(--frigate-card-expand-max-height)', + : 'var(--advanced-camera-card-expand-max-height)', ); } @@ -76,7 +76,7 @@ export class StyleManager { protected _getThemeNames(themeConfig: ThemeConfig): ThemeName[] | null { return themeConfig.themes.length ? themeConfig.themes - : frigateCardConfigDefaults.view.theme.themes; + : configDefaults.view.theme.themes; } protected _setDimmable(): void { @@ -92,7 +92,7 @@ export class StyleManager { const config = this._api.getConfigManager().getConfig(); if (config) { const card = this._api.getCardElementManager().getElement(); - card.style.setProperty('--frigate-card-height', config.dimensions.height); + card.style.setProperty('--advanced-camera-card-height', config.dimensions.height); } } @@ -106,7 +106,7 @@ export class StyleManager { const styles = performance?.style ?? {}; for (const configKey of Object.keys(styles)) { - const CSSKey = `--frigate-card-css-${configKey.replaceAll('_', '-')}`; + const CSSKey = `--advanced-camera-card-css-${configKey.replaceAll('_', '-')}`; if (styles[configKey] === false) { element.style.setProperty(CSSKey, STYLE_DISABLE_MAP[configKey]); } else { @@ -116,7 +116,7 @@ export class StyleManager { } protected _isAspectRatioEnforced( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, view?: View | null, ): boolean { const aspectRatioMode = config.dimensions.aspect_ratio_mode; diff --git a/src/card-controller/view/factory.ts b/src/card-controller/view/factory.ts index 6131bb4c..828d2044 100644 --- a/src/card-controller/view/factory.ts +++ b/src/card-controller/view/factory.ts @@ -1,7 +1,7 @@ import { - FRIGATE_CARD_VIEW_DEFAULT, - FrigateCardConfig, - FrigateCardView, + VIEW_DEFAULT, + AdvancedCameraCardConfig, + AdvancedCameraCardView, ViewDisplayMode, } from '../../config/types'; import { localize } from '../../localize/localize'; @@ -26,7 +26,7 @@ export class ViewFactory { // Neither options.baseView.camera nor options.baseView.view are respected // here, since this is the default view / camera. - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1564 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1564 let cameraID: string | null = null; const viewName = options?.params?.view ?? config.view.default; @@ -101,11 +101,8 @@ export class ViewFactory { } if (!this.isViewSupportedByCamera(cameraID, viewName)) { - if ( - options?.failSafe && - this.isViewSupportedByCamera(cameraID, FRIGATE_CARD_VIEW_DEFAULT) - ) { - viewName = FRIGATE_CARD_VIEW_DEFAULT; + if (options?.failSafe && this.isViewSupportedByCamera(cameraID, VIEW_DEFAULT)) { + viewName = VIEW_DEFAULT; } else { const capabilities = this._api .getCameraManager() @@ -124,7 +121,7 @@ export class ViewFactory { const configuredDisplayMode = this._getDefaultDisplayModeForView(viewName, config); const displayMode = // Prioritize the configured display mode (if present). - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1812 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1812 (viewName !== options?.baseView?.view ? configuredDisplayMode : null) ?? options?.params?.displayMode ?? options?.baseView?.displayMode ?? @@ -147,13 +144,16 @@ export class ViewFactory { return view; } - public isViewSupportedByCamera(cameraID: string, view: FrigateCardView): boolean { + public isViewSupportedByCamera( + cameraID: string, + view: AdvancedCameraCardView, + ): boolean { return !!getCameraIDsForViewName(this._api.getCameraManager(), view, cameraID).size; } protected _getDefaultDisplayModeForView( - viewName: FrigateCardView, - config: FrigateCardConfig, + viewName: AdvancedCameraCardView, + config: AdvancedCameraCardConfig, ): ViewDisplayMode | null { let mode: ViewDisplayMode | null = null; switch (viewName) { diff --git a/src/card-controller/view/types.ts b/src/card-controller/view/types.ts index 5b769935..b9e449ea 100644 --- a/src/card-controller/view/types.ts +++ b/src/card-controller/view/types.ts @@ -1,6 +1,6 @@ import { ViewContext } from 'view'; -import { FrigateCardView } from '../../config/types.js'; -import { FrigateCardError } from '../../types.js'; +import { AdvancedCameraCardView } from '../../config/types.js'; +import { AdvancedCameraCardError } from '../../types.js'; import { MediaQueriesResults } from '../../view/media-queries-results.js'; import { MediaQueries } from '../../view/media-queries.js'; import { ViewMedia } from '../../view/media.js'; @@ -72,9 +72,9 @@ export interface ViewManagerInterface { setViewWithMergedContext(context: ViewContext | null): void; - isViewSupportedByCamera(cameraID: string, view: FrigateCardView): boolean; + isViewSupportedByCamera(cameraID: string, view: AdvancedCameraCardView): boolean; hasMajorMediaChange(oldView?: View | null, newView?: View | null): boolean; } -export class ViewNoCameraError extends FrigateCardError {} -export class ViewIncompatible extends FrigateCardError {} +export class ViewNoCameraError extends AdvancedCameraCardError {} +export class ViewIncompatible extends AdvancedCameraCardError {} diff --git a/src/card-controller/view/view-manager.ts b/src/card-controller/view/view-manager.ts index e63512db..e9671b7f 100644 --- a/src/card-controller/view/view-manager.ts +++ b/src/card-controller/view/view-manager.ts @@ -1,5 +1,5 @@ import { ViewContext } from 'view'; -import { FrigateCardView } from '../../config/types'; +import { AdvancedCameraCardView } from '../../config/types'; import { log } from '../../utils/debug'; import { getStreamCameraID } from '../../utils/substream'; import { MediaQueriesClassifier } from '../../view/media-queries-classifier'; @@ -217,7 +217,7 @@ export class ViewManager implements ViewManagerInterface { // target media of the gallery (e.g. clips, snapshots or recordings) is // equal to the queries that are currently used in the viewer. // - // See: https://github.com/dermotduffy/frigate-hass-card/issues/885 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/885 const switchingFromViewerToGallery = this._view?.isViewerView() && newView?.isGalleryView(); @@ -233,7 +233,10 @@ export class ViewManager implements ViewManagerInterface { } } - public isViewSupportedByCamera(cameraID: string, view: FrigateCardView): boolean { + public isViewSupportedByCamera( + cameraID: string, + view: AdvancedCameraCardView, + ): boolean { return !!getCameraIDsForViewName(this._api.getCameraManager(), view, cameraID).size; } @@ -266,7 +269,7 @@ export class ViewManager implements ViewManagerInterface { // If the query string contains a view related action, we don't set any view // here and allow that action to be triggered by the next call of to execute // query actions (called at least once per render cycle). - // Related: https://github.com/dermotduffy/frigate-hass-card/issues/1200 + // Related: https://github.com/dermotduffy/advanced-camera-card/issues/1200 if (!this._api.getQueryStringManager().hasViewRelatedActionsToRun()) { // This is not awaited to allow the initialization to complete before the // query is answered. @@ -280,7 +283,7 @@ export class ViewManager implements ViewManagerInterface { log( this._api.getConfigManager().getCardWideConfig(), - `Frigate Card view change: `, + `Advanced Camera Card view change: `, view, ); diff --git a/src/card-controller/view/view-query-executor.ts b/src/card-controller/view/view-query-executor.ts index 1953a6ef..de255996 100644 --- a/src/card-controller/view/view-query-executor.ts +++ b/src/card-controller/view/view-query-executor.ts @@ -137,7 +137,7 @@ export class ViewQueryExecutor { new MergeContextViewModifier({ // Force the window to start at the most recent time, not // necessarily when the most recent event/recording was: - // https://github.com/dermotduffy/frigate-hass-card/issues/1301 + // https://github.com/dermotduffy/advanced-camera-card/issues/1301 timeline: { window: { start: sub(now, { diff --git a/src/card.ts b/src/card.ts index cb0b739e..944a6b12 100644 --- a/src/card.ts +++ b/src/card.ts @@ -11,29 +11,29 @@ import { ConditionsEvaluateRequestEvent } from './card-controller/conditions-man import { CardController } from './card-controller/controller'; import { MenuButtonController } from './components-lib/menu-button-controller'; import './components/elements.js'; -import { FrigateCardElements } from './components/elements.js'; +import { AdvancedCameraCardElements } from './components/elements.js'; import './components/loading.js'; import './components/menu.js'; -import { FrigateCardMenu } from './components/menu.js'; +import { AdvancedCameraCardMenu } from './components/menu.js'; import './components/message.js'; import { renderMessage } from './components/message.js'; import './components/overlay.js'; -import { FrigateCardOverlay } from './components/overlay.js'; +import { AdvancedCameraCardOverlay } from './components/overlay.js'; import './components/status-bar'; import './components/thumbnail-carousel.js'; import './components/views.js'; -import { FrigateCardViews } from './components/views.js'; +import { AdvancedCameraCardViews } from './components/views.js'; import { - FrigateCardConfig, + AdvancedCameraCardConfig, MenuItem, - RawFrigateCardConfig, + RawAdvancedCameraCardConfig, StatusBarItem, } from './config/types'; import { REPO_URL } from './const.js'; import { localize } from './localize/localize.js'; import cardStyle from './scss/card.scss'; import { ExtendedHomeAssistant, MediaLoadedInfo, Message } from './types.js'; -import { frigateCardHasAction } from './utils/action.js'; +import { hasAction } from './utils/action.js'; import { getReleaseVersion } from './utils/diagnostics'; // *************************************************************************** @@ -57,23 +57,14 @@ import { getReleaseVersion } from './utils/diagnostics'; * - Directly specifying hooks (e.g. for snapshot viewing with simple tags) */ -/** Actions (action/menu/ll-custom events): - * - * The card supports actions being configured in a number of places (e.g. tap on - * an element, double_tap on a menu item, hold on the live view). These actions - * are handled by frigateCardHandleActionConfig(). For Frigate-card specific - * actions, the frigateCardHandleActionConfig() call will result in an ll-custom - * DOM event being fired, which needs to be caught at the card level to handle. - */ - // *************************************************************************** // Static Initializers // *************************************************************************** console.info( - `%c FRIGATE-HASS-CARD \n` + `%c ${localize('common.version')} ` + getReleaseVersion(), - 'color: pink; font-weight: bold; background: black', - 'color: white; font-weight: bold; background: dimgray', + `%c 📷 Advanced Camera Card %c ${getReleaseVersion()} `, + 'padding: 3px; color: black; background: pink;', + 'padding: 3px; color: black; background: white;', ); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -81,22 +72,22 @@ console.info( // eslint-disable-next-line @typescript-eslint/no-explicit-any (window as any).customCards.push({ - type: 'frigate-card', - name: localize('common.frigate_card'), - description: localize('common.frigate_card_description'), + type: 'advanced-camera-card', + name: localize('common.advanced_camera_card'), + description: localize('common.advanced_camera_card_description'), preview: true, documentationURL: REPO_URL, }); // *************************************************************************** -// Main FrigateCard WebComponent +// Main AdvancedCameraCard WebComponent // // Any non-rendering / non-lit related functionality should be added to // CardController instead of this file. // *************************************************************************** -@customElement('frigate-card') -class FrigateCard extends LitElement { +@customElement('advanced-camera-card') +class AdvancedCameraCard extends LitElement { protected _controller = new CardController( this, // Callback to scroll the main pane back to the top (example usecase: scrolling @@ -109,14 +100,14 @@ class FrigateCard extends LitElement { protected _menuButtonController = new MenuButtonController(); - protected _refMenu: Ref = createRef(); - protected _refOverlay: Ref = createRef(); + protected _refMenu: Ref = createRef(); + protected _refOverlay: Ref = createRef(); protected _refMain: Ref = createRef(); - protected _refElements: Ref = createRef(); - protected _refViews: Ref = createRef(); + protected _refElements: Ref = createRef(); + protected _refViews: Ref = createRef(); // Convenience methods for very frequently accessed attributes. - get _config(): FrigateCardConfig | null { + get _config(): AdvancedCameraCardConfig | null { return this._controller.getConfigManager().getConfig(); } @@ -145,7 +136,10 @@ class FrigateCard extends LitElement { return await CardController.getConfigElement(); } - public static getStubConfig(_: HomeAssistant, entities: string[]): FrigateCardConfig { + public static getStubConfig( + _: HomeAssistant, + entities: string[], + ): AdvancedCameraCardConfig { return CardController.getStubConfig(entities); } @@ -153,7 +147,7 @@ class FrigateCard extends LitElement { // Update the components that need to know about condition changes. Trigger // updates directly on them to them to avoid the performance hit of a entire // card re-render (esp. when using card-mod). - // https://github.com/dermotduffy/frigate-hass-card/issues/678 + // https://github.com/dermotduffy/advanced-camera-card/issues/678 if (this._refViews.value) { this._refViews.value.conditionsManagerEpoch = this._controller.getConditionsManager().getEpoch() ?? undefined; @@ -164,7 +158,7 @@ class FrigateCard extends LitElement { } } - public setConfig(config: RawFrigateCardConfig): void { + public setConfig(config: RawAdvancedCameraCardConfig): void { this._controller.getConfigManager().setConfig(config); } @@ -246,7 +240,9 @@ class FrigateCard extends LitElement { return html` ${position === 'overlay' - ? html`${getContents('overlay')}` + ? html`${getContents('overlay')}` : html`
${getContents('outerlay')}
`} @@ -259,7 +255,7 @@ class FrigateCard extends LitElement { return; } return html` - + > `; } @@ -290,7 +286,7 @@ class FrigateCard extends LitElement { } return html` - + > `; } @@ -359,36 +355,36 @@ class FrigateCard extends LitElement { html` ) => + @advanced-camera-card:message=${(ev: CustomEvent) => this._controller.getMessageManager().setMessageIfHigherPriority(ev.detail)} - @frigate-card:media:loaded=${(ev: CustomEvent) => + @advanced-camera-card:media:loaded=${(ev: CustomEvent) => this._controller.getMediaLoadedInfoManager().set(ev.detail)} - @frigate-card:media:unloaded=${() => + @advanced-camera-card:media:unloaded=${() => this._controller.getMediaLoadedInfoManager().clear()} - @frigate-card:media:volumechange=${ + @advanced-camera-card:media:volumechange=${ () => this.requestUpdate() /* Refresh mute menu button */ } - @frigate-card:media:play=${ + @advanced-camera-card:media:play=${ () => this.requestUpdate() /* Refresh play/pause menu button */ } - @frigate-card:media:pause=${ + @advanced-camera-card:media:pause=${ () => this.requestUpdate() /* Refresh play/pause menu button */ } - @frigate-card:focus=${() => this.focus()} + @advanced-camera-card:focus=${() => this.focus()} > ${showLoading - ? html`` + >` : ''} ${this._renderMenuStatusContainer('top')} ${this._renderMenuStatusContainer('overlay')}
- + > ${this._controller.getMessageManager().hasMessage() ? // Keep message rendering to last to show messages that may have been // generated during the render. @@ -421,32 +417,36 @@ class FrigateCard extends LitElement { ${this._config?.elements ? // Elements need to render after the main views so it can render 'on // top'. - html` ) => { + @advanced-camera-card:menu:add=${(ev: CustomEvent) => { this._menuButtonController.addDynamicMenuButton(ev.detail); this.requestUpdate(); }} - @frigate-card:menu:remove=${(ev: CustomEvent) => { + @advanced-camera-card:menu:remove=${(ev: CustomEvent) => { this._menuButtonController.removeDynamicMenuButton(ev.detail); this.requestUpdate(); }} - @frigate-card:status-bar:add=${(ev: CustomEvent) => { + @advanced-camera-card:status-bar:add=${( + ev: CustomEvent, + ) => { this._controller .getStatusBarItemManager() .addDynamicStatusBarItem(ev.detail); }} - @frigate-card:status-bar:remove=${(ev: CustomEvent) => { + @advanced-camera-card:status-bar:remove=${( + ev: CustomEvent, + ) => { this._controller .getStatusBarItemManager() .removeDynamicStatusBarItem(ev.detail); }} - @frigate-card:conditions:evaluate=${( + @advanced-camera-card:conditions:evaluate=${( ev: ConditionsEvaluateRequestEvent, ) => { ev.evaluation = this._controller @@ -454,7 +454,7 @@ class FrigateCard extends LitElement { ?.evaluateConditions(ev.conditions); }} > - ` + ` : ``} `, ); @@ -477,8 +477,13 @@ class FrigateCard extends LitElement { } } +// Keep the old name around for backwards compatibility. +@customElement('frigate-card') +class FrigateCard extends AdvancedCameraCard {} + declare global { interface HTMLElementTagNameMap { + 'advanced-camera-card': AdvancedCameraCard; 'frigate-card': FrigateCard; } } diff --git a/src/components-lib/live/live-controller.ts b/src/components-lib/live/live-controller.ts index 5616292c..f2121fda 100644 --- a/src/components-lib/live/live-controller.ts +++ b/src/components-lib/live/live-controller.ts @@ -1,10 +1,10 @@ import { LitElement, ReactiveController } from 'lit'; import { MediaLoadedInfo } from '../../types.js'; import { - FrigateCardMediaLoadedEventTarget, + AdvancedCameraCardMediaLoadedEventTarget, dispatchExistingMediaLoadedInfoAsEvent, } from '../../utils/media-info.js'; -import { FrigateCardMessageEventTarget } from '../message/dispatch.js'; +import { AdvancedCameraCardMessageEventTarget } from '../message/dispatch.js'; interface LiveViewContext { // A cameraID override (used for dependencies/substreams to force a different @@ -24,8 +24,8 @@ interface LastMediaLoadedInfo { } type LiveControllerHost = LitElement & - FrigateCardMediaLoadedEventTarget & - FrigateCardMessageEventTarget; + AdvancedCameraCardMediaLoadedEventTarget & + AdvancedCameraCardMessageEventTarget; export class LiveController implements ReactiveController { protected _host: LiveControllerHost; @@ -56,13 +56,19 @@ export class LiveController implements ReactiveController { public hostConnected(): void { this._intersectionObserver.observe(this._host); - this._host.addEventListener('frigate-card:media:loaded', this._handleMediaLoaded); + this._host.addEventListener( + 'advanced-camera-card:media:loaded', + this._handleMediaLoaded, + ); } public hostDisconnected(): void { this._intersectionObserver.disconnect(); - this._host.removeEventListener('frigate-card:media:loaded', this._handleMediaLoaded); + this._host.removeEventListener( + 'advanced-camera-card:media:loaded', + this._handleMediaLoaded, + ); } public isInBackground(): boolean { diff --git a/src/components-lib/live/utils/dispatch-live-error.ts b/src/components-lib/live/utils/dispatch-live-error.ts index ed0b99dd..16e37f48 100644 --- a/src/components-lib/live/utils/dispatch-live-error.ts +++ b/src/components-lib/live/utils/dispatch-live-error.ts @@ -1,5 +1,5 @@ -import { dispatchFrigateCardEvent } from '../../../utils/basic'; +import { dispatchAdvancedCameraCardEvent } from '../../../utils/basic'; export function dispatchLiveErrorEvent(element: EventTarget): void { - dispatchFrigateCardEvent(element, 'live:error'); + dispatchAdvancedCameraCardEvent(element, 'live:error'); } diff --git a/src/components-lib/media-actions-controller.ts b/src/components-lib/media-actions-controller.ts index 4f34c122..8f1bd14e 100644 --- a/src/components-lib/media-actions-controller.ts +++ b/src/components-lib/media-actions-controller.ts @@ -5,8 +5,8 @@ import { AutoPlayCondition, AutoUnmuteCondition, } from '../config/types.js'; -import { FrigateCardMediaPlayer } from '../types.js'; -import { FrigateCardMediaLoadedEventTarget } from '../utils/media-info.js'; +import { AdvancedCameraCardMediaPlayer } from '../types.js'; +import { AdvancedCameraCardMediaLoadedEventTarget } from '../utils/media-info.js'; import { Timer } from '../utils/timer.js'; export interface MediaActionsControllerOptions { @@ -21,8 +21,8 @@ export interface MediaActionsControllerOptions { microphoneMuteSeconds?: number; } -type RenderRoot = HTMLElement & FrigateCardMediaLoadedEventTarget; -type PlayerElement = HTMLElement & FrigateCardMediaPlayer; +type RenderRoot = HTMLElement & AdvancedCameraCardMediaLoadedEventTarget; +type PlayerElement = HTMLElement & AdvancedCameraCardMediaPlayer; /** * General note: Always unmute before playing, since Chrome may pause a piece of @@ -191,7 +191,7 @@ export class MediaActionsController { protected _removeChildHandlers(): void { for (const [child, callback] of this._eventListeners.entries()) { - child.removeEventListener('frigate-card:media:loaded', callback); + child.removeEventListener('advanced-camera-card:media:loaded', callback); } this._eventListeners.clear(); } @@ -223,7 +223,7 @@ export class MediaActionsController { for (const [index, child] of this._children.entries()) { const eventListener = () => this._mediaLoadedHandler(index); this._eventListeners.set(child, eventListener); - child.addEventListener('frigate-card:media:loaded', eventListener); + child.addEventListener('advanced-camera-card:media:loaded', eventListener); } } protected async _intersectionHandler( diff --git a/src/components-lib/media-grid-controller.ts b/src/components-lib/media-grid-controller.ts index 1ca2c48e..73aa9ffc 100644 --- a/src/components-lib/media-grid-controller.ts +++ b/src/components-lib/media-grid-controller.ts @@ -4,12 +4,12 @@ import Masonry from 'masonry-layout'; import { ViewDisplayConfig } from '../config/types'; import { MediaLoadedInfo } from '../types'; import { - dispatchFrigateCardEvent, + dispatchAdvancedCameraCardEvent, getChildrenFromElement, setOrRemoveAttribute, } from '../utils/basic'; import { - FrigateCardMediaLoadedEventTarget, + AdvancedCameraCardMediaLoadedEventTarget, dispatchExistingMediaLoadedInfoAsEvent, dispatchMediaUnloadedEvent, } from '../utils/media-info'; @@ -24,7 +24,7 @@ const MEDIA_GRID_DEFAULT_SELECTED_WIDTH_FACTOR = 2.0; const MEDIA_GRID_HORIZONTAL_GUTTER_WIDTH = 1; type GridID = string; -type MediaGridChild = HTMLElement & FrigateCardMediaLoadedEventTarget; +type MediaGridChild = HTMLElement & AdvancedCameraCardMediaLoadedEventTarget; type MediaGridContents = Map; export interface MediaGridSelected { @@ -137,7 +137,7 @@ export class MediaGridController { } this._selected = id; - dispatchFrigateCardEvent(this._host, 'media-grid:selected', { selected: id }); + dispatchAdvancedCameraCardEvent(this._host, 'media-grid:selected', { selected: id }); const mediaLoadedInfo = this._mediaLoadedInfoMap.get(id); if (mediaLoadedInfo) { @@ -155,7 +155,7 @@ export class MediaGridController { public unselectAll() { if (this._selected !== null) { dispatchMediaUnloadedEvent(this._host); - dispatchFrigateCardEvent(this._host, 'media-grid:unselected'); + dispatchAdvancedCameraCardEvent(this._host, 'media-grid:unselected'); } this._selected = null; this._updateSelectedStylesOnElements(); @@ -251,7 +251,7 @@ export class MediaGridController { }); child.addEventListener( - 'frigate-card:media:loaded', + 'advanced-camera-card:media:loaded', this._handleMediaLoadedInfoEvent, ); } @@ -262,7 +262,7 @@ export class MediaGridController { }); child.removeEventListener( - 'frigate-card:media:loaded', + 'advanced-camera-card:media:loaded', this._handleMediaLoadedInfoEvent, ); } @@ -345,12 +345,12 @@ export class MediaGridController { protected _setColumnSizeStyles(): void { this._host.style.setProperty( - '--frigate-card-grid-column-size', + '--advanced-camera-card-grid-column-size', `${this._getColumnSize()}px`, ); this._host.style.setProperty( - '--frigate-card-grid-selected-width-factor', + '--advanced-camera-card-grid-selected-width-factor', `${ this._displayConfig?.grid_selected_width_factor ?? MEDIA_GRID_DEFAULT_SELECTED_WIDTH_FACTOR diff --git a/src/components-lib/menu-button-controller.ts b/src/components-lib/menu-button-controller.ts index 17131539..75e0505f 100644 --- a/src/components-lib/menu-button-controller.ts +++ b/src/components-lib/menu-button-controller.ts @@ -6,12 +6,11 @@ import { MediaPlayerManager } from '../card-controller/media-player-manager'; import { MicrophoneManager } from '../card-controller/microphone-manager'; import { ViewManager } from '../card-controller/view/view-manager'; import { - FRIGATE_CARD_VIEWS_USER_SPECIFIED, - FrigateCardConfig, - FrigateCardCustomAction, + AdvancedCameraCardConfig, + AdvancedCameraCardCustomAction, MenuItem, + VIEWS_USER_SPECIFIED, } from '../config/types'; -import { FRIGATE_BUTTON_MENU_ICON } from '../const'; import { localize } from '../localize/localize.js'; import { MediaLoadedInfo } from '../types'; import { @@ -63,12 +62,12 @@ export class MenuButtonController { */ public calculateButtons( hass: HomeAssistant, - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, cameraManager: CameraManager, options?: MenuButtonControllerOptions, ): MenuItem[] { return [ - this._getDefaultButton(config), + this._getIrisButton(config), this._getCamerasButton(config, cameraManager, options?.view), this._getSubstreamsButton(config, cameraManager, options?.view), this._getLiveButton(config, options?.view, options?.viewManager), @@ -107,27 +106,25 @@ export class MenuButtonController { ].filter(isTruthy); } - protected _getDefaultButton(config: FrigateCardConfig): MenuItem { + protected _getIrisButton(config: AdvancedCameraCardConfig): MenuItem { return { - // Use a magic icon value that the menu will use to render the custom - // Frigate icon. - icon: FRIGATE_BUTTON_MENU_ICON, - ...config.menu.buttons.frigate, - type: 'custom:frigate-card-menu-icon', - title: localize('config.menu.buttons.frigate'), + icon: 'iris', + ...config.menu.buttons.iris, + type: 'custom:advanced-camera-card-menu-icon', + title: localize('config.menu.buttons.iris'), // The default button always shows regardless of whether the menu is // hidden or not. permanent: true, tap_action: config.menu?.style === 'hidden' - ? (createGeneralAction('menu_toggle') as FrigateCardCustomAction) - : (createGeneralAction('default') as FrigateCardCustomAction), - hold_action: createGeneralAction('diagnostics') as FrigateCardCustomAction, + ? (createGeneralAction('menu_toggle') as AdvancedCameraCardCustomAction) + : (createGeneralAction('default') as AdvancedCameraCardCustomAction), + hold_action: createGeneralAction('diagnostics') as AdvancedCameraCardCustomAction, }; } protected _getCamerasButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, cameraManager: CameraManager, view?: View | null, ): MenuItem | null { @@ -153,7 +150,7 @@ export class MenuButtonController { return { icon: 'mdi:video-switch', ...config.menu.buttons.cameras, - type: 'custom:frigate-card-menu-submenu', + type: 'custom:advanced-camera-card-menu-submenu', title: localize('config.menu.buttons.cameras'), items: submenuItems, }; @@ -162,7 +159,7 @@ export class MenuButtonController { } protected _getSubstreamsButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, cameraManager: CameraManager, view?: View | null, ): MenuItem | null { @@ -190,10 +187,10 @@ export class MenuButtonController { substreamAwareCameraID !== view.camera ? this._getEmphasizedStyle() : {}, title: localize('config.menu.buttons.substreams'), ...config.menu.buttons.substreams, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', tap_action: createGeneralAction( hasSubstream(view) ? 'live_substream_off' : 'live_substream_on', - ) as FrigateCardCustomAction, + ) as AdvancedCameraCardCustomAction, }; } else if (streams.length > 2) { const menuItems = Array.from(streams, (streamID) => { @@ -216,7 +213,7 @@ export class MenuButtonController { style: substreamAwareCameraID !== view.camera ? this._getEmphasizedStyle() : {}, ...config.menu.buttons.substreams, - type: 'custom:frigate-card-menu-submenu', + type: 'custom:advanced-camera-card-menu-submenu', items: menuItems, }; } @@ -225,7 +222,7 @@ export class MenuButtonController { } protected _getLiveButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, view?: View | null, viewManager?: ViewManager | null, ): MenuItem | null { @@ -233,16 +230,16 @@ export class MenuButtonController { ? { icon: 'mdi:cctv', ...config.menu.buttons.live, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.view.views.live'), style: view.is('live') ? this._getEmphasizedStyle() : {}, - tap_action: createGeneralAction('live') as FrigateCardCustomAction, + tap_action: createGeneralAction('live') as AdvancedCameraCardCustomAction, } : null; } protected _getClipsButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, view?: View | null, viewManager?: ViewManager | null, ): MenuItem | null { @@ -250,17 +247,17 @@ export class MenuButtonController { ? { icon: 'mdi:filmstrip', ...config.menu.buttons.clips, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.view.views.clips'), style: view?.is('clips') ? this._getEmphasizedStyle() : {}, - tap_action: createGeneralAction('clips') as FrigateCardCustomAction, - hold_action: createGeneralAction('clip') as FrigateCardCustomAction, + tap_action: createGeneralAction('clips') as AdvancedCameraCardCustomAction, + hold_action: createGeneralAction('clip') as AdvancedCameraCardCustomAction, } : null; } protected _getSnapshotsButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, view?: View | null, viewManager?: ViewManager | null, ): MenuItem | null { @@ -268,17 +265,17 @@ export class MenuButtonController { ? { icon: 'mdi:camera', ...config.menu.buttons.snapshots, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.view.views.snapshots'), style: view?.is('snapshots') ? this._getEmphasizedStyle() : {}, - tap_action: createGeneralAction('snapshots') as FrigateCardCustomAction, - hold_action: createGeneralAction('snapshot') as FrigateCardCustomAction, + tap_action: createGeneralAction('snapshots') as AdvancedCameraCardCustomAction, + hold_action: createGeneralAction('snapshot') as AdvancedCameraCardCustomAction, } : null; } protected _getRecordingsButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, view?: View | null, viewManager?: ViewManager | null, ): MenuItem | null { @@ -286,17 +283,21 @@ export class MenuButtonController { ? { icon: 'mdi:album', ...config.menu.buttons.recordings, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.view.views.recordings'), style: view.is('recordings') ? this._getEmphasizedStyle() : {}, - tap_action: createGeneralAction('recordings') as FrigateCardCustomAction, - hold_action: createGeneralAction('recording') as FrigateCardCustomAction, + tap_action: createGeneralAction( + 'recordings', + ) as AdvancedCameraCardCustomAction, + hold_action: createGeneralAction( + 'recording', + ) as AdvancedCameraCardCustomAction, } : null; } protected _getImageButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, view?: View | null, viewManager?: ViewManager | null, ): MenuItem | null { @@ -304,16 +305,16 @@ export class MenuButtonController { ? { icon: 'mdi:image', ...config.menu.buttons.image, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.view.views.image'), style: view?.is('image') ? this._getEmphasizedStyle() : {}, - tap_action: createGeneralAction('image') as FrigateCardCustomAction, + tap_action: createGeneralAction('image') as AdvancedCameraCardCustomAction, } : null; } protected _getTimelineButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, view?: View | null, viewManager?: ViewManager | null, ): MenuItem | null { @@ -321,16 +322,16 @@ export class MenuButtonController { ? { icon: 'mdi:chart-gantt', ...config.menu.buttons.timeline, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.view.views.timeline'), style: view.is('timeline') ? this._getEmphasizedStyle() : {}, - tap_action: createGeneralAction('timeline') as FrigateCardCustomAction, + tap_action: createGeneralAction('timeline') as AdvancedCameraCardCustomAction, } : null; } protected _getDownloadButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, cameraManager: CameraManager, view?: View | null, ): MenuItem | null { @@ -342,31 +343,31 @@ export class MenuButtonController { return { icon: 'mdi:download', ...config.menu.buttons.download, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.menu.buttons.download'), - tap_action: createGeneralAction('download') as FrigateCardCustomAction, + tap_action: createGeneralAction('download') as AdvancedCameraCardCustomAction, }; } return null; } protected _getCameraUIButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, showCameraUIButton?: boolean, ): MenuItem | null { return showCameraUIButton ? { icon: 'mdi:web', ...config.menu.buttons.camera_ui, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.menu.buttons.camera_ui'), - tap_action: createGeneralAction('camera_ui') as FrigateCardCustomAction, + tap_action: createGeneralAction('camera_ui') as AdvancedCameraCardCustomAction, } : null; } protected _getMicrophoneButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, microphoneManager?: MicrophoneManager | null, currentMediaLoadedInfo?: MediaLoadedInfo | null, ): MenuItem | null { @@ -382,23 +383,23 @@ export class MenuButtonController { ? 'mdi:microphone-off' : 'mdi:microphone', ...config.menu.buttons.microphone, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.menu.buttons.microphone'), style: unavailable || muted ? {} : this._getEmphasizedStyle(true), ...(!unavailable && buttonType === 'momentary' && { start_tap_action: createGeneralAction( 'microphone_unmute', - ) as FrigateCardCustomAction, + ) as AdvancedCameraCardCustomAction, end_tap_action: createGeneralAction( 'microphone_mute', - ) as FrigateCardCustomAction, + ) as AdvancedCameraCardCustomAction, }), ...(!unavailable && buttonType === 'toggle' && { tap_action: createGeneralAction( muted ? 'microphone_unmute' : 'microphone_mute', - ) as FrigateCardCustomAction, + ) as AdvancedCameraCardCustomAction, }), }; } @@ -406,21 +407,21 @@ export class MenuButtonController { } protected _getExpandButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, inExpandedMode?: boolean, ): MenuItem { return { icon: inExpandedMode ? 'mdi:arrow-collapse-all' : 'mdi:arrow-expand-all', ...config.menu.buttons.expand, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.menu.buttons.expand'), - tap_action: createGeneralAction('expand') as FrigateCardCustomAction, + tap_action: createGeneralAction('expand') as AdvancedCameraCardCustomAction, style: inExpandedMode ? this._getEmphasizedStyle() : {}, }; } protected _getFullscreenButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, fullscreenManager?: FullscreenManager | null, ): MenuItem | null { const inFullscreen = fullscreenManager?.isInFullscreen(); @@ -428,9 +429,11 @@ export class MenuButtonController { ? { icon: inFullscreen ? 'mdi:fullscreen-exit' : 'mdi:fullscreen', ...config.menu.buttons.fullscreen, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.menu.buttons.fullscreen'), - tap_action: createGeneralAction('fullscreen') as FrigateCardCustomAction, + tap_action: createGeneralAction( + 'fullscreen', + ) as AdvancedCameraCardCustomAction, style: inFullscreen ? this._getEmphasizedStyle() : {}, } : null; @@ -438,7 +441,7 @@ export class MenuButtonController { protected _getCastButton( hass: HomeAssistant, - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, cameraManager: CameraManager, view?: View | null, mediaPlayerController?: MediaPlayerManager | null, @@ -475,7 +478,7 @@ export class MenuButtonController { return { icon: 'mdi:cast', ...config.menu.buttons.media_player, - type: 'custom:frigate-card-menu-submenu', + type: 'custom:advanced-camera-card-menu-submenu', title: localize('config.menu.buttons.media_player'), items: mediaPlayerItems, }; @@ -484,7 +487,7 @@ export class MenuButtonController { } protected _getPlayPauseButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, currentMediaLoadedInfo?: MediaLoadedInfo | null, ): MenuItem | null { if ( @@ -496,18 +499,18 @@ export class MenuButtonController { return { icon: paused ? 'mdi:play' : 'mdi:pause', ...config.menu.buttons.play, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.menu.buttons.play'), tap_action: createGeneralAction( paused ? 'play' : 'pause', - ) as FrigateCardCustomAction, + ) as AdvancedCameraCardCustomAction, }; } return null; } protected _getMuteUnmuteButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, currentMediaLoadedInfo?: MediaLoadedInfo | null, ): MenuItem | null { if ( @@ -519,34 +522,34 @@ export class MenuButtonController { return { icon: muted ? 'mdi:volume-off' : 'mdi:volume-high', ...config.menu.buttons.mute, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.menu.buttons.mute'), tap_action: createGeneralAction( muted ? 'unmute' : 'mute', - ) as FrigateCardCustomAction, + ) as AdvancedCameraCardCustomAction, }; } return null; } protected _getScreenshotButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, currentMediaLoadedInfo?: MediaLoadedInfo | null, ): MenuItem | null { if (currentMediaLoadedInfo && currentMediaLoadedInfo.player) { return { icon: 'mdi:monitor-screenshot', ...config.menu.buttons.screenshot, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.menu.buttons.screenshot'), - tap_action: createGeneralAction('screenshot') as FrigateCardCustomAction, + tap_action: createGeneralAction('screenshot') as AdvancedCameraCardCustomAction, }; } return null; } protected _getDisplayModeButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, cameraManager: CameraManager, view?: View | null, ): MenuItem | null { @@ -563,7 +566,7 @@ export class MenuButtonController { icon: isGrid ? 'mdi:grid-off' : 'mdi:grid', ...config.menu.buttons.display_mode, style: isGrid ? this._getEmphasizedStyle() : {}, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: isGrid ? localize('display_modes.single') : localize('display_modes.grid'), @@ -574,7 +577,7 @@ export class MenuButtonController { } protected _getPTZControlsButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, cameraManager: CameraManager, view?: View | null, ): MenuItem | null { @@ -602,7 +605,7 @@ export class MenuButtonController { icon: 'mdi:pan', ...config.menu.buttons.ptz_controls, style: isOn ? this._getEmphasizedStyle() : {}, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.menu.buttons.ptz_controls'), tap_action: createPTZControlsAction(!isOn), }; @@ -611,7 +614,7 @@ export class MenuButtonController { } protected _getPTZHomeButton( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, cameraManager: CameraManager, view?: View | null, ): MenuItem | null { @@ -633,11 +636,11 @@ export class MenuButtonController { return { icon: 'mdi:home', ...config.menu.buttons.ptz_home, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: localize('config.menu.buttons.ptz_home'), tap_action: createPTZMultiAction({ targetID: target.targetID, - }) as FrigateCardCustomAction, + }) as AdvancedCameraCardCustomAction, }; } @@ -649,11 +652,11 @@ export class MenuButtonController { if (critical) { return { animation: 'pulse 3s infinite', - color: 'var(--frigate-card-menu-button-critical-color)', + color: 'var(--advanced-camera-card-menu-button-critical-color)', }; } return { - color: 'var(--frigate-card-menu-button-active-color)', + color: 'var(--advanced-camera-card-menu-button-active-color)', }; } @@ -664,7 +667,7 @@ export class MenuButtonController { * @returns A StyleInfo object. */ protected _getStyleFromActions( - config: FrigateCardConfig, + config: AdvancedCameraCardConfig, button: MenuItem, options?: MenuButtonControllerOptions, ): StyleInfo { @@ -677,28 +680,28 @@ export class MenuButtonController { ]) { const actions = Array.isArray(actionSet) ? actionSet : [actionSet]; for (const action of actions) { - // All frigate card actions will have action of 'fire-dom-event' and + // All advanced camera card actions will have action of 'fire-dom-event' and // styling only applies to those. if ( !action || action.action !== 'fire-dom-event' || - !('frigate_card_action' in action) + !('advanced_camera_card_action' in action) ) { continue; } - const frigateCardAction = action as FrigateCardCustomAction; + const customCardAction = action as AdvancedCameraCardCustomAction; if ( - FRIGATE_CARD_VIEWS_USER_SPECIFIED.some( + VIEWS_USER_SPECIFIED.some( (viewName) => - viewName === frigateCardAction.frigate_card_action && - options?.view?.is(frigateCardAction.frigate_card_action), + viewName === customCardAction.advanced_camera_card_action && + options?.view?.is(customCardAction.advanced_camera_card_action), ) || - (frigateCardAction.frigate_card_action === 'default' && + (customCardAction.advanced_camera_card_action === 'default' && options?.view?.is(config.view.default)) || - (frigateCardAction.frigate_card_action === 'fullscreen' && + (customCardAction.advanced_camera_card_action === 'fullscreen' && !!options?.fullscreenManager?.isInFullscreen()) || - (frigateCardAction.frigate_card_action === 'camera_select' && - options?.view?.camera === frigateCardAction.camera) + (customCardAction.advanced_camera_card_action === 'camera_select' && + options?.view?.camera === customCardAction.camera) ) { return this._getEmphasizedStyle(); } diff --git a/src/components-lib/menu-controller.ts b/src/components-lib/menu-controller.ts index a5fb4a8f..c14d65c4 100644 --- a/src/components-lib/menu-controller.ts +++ b/src/components-lib/menu-controller.ts @@ -3,7 +3,7 @@ import { LitElement } from 'lit'; import { orderBy } from 'lodash-es'; import { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request.js'; import { - FRIGATE_MENU_PRIORITY_MAX, + MENU_PRIORITY_MAX, type ActionType, type ActionsConfig, type MenuConfig, @@ -28,7 +28,7 @@ export class MenuController { public setMenuConfig(config: MenuConfig): void { this._config = config; this._host.style.setProperty( - '--frigate-card-menu-button-size', + '--advanced-camera-card-menu-button-size', `${config.button_size}px`, ); @@ -164,8 +164,7 @@ export class MenuController { // If the menu is hidden, the buttons that toggle the menu must come // first. return ( - priority + - (this._isHidingMenu() && button.permanent ? FRIGATE_MENU_PRIORITY_MAX : 0) + priority + (this._isHidingMenu() && button.permanent ? MENU_PRIORITY_MAX : 0) ); }, ['desc'], @@ -178,6 +177,6 @@ export class MenuController { protected _isUnknownActionMenuToggleAction(action: ActionType): boolean { const parsedAction = convertActionToCardCustomAction(action); - return !!parsedAction && parsedAction.frigate_card_action == 'menu_toggle'; + return !!parsedAction && parsedAction.advanced_camera_card_action == 'menu_toggle'; } } diff --git a/src/components-lib/message/dispatch.ts b/src/components-lib/message/dispatch.ts index 052730d5..4928cdfd 100644 --- a/src/components-lib/message/dispatch.ts +++ b/src/components-lib/message/dispatch.ts @@ -1,11 +1,14 @@ -import { FrigateCardError, Message } from '../../types'; -import { dispatchFrigateCardEvent } from '../../utils/basic'; +import { AdvancedCameraCardError, Message } from '../../types'; +import { dispatchAdvancedCameraCardEvent } from '../../utils/basic'; // Facilitates correct typing of event handlers. -export interface FrigateCardMessageEventTarget extends EventTarget { +export interface AdvancedCameraCardMessageEventTarget extends EventTarget { addEventListener( - event: 'frigate-card:message', - listener: (this: FrigateCardMessageEventTarget, ev: CustomEvent) => void, + event: 'advanced-camera-card:message', + listener: ( + this: AdvancedCameraCardMessageEventTarget, + ev: CustomEvent, + ) => void, options?: AddEventListenerOptions | boolean, ): void; addEventListener( @@ -14,8 +17,11 @@ export interface FrigateCardMessageEventTarget extends EventTarget { options?: AddEventListenerOptions | boolean, ): void; removeEventListener( - event: 'frigate-card:message', - listener: (this: FrigateCardMessageEventTarget, ev: CustomEvent) => void, + event: 'advanced-camera-card:message', + listener: ( + this: AdvancedCameraCardMessageEventTarget, + ev: CustomEvent, + ) => void, options?: boolean | EventListenerOptions, ): void; removeEventListener( @@ -33,15 +39,15 @@ export interface FrigateCardMessageEventTarget extends EventTarget { * @param element The element to send the event. * @param message The message to show. */ -export const dispatchFrigateCardErrorEvent = ( +export const dispatchAdvancedCameraCardErrorEvent = ( element: EventTarget, error: unknown, ): void => { if (error instanceof Error) { - dispatchFrigateCardEvent(element, 'message', { + dispatchAdvancedCameraCardEvent(element, 'message', { message: error.message, type: 'error', - ...(error instanceof FrigateCardError && { context: error.context }), + ...(error instanceof AdvancedCameraCardError && { context: error.context }), }); } }; diff --git a/src/components-lib/status-bar-controller.ts b/src/components-lib/status-bar-controller.ts index fc16323c..401726e1 100644 --- a/src/components-lib/status-bar-controller.ts +++ b/src/components-lib/status-bar-controller.ts @@ -5,7 +5,7 @@ import orderBy from 'lodash-es/orderBy'; import { dispatchActionExecutionRequest } from '../card-controller/actions/utils/execution-request'; import { ActionsConfig, - FRIGATE_STATUS_BAR_PRIORITY_DEFAULT, + STATUS_BAR_PRIORITY_DEFAULT, StatusBarConfig, StatusBarItem, } from '../config/types'; @@ -33,7 +33,7 @@ export class StatusBarController { const newItems = orderBy( exclusiveItems.length ? exclusiveItems : items, - (item) => item.priority ?? FRIGATE_STATUS_BAR_PRIORITY_DEFAULT, + (item) => item.priority ?? STATUS_BAR_PRIORITY_DEFAULT, 'desc', ); @@ -53,7 +53,7 @@ export class StatusBarController { public setConfig(config: StatusBarConfig): void { this._config = config; this._host.style.setProperty( - '--frigate-card-status-bar-height', + '--advanced-camera-card-status-bar-height', `${config.height}px`, ); @@ -97,11 +97,11 @@ export class StatusBarController { protected _getSufficientValue(item: StatusBarItem): string | null { /* istanbul ignore else: cannot happen -- @preserve */ - if (item.type === 'custom:frigate-card-status-bar-icon') { + if (item.type === 'custom:advanced-camera-card-status-bar-icon') { return item.icon; - } else if (item.type === 'custom:frigate-card-status-bar-string') { + } else if (item.type === 'custom:advanced-camera-card-status-bar-string') { return item.string; - } else if (item.type === 'custom:frigate-card-status-bar-image') { + } else if (item.type === 'custom:advanced-camera-card-status-bar-image') { return item.image; } else { return null; diff --git a/src/components-lib/timeline-source.ts b/src/components-lib/timeline-source.ts index 054cc0ef..4d9819b6 100644 --- a/src/components-lib/timeline-source.ts +++ b/src/components-lib/timeline-source.ts @@ -24,7 +24,7 @@ const TIMELINE_FRESHNESS_TOLERANCE_SECONDS = 30; // instead of clean recording blocks. const TIMELINE_RECORDING_SEGMENT_CONSECUTIVE_TOLERANCE_SECONDS = 60; -export interface FrigateCardTimelineItem extends TimelineItem { +export interface AdvancedCameraCardTimelineItem extends TimelineItem { // Use numbers to avoid significant volumes of Date object construction (for // high-quantity recording segments). start: number; @@ -38,7 +38,7 @@ export interface FrigateCardTimelineItem extends TimelineItem { export class TimelineDataSource { protected _cameraManager: CameraManager; - protected _dataset: DataSet = new DataSet(); + protected _dataset: DataSet = new DataSet(); // The ranges in which recordings have been calculated and added for. // Calculating recordings is a very expensive process since it is based on @@ -66,7 +66,7 @@ export class TimelineDataSource { this._showRecordings = showRecordings; } - get dataset(): DataSet { + get dataset(): DataSet { return this._dataset; } @@ -135,7 +135,7 @@ export class TimelineDataSource { } const mediaArray = await this._cameraManager.executeMediaQueries(eventQueries); - const data: FrigateCardTimelineItem[] = []; + const data: AdvancedCameraCardTimelineItem[] = []; for (const media of mediaArray ?? []) { const startTime = media.getStartTime(); const id = media.getID(); @@ -160,15 +160,15 @@ export class TimelineDataSource { } protected async _refreshRecordings(window: TimelineWindow): Promise { - type FrigateCardTimelineItemWithEnd = ModifyInterface< - FrigateCardTimelineItem, + type AdvancedCameraCardTimelineItemWithEnd = ModifyInterface< + AdvancedCameraCardTimelineItem, { end: number } >; const convertSegmentToRecording = ( cameraID: string, segment: RecordingSegment, - ): FrigateCardTimelineItemWithEnd => { + ): AdvancedCameraCardTimelineItemWithEnd => { return { id: `recording-${cameraID}-${segment.id}`, group: cameraID, @@ -181,11 +181,11 @@ export class TimelineDataSource { const getExistingRecordingsForCameraID = ( cameraID: string, - ): FrigateCardTimelineItemWithEnd[] => { + ): AdvancedCameraCardTimelineItemWithEnd[] => { return this._dataset.get({ filter: (item) => item.type == 'background' && item.group === cameraID && item.end !== undefined, - }) as FrigateCardTimelineItemWithEnd[]; + }) as AdvancedCameraCardTimelineItemWithEnd[]; }; const deleteRecordingsForCameraID = (cameraID: string): void => { @@ -196,7 +196,9 @@ export class TimelineDataSource { ); }; - const addRecordings = (recordings: FrigateCardTimelineItemWithEnd[]): void => { + const addRecordings = ( + recordings: AdvancedCameraCardTimelineItemWithEnd[], + ): void => { this._dataset.add(recordings); }; @@ -247,7 +249,7 @@ export class TimelineDataSource { const compressedRecordings = compressRanges( mergedRecordings, TIMELINE_RECORDING_SEGMENT_CONSECUTIVE_TOLERANCE_SECONDS, - ) as FrigateCardTimelineItemWithEnd[]; + ) as AdvancedCameraCardTimelineItemWithEnd[]; deleteRecordingsForCameraID(cameraID); addRecordings(compressedRecordings); diff --git a/src/components-lib/zoom/zoom-controller.ts b/src/components-lib/zoom/zoom-controller.ts index 2261e35a..3dd05075 100644 --- a/src/components-lib/zoom/zoom-controller.ts +++ b/src/components-lib/zoom/zoom-controller.ts @@ -1,9 +1,9 @@ import Panzoom, { PanzoomEventDetail, PanzoomObject } from '@dermotduffy/panzoom'; -import throttle from 'lodash-es/throttle'; import round from 'lodash-es/round'; +import throttle from 'lodash-es/throttle'; import { arefloatsApproximatelyEqual, - dispatchFrigateCardEvent, + dispatchAdvancedCameraCardEvent, isHoverableDevice, } from '../../utils/basic'; import { @@ -76,7 +76,7 @@ export class ZoomController { // Even though the click is stopped,the card still needs to gain focus so // that keyboard shortcuts will work immediately after the card is clicked // upon. - dispatchFrigateCardEvent(this._element, 'focus'); + dispatchAdvancedCameraCardEvent(this._element, 'focus'); } this._allowClick = true; }; @@ -133,7 +133,7 @@ export class ZoomController { // Disable automatic touchAction setting from Panzoom() as otherwise it // effectively disables dashboard scrolling. See: - // https://github.com/dermotduffy/frigate-hass-card/issues/1181 + // https://github.com/dermotduffy/advanced-camera-card/issues/1181 touchAction: '', // Set the initial pan/zoom values to avoid an initial unzoomed view. @@ -211,11 +211,11 @@ export class ZoomController { if (unzoomed && this._zoomed) { this._zoomed = false; this._setTouchAction(true); - dispatchFrigateCardEvent(this._element, 'zoom:unzoomed'); + dispatchAdvancedCameraCardEvent(this._element, 'zoom:unzoomed'); } else if (!unzoomed && !this._zoomed) { this._zoomed = true; this._setTouchAction(false); - dispatchFrigateCardEvent(this._element, 'zoom:zoomed'); + dispatchAdvancedCameraCardEvent(this._element, 'zoom:zoomed'); } const converted = this._convertXYPanToPercent(pz.x, pz.y, pz.scale); @@ -229,7 +229,7 @@ export class ZoomController { unzoomed: unzoomed, }; - dispatchFrigateCardEvent(this._element, 'zoom:change', observed); + dispatchAdvancedCameraCardEvent(this._element, 'zoom:change', observed); } protected _isZoomEqual(a: PartialZoomSettings, b: PartialZoomSettings): boolean { @@ -319,7 +319,7 @@ export class ZoomController { } /** - * Convert from Frigate card pan % values to Panzoom X/Y transformation + * Convert from Advanced Camera Card pan % values to Panzoom X/Y transformation * coordinates. * @param x The x translation value. * @param y The y translation value. @@ -439,7 +439,7 @@ export class ZoomController { return ( !this._isUnzoomed(this._panzoom?.getScale()) || // TouchEvent does not exist on Firefox on non-touch events. See: - // https://github.com/dermotduffy/frigate-hass-card/issues/1174 + // https://github.com/dermotduffy/advanced-camera-card/issues/1174 (window.TouchEvent && ev instanceof TouchEvent && ev.touches.length > 1) || (ev instanceof WheelEvent && ev.ctrlKey) ); diff --git a/src/components/carousel.ts b/src/components/carousel.ts index e1dabd85..7545cd1f 100644 --- a/src/components/carousel.ts +++ b/src/components/carousel.ts @@ -22,8 +22,8 @@ export type EmblaCarouselPlugins = CreatePluginType< Record >[]; -@customElement('frigate-card-carousel') -export class FrigateCardCarousel extends LitElement { +@customElement('advanced-camera-card-carousel') +export class AdvancedCameraCardCarousel extends LitElement { @property({ attribute: true, reflect: true }) public direction: CarouselDirection = 'horizontal'; @@ -124,6 +124,6 @@ export class FrigateCardCarousel extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-carousel': FrigateCardCarousel; + 'advanced-camera-card-carousel': AdvancedCameraCardCarousel; } } diff --git a/src/components/date-picker.ts b/src/components/date-picker.ts index 6401f719..47799c21 100644 --- a/src/components/date-picker.ts +++ b/src/components/date-picker.ts @@ -4,15 +4,15 @@ import { createRef, ref, Ref } from 'lit/directives/ref.js'; import { localize } from '../localize/localize'; import datePickerStyle from '../scss/date-picker.scss'; import { stopEventFromActivatingCardWideActions } from '../utils/action'; -import { dispatchFrigateCardEvent } from '../utils/basic'; +import { dispatchAdvancedCameraCardEvent } from '../utils/basic'; import './icon'; export interface DatePickerEvent { date: Date | null; } -@customElement('frigate-card-date-picker') -export class FrigateCardDatePicker extends LitElement { +@customElement('advanced-camera-card-date-picker') +export class AdvancedCameraCardDatePicker extends LitElement { @property({ attribute: false }) public icon?: string; @@ -32,7 +32,7 @@ export class FrigateCardDatePicker extends LitElement { const changed = () => { const value = this._refInput.value?.value; - dispatchFrigateCardEvent(this, 'date-picker:change', { + dispatchAdvancedCameraCardEvent(this, 'date-picker:change', { date: value ? new Date(value) : null, }); }; @@ -45,7 +45,7 @@ export class FrigateCardDatePicker extends LitElement { @input=${() => changed()} @change=${() => changed()} /> - - `; + `; } static get styles(): CSSResultGroup { @@ -64,6 +64,6 @@ export class FrigateCardDatePicker extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-date-picker': FrigateCardDatePicker; + 'advanced-camera-card-date-picker': AdvancedCameraCardDatePicker; } } diff --git a/src/components/diagnostics.ts b/src/components/diagnostics.ts index 650704e8..f9c3f56b 100644 --- a/src/components/diagnostics.ts +++ b/src/components/diagnostics.ts @@ -2,15 +2,15 @@ import { HomeAssistant } from '@dermotduffy/custom-card-helpers'; import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { until } from 'lit/directives/until.js'; -import { RawFrigateCardConfig } from '../config/types'; +import { RawAdvancedCameraCardConfig } from '../config/types'; import { localize } from '../localize/localize'; import basicBlockStyle from '../scss/basic-block.scss'; import { getDiagnostics } from '../utils/diagnostics'; import { DeviceRegistryManager } from '../utils/ha/registry/device'; import { renderMessage } from './message'; -@customElement('frigate-card-diagnostics') -export class FrigateCardDiagnostics extends LitElement { +@customElement('advanced-camera-card-diagnostics') +export class AdvancedCameraCardDiagnostics extends LitElement { // Not a reactive property to avoid multiple diagnostics fetches. public hass?: HomeAssistant; @@ -18,7 +18,7 @@ export class FrigateCardDiagnostics extends LitElement { public deviceRegistryManager?: DeviceRegistryManager; @property({ attribute: false }) - public rawConfig?: RawFrigateCardConfig; + public rawConfig?: RawAdvancedCameraCardConfig; protected async _renderDiagnostics(): Promise { const diagnostics = await getDiagnostics( @@ -52,6 +52,6 @@ export class FrigateCardDiagnostics extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-diagnostics': FrigateCardDiagnostics; + 'advanced-camera-card-diagnostics': AdvancedCameraCardDiagnostics; } } diff --git a/src/components/drawer.ts b/src/components/drawer.ts index 2913ef69..0fe03136 100644 --- a/src/components/drawer.ts +++ b/src/components/drawer.ts @@ -21,8 +21,8 @@ export interface DrawerIcons { closed?: string; } -@customElement('frigate-card-drawer') -export class FrigateCardDrawer extends LitElement { +@customElement('advanced-camera-card-drawer') +export class AdvancedCameraCardDrawer extends LitElement { @property({ attribute: true, reflect: true }) public location: 'left' | 'right' = 'left'; @@ -122,7 +122,7 @@ export class FrigateCardDrawer extends LitElement { this.open = !this.open; }} > - @@ -63,9 +63,9 @@ export class FrigateCardKeyAssigner extends LitElement { this._controller.setValue(null); }} > - + > ${localize('key_assigner.unassign')} ` : '' @@ -87,6 +87,6 @@ export class FrigateCardKeyAssigner extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-key-assigner': FrigateCardKeyAssigner; + 'advanced-camera-card-key-assigner': AdvancedCameraCardKeyAssigner; } } diff --git a/src/components/live/carousel.ts b/src/components/live/carousel.ts index 9002e08c..53f6724e 100644 --- a/src/components/live/carousel.ts +++ b/src/components/live/carousel.ts @@ -18,13 +18,13 @@ import { import { MicrophoneState } from '../../card-controller/types.js'; import { ViewManagerEpoch } from '../../card-controller/view/types.js'; import { MediaActionsController } from '../../components-lib/media-actions-controller.js'; -import { dispatchFrigateCardErrorEvent } from '../../components-lib/message/dispatch.js'; +import { dispatchAdvancedCameraCardErrorEvent } from '../../components-lib/message/dispatch.js'; import { ZoomSettingsObserved } from '../../components-lib/zoom/types.js'; import { handleZoomSettingsObservedEvent } from '../../components-lib/zoom/zoom-view-context.js'; import { CameraConfig, CardWideConfig, - frigateCardConfigDefaults, + configDefaults, LiveConfig, liveConfigAbsoluteRootSchema, Overrides, @@ -45,11 +45,11 @@ import '../carousel'; import { EmblaCarouselPlugins } from '../carousel.js'; import '../next-prev-control.js'; import '../ptz.js'; -import { FrigateCardPTZ } from '../ptz.js'; +import { AdvancedCameraCardPTZ } from '../ptz.js'; import './provider.js'; -import { FrigateCardLiveProvider } from './provider.js'; +import { AdvancedCameraCardLiveProvider } from './provider.js'; -const FRIGATE_CARD_LIVE_PROVIDER = 'frigate-card-live-provider'; +const ADVANCED_CAMERA_CARD_LIVE_PROVIDER = 'advanced-camera-card-live-provider'; interface CameraNeighbor { id: string; @@ -61,8 +61,8 @@ interface CameraNeighbors { next?: CameraNeighbor; } -@customElement('frigate-card-live-carousel') -export class FrigateCardLiveCarousel extends LitElement { +@customElement('advanced-camera-card-live-carousel') +export class AdvancedCameraCardLiveCarousel extends LitElement { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; @@ -95,7 +95,7 @@ export class FrigateCardLiveCarousel extends LitElement { // Index between camera name and slide number. protected _cameraToSlide: Record = {}; - protected _refPTZControl: Ref = createRef(); + protected _refPTZControl: Ref = createRef(); protected _refCarousel: Ref = createRef(); protected _mediaActionsController = new MediaActionsController(); @@ -118,7 +118,7 @@ export class FrigateCardLiveCarousel extends LitElement { protected _getTransitionEffect(): TransitionEffect { return ( this.overriddenLiveConfig?.transition_effect ?? - frigateCardConfigDefaults.live.transition_effect + configDefaults.live.transition_effect ); } @@ -143,7 +143,7 @@ export class FrigateCardLiveCarousel extends LitElement { changedProps.has('overriddenLiveConfig') ) { this._mediaActionsController.setOptions({ - playerSelector: FRIGATE_CARD_LIVE_PROVIDER, + playerSelector: ADVANCED_CAMERA_CARD_LIVE_PROVIDER, ...(this.overriddenLiveConfig?.auto_play && { autoPlayConditions: this.overriddenLiveConfig.auto_play, }), @@ -254,8 +254,8 @@ export class FrigateCardLiveCarousel extends LitElement { } const liveProvider = slide?.querySelector( - FRIGATE_CARD_LIVE_PROVIDER, - ) as FrigateCardLiveProvider | null; + ADVANCED_CAMERA_CARD_LIVE_PROVIDER, + ) as AdvancedCameraCardLiveProvider | null; if (liveProvider) { liveProvider.load = action === 'load'; } @@ -280,7 +280,7 @@ export class FrigateCardLiveCarousel extends LitElement { try { // The condition controller object contains the currently live camera, which // (in the carousel for example) is not necessarily the live camera *this* - // is rendering right now, so we provide a + // is rendering right now, so we provide a // stateOverride to evaluate the condition in that context. liveConfig = getOverriddenConfig( this.conditionsManagerEpoch.manager, @@ -292,7 +292,7 @@ export class FrigateCardLiveCarousel extends LitElement { }, ).live; } catch (ev) { - return dispatchFrigateCardErrorEvent(this, ev); + return dispatchAdvancedCameraCardErrorEvent(this, ev); } const cameraMetadata = this.cameraManager.getCameraMetadata(cameraID); @@ -300,7 +300,7 @@ export class FrigateCardLiveCarousel extends LitElement { return html`
- ) => + @advanced-camera-card:zoom:change=${(ev: CustomEvent) => handleZoomSettingsObservedEvent( ev, this.viewManagerEpoch?.manager, cameraID, )} > - +
`; } @@ -381,7 +381,7 @@ export class FrigateCardLiveCarousel extends LitElement { ? neighbors?.previous : neighbors?.next; - return html` - `; + `; } protected render(): TemplateResult | void { @@ -424,7 +424,7 @@ export class FrigateCardLiveCarousel extends LitElement { // options/plugins actually change. return html` - { + @advanced-camera-card:carousel:select=${this._setViewHandler.bind(this)} + @advanced-camera-card:media:loaded=${() => { this._mediaHasLoaded = true; }} - @frigate-card:media:unloaded=${() => { + @advanced-camera-card:media:unloaded=${() => { this._mediaHasLoaded = false; }} > @@ -447,14 +447,14 @@ export class FrigateCardLiveCarousel extends LitElement { ${slides} ${this._renderNextPrevious('right', neighbors)} - - + - + `; } @@ -486,7 +486,7 @@ export class FrigateCardLiveCarousel extends LitElement { // If the view has changed, or if the media actions controller has just been // initialized, then call the necessary media action. - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1626 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1626 if (initialized || changedProperties.has('viewManagerEpoch')) { this._setMediaTarget(); } @@ -499,6 +499,6 @@ export class FrigateCardLiveCarousel extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-live-carousel': FrigateCardLiveCarousel; + 'advanced-camera-card-live-carousel': AdvancedCameraCardLiveCarousel; } } diff --git a/src/components/live/grid.ts b/src/components/live/grid.ts index 2698be4b..3a738bee 100644 --- a/src/components/live/grid.ts +++ b/src/components/live/grid.ts @@ -19,8 +19,8 @@ import { ExtendedHomeAssistant } from '../../types.js'; import { contentsChanged } from '../../utils/basic.js'; import './carousel.js'; -@customElement('frigate-card-live-grid') -export class FrigateCardLiveGrid extends LitElement { +@customElement('advanced-camera-card-live-grid') +export class AdvancedCameraCardLiveGrid extends LitElement { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; @@ -56,7 +56,7 @@ export class FrigateCardLiveGrid extends LitElement { const triggeredCameraID = cameraID ?? view?.camera; return html` - - + `; } @@ -110,14 +110,15 @@ export class FrigateCardLiveGrid extends LitElement { } return html` - ) => - this._gridSelectCamera(ev.detail.selected)} + @advanced-camera-card:media-grid:selected=${( + ev: CustomEvent, + ) => this._gridSelectCamera(ev.detail.selected)} > ${[...cameraIDs].map((cameraID) => this._renderCarousel(cameraID))} - + `; } @@ -128,6 +129,6 @@ export class FrigateCardLiveGrid extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-live-grid': FrigateCardLiveGrid; + 'advanced-camera-card-live-grid': AdvancedCameraCardLiveGrid; } } diff --git a/src/components/live/index.ts b/src/components/live/index.ts index 758bc4b5..200097a7 100644 --- a/src/components/live/index.ts +++ b/src/components/live/index.ts @@ -11,8 +11,8 @@ import { ExtendedHomeAssistant } from '../../types.js'; import { contentsChanged } from '../../utils/basic.js'; import './grid.js'; -@customElement('frigate-card-live') -export class FrigateCardLive extends LitElement { +@customElement('advanced-camera-card-live') +export class AdvancedCameraCardLive extends LitElement { @property({ attribute: false }) public conditionsManagerEpoch?: ConditionsManagerEpoch; @@ -57,7 +57,7 @@ export class FrigateCardLive extends LitElement { // - Various events are captured to prevent them propagating upwards if the // card is in the background. return html` - - + `; } @@ -81,6 +81,6 @@ export class FrigateCardLive extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-live': FrigateCardLive; + 'advanced-camera-card-live': AdvancedCameraCardLive; } } diff --git a/src/components/live/provider.ts b/src/components/live/provider.ts index b9b67e26..38a11ac2 100644 --- a/src/components/live/provider.ts +++ b/src/components/live/provider.ts @@ -17,7 +17,7 @@ import { PartialZoomSettings } from '../../components-lib/zoom/types.js'; import { CameraConfig, CardWideConfig, - frigateCardConfigDefaults, + configDefaults, LiveConfig, LiveProvider, } from '../../config/types.js'; @@ -26,7 +26,7 @@ import { localize } from '../../localize/localize.js'; import liveProviderStyle from '../../scss/live-provider.scss'; import { ExtendedHomeAssistant, - FrigateCardMediaPlayer, + AdvancedCameraCardMediaPlayer, FullscreenElement, } from '../../types.js'; import { aspectRatioToString } from '../../utils/basic.js'; @@ -39,10 +39,10 @@ import '../next-prev-control.js'; import '../ptz.js'; import '../surround.js'; -@customElement('frigate-card-live-provider') -export class FrigateCardLiveProvider +@customElement('advanced-camera-card-live-provider') +export class AdvancedCameraCardLiveProvider extends LitElement - implements FrigateCardMediaPlayer + implements AdvancedCameraCardMediaPlayer { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; @@ -84,7 +84,7 @@ export class FrigateCardLiveProvider @state() protected _showStreamTroubleshooting = false; - protected _refProvider: Ref = createRef(); + protected _refProvider: Ref = createRef(); // A note on dynamic imports: // @@ -94,7 +94,7 @@ export class FrigateCardLiveProvider // underlying code is not yet loaded. // // Test case: A card with a non-live view, but live pre-loaded, attempts to - // call mute() when the element first renders in the + // call mute() when the element first renders in the // background. These calls fail without waiting for loading here. protected _importPromises: Promise[] = []; @@ -168,7 +168,7 @@ export class FrigateCardLiveProvider } else if (this.cameraConfig?.frigate.camera_name) { return 'jsmpeg'; } - return frigateCardConfigDefaults.cameras.live_provider; + return configDefaults.cameras.live_provider; } return this.cameraConfig?.live_provider || 'image'; } @@ -254,7 +254,7 @@ export class FrigateCardLiveProvider protected _useZoomIfRequired(template: TemplateResult): TemplateResult { return this.liveConfig?.zoomable - ? html` this.cameraConfig?.dimensions?.layout ? { @@ -264,11 +264,11 @@ export class FrigateCardLiveProvider : undefined, )} .settings=${this.zoomSettings} - @frigate-card:zoom:zoomed=${() => this.setControls(false)} - @frigate-card:zoom:unzoomed=${() => this.setControls()} + @advanced-camera-card:zoom:zoomed=${() => this.setControls(false)} + @advanced-camera-card:zoom:unzoomed=${() => this.setControls()} > ${template} - ` + ` : template; } @@ -331,12 +331,12 @@ export class FrigateCardLiveProvider return html`${this._useZoomIfRequired(html` ${showImageDuringLoading || provider === 'image' - ? html` this._providerErrorHandler()} - @frigate-card:media:loaded=${(ev: Event) => { + @advanced-camera-card:live:error=${() => this._providerErrorHandler()} + @advanced-camera-card:media:loaded=${(ev: Event) => { if (provider === 'image') { // Only count the media has loaded if the required provider is // the image (not just the temporary image shown during @@ -347,21 +347,21 @@ export class FrigateCardLiveProvider } }} > - ` + ` : html``} ${provider === 'ha' - ? html` this._providerErrorHandler()} - @frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)} + @advanced-camera-card:live:error=${() => this._providerErrorHandler()} + @advanced-camera-card:media:loaded=${this._videoMediaShowHandler.bind(this)} > - ` + ` : provider === 'go2rtc' - ? html` this._providerErrorHandler()} - @frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)} + @advanced-camera-card:live:error=${() => this._providerErrorHandler()} + @advanced-camera-card:media:loaded=${this._videoMediaShowHandler.bind( + this, + )} > - ` + ` : provider === 'webrtc-card' - ? html` this._providerErrorHandler()} - @frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)} + @advanced-camera-card:live:error=${() => this._providerErrorHandler()} + @advanced-camera-card:media:loaded=${this._videoMediaShowHandler.bind( + this, + )} > - ` + ` : provider === 'jsmpeg' - ? html` this._providerErrorHandler()} - @frigate-card:media:loaded=${this._videoMediaShowHandler.bind(this)} + @advanced-camera-card:live:error=${() => this._providerErrorHandler()} + @advanced-camera-card:media:loaded=${this._videoMediaShowHandler.bind( + this, + )} > - ` + ` : html``} `)} ${showLoadingIcon - ? html` { this._showStreamTroubleshooting = !this._showStreamTroubleshooting; }} - >` + >` : ''} ${this._showStreamTroubleshooting ? renderMessage( @@ -430,6 +436,6 @@ export class FrigateCardLiveProvider declare global { interface HTMLElementTagNameMap { - 'frigate-card-live-provider': FrigateCardLiveProvider; + 'advanced-camera-card-live-provider': AdvancedCameraCardLiveProvider; } } diff --git a/src/components/live/providers/go2rtc/README.md b/src/components/live/providers/go2rtc/README.md index 8a43e428..fc602967 100644 --- a/src/components/live/providers/go2rtc/README.md +++ b/src/components/live/providers/go2rtc/README.md @@ -1,7 +1,7 @@ # go2rtc Player This is a modified version of the go2rtc example video player code, modified for -use in the Frigate card. In order to make future maintenance easier, +use in the Advanced Camera Card. In order to make future maintenance easier, modifications have been kept to a minimum. ## Original Example Code diff --git a/src/components/live/providers/go2rtc/index.ts b/src/components/live/providers/go2rtc/index.ts index 045d844b..09c5f21c 100644 --- a/src/components/live/providers/go2rtc/index.ts +++ b/src/components/live/providers/go2rtc/index.ts @@ -15,7 +15,7 @@ import { localize } from '../../../../localize/localize.js'; import liveGo2RTCStyle from '../../../../scss/live-go2rtc.scss'; import { ExtendedHomeAssistant, - FrigateCardMediaPlayer, + AdvancedCameraCardMediaPlayer, FullscreenElement, Message, } from '../../../../types.js'; @@ -25,7 +25,7 @@ import { screenshotMedia } from '../../../../utils/screenshot.js'; import { renderMessage } from '../../../message.js'; import { VideoRTC } from './video-rtc.js'; -customElements.define('frigate-card-live-go2rtc-player', VideoRTC); +customElements.define('advanced-camera-card-live-go2rtc-player', VideoRTC); // Note (2023-02-18): Depending on the behavior of the player / browser is // possible this URL will need to be re-signed in order to avoid HA spamming @@ -34,8 +34,11 @@ customElements.define('frigate-card-live-go2rtc-player', VideoRTC); // provider). const GO2RTC_URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60; -@customElement('frigate-card-live-go2rtc') -export class FrigateCardGo2RTC extends LitElement implements FrigateCardMediaPlayer { +@customElement('advanced-camera-card-live-go2rtc') +export class AdvancedCameraCardGo2RTC + extends LitElement + implements AdvancedCameraCardMediaPlayer +{ // Not an reactive property to avoid resetting the video. public hass?: ExtendedHomeAssistant; @@ -116,7 +119,7 @@ export class FrigateCardGo2RTC extends LitElement implements FrigateCardMediaPla super.connectedCallback(); // Reset the player when reconnected to the DOM. - // https://github.com/dermotduffy/frigate-hass-card/issues/996 + // https://github.com/dermotduffy/advanced-camera-card/issues/996 this.requestUpdate(); } @@ -205,6 +208,6 @@ export class FrigateCardGo2RTC extends LitElement implements FrigateCardMediaPla declare global { interface HTMLElementTagNameMap { - 'frigate-card-live-go2rtc': FrigateCardGo2RTC; + 'advanced-camera-card-live-go2rtc': AdvancedCameraCardGo2RTC; } } diff --git a/src/components/live/providers/go2rtc/video-rtc.d.ts b/src/components/live/providers/go2rtc/video-rtc.d.ts index 3ed4fc9d..614408f4 100644 --- a/src/components/live/providers/go2rtc/video-rtc.d.ts +++ b/src/components/live/providers/go2rtc/video-rtc.d.ts @@ -29,7 +29,7 @@ export class VideoRTC extends HTMLElement { onmessage: Record void>; // Custom methods/members. - containingPlayer: FrigateCardMediaPlayer | null; + containingPlayer: AdvancedCameraCardMediaPlayer | null; microphoneStream: MediaStream | null; reconnect(); diff --git a/src/components/live/providers/go2rtc/video-rtc.js b/src/components/live/providers/go2rtc/video-rtc.js index 8a1e6b10..8f91760c 100644 --- a/src/components/live/providers/go2rtc/video-rtc.js +++ b/src/components/live/providers/go2rtc/video-rtc.js @@ -162,8 +162,8 @@ export class VideoRTC extends HTMLElement { this.microphoneStream = null; /** - * A reference to a containing FrigateCardMediaPlayer object. - * @type {FrigateCardMediaPlayer}} + * A reference to a containing AdvancedCameraCardMediaPlayer object. + * @type {AdvancedCameraCardMediaPlayer}} */ this.containingPlayer = null; @@ -222,7 +222,7 @@ export class VideoRTC extends HTMLElement { * https://developer.chrome.com/blog/autoplay/ */ play() { - // Frigate card controls playing at a higher level. + // Advanced Camera Card controls playing at a higher level. } /** @@ -303,7 +303,7 @@ export class VideoRTC extends HTMLElement { // eslint-disable-next-line @typescript-eslint/no-unused-vars this.video.addEventListener('error', (_ev) => { - // For Frigate Card, we avoid log spam here from errors, and also don't + // For Advanced Camera Card, we avoid log spam here from errors, and also don't // attempt to close the websocket unless the connection is open (otherwise // on reconnect() an exception will be thrown here that we're attempting // to close a connection that's not open) @@ -405,7 +405,7 @@ export class VideoRTC extends HTMLElement { if (this.pc) { // Do not close the (microphone) track attached to the peer connection as // that is controlled by MicrophoneManager. - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1810 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1810 this.pc.close(); this.pc = null; diff --git a/src/components/live/providers/ha.ts b/src/components/live/providers/ha.ts index ff4dd4df..6572325b 100644 --- a/src/components/live/providers/ha.ts +++ b/src/components/live/providers/ha.ts @@ -7,10 +7,13 @@ import '../../../patches/ha-camera-stream'; import '../../../patches/ha-hls-player.js'; import '../../../patches/ha-web-rtc-player.js'; import liveHAStyle from '../../../scss/live-ha.scss'; -import { FrigateCardMediaPlayer, FullscreenElement } from '../../../types.js'; +import { AdvancedCameraCardMediaPlayer, FullscreenElement } from '../../../types.js'; -@customElement('frigate-card-live-ha') -export class FrigateCardLiveHA extends LitElement implements FrigateCardMediaPlayer { +@customElement('advanced-camera-card-live-ha') +export class AdvancedCameraCardLiveHA + extends LitElement + implements AdvancedCameraCardMediaPlayer +{ @property({ attribute: false }) public hass?: HomeAssistant; @@ -20,7 +23,7 @@ export class FrigateCardLiveHA extends LitElement implements FrigateCardMediaPla @property({ attribute: true, type: Boolean }) public controls = false; - protected _playerRef: Ref = createRef(); + protected _playerRef: Ref = createRef(); public async play(): Promise { return this._playerRef.value?.play(); @@ -67,7 +70,7 @@ export class FrigateCardLiveHA extends LitElement implements FrigateCardMediaPla return; } - return html` - `; + `; } static get styles(): CSSResultGroup { @@ -86,6 +89,6 @@ export class FrigateCardLiveHA extends LitElement implements FrigateCardMediaPla declare global { interface HTMLElementTagNameMap { - 'frigate-card-live-ha': FrigateCardLiveHA; + 'advanced-camera-card-live-ha': AdvancedCameraCardLiveHA; } } diff --git a/src/components/live/providers/image.ts b/src/components/live/providers/image.ts index 88c29eda..62131786 100644 --- a/src/components/live/providers/image.ts +++ b/src/components/live/providers/image.ts @@ -4,18 +4,21 @@ import { customElement, property } from 'lit/decorators.js'; import { createRef, ref, Ref } from 'lit/directives/ref.js'; import { CameraConfig } from '../../../config/types'; import basicBlockStyle from '../../../scss/basic-block.scss'; -import { FrigateCardMediaPlayer, FullscreenElement } from '../../../types.js'; +import { AdvancedCameraCardMediaPlayer, FullscreenElement } from '../../../types.js'; import '../../image-base.js'; -@customElement('frigate-card-live-image') -export class FrigateCardLiveImage extends LitElement implements FrigateCardMediaPlayer { +@customElement('advanced-camera-card-live-image') +export class AdvancedCameraCardLiveImage + extends LitElement + implements AdvancedCameraCardMediaPlayer +{ @property({ attribute: false }) public hass?: HomeAssistant; @property({ attribute: false }) public cameraConfig?: CameraConfig; - protected _refImage: Ref = createRef(); + protected _refImage: Ref = createRef(); public async play(): Promise { await this._refImage.value?.play(); @@ -63,13 +66,13 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia } return html` - - + `; } @@ -80,6 +83,6 @@ export class FrigateCardLiveImage extends LitElement implements FrigateCardMedia declare global { interface HTMLElementTagNameMap { - 'frigate-card-live-image': FrigateCardLiveImage; + 'advanced-camera-card-live-image': AdvancedCameraCardLiveImage; } } diff --git a/src/components/live/providers/jsmpeg.ts b/src/components/live/providers/jsmpeg.ts index e4063c05..279ec82b 100644 --- a/src/components/live/providers/jsmpeg.ts +++ b/src/components/live/providers/jsmpeg.ts @@ -15,8 +15,8 @@ import { CameraConfig, CardWideConfig } from '../../../config/types.js'; import { localize } from '../../../localize/localize.js'; import liveJSMPEGStyle from '../../../scss/live-jsmpeg.scss'; import { + AdvancedCameraCardMediaPlayer, ExtendedHomeAssistant, - FrigateCardMediaPlayer, FullscreenElement, Message, } from '../../../types.js'; @@ -38,8 +38,11 @@ const JSMPEG_URL_SIGN_EXPIRY_SECONDS = 24 * 60 * 60; // Number of seconds before the expiry to trigger a refresh. const JSMPEG_URL_SIGN_REFRESH_THRESHOLD_SECONDS = 1 * 60 * 60; -@customElement('frigate-card-live-jsmpeg') -export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMediaPlayer { +@customElement('advanced-camera-card-live-jsmpeg') +export class AdvancedCameraCardLiveJSMPEG + extends LitElement + implements AdvancedCameraCardMediaPlayer +{ @property({ attribute: false }) public cameraConfig?: CameraConfig; @@ -303,6 +306,6 @@ export class FrigateCardLiveJSMPEG extends LitElement implements FrigateCardMedi declare global { interface HTMLElementTagNameMap { - 'frigate-card-live-jsmpeg': FrigateCardLiveJSMPEG; + 'advanced-camera-card-live-jsmpeg': AdvancedCameraCardLiveJSMPEG; } } diff --git a/src/components/live/providers/webrtc-card.ts b/src/components/live/providers/webrtc-card.ts index d9b36532..8cd90a3d 100644 --- a/src/components/live/providers/webrtc-card.ts +++ b/src/components/live/providers/webrtc-card.ts @@ -16,8 +16,8 @@ import { CameraConfig, CardWideConfig } from '../../../config/types.js'; import { localize } from '../../../localize/localize.js'; import liveWebRTCCardStyle from '../../../scss/live-webrtc-card.scss'; import { - FrigateCardError, - FrigateCardMediaPlayer, + AdvancedCameraCardError, + AdvancedCameraCardMediaPlayer, FullscreenElement, Message, } from '../../../types.js'; @@ -43,10 +43,10 @@ import { VideoRTC } from './go2rtc/video-rtc.js'; // Create a wrapper for AlexxIT's WebRTC card // - https://github.com/AlexxIT/WebRTC -@customElement('frigate-card-live-webrtc-card') -export class FrigateCardLiveWebRTCCard +@customElement('advanced-camera-card-live-webrtc-card') +export class AdvancedCameraCardLiveWebRTCCard extends LitElement - implements FrigateCardMediaPlayer + implements AdvancedCameraCardMediaPlayer { @property({ attribute: false }) public cameraConfig?: CameraConfig; @@ -125,7 +125,7 @@ export class FrigateCardLiveWebRTCCard super.connectedCallback(); // Reset the player when reconnected to the DOM. - // https://github.com/dermotduffy/frigate-hass-card/issues/996 + // https://github.com/dermotduffy/advanced-camera-card/issues/996 this.requestUpdate(); } @@ -177,11 +177,11 @@ export class FrigateCardLiveWebRTCCard // hidden. This is incompatible with the card zoom support, since the // video will easily stop if the user zooms in too much. Disable this // feature by default. - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1614 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1614 intersection: 0, - // Frigate card always starts muted (unlike webrtc-card). - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1654 + // Advanced Camera Card always starts muted (unlike webrtc-card). + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1654 muted: true, ...this.cameraConfig.webrtc_card, @@ -209,12 +209,12 @@ export class FrigateCardLiveWebRTCCard this._message = { type: 'error', message: - e instanceof FrigateCardError + e instanceof AdvancedCameraCardError ? e.message : localize('error.webrtc_card_reported_error') + ': ' + (e as Error).message, - context: (e as FrigateCardError).context, + context: (e as AdvancedCameraCardError).context, }; dispatchLiveErrorEvent(this); return; @@ -275,6 +275,6 @@ export class FrigateCardLiveWebRTCCard declare global { interface HTMLElementTagNameMap { - 'frigate-card-live-webrtc-card': FrigateCardLiveWebRTCCard; + 'advanced-camera-card-live-webrtc-card': AdvancedCameraCardLiveWebRTCCard; } } diff --git a/src/components/loading.ts b/src/components/loading.ts index f0ca552e..7fc57a94 100644 --- a/src/components/loading.ts +++ b/src/components/loading.ts @@ -4,10 +4,12 @@ import loadingStyle from '../scss/loading.scss'; import { getReleaseVersion } from '../utils/diagnostics'; import './icon'; -@customElement('frigate-card-loading') -export class FrigateCardLoading extends LitElement { +@customElement('advanced-camera-card-loading') +export class AdvancedCameraCardLoading extends LitElement { protected render(): TemplateResult { - return html`${getReleaseVersion()}`; } @@ -18,6 +20,6 @@ export class FrigateCardLoading extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-loading': FrigateCardLoading; + 'advanced-camera-card-loading': AdvancedCameraCardLoading; } } diff --git a/src/components/media-filter.ts b/src/components/media-filter.ts index e7a6bb06..7cf14393 100644 --- a/src/components/media-filter.ts +++ b/src/components/media-filter.ts @@ -21,14 +21,14 @@ import { import { CardWideConfig } from '../config/types'; import { localize } from '../localize/localize'; import mediaFilterStyle from '../scss/media-filter.scss'; -import { FrigateCardDatePicker } from './date-picker'; +import { AdvancedCameraCardDatePicker } from './date-picker'; import './date-picker.js'; -import { FrigateCardSelect } from './select'; +import { AdvancedCameraCardSelect } from './select'; import './select.js'; import { ViewManagerEpoch } from '../card-controller/view/types'; -@customElement('frigate-card-media-filter') -class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) { +@customElement('advanced-camera-card-media-filter') +class AdvancedCameraCardMediaFilter extends ScopedRegistryHost(LitElement) { @property({ attribute: false }) public hass?: HomeAssistant; @@ -42,21 +42,21 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) { public cardWideConfig?: CardWideConfig; static elementDefinitions = { - 'frigate-card-select': FrigateCardSelect, - 'frigate-card-date-picker': FrigateCardDatePicker, + 'advanced-camera-card-select': AdvancedCameraCardSelect, + 'advanced-camera-card-date-picker': AdvancedCameraCardDatePicker, }; protected _mediaFilterController = new MediaFilterController(this); - protected _refMediaType: Ref = createRef(); - protected _refCamera: Ref = createRef(); - protected _refWhen: Ref = createRef(); - protected _refWhenFrom: Ref = createRef(); - protected _refWhenTo: Ref = createRef(); - protected _refWhat: Ref = createRef(); - protected _refWhere: Ref = createRef(); - protected _refFavorite: Ref = createRef(); - protected _refTags: Ref = createRef(); + protected _refMediaType: Ref = createRef(); + protected _refCamera: Ref = createRef(); + protected _refWhen: Ref = createRef(); + protected _refWhenFrom: Ref = createRef(); + protected _refWhenTo: Ref = createRef(); + protected _refWhat: Ref = createRef(); + protected _refWhere: Ref = createRef(); + protected _refFavorite: Ref = createRef(); + protected _refTags: Ref = createRef(); protected willUpdate(changedProps: PropertyValues): void { if (changedProps.has('viewManagerEpoch')) { @@ -132,48 +132,48 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) { const tagsOptions = this._mediaFilterController.getTagsOptions(); const whereOptions = this._mediaFilterController.getWhereOptions(); - return html` valueChange()} + @advanced-camera-card:select:change=${() => valueChange()} > - +
- whenChange('selected')} + @advanced-camera-card:select:change=${() => whenChange('selected')} > - - + whenChange('custom')} + @advanced-camera-card:date-picker:change=${() => whenChange('custom')} > - - + whenChange('custom')} + @advanced-camera-card:date-picker:change=${() => whenChange('custom')} > - +
- valueChange()} + @advanced-camera-card:select:change=${() => valueChange()} > - + ${controls.events && whatOptions.length - ? html` valueChange()} + @advanced-camera-card:select:change=${() => valueChange()} > - ` + ` : ''} ${controls.events && tagsOptions.length - ? html` valueChange()} + @advanced-camera-card:select:change=${() => valueChange()} > - ` + ` : ''} ${controls.events && whereOptions.length - ? html` valueChange()} + @advanced-camera-card:select:change=${() => valueChange()} > - ` + ` : ''} ${controls.favorites ? html` - valueChange()} + @advanced-camera-card:select:change=${() => valueChange()} > - + ` : ''}`; } @@ -246,6 +246,6 @@ class FrigateCardMediaFilter extends ScopedRegistryHost(LitElement) { declare global { interface HTMLElementTagNameMap { - 'frigate-card-media-filter': FrigateCardMediaFilter; + 'advanced-camera-card-media-filter': AdvancedCameraCardMediaFilter; } } diff --git a/src/components/media-grid.ts b/src/components/media-grid.ts index 99b72b30..fe3f7c11 100644 --- a/src/components/media-grid.ts +++ b/src/components/media-grid.ts @@ -12,8 +12,8 @@ import { MediaGridController } from '../components-lib/media-grid-controller.js' import { ViewDisplayConfig } from '../config/types'; import mediaGridStyle from '../scss/media-grid.scss'; -@customElement('frigate-card-media-grid') -export class FrigateCardMediaGrid extends LitElement { +@customElement('advanced-camera-card-media-grid') +export class AdvancedCameraCardMediaGrid extends LitElement { @property({ attribute: false }) public selected?: string; @@ -68,6 +68,6 @@ export class FrigateCardMediaGrid extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-media-grid': FrigateCardMediaGrid; + 'advanced-camera-card-media-grid': AdvancedCameraCardMediaGrid; } } diff --git a/src/components/menu.ts b/src/components/menu.ts index 42c2ac21..2d14d50e 100644 --- a/src/components/menu.ts +++ b/src/components/menu.ts @@ -6,14 +6,14 @@ import { actionHandler } from '../action-handler-directive.js'; import { MenuController } from '../components-lib/menu-controller.js'; import type { MenuConfig, MenuItem } from '../config/types.js'; import menuStyle from '../scss/menu.scss'; -import { frigateCardHasAction } from '../utils/action.js'; +import { hasAction } from '../utils/action.js'; import { getEntityTitle } from '../utils/ha/index.js'; import { EntityRegistryManager } from '../utils/ha/registry/entity/index.js'; import './icon.js'; import './submenu.js'; -@customElement('frigate-card-menu') -export class FrigateCardMenu extends LitElement { +@customElement('advanced-camera-card-menu') +export class AdvancedCameraCardMenu extends LitElement { protected _controller = new MenuController(this); @property({ attribute: false }) @@ -43,37 +43,39 @@ export class FrigateCardMenu extends LitElement { return; } - if (button.type === 'custom:frigate-card-menu-submenu') { - return html` this._controller.actionHandler(ev)} > - `; - } else if (button.type === 'custom:frigate-card-menu-submenu-select') { - return html` `; + } else if (button.type === 'custom:advanced-camera-card-menu-submenu-select') { + return html` this._controller.actionHandler(ev)} > - `; + `; } const title = - this.hass && button.type === 'custom:frigate-card-menu-state-icon' && !button.title + this.hass && + button.type === 'custom:advanced-camera-card-menu-state-icon' && + !button.title ? getEntityTitle(this.hass, button.entity) : button.title; return html` this._controller.actionHandler(ev, button)} > - + > `; } @@ -108,13 +110,13 @@ export class FrigateCardMenu extends LitElement { const generateValue = (suffix: string): string => { return ` - var(--frigate-card-menu-override-${suffix}, - var(--frigate-card-menu-position-${position}-alignment-${alignment}-style-${style}-${suffix}, - var(--frigate-card-menu-position-${position}-alignment-${alignment}-${suffix}, - var(--frigate-card-menu-position-${position}-${suffix}, - var(--frigate-card-menu-style-${style}-${suffix}, - var(--frigate-card-menu-alignment-${alignment}-${suffix}, - var(--frigate-card-menu-${suffix})))))))`; + var(--advanced-camera-card-menu-override-${suffix}, + var(--advanced-camera-card-menu-position-${position}-alignment-${alignment}-style-${style}-${suffix}, + var(--advanced-camera-card-menu-position-${position}-alignment-${alignment}-${suffix}, + var(--advanced-camera-card-menu-position-${position}-${suffix}, + var(--advanced-camera-card-menu-style-${style}-${suffix}, + var(--advanced-camera-card-menu-alignment-${alignment}-${suffix}, + var(--advanced-camera-card-menu-${suffix})))))))`; }; // By definition `rule` will match the current configuration, the choice is @@ -169,6 +171,6 @@ export class FrigateCardMenu extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-menu': FrigateCardMenu; + 'advanced-camera-card-menu': AdvancedCameraCardMenu; } } diff --git a/src/components/message.ts b/src/components/message.ts index f944da9c..11dcd46c 100644 --- a/src/components/message.ts +++ b/src/components/message.ts @@ -13,13 +13,13 @@ export function renderMessage( overlay?: boolean; }, ): TemplateResult { - return html` `; + >`; } -@customElement('frigate-card-message') -export class FrigateCardMessage extends LitElement { +@customElement('advanced-camera-card-message') +export class AdvancedCameraCardMessage extends LitElement { @property({ attribute: false }) public message?: Message; @@ -51,7 +51,9 @@ export class FrigateCardMessage extends LitElement { return html`
- +
${messageTemplate} @@ -70,6 +72,6 @@ export class FrigateCardMessage extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-message': FrigateCardMessage; + 'advanced-camera-card-message': AdvancedCameraCardMessage; } } diff --git a/src/components/next-prev-control.ts b/src/components/next-prev-control.ts index 4c03f81c..db4999af 100644 --- a/src/components/next-prev-control.ts +++ b/src/components/next-prev-control.ts @@ -8,14 +8,17 @@ import { Icon } from '../types.js'; import { renderTask } from '../utils/task.js'; import { createFetchThumbnailTask } from '../utils/thumbnail.js'; -@customElement('frigate-card-next-previous-control') -export class FrigateCardNextPreviousControl extends LitElement { +@customElement('advanced-camera-card-next-previous-control') +export class AdvancedCameraCardNextPreviousControl extends LitElement { @property({ attribute: false }) public side?: 'left' | 'right'; set controlConfig(controlConfig: NextPreviousControlConfig | undefined) { if (controlConfig?.size) { - this.style.setProperty('--frigate-card-next-prev-size', `${controlConfig.size}px`); + this.style.setProperty( + '--advanced-camera-card-next-prev-size', + `${controlConfig.size}px`, + ); } this._controlConfig = controlConfig; } @@ -74,7 +77,10 @@ export class FrigateCardNextPreviousControl extends LitElement { : { icon: 'mdi:chevron-right' }; return html` - + `; } @@ -106,6 +112,6 @@ export class FrigateCardNextPreviousControl extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-next-previous-control': FrigateCardNextPreviousControl; + 'advanced-camera-card-next-previous-control': AdvancedCameraCardNextPreviousControl; } } diff --git a/src/components/overlay.ts b/src/components/overlay.ts index 2c98d7ba..8277bb5a 100644 --- a/src/components/overlay.ts +++ b/src/components/overlay.ts @@ -2,8 +2,8 @@ import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit import { customElement } from 'lit/decorators.js'; import overlayStyle from '../scss/overlay.scss'; -@customElement('frigate-card-overlay') -export class FrigateCardOverlay extends LitElement { +@customElement('advanced-camera-card-overlay') +export class AdvancedCameraCardOverlay extends LitElement { protected render(): TemplateResult | void { return html` @@ -20,6 +20,6 @@ export class FrigateCardOverlay extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-overlay': FrigateCardOverlay; + 'advanced-camera-card-overlay': AdvancedCameraCardOverlay; } } diff --git a/src/components/progress-indicator.ts b/src/components/progress-indicator.ts index 196a4367..30e64eaf 100644 --- a/src/components/progress-indicator.ts +++ b/src/components/progress-indicator.ts @@ -6,17 +6,17 @@ import { CardWideConfig } from '../config/types.js'; import messageStyle from '../scss/message.scss'; import './icon.js'; -type FrigateCardProgressIndicatorSize = 'tiny' | 'small' | 'medium' | 'large'; +type AdvancedCameraCardProgressIndicatorSize = 'tiny' | 'small' | 'medium' | 'large'; export function renderProgressIndicator(options?: { message?: string; cardWideConfig?: CardWideConfig | null; componentRef?: Ref; classes?: ClassInfo; - size?: FrigateCardProgressIndicatorSize; + size?: AdvancedCameraCardProgressIndicatorSize; }): TemplateResult { return html` - - + `; } -@customElement('frigate-card-progress-indicator') -export class FrigateCardProgressIndicator extends LitElement { +@customElement('advanced-camera-card-progress-indicator') +export class AdvancedCameraCardProgressIndicator extends LitElement { @property({ attribute: false }) public message: string = ''; @@ -37,16 +37,16 @@ export class FrigateCardProgressIndicator extends LitElement { public animated = false; @property({ attribute: false }) - public size: FrigateCardProgressIndicatorSize = 'large'; + public size: AdvancedCameraCardProgressIndicatorSize = 'large'; protected render(): TemplateResult { return html`
${this.animated ? html` ` - : html``} + >`} ${this.message ? html`${this.message}` : html``}
`; } @@ -58,6 +58,6 @@ export class FrigateCardProgressIndicator extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-progress-indicator': FrigateCardProgressIndicator; + 'advanced-camera-card-progress-indicator': AdvancedCameraCardProgressIndicator; } } diff --git a/src/components/ptz.ts b/src/components/ptz.ts index d9639a50..cbf81c5f 100644 --- a/src/components/ptz.ts +++ b/src/components/ptz.ts @@ -16,11 +16,11 @@ import { PTZActionPresence } from '../components-lib/ptz/types.js'; import { Actions, PTZControlsConfig } from '../config/types.js'; import { localize } from '../localize/localize.js'; import ptzStyle from '../scss/ptz.scss'; -import { frigateCardHasAction } from '../utils/action.js'; +import { hasAction } from '../utils/action.js'; import './icon.js'; -@customElement('frigate-card-ptz') -export class FrigateCardPTZ extends LitElement { +@customElement('advanced-camera-card-ptz') +export class AdvancedCameraCardPTZ extends LitElement { @property({ attribute: false }) public config?: PTZControlsConfig; @@ -68,17 +68,17 @@ export class FrigateCardPTZ extends LitElement { }; return actions - ? html`) => this._controller.handleAction(ev, actions)} - >` + >` : html``; }; @@ -113,6 +113,6 @@ export class FrigateCardPTZ extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-ptz': FrigateCardPTZ; + 'advanced-camera-card-ptz': AdvancedCameraCardPTZ; } } diff --git a/src/components/select.ts b/src/components/select.ts index 94393e96..741018a3 100644 --- a/src/components/select.ts +++ b/src/components/select.ts @@ -9,7 +9,7 @@ import { import { property } from 'lit/decorators.js'; import { createRef, ref, Ref } from 'lit/directives/ref.js'; import selectStyle from '../scss/select.scss'; -import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic'; +import { contentsChanged, dispatchAdvancedCameraCardEvent } from '../utils/basic'; import { ScopedRegistryHost } from '@lit-labs/scoped-registry-mixin'; import { grSelectElements } from '../scoped-elements/gr-select'; import isEqual from 'lodash-es/isEqual'; @@ -26,7 +26,7 @@ type SelectElement = HTMLElement & { value: SelectValues; }; -export class FrigateCardSelect extends ScopedRegistryHost(LitElement) { +export class AdvancedCameraCardSelect extends ScopedRegistryHost(LitElement) { @property({ attribute: false, hasChanged: contentsChanged }) public options?: SelectOption[]; @@ -73,7 +73,7 @@ export class FrigateCardSelect extends ScopedRegistryHost(LitElement) { // (even when the user has not interacted with the control). Do not // dispatch events for this. if (!initialValueSet) { - dispatchFrigateCardEvent(this, 'select:change', value); + dispatchAdvancedCameraCardEvent(this, 'select:change', value); } } } @@ -111,6 +111,6 @@ export class FrigateCardSelect extends ScopedRegistryHost(LitElement) { declare global { interface HTMLElementTagNameMap { - 'frigate-card-select': FrigateCardSelect; + 'advanced-camera-card-select': AdvancedCameraCardSelect; } } diff --git a/src/components/status-bar.ts b/src/components/status-bar.ts index 13c90814..d6617e83 100644 --- a/src/components/status-bar.ts +++ b/src/components/status-bar.ts @@ -12,11 +12,11 @@ import { actionHandler } from '../action-handler-directive.js'; import { StatusBarController } from '../components-lib/status-bar-controller'; import { StatusBarConfig, StatusBarItem } from '../config/types'; import statusStyle from '../scss/status.scss'; -import { frigateCardHasAction } from '../utils/action'; +import { hasAction } from '../utils/action'; import './icon.js'; -@customElement('frigate-card-status-bar') -export class FrigateCardStatusBar extends LitElement { +@customElement('advanced-camera-card-status-bar') +export class AdvancedCameraCardStatusBar extends LitElement { protected _controller = new StatusBarController(this); @property({ attribute: false }) @@ -54,11 +54,11 @@ export class FrigateCardStatusBar extends LitElement { const generateValue = (suffix: string): string => { return ` - var(--frigate-card-status-bar-override-${suffix}, - var(--frigate-card-status-bar-position-${position}-style-${style}-${suffix}, - var(--frigate-card-status-bar-position-${position}-${suffix}, - var(--frigate-card-status-bar-style-${style}-${suffix}, - var(--frigate-card-status-bar-${suffix})))))`; + var(--advanced-camera-card-status-bar-override-${suffix}, + var(--advanced-camera-card-status-bar-position-${position}-style-${style}-${suffix}, + var(--advanced-camera-card-status-bar-position-${position}-${suffix}, + var(--advanced-camera-card-status-bar-style-${style}-${suffix}, + var(--advanced-camera-card-status-bar-${suffix})))))`; }; const rule = `[data-position='${position}']` + `[data-style='${style}']`; @@ -91,11 +91,11 @@ export class FrigateCardStatusBar extends LitElement { }); const handler = actionHandler({ - hasHold: frigateCardHasAction(item.actions?.hold_action), - hasDoubleClick: frigateCardHasAction(item.actions?.double_tap_action), + hasHold: hasAction(item.actions?.hold_action), + hasDoubleClick: hasAction(item.actions?.double_tap_action), }); - if (item.type === 'custom:frigate-card-status-bar-string') { + if (item.type === 'custom:advanced-camera-card-status-bar-string') { return html`
${item.string}
`; - } else if (item.type === 'custom:frigate-card-status-bar-icon') { - return html` this._controller.actionHandler(ev, item.actions)} - >`; - } else if (item.type === 'custom:frigate-card-status-bar-image') { + >`; + } else if (item.type === 'custom:advanced-camera-card-status-bar-image') { return html` ${title ?? ''} ${item.subtitle ? html`${item.subtitle}` : ''} - + > `; } @@ -90,7 +87,7 @@ export class FrigateCardSubmenu extends LitElement { @closed=${ // Prevent the submenu closing from closing anything upstream (e.g. // selecting a submenu in the editor dialog should not close the - // editor, see https://github.com/dermotduffy/frigate-hass-card/issues/377). + // editor, see https://github.com/dermotduffy/advanced-camera-card/issues/377). (ev) => ev.stopPropagation() } @click=${(ev) => stopEventFromActivatingCardWideActions(ev)} @@ -105,11 +102,11 @@ export class FrigateCardSubmenu extends LitElement { // propagation is forbidden by the @click handler on // . allowPropagation: true, - hasHold: frigateCardHasAction(this.submenu.hold_action), - hasDoubleClick: frigateCardHasAction(this.submenu.double_tap_action), + hasHold: hasAction(this.submenu.hold_action), + hasDoubleClick: hasAction(this.submenu.double_tap_action), })} > - + > ${items.map(this._renderItem.bind(this))} @@ -130,8 +127,8 @@ export class FrigateCardSubmenu extends LitElement { } } -@customElement('frigate-card-submenu-select') -export class FrigateCardSubmenuSelect extends LitElement { +@customElement('advanced-camera-card-submenu-select') +export class AdvancedCameraCardSubmenuSelect extends LitElement { @property({ attribute: false }) public hass?: HomeAssistant; @@ -220,7 +217,7 @@ export class FrigateCardSubmenuSelect extends LitElement { fallback: 'mdi:format-list-bulleted', }, - type: 'custom:frigate-card-menu-submenu', + type: 'custom:advanced-camera-card-menu-submenu', items: [], }; @@ -261,16 +258,16 @@ export class FrigateCardSubmenuSelect extends LitElement { } protected render(): TemplateResult { - return html` `; + >`; } } declare global { interface HTMLElementTagNameMap { - 'frigate-card-submenu': FrigateCardSubmenu; - 'frigate-card-submenu-select': FrigateCardSubmenuSelect; + 'advanced-camera-card-submenu': AdvancedCameraCardSubmenu; + 'advanced-camera-card-submenu-select': AdvancedCameraCardSubmenuSelect; } } diff --git a/src/components/surround-basic.ts b/src/components/surround-basic.ts index 1975b708..4a0a8ddc 100644 --- a/src/components/surround-basic.ts +++ b/src/components/surround-basic.ts @@ -1,26 +1,26 @@ import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; import { createRef, ref, Ref } from 'lit/directives/ref.js'; import { customElement, property } from 'lit/decorators.js'; -import { DrawerIcons, FrigateCardDrawer } from './drawer.js'; +import { DrawerIcons, AdvancedCameraCardDrawer } from './drawer.js'; import './drawer.js'; import surroundBasicStyle from '../scss/surround-basic.scss'; -interface FrigateCardDrawerOpen { +interface AdvancedCameraCardDrawerOpen { drawer: 'left' | 'right'; } -@customElement('frigate-card-surround-basic') -export class FrigateCardSurroundBasic extends LitElement { +@customElement('advanced-camera-card-surround-basic') +export class AdvancedCameraCardSurroundBasic extends LitElement { @property({ attribute: false }) public drawerIcons?: { left?: DrawerIcons; right?: DrawerIcons; }; - protected _refDrawerLeft: Ref = createRef(); - protected _refDrawerRight: Ref = createRef(); + protected _refDrawerLeft: Ref = createRef(); + protected _refDrawerRight: Ref = createRef(); protected _boundDrawerHandler = this._drawerHandler.bind(this); /** @@ -28,8 +28,8 @@ export class FrigateCardSurroundBasic extends LitElement { */ connectedCallback(): void { super.connectedCallback(); - this.addEventListener('frigate-card:drawer:open', this._boundDrawerHandler); - this.addEventListener('frigate-card:drawer:close', this._boundDrawerHandler); + this.addEventListener('advanced-camera-card:drawer:open', this._boundDrawerHandler); + this.addEventListener('advanced-camera-card:drawer:close', this._boundDrawerHandler); } /** @@ -37,12 +37,18 @@ export class FrigateCardSurroundBasic extends LitElement { */ disconnectedCallback(): void { super.disconnectedCallback(); - this.removeEventListener('frigate-card:drawer:open', this._boundDrawerHandler); - this.removeEventListener('frigate-card:drawer:close', this._boundDrawerHandler); + this.removeEventListener( + 'advanced-camera-card:drawer:open', + this._boundDrawerHandler, + ); + this.removeEventListener( + 'advanced-camera-card:drawer:close', + this._boundDrawerHandler, + ); } protected _drawerHandler(ev: Event) { - const drawer = (ev as CustomEvent).detail.drawer; + const drawer = (ev as CustomEvent).detail.drawer; const open = ev.type.endsWith(':open'); if (drawer === 'left' && this._refDrawerLeft.value) { this._refDrawerLeft.value.open = open; @@ -54,20 +60,20 @@ export class FrigateCardSurroundBasic extends LitElement { protected render(): TemplateResult | void { return html` - - - + - + `; } @@ -81,6 +87,6 @@ export class FrigateCardSurroundBasic extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-surround-basic': FrigateCardSurroundBasic; + 'advanced-camera-card-surround-basic': AdvancedCameraCardSurroundBasic; } } diff --git a/src/components/surround.ts b/src/components/surround.ts index c48b5c7b..b63df56e 100644 --- a/src/components/surround.ts +++ b/src/components/surround.ts @@ -17,12 +17,12 @@ import { } from '../config/types.js'; import basicBlockStyle from '../scss/basic-block.scss'; import { ExtendedHomeAssistant } from '../types.js'; -import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic.js'; +import { contentsChanged, dispatchAdvancedCameraCardEvent } from '../utils/basic.js'; import './surround-basic.js'; import { ThumbnailCarouselTap } from './thumbnail-carousel.js'; -@customElement('frigate-card-surround') -export class FrigateCardSurround extends LitElement { +@customElement('advanced-camera-card-surround') +export class AdvancedCameraCardSurround extends LitElement { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; @@ -110,20 +110,22 @@ export class FrigateCardSurround extends LitElement { // request to view thumbnails and re-dispatches a request to open the drawer // (if the thumbnails are in a drawer). The new event needs to be dispatched // from the origin of the inbound event, so it can be handled by - // . + // . if (this.thumbnailConfig && this._hasDrawer()) { - dispatchFrigateCardEvent(ev.composedPath()[0], 'drawer:' + action, { + dispatchAdvancedCameraCardEvent(ev.composedPath()[0], 'drawer:' + action, { drawer: this.thumbnailConfig.mode, }); } }; - return html` changeDrawer(ev, 'open')} - @frigate-card:thumbnails:close=${(ev: CustomEvent) => changeDrawer(ev, 'close')} + return html` + changeDrawer(ev, 'open')} + @advanced-camera-card:thumbnails:close=${(ev: CustomEvent) => + changeDrawer(ev, 'close')} > ${this.thumbnailConfig && this.thumbnailConfig.mode !== 'none' - ? html` , ) => { const media = ev.detail.queryResults.getSelectedResult(); @@ -150,10 +152,10 @@ export class FrigateCardSurround extends LitElement { } }} > - ` + ` : ''} ${this.timelineConfig && this.timelineConfig.mode !== 'none' - ? html` - ` + ` : ''} - `; + `; } static get styles(): CSSResultGroup { @@ -182,6 +184,6 @@ export class FrigateCardSurround extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-surround': FrigateCardSurround; + 'advanced-camera-card-surround': AdvancedCameraCardSurround; } } diff --git a/src/components/thumbnail-carousel.ts b/src/components/thumbnail-carousel.ts index 52361d93..ea437449 100644 --- a/src/components/thumbnail-carousel.ts +++ b/src/components/thumbnail-carousel.ts @@ -9,23 +9,23 @@ import { import { customElement, property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { CameraManager } from '../camera-manager/manager.js'; +import { ViewManagerEpoch } from '../card-controller/view/types.js'; import { ThumbnailsControlConfig } from '../config/types.js'; import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss'; import { ExtendedHomeAssistant } from '../types.js'; import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; -import { dispatchFrigateCardEvent } from '../utils/basic.js'; +import { dispatchAdvancedCameraCardEvent } from '../utils/basic.js'; import { CarouselDirection } from '../utils/embla/carousel-controller.js'; import { MediaQueriesResults } from '../view/media-queries-results'; import './carousel.js'; import './thumbnail.js'; -import { ViewManagerEpoch } from '../card-controller/view/types.js'; export interface ThumbnailCarouselTap { queryResults: MediaQueriesResults; } -@customElement('frigate-card-thumbnail-carousel') -export class FrigateCardThumbnailCarousel extends LitElement { +@customElement('advanced-camera-card-thumbnail-carousel') +export class AdvancedCameraCardThumbnailCarousel extends LitElement { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; @@ -46,7 +46,10 @@ export class FrigateCardThumbnailCarousel extends LitElement { protected willUpdate(changedProps: PropertyValues): void { if (changedProps.has('config')) { if (this.config?.size) { - this.style.setProperty('--frigate-card-thumbnail-size', `${this.config.size}px`); + this.style.setProperty( + '--advanced-camera-card-thumbnail-size', + `${this.config.size}px`, + ); } const direction = this._getDirection(); if (direction) { @@ -68,7 +71,7 @@ export class FrigateCardThumbnailCarousel extends LitElement { if (changedProps.has('viewManagerEpoch')) { this.style.setProperty( - '--frigate-card-carousel-thumbnail-opacity', + '--advanced-camera-card-carousel-thumbnail-opacity', !this.fadeThumbnails || this._getSelectedSlide() === null ? '1.0' : '0.4', ); } @@ -94,7 +97,7 @@ export class FrigateCardThumbnailCarousel extends LitElement { }; slides.push( - html` { const view = this.viewManagerEpoch?.manager.getView(); if (view && view.queryResults) { - dispatchFrigateCardEvent( + dispatchAdvancedCameraCardEvent( this, 'thumbnail-carousel:tap', { @@ -119,7 +122,7 @@ export class FrigateCardThumbnailCarousel extends LitElement { stopEventFromActivatingCardWideActions(ev); }} > - `, + `, ); } return slides; @@ -140,13 +143,13 @@ export class FrigateCardThumbnailCarousel extends LitElement { return; } - return html` ${this._thumbnailSlides} - `; + `; } static get styles(): CSSResultGroup { @@ -156,6 +159,6 @@ export class FrigateCardThumbnailCarousel extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-thumbnail-carousel': FrigateCardThumbnailCarousel; + 'advanced-camera-card-thumbnail-carousel': AdvancedCameraCardThumbnailCarousel; } } diff --git a/src/components/thumbnail.ts b/src/components/thumbnail.ts index 23c8bd86..7a54bddc 100644 --- a/src/components/thumbnail.ts +++ b/src/components/thumbnail.ts @@ -14,7 +14,7 @@ import { CameraManager } from '../camera-manager/manager.js'; import { CameraManagerCameraMetadata } from '../camera-manager/types.js'; import { RemoveContextViewModifier } from '../card-controller/view/modifiers/remove-context.js'; import { ViewManagerEpoch } from '../card-controller/view/types.js'; -import { dispatchFrigateCardErrorEvent } from '../components-lib/message/dispatch.js'; +import { dispatchAdvancedCameraCardErrorEvent } from '../components-lib/message/dispatch.js'; import { localize } from '../localize/localize.js'; import thumbnailDetailsStyle from '../scss/thumbnail-details.scss'; import thumbnailFeatureTextStyle from '../scss/thumbnail-feature-text.scss'; @@ -37,8 +37,8 @@ import { EventViewMedia, RecordingViewMedia, ViewMedia } from '../view/media.js' // The minimum width of a thumbnail with details enabled. export const THUMBNAIL_DETAILS_WIDTH_MIN = 300; -@customElement('frigate-card-thumbnail-feature-thumbnail') -export class FrigateCardThumbnailFeatureThumbnail extends LitElement { +@customElement('advanced-camera-card-thumbnail-feature-thumbnail') +export class AdvancedCameraCardThumbnailFeatureThumbnail extends LitElement { @property({ attribute: false }) public thumbnail?: string; @@ -105,10 +105,10 @@ export class FrigateCardThumbnailFeatureThumbnail extends LitElement { } protected render(): TemplateResult | void { - const imageOff = html` `; + > `; if (!this._embedThumbnailTask || this._thumbnailError) { return imageOff; @@ -134,8 +134,8 @@ export class FrigateCardThumbnailFeatureThumbnail extends LitElement { } } -@customElement('frigate-card-thumbnail-feature-text') -export class FrigateCardThumbnailFeatureText extends LitElement { +@customElement('advanced-camera-card-thumbnail-feature-text') +export class AdvancedCameraCardThumbnailFeatureText extends LitElement { @property({ attribute: false }) public date?: Date; @@ -151,10 +151,10 @@ export class FrigateCardThumbnailFeatureText extends LitElement { } return html` ${this.cameraMetadata?.engineIcon - ? html`` + >` : ''}
${format(this.date, 'HH:mm')}
@@ -171,8 +171,8 @@ export class FrigateCardThumbnailFeatureText extends LitElement { } } -@customElement('frigate-card-thumbnail-details-event') -export class FrigateCardThumbnailDetailsEvent extends LitElement { +@customElement('advanced-camera-card-thumbnail-details-event') +export class AdvancedCameraCardThumbnailDetailsEvent extends LitElement { @property({ attribute: false }) public media?: EventViewMedia; @@ -214,18 +214,18 @@ export class FrigateCardThumbnailDetailsEvent extends LitElement {
${startTime ? html`
- + > ${startTime}
${duration || inProgress ? html`
- + > ${duration ? html`${duration}` : ''} ${inProgress ? html`${inProgress}` @@ -235,37 +235,37 @@ export class FrigateCardThumbnailDetailsEvent extends LitElement { : ''} ${this.cameraTitle ? html`
- + > ${this.cameraTitle}
` : ''} ${where ? html`
- + > ${where}
` : html``} ${tags ? html`
- + > ${tags}
` : html``} ${seek ? html`
- + > ${seek}
` : html``} @@ -278,8 +278,8 @@ export class FrigateCardThumbnailDetailsEvent extends LitElement { } } -@customElement('frigate-card-thumbnail-details-recording') -export class FrigateCardThumbnailDetailsRecording extends LitElement { +@customElement('advanced-camera-card-thumbnail-details-recording') +export class AdvancedCameraCardThumbnailDetailsRecording extends LitElement { @property({ attribute: false }) public media?: RecordingViewMedia; @@ -315,18 +315,18 @@ export class FrigateCardThumbnailDetailsRecording extends LitElement {
${startTime ? html`
- + > ${startTime}
${duration || inProgress ? html`
- + > ${duration ? html`${duration}` : ''} ${inProgress ? html`${inProgress}` @@ -336,19 +336,19 @@ export class FrigateCardThumbnailDetailsRecording extends LitElement { : ''} ${seek ? html`
- + > ${seek}
` : html``} ${eventCount !== null ? html`
- + > ${eventCount}
` : ``} @@ -361,8 +361,8 @@ export class FrigateCardThumbnailDetailsRecording extends LitElement { } } -@customElement('frigate-card-thumbnail') -export class FrigateCardThumbnail extends LitElement { +@customElement('advanced-camera-card-thumbnail') +export class AdvancedCameraCardThumbnail extends LitElement { // Performance: During timeline scrubbing, hass may be updated continuously. // As it is not needed for the thumbnail rendering itself, it does not trigger // a re-render. The HomeAssistant object may be required for thumbnail signing @@ -439,25 +439,25 @@ export class FrigateCardThumbnail extends LitElement { return html` ${ViewMediaClassifier.isEvent(this.media) && thumbnail - ? html`` + >` : ViewMediaClassifier.isEvent(this.media) || ViewMediaClassifier.isRecording(this.media) - ? html`` + >` : html``} ${shouldShowFavoriteControl - ? html` ` + />` : ``} ${this.details && ViewMediaClassifier.isEvent(this.media) - ? html`` + >` : this.details && ViewMediaClassifier.isRecording(this.media) - ? html`` + >` : html``} ${shouldShowTimelineControl - ? html`` + >` : ''} ${shouldShowDownloadControl - ? html` ` + >` : ``} `; } @@ -544,10 +544,10 @@ export class FrigateCardThumbnail extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-thumbnail': FrigateCardThumbnail; - 'frigate-card-thumbnail-details-recording': FrigateCardThumbnailDetailsRecording; - 'frigate-card-thumbnail-details-event': FrigateCardThumbnailDetailsEvent; - 'frigate-card-thumbnail-feature-text': FrigateCardThumbnailFeatureText; - 'frigate-card-thumbnail-feature-thumbnail': FrigateCardThumbnailFeatureThumbnail; + 'advanced-camera-card-thumbnail': AdvancedCameraCardThumbnail; + 'advanced-camera-card-thumbnail-details-recording': AdvancedCameraCardThumbnailDetailsRecording; + 'advanced-camera-card-thumbnail-details-event': AdvancedCameraCardThumbnailDetailsEvent; + 'advanced-camera-card-thumbnail-feature-text': AdvancedCameraCardThumbnailFeatureText; + 'advanced-camera-card-thumbnail-feature-thumbnail': AdvancedCameraCardThumbnailFeatureThumbnail; } } diff --git a/src/components/timeline-core.ts b/src/components/timeline-core.ts index 058ae3ba..07274dc7 100644 --- a/src/components/timeline-core.ts +++ b/src/components/timeline-core.ts @@ -34,17 +34,17 @@ import { convertRangeToCacheFriendlyTimes } from '../camera-manager/utils/range- import { MergeContextViewModifier } from '../card-controller/view/modifiers/merge-context'; import { ViewManagerEpoch } from '../card-controller/view/types'; import { - FrigateCardTimelineItem, + AdvancedCameraCardTimelineItem, TimelineDataSource, } from '../components-lib/timeline-source'; import { + AdvancedCameraCardView, CameraConfig, CardWideConfig, - FrigateCardView, ThumbnailsControlBaseConfig, TimelineCoreConfig, TimelinePanMode, - frigateCardConfigDefaults, + configDefaults, } from '../config/types'; import { localize } from '../localize/localize'; import timelineCoreStyle from '../scss/timeline-core.scss'; @@ -52,7 +52,7 @@ import { ExtendedHomeAssistant } from '../types'; import { stopEventFromActivatingCardWideActions } from '../utils/action'; import { contentsChanged, - dispatchFrigateCardEvent, + dispatchAdvancedCameraCardEvent, formatDateAndTime, isHoverableDevice, isTruthy, @@ -73,11 +73,11 @@ import { import { MediaQueriesResults } from '../view/media-queries-results'; import { mergeViewContext } from '../view/view'; import './date-picker.js'; -import { DatePickerEvent, FrigateCardDatePicker } from './date-picker.js'; +import { AdvancedCameraCardDatePicker, DatePickerEvent } from './date-picker.js'; import './icon'; import './thumbnail.js'; -interface FrigateCardGroupData { +interface AdvancedCameraCardGroupData { id: string; content: string; } @@ -123,8 +123,8 @@ const TIMELINE_TARGET_BAR_ID = 'target_bar'; * A simgple thumbnail wrapper class for use in the timeline where Lit data * bindings are not available. */ -@customElement('frigate-card-timeline-thumbnail') -export class FrigateCardTimelineThumbnail extends LitElement { +@customElement('advanced-camera-card-timeline-thumbnail') +export class AdvancedCameraCardTimelineThumbnail extends LitElement { @property({ attribute: true }) public item?: IdType; @@ -153,11 +153,14 @@ export class FrigateCardTimelineThumbnail extends LitElement { item: this.item, }; this.dispatchEvent( - new ThumbnailDataRequestEvent(`frigate-card:timeline:thumbnail-data-request`, { - composed: true, - bubbles: true, - detail: dataRequest, - }), + new ThumbnailDataRequestEvent( + `advanced-camera-card:timeline:thumbnail-data-request`, + { + composed: true, + bubbles: true, + detail: dataRequest, + }, + ), ); if ( @@ -170,19 +173,19 @@ export class FrigateCardTimelineThumbnail extends LitElement { return html``; } - return html` - `; + `; } } -@customElement('frigate-card-timeline-core') -export class FrigateCardTimelineCore extends LitElement { +@customElement('advanced-camera-card-timeline-core') +export class AdvancedCameraCardTimelineCore extends LitElement { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; @@ -219,7 +222,7 @@ export class FrigateCardTimelineCore extends LitElement { protected _targetBarVisible = false; - protected _refDatePicker: Ref = createRef(); + protected _refDatePicker: Ref = createRef(); protected _refTimeline: Ref = createRef(); protected _timeline?: ExtendedTimeline; @@ -256,11 +259,11 @@ export class FrigateCardTimelineCore extends LitElement { // Note that changes to attributes here must be mirrored in the xss // whitelist in `_getOptions()` . return ` - - `; + `; } protected _handleThumbnailDataRequest(request: ThumbnailDataRequestEvent): void { @@ -302,7 +305,7 @@ export class FrigateCardTimelineCore extends LitElement { : 'mdi:camera-lock'; return html`
${this._shouldSupportSeeking() - ? html` { this._panMode = @@ -325,17 +328,19 @@ export class FrigateCardTimelineCore extends LitElement { aria-label="${panTitle}" title="${panTitle}" > - ` + ` : ''} - ) => { + @advanced-camera-card:date-picker:change=${( + ev: CustomEvent, + ) => { if (ev.detail.date) { this._timeline?.moveTo(ev.detail.date); } }} > - +
`; } @@ -498,7 +503,7 @@ export class FrigateCardTimelineCore extends LitElement { newResults = results; } - const desiredView: FrigateCardView = this.mini + const desiredView: AdvancedCameraCardView = this.mini ? targetTime >= new Date() ? 'live' : 'media' @@ -629,7 +634,7 @@ export class FrigateCardTimelineCore extends LitElement { } } - dispatchFrigateCardEvent(this, `thumbnails:${drawerAction}`); + dispatchAdvancedCameraCardEvent(this, `thumbnails:${drawerAction}`); this._ignoreClick = false; } @@ -726,7 +731,7 @@ export class FrigateCardTimelineCore extends LitElement { * @returns The dataset. */ protected _getGroups(): DataGroupCollectionType { - const groups: FrigateCardGroupData[] = []; + const groups: AdvancedCameraCardGroupData[] = []; (this.cameraIDs ?? []).forEach((cameraID: string) => { if (!this.hass || !this.cameraManager) { return; @@ -789,10 +794,7 @@ export class FrigateCardTimelineCore extends LitElement { * Get the configured window length in seconds. */ protected _getConfiguredWindowSeconds(): number { - return ( - this.timelineConfig?.window_seconds ?? - frigateCardConfigDefaults.timeline.window_seconds - ); + return this.timelineConfig?.window_seconds ?? configDefaults.timeline.window_seconds; } /** @@ -862,8 +864,8 @@ export class FrigateCardTimelineCore extends LitElement { clusterCriteria: (first: TimelineItem, second: TimelineItem): boolean => { const selectedIDs = this._getAllSelectedMediaIDsFromView(); - const firstMedia = (first).media; - const secondMedia = (second).media; + const firstMedia = (first).media; + const secondMedia = (second).media; // Never include the currently selected item in a cluster, and // never group different object types together (e.g. person and @@ -909,7 +911,7 @@ export class FrigateCardTimelineCore extends LitElement { disabled: false, filterOptions: { whiteList: { - 'frigate-card-timeline-thumbnail': ['details', 'item'], + 'advanced-camera-card-timeline-thumbnail': ['details', 'item'], div: ['title'], span: ['style'], }, @@ -1117,11 +1119,11 @@ export class FrigateCardTimelineCore extends LitElement { if (changedProps.has('thumbnailConfig')) { if (this.thumbnailConfig) { this.style.setProperty( - '--frigate-card-thumbnail-size', + '--advanced-camera-card-thumbnail-size', `${this.thumbnailConfig.size}px`, ); } else { - this.style.removeProperty('--frigate-card-thumbnail-size'); + this.style.removeProperty('--advanced-camera-card-thumbnail-size'); } } @@ -1248,7 +1250,7 @@ export class FrigateCardTimelineCore extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-timeline-thumbnail': FrigateCardTimelineThumbnail; - 'frigate-card-timeline-core': FrigateCardTimelineCore; + 'advanced-camera-card-timeline-thumbnail': AdvancedCameraCardTimelineThumbnail; + 'advanced-camera-card-timeline-core': AdvancedCameraCardTimelineCore; } } diff --git a/src/components/timeline.ts b/src/components/timeline.ts index 4e0c11e9..d8554af8 100644 --- a/src/components/timeline.ts +++ b/src/components/timeline.ts @@ -8,8 +8,8 @@ import { ExtendedHomeAssistant } from '../types'; import './surround.js'; import './timeline-core.js'; -@customElement('frigate-card-timeline') -export class FrigateCardTimeline extends LitElement { +@customElement('advanced-camera-card-timeline') +export class AdvancedCameraCardTimeline extends LitElement { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; @@ -31,7 +31,7 @@ export class FrigateCardTimeline extends LitElement { } return html` - - + `; } @@ -56,6 +56,6 @@ export class FrigateCardTimeline extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-timeline': FrigateCardTimeline; + 'advanced-camera-card-timeline': AdvancedCameraCardTimeline; } } diff --git a/src/components/viewer/carousel.ts b/src/components/viewer/carousel.ts index 9b0169af..44546a11 100644 --- a/src/components/viewer/carousel.ts +++ b/src/components/viewer/carousel.ts @@ -15,7 +15,7 @@ import { ViewManagerEpoch } from '../../card-controller/view/types.js'; import { MediaActionsController } from '../../components-lib/media-actions-controller.js'; import { CardWideConfig, - frigateCardConfigDefaults, + configDefaults, TransitionEffect, ViewerConfig, } from '../../config/types.js'; @@ -23,8 +23,8 @@ import { localize } from '../../localize/localize.js'; import '../../patches/ha-hls-player.js'; import viewerCarouselStyle from '../../scss/viewer-carousel.scss'; import { + AdvancedCameraCardMediaPlayer, ExtendedHomeAssistant, - FrigateCardMediaPlayer, MediaLoadedInfo, } from '../../types.js'; import { stopEventFromActivatingCardWideActions } from '../../utils/action.js'; @@ -42,7 +42,7 @@ import { renderMessage } from '../message.js'; import '../next-prev-control.js'; import '../ptz.js'; import './provider.js'; -import { FrigateCardViewerProvider } from './provider.js'; +import { AdvancedCameraCardViewerProvider } from './provider.js'; interface MediaNeighbor { index: number; @@ -54,10 +54,10 @@ interface MediaNeighbors { next?: MediaNeighbor; } -const FRIGATE_CARD_VIEWER_PROVIDER = 'frigate-card-viewer-provider'; +const ADVANCED_CAMERA_CARD_VIEWER_PROVIDER = 'advanced-camera-card-viewer-provider'; -@customElement('frigate-card-viewer-carousel') -export class FrigateCardViewerCarousel extends LitElement { +@customElement('advanced-camera-card-viewer-carousel') +export class AdvancedCameraCardViewerCarousel extends LitElement { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; @@ -92,7 +92,7 @@ export class FrigateCardViewerCarousel extends LitElement { protected _media: ViewMedia[] | null = null; protected _mediaActionsController = new MediaActionsController(); - protected _player: FrigateCardMediaPlayer | null = null; + protected _player: AdvancedCameraCardMediaPlayer | null = null; protected _refCarousel: Ref = createRef(); updated(changedProperties: PropertyValues): void { @@ -134,7 +134,7 @@ export class FrigateCardViewerCarousel extends LitElement { protected _getTransitionEffect(): TransitionEffect { return ( this.viewerConfig?.transition_effect ?? - frigateCardConfigDefaults.media_viewer.transition_effect + configDefaults.media_viewer.transition_effect ); } @@ -227,8 +227,8 @@ export class FrigateCardViewerCarousel extends LitElement { } const viewerProvider = slide?.querySelector( - 'frigate-card-viewer-provider', - ) as FrigateCardViewerProvider | null; + 'advanced-camera-card-viewer-provider', + ) as AdvancedCameraCardViewerProvider | null; if (viewerProvider) { viewerProvider.load = true; } @@ -259,7 +259,7 @@ export class FrigateCardViewerCarousel extends LitElement { protected willUpdate(changedProps: PropertyValues): void { if (changedProps.has('viewerConfig')) { this._mediaActionsController.setOptions({ - playerSelector: FRIGATE_CARD_VIEWER_PROVIDER, + playerSelector: ADVANCED_CAMERA_CARD_VIEWER_PROVIDER, ...(this.viewerConfig?.auto_play && { autoPlayConditions: this.viewerConfig.auto_play, }), @@ -330,7 +330,7 @@ export class FrigateCardViewerCarousel extends LitElement { ? 'previous' : 'next'; - return html` `; + >`; } protected render(): TemplateResult | void { @@ -373,40 +373,40 @@ export class FrigateCardViewerCarousel extends LitElement { const view = this.viewManagerEpoch?.manager.getView(); return html` - ) => { + @advanced-camera-card:carousel:select=${(ev: CustomEvent) => { this._setViewSelectedIndex(ev.detail.index); }} - @frigate-card:media:loaded=${(ev: CustomEvent) => { + @advanced-camera-card:media:loaded=${(ev: CustomEvent) => { this._player = ev.detail.player ?? null; this._seekHandler(); }} - @frigate-card:media:unloaded=${() => { + @advanced-camera-card:media:unloaded=${() => { this._player = null; }} > ${this.showControls ? this._renderNextPrevious('left', neighbors) : ''} ${guard([this._media, view], () => this._getSlides())} ${this.showControls ? this._renderNextPrevious('right', neighbors) : ''} - + ${view - ? html` - ` + ` : ''}
- - +
`; } @@ -448,7 +448,7 @@ export class FrigateCardViewerCarousel extends LitElement { } return html`
- + >
`; } @@ -468,6 +468,6 @@ export class FrigateCardViewerCarousel extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-viewer-carousel': FrigateCardViewerCarousel; + 'advanced-camera-card-viewer-carousel': AdvancedCameraCardViewerCarousel; } } diff --git a/src/components/viewer/grid.ts b/src/components/viewer/grid.ts index 5512a5f4..20b697dc 100644 --- a/src/components/viewer/grid.ts +++ b/src/components/viewer/grid.ts @@ -18,8 +18,8 @@ import { ExtendedHomeAssistant } from '../../types.js'; import { ResolvedMediaCache } from '../../utils/ha/resolved-media.js'; import './carousel'; -@customElement('frigate-card-viewer-grid') -export class FrigateCardViewerGrid extends LitElement { +@customElement('advanced-camera-card-viewer-grid') +export class AdvancedCameraCardViewerGrid extends LitElement { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; @@ -41,7 +41,7 @@ export class FrigateCardViewerGrid extends LitElement { protected _renderCarousel(filterCamera?: string): TemplateResult { const selectedCameraID = this.viewManagerEpoch?.manager.getView()?.camera; return html` - - + `; } @@ -92,14 +92,15 @@ export class FrigateCardViewerGrid extends LitElement { } return html` - ) => - this._gridSelectCamera(ev.detail.selected)} + @advanced-camera-card:media-grid:selected=${( + ev: CustomEvent, + ) => this._gridSelectCamera(ev.detail.selected)} > ${[...cameraIDs].map((cameraID) => this._renderCarousel(cameraID))} - + `; } @@ -110,6 +111,6 @@ export class FrigateCardViewerGrid extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-viewer-grid': FrigateCardViewerGrid; + 'advanced-camera-card-viewer-grid': AdvancedCameraCardViewerGrid; } } diff --git a/src/components/viewer/index.ts b/src/components/viewer/index.ts index 5c10ff30..41b8b8a2 100644 --- a/src/components/viewer/index.ts +++ b/src/components/viewer/index.ts @@ -21,8 +21,8 @@ declare module 'view' { } } -@customElement('frigate-card-viewer') -export class FrigateCardViewer extends LitElement { +@customElement('advanced-camera-card-viewer') +export class AdvancedCameraCardViewer extends LitElement { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; @@ -68,7 +68,7 @@ export class FrigateCardViewer extends LitElement { }); } - return html` - `; + `; } static get styles(): CSSResultGroup { @@ -86,6 +86,6 @@ export class FrigateCardViewer extends LitElement { declare global { interface HTMLElementTagNameMap { - 'frigate-card-viewer': FrigateCardViewer; + 'advanced-camera-card-viewer': AdvancedCameraCardViewer; } } diff --git a/src/components/viewer/provider.ts b/src/components/viewer/provider.ts index 0c10ca8d..4208a385 100644 --- a/src/components/viewer/provider.ts +++ b/src/components/viewer/provider.ts @@ -17,8 +17,8 @@ import { CardWideConfig, ViewerConfig } from '../../config/types.js'; import '../../patches/ha-hls-player.js'; import viewerProviderStyle from '../../scss/viewer-provider.scss'; import { + AdvancedCameraCardMediaPlayer, ExtendedHomeAssistant, - FrigateCardMediaPlayer, FullscreenElement, ResolvedMedia, } from '../../types.js'; @@ -54,10 +54,10 @@ import { MediaQueriesClassifier } from '../../view/media-queries-classifier.js'; import { VideoContentType, ViewMedia } from '../../view/media.js'; import { renderProgressIndicator } from '../progress-indicator.js'; -@customElement('frigate-card-viewer-provider') -export class FrigateCardViewerProvider +@customElement('advanced-camera-card-viewer-provider') +export class AdvancedCameraCardViewerProvider extends LitElement - implements FrigateCardMediaPlayer + implements AdvancedCameraCardMediaPlayer { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; @@ -86,8 +86,9 @@ export class FrigateCardViewerProvider @property({ attribute: false }) public cardWideConfig?: CardWideConfig; - protected _refFrigateCardMediaPlayer: Ref = - createRef(); + protected _refAdvancedCameraCardMediaPlayer: Ref< + Element & AdvancedCameraCardMediaPlayer + > = createRef(); protected _refVideoProvider: Ref = createRef(); protected _refImageProvider: Ref = createRef(); @@ -97,33 +98,35 @@ export class FrigateCardViewerProvider public async play(): Promise { await playMediaMutingIfNecessary( this, - this._refFrigateCardMediaPlayer.value ?? this._refVideoProvider.value, + this._refAdvancedCameraCardMediaPlayer.value ?? this._refVideoProvider.value, ); } public async pause(): Promise { - (this._refFrigateCardMediaPlayer.value || this._refVideoProvider.value)?.pause(); + ( + this._refAdvancedCameraCardMediaPlayer.value || this._refVideoProvider.value + )?.pause(); } public async mute(): Promise { - if (this._refFrigateCardMediaPlayer.value) { - this._refFrigateCardMediaPlayer.value?.mute(); + if (this._refAdvancedCameraCardMediaPlayer.value) { + this._refAdvancedCameraCardMediaPlayer.value?.mute(); } else if (this._refVideoProvider.value) { this._refVideoProvider.value.muted = true; } } public async unmute(): Promise { - if (this._refFrigateCardMediaPlayer.value) { - this._refFrigateCardMediaPlayer.value?.mute(); + if (this._refAdvancedCameraCardMediaPlayer.value) { + this._refAdvancedCameraCardMediaPlayer.value?.mute(); } else if (this._refVideoProvider.value) { this._refVideoProvider.value.muted = false; } } public isMuted(): boolean { - if (this._refFrigateCardMediaPlayer.value) { - return this._refFrigateCardMediaPlayer.value?.isMuted() ?? true; + if (this._refAdvancedCameraCardMediaPlayer.value) { + return this._refAdvancedCameraCardMediaPlayer.value?.isMuted() ?? true; } else if (this._refVideoProvider.value) { return this._refVideoProvider.value.muted; } @@ -131,8 +134,8 @@ export class FrigateCardViewerProvider } public async seek(seconds: number): Promise { - if (this._refFrigateCardMediaPlayer.value) { - return this._refFrigateCardMediaPlayer.value.seek(seconds); + if (this._refAdvancedCameraCardMediaPlayer.value) { + return this._refAdvancedCameraCardMediaPlayer.value.seek(seconds); } else if (this._refVideoProvider.value) { hideMediaControlsTemporarily(this._refVideoProvider.value); this._refVideoProvider.value.currentTime = seconds; @@ -140,8 +143,8 @@ export class FrigateCardViewerProvider } public async setControls(controls?: boolean): Promise { - if (this._refFrigateCardMediaPlayer.value) { - return this._refFrigateCardMediaPlayer.value.setControls(controls); + if (this._refAdvancedCameraCardMediaPlayer.value) { + return this._refAdvancedCameraCardMediaPlayer.value.setControls(controls); } else if (this._refVideoProvider.value) { setControlsOnVideo( this._refVideoProvider.value, @@ -151,8 +154,8 @@ export class FrigateCardViewerProvider } public isPaused(): boolean { - if (this._refFrigateCardMediaPlayer.value) { - return this._refFrigateCardMediaPlayer.value.isPaused(); + if (this._refAdvancedCameraCardMediaPlayer.value) { + return this._refAdvancedCameraCardMediaPlayer.value.isPaused(); } else if (this._refVideoProvider.value) { return this._refVideoProvider.value.paused; } @@ -160,8 +163,8 @@ export class FrigateCardViewerProvider } public async getScreenshotURL(): Promise { - if (this._refFrigateCardMediaPlayer.value) { - return await this._refFrigateCardMediaPlayer.value.getScreenshotURL(); + if (this._refAdvancedCameraCardMediaPlayer.value) { + return await this._refAdvancedCameraCardMediaPlayer.value.getScreenshotURL(); } else if (this._refVideoProvider.value) { return screenshotMedia(this._refVideoProvider.value); } else if (this._refImageProvider.value) { @@ -171,8 +174,8 @@ export class FrigateCardViewerProvider } public getFullscreenElement(): FullscreenElement | null { - if (this._refFrigateCardMediaPlayer.value) { - return this._refFrigateCardMediaPlayer.value.getFullscreenElement(); + if (this._refAdvancedCameraCardMediaPlayer.value) { + return this._refAdvancedCameraCardMediaPlayer.value.getFullscreenElement(); } else if (this._refVideoProvider.value) { return this._refVideoProvider.value; } @@ -319,7 +322,7 @@ export class FrigateCardViewerProvider const view = this.viewManagerEpoch?.manager.getView(); return this.viewerConfig?.zoomable - ? html` cameraConfig?.dimensions?.layout ? { @@ -329,13 +332,13 @@ export class FrigateCardViewerProvider : undefined, )} .settings=${mediaID ? view?.context?.zoom?.[mediaID]?.requested : undefined} - @frigate-card:zoom:zoomed=${() => this.setControls(false)} - @frigate-card:zoom:unzoomed=${() => this.setControls()} - @frigate-card:zoom:change=${(ev: CustomEvent) => + @advanced-camera-card:zoom:zoomed=${() => this.setControls(false)} + @advanced-camera-card:zoom:unzoomed=${() => this.setControls()} + @advanced-camera-card:zoom:change=${(ev: CustomEvent) => handleZoomSettingsObservedEvent(ev, this.viewManagerEpoch?.manager, mediaID)} > ${template} - ` + ` : template; } @@ -355,8 +358,8 @@ export class FrigateCardViewerProvider return this._useZoomIfRequired(html` ${ViewMediaClassifier.isVideo(this.media) ? this.media.getVideoContentType() === VideoContentType.HLS - ? html` - ` + ` : html`
${selected ? html`
${template}
` : ''} @@ -1878,7 +1878,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor * @returns A rendered template. */ protected _renderCamera( - cameras: RawFrigateCardConfigArray, + cameras: RawAdvancedCameraCardConfigArray, cameraIndex: number, entities: string[], addNewCamera?: boolean, @@ -1916,7 +1916,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor }); // Make a new config and update the editor with changes on it, - const modifyConfig = (func: (config: RawFrigateCardConfig) => boolean): void => { + const modifyConfig = ( + func: (config: RawAdvancedCameraCardConfig) => boolean, + ): void => { if (this._config) { const newConfig = copyConfig(this._config); if (func(newConfig)) { @@ -1938,9 +1940,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor .domain=${MENU_CAMERAS} .key=${cameraIndex} > - + > ${addNewCamera ? html` @@ -1965,7 +1967,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor cameraIndex <= 0} @click=${() => !addNewCamera && - modifyConfig((config: RawFrigateCardConfig): boolean => { + modifyConfig((config: RawAdvancedCameraCardConfig): boolean => { if (Array.isArray(config.cameras) && cameraIndex > 0) { arrayMove(config.cameras, cameraIndex, cameraIndex - 1); this._openMenu(MENU_CAMERAS, cameraIndex - 1); @@ -1974,9 +1976,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor return false; })} > - + > = this._config.cameras.length - 1} @click=${() => !addNewCamera && - modifyConfig((config: RawFrigateCardConfig): boolean => { + modifyConfig((config: RawAdvancedCameraCardConfig): boolean => { if ( Array.isArray(config.cameras) && cameraIndex < config.cameras.length - 1 @@ -1998,15 +2000,15 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor return false; })} > - + > { - modifyConfig((config: RawFrigateCardConfig): boolean => { + modifyConfig((config: RawAdvancedCameraCardConfig): boolean => { if (Array.isArray(config.cameras)) { config.cameras.splice(cameraIndex, 1); this._closeMenu(MENU_CAMERAS); @@ -2016,7 +2018,9 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor }); }} > - +
${this._renderEntitySelector( @@ -2497,7 +2501,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor `; } - protected _updateConfig(config: RawFrigateCardConfig): void { + protected _updateConfig(config: RawAdvancedCameraCardConfig): void { this._config = config; fireEvent(this, 'config-changed', { config: this._config }); } @@ -2509,7 +2513,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor const entities = getEntitiesFromHASS(this.hass); const cameras = (getConfigValue(this._config, CONF_CAMERAS) || - []) as RawFrigateCardConfigArray; + []) as RawAdvancedCameraCardConfigArray; return html` ${this._configUpgradeable @@ -2587,7 +2591,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ${this._renderNumberInput(CONF_MENU_BUTTON_SIZE, { min: BUTTON_SIZE_MIN, })} - ${this._renderMenuButton('frigate') /* */} + ${this._renderMenuButton('iris') /* */} ${this._renderMenuButton('cameras') /* */} ${this._renderMenuButton('substreams') /* */} ${this._renderMenuButton('live') /* */} @@ -3128,12 +3132,12 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor } static get styles(): CSSResultGroup { - return unsafeCSS(frigate_card_editor_style); + return unsafeCSS(editorStyle); } } declare global { interface HTMLElementTagNameMap { - 'frigate-card-editor': FrigateCardEditor; + 'advanced-camera-card-editor': AdvancedCameraCardEditor; } } diff --git a/src/images/README.md b/src/images/README.md index 0a561e50..a5817903 100644 --- a/src/images/README.md +++ b/src/images/README.md @@ -1,25 +1,22 @@ # Image Copyright -## frigate-bird-in-sky.jpg +## iris-screensaver.jpg -**Link**: https://www.flickr.com/photos/dianasch/47543120431 +**Link**: https://unsplash.com/photos/purple-and-white-flower-in-tilt-shift-lens-y4xISRK8TUg -**Description**: A Frigate bird with a very large wingspan (7 ft) soaring overhead on Sanibel Island, Florida. +**Description**: Iris flowers in Vancouver Park. -**Copyright**: Diana Robinson +**Copyright**: Kevin Castel -**License**: https://creativecommons.org/licenses/by-nc-nd/2.0/ +**License**: https://unsplash.com/license **Image Formatting Process**: -- Horizontal mirror (to face the opposite direction) -- Cropped for 16:9 - Scaled down to 492x277 - Quality @ 85% ```sh -$ convert -extent 2048x1152 -flop -gravity center 47543120431_b285c45ac8_k.jpg frigate-bird-in-sky.jpg -$ mogrify -strip -interlace Plane -quality 85% -scale 492x277 frigate-bird-in-sky.jpg +$ convert -strip -interlace Plane -quality 85% -scale 492x277 iris-screensaver-original.jpg iris-screensaver.jpg ``` ## iris.svg / iris-outline.svg diff --git a/src/images/frigate-bird-in-sky.jpg b/src/images/frigate-bird-in-sky.jpg deleted file mode 100644 index f5c3cadd48c1985f8361461bb0846eccb320a3e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18003 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 diff --git a/src/images/iris-screensaver.jpg b/src/images/iris-screensaver.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f9f06e7c962af83f1e535d802b51c834b965028f GIT binary patch literal 30949 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 M5IRQj=I_e?0VBuZr~m)} literal 0 HcmV?d00001 diff --git a/src/localize/languages/ca.json b/src/localize/languages/ca.json index 94fd2814..2552071b 100644 --- a/src/localize/languages/ca.json +++ b/src/localize/languages/ca.json @@ -1,7 +1,7 @@ { "common": { - "frigate_card": "Tarjeta Frigate", - "frigate_card_description": "Una targeta Lovelace per a utilitzar amb Frigate", + "advanced_camera_card": "", + "advanced_camera_card_description": "", "live": "En directe", "no_media": "No hi ha suport per mostrar", "recordings": "Enregistraments", @@ -265,7 +265,7 @@ "modes": { "camera": "Instantània de l'entitat de la càmera de Home Assistant", "entity": "", - "screensaver": "Logotip de Frigate incrustat", + "screensaver": "", "url": "Imatge arbitrària especificada per URL" }, "refresh_seconds": "Nombre de segons després dels quals cal actualitzar (0=mai)", @@ -394,10 +394,10 @@ "download": "Descarregar", "enabled": "Botó habilitat", "expand": "Expandir", - "frigate": "Menú de Frigate / Vista per defecte", "fullscreen": "Pantalla completa", "icon": "Icona", "image": "Imatge", + "iris": "", "live": "En directe", "media_player": "Envia al reproductor multimèdia", "microphone": "Micròfon", @@ -646,7 +646,6 @@ "no_camera_entity": "No s'ha pogut trobar l'entitat de la càmera", "no_camera_entity_for_triggers": "Es requereix una entitat de càmera per detectar automàticament els activadors", "no_camera_id": "No s'ha pogut determinar l'identificador de la càmera per a la següent càmera, és possible que hagis d'establir el paràmetre 'id' manualment", - "no_camera_name": "No s'ha pogut determinar el nom de la càmera Frigate per a la càmera (o una de les seves dependències), especifiqueu 'camera_entity' o 'camera_name'", "no_dashboard_or_view": "Tant els paràmetres 'dashboard_path' com 'view_path' són necessaris per al mètode d'emissió 'dashboard'", "no_live_camera": "El paràmetre 'camera_entity' s'ha de configurar i validar per a aquest proveïdor en directe", "no_supported_camera": "", diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index 614d26b7..da9942ed 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -1,7 +1,7 @@ { "common": { - "frigate_card": "Frigate card", - "frigate_card_description": "A Lovelace card for use with Frigate", + "advanced_camera_card": "Advanced Camera Card", + "advanced_camera_card_description": "An Advanced Camera Card", "live": "Live", "no_media": "No media to display", "recordings": "Recordings", @@ -177,7 +177,7 @@ }, "webrtc_card": { "editor_label": "WebRTC Card Options", - "entity": "WebRTC Card Camera Entity (Not a Frigate camera)", + "entity": "WebRTC Card Camera Entity", "url": "WebRTC Card Camera URL" } }, @@ -265,7 +265,7 @@ "modes": { "camera": "Home Assistant camera snapshot of camera entity", "entity": "Entity with entity_picture attribute", - "screensaver": "Embedded Frigate logo", + "screensaver": "Embedded screensaver image", "url": "Arbitrary image specified by URL" }, "refresh_seconds": "Number of seconds after which to refresh (0=never)", @@ -394,10 +394,10 @@ "download": "Download", "enabled": "Button enabled", "expand": "Expand", - "frigate": "Frigate menu / Default view", "fullscreen": "Fullscreen", "icon": "Icon", "image": "Image", + "iris": "Iris / Default View / Unhide menu", "live": "Live", "media_player": "Send to media player", "microphone": "Microphone", @@ -626,7 +626,7 @@ "diagnostics": "Card diagnostics. Please review for confidential information prior to sharing", "download_no_media": "No media to download", "download_sign_failed": "Could not sign media URL for download", - "duplicate_camera_id": "Duplicate Frigate camera id for the following camera, use the 'id' parameter to uniquely identify cameras", + "duplicate_camera_id": "Duplicate camera id for the following camera, use the 'id' parameter to uniquely identify cameras", "empty_response": "Received empty response from Home Assistant for request", "failed_response": "Failed to receive response from Home Assistant for request", "failed_retain": "Could not retain event", @@ -646,7 +646,6 @@ "no_camera_entity": "Could not find camera entity", "no_camera_entity_for_triggers": "A camera entity is required in order to autodetect triggers", "no_camera_id": "Could not determine camera id for the following camera, may need to set 'id' parameter manually", - "no_camera_name": "Could not determine a Frigate camera name for camera (or one of its dependents), please specify either 'camera_entity' or 'camera_name'", "no_dashboard_or_view": "Both 'dashboard_path' and 'view_path' parameters are required for the 'dashboard' cast method", "no_live_camera": "The camera_entity parameter must be set and valid for this live provider", "no_supported_camera": "The selected camera does not support this view", diff --git a/src/localize/languages/fr.json b/src/localize/languages/fr.json index d45aeebf..63d6b7d4 100644 --- a/src/localize/languages/fr.json +++ b/src/localize/languages/fr.json @@ -1,7 +1,7 @@ { "common": { - "frigate_card": "Carte Frigate", - "frigate_card_description": "Une carte Lovelace à utiliser avec Frigate", + "advanced_camera_card": "", + "advanced_camera_card_description": "", "live": "En direct", "no_media": "Aucun média à afficher", "recordings": "Enregistrements", @@ -265,7 +265,7 @@ "modes": { "camera": "Instantané de la caméra Home Assistant de l'entité caméra", "entity": "Entité avec l'attribut entity_picture", - "screensaver": "Logo Frigate intégré", + "screensaver": "", "url": "Image arbitraire spécifiée par URL" }, "refresh_seconds": "Nombre de secondes après lesquelles actualiser (0=jamais)", @@ -394,10 +394,10 @@ "download": "Télécharger", "enabled": "Bouton activé", "expand": "Étendre", - "frigate": "Menu Frigate / Vue par défaut", "fullscreen": "Plein écran", "icon": "Icône", "image": "Image", + "iris": "", "live": "En direct", "media_player": "Envoyer au lecteur multimédia", "microphone": "Microphone", @@ -646,7 +646,6 @@ "no_camera_entity": "Impossible de trouver l'entité de caméra", "no_camera_entity_for_triggers": "Une entité caméra est requise pour détecter automatiquement les déclencheurs", "no_camera_id": "Impossible de déterminer l'identifiant de la caméra suivante. Il faudra peut-être définir le paramètre « ID » manuellement", - "no_camera_name": "Impossible de déterminer le nom de la caméra Frigate pour la caméra (ou l'une de ses dépendances). Veuillez spécifier soit « camera_entity » soit « camera_name ».", "no_dashboard_or_view": "Les paramètres 'dashboard_path' et 'view_path' sont requis pour la méthode de conversion 'dashboard'", "no_live_camera": "Le paramètre camera_entity doit être défini et valide pour ce fournisseur en direct", "no_supported_camera": "La caméra sélectionnée ne prend pas en charge cette vue", diff --git a/src/localize/languages/it.json b/src/localize/languages/it.json index 6299f458..7f5f40d8 100644 --- a/src/localize/languages/it.json +++ b/src/localize/languages/it.json @@ -1,7 +1,7 @@ { "common": { - "frigate_card": "Frigate card", - "frigate_card_description": "Una scheda Lovelace per l'uso con Frigate", + "advanced_camera_card": "", + "advanced_camera_card_description": "", "live": "Live", "no_media": "Nessun contenuto multimediale da visualizzare", "recordings": "Registrazioni", @@ -265,7 +265,7 @@ "modes": { "camera": "Istantanea della telecamera di Home Assistant dell'entità telecamera", "entity": "", - "screensaver": "Logo Frigate incorporato", + "screensaver": "", "url": "Immagine arbitraria specificata dall'URL" }, "refresh_seconds": "Numero di secondi dopo i quali aggiornare (0 = mai)", @@ -394,10 +394,10 @@ "download": "Download", "enabled": "Pulsante abilitato", "expand": "Espandere", - "frigate": "Frigate menu / Visualizzazione predefinita", "fullscreen": "A schermo intero", "icon": "Icona", "image": "Immagine", + "iris": "", "live": "Abitare", "media_player": "Invia a Media Player", "microphone": "", @@ -646,7 +646,6 @@ "no_camera_entity": "Impossibile trovare l'entità fotocamera", "no_camera_entity_for_triggers": "È necessaria un'entità telecamera per rilevare automaticamente i trigger", "no_camera_id": "Impossibile determinare l'ID della telecamera , potrebbe essere necessario impostare manualmente il parametro 'ID'", - "no_camera_name": "Impossibile determinare un nome della telecamera in Frigate, si prega di specificare 'camera_enty' o 'camera_name'", "no_dashboard_or_view": "", "no_live_camera": "Il parametro fotocamera_enty deve essere impostato e valido per questo provider live", "no_supported_camera": "", diff --git a/src/localize/languages/pt-BR.json b/src/localize/languages/pt-BR.json index 680afc01..5b870621 100644 --- a/src/localize/languages/pt-BR.json +++ b/src/localize/languages/pt-BR.json @@ -1,7 +1,7 @@ { "common": { - "frigate_card": "Cartão Frigate", - "frigate_card_description": "Um cartão da Lovelace para usar com Frigate", + "advanced_camera_card": "", + "advanced_camera_card_description": "", "live": "Ao Vivo", "no_media": "Nenhuma mídia para exibir", "recordings": "Gravações", @@ -265,7 +265,7 @@ "modes": { "camera": "Instantâneo da câmera do Home Assistant, da entidade de câmera", "entity": "", - "screensaver": "Logo Frigate embutido", + "screensaver": "", "url": "Imagem arbitrária especificada por URL" }, "refresh_seconds": "Número de segundos após o qual atualizar (0 = nunca)", @@ -394,10 +394,10 @@ "download": "Baixe a mídia do evento", "enabled": "Botão ativado", "expand": "Expandir", - "frigate": "Frigate menu / Visualização padrão", "fullscreen": "Tela cheia", "icon": "Ícone", "image": "Imagem", + "iris": "", "live": "Ao vivo", "media_player": "Enviar para o reprodutor de mídia", "microphone": "", @@ -646,7 +646,6 @@ "no_camera_entity": "Não foi possível encontrar a entidade da câmera", "no_camera_entity_for_triggers": "Uma entidade de câmera é necessária para detectar automaticamente os gatilhos", "no_camera_id": "Não foi possível determinar o ID da câmera para a câmera a seguir, pode ser necessário definir o parâmetro 'id' manualmente", - "no_camera_name": "Não foi possível determinar o nome da câmera da Frigate, especifique 'camera_entity' ou 'camera_name' para a câmera a seguir", "no_dashboard_or_view": "", "no_live_camera": "O parâmetro camera_entity deve ser definido e válido para este provedor ativo", "no_supported_camera": "", diff --git a/src/localize/languages/pt-PT.json b/src/localize/languages/pt-PT.json index a45e6258..37d49ce4 100644 --- a/src/localize/languages/pt-PT.json +++ b/src/localize/languages/pt-PT.json @@ -1,7 +1,7 @@ { "common": { - "frigate_card": "Cartão Frigate", - "frigate_card_description": "Um cartão da Lovelace para usar com Frigate", + "advanced_camera_card": "", + "advanced_camera_card_description": "", "live": "Ao Vivo", "no_media": "Sem média", "recordings": "Gravações", @@ -265,7 +265,7 @@ "modes": { "camera": "Instantâneo da câmera do Home Assistant, da entidade de câmera", "entity": "", - "screensaver": "Logo Frigate embutido", + "screensaver": "", "url": "Imagem arbitrária especificada por URL" }, "refresh_seconds": "Número de segundos após o qual atualizar (0 = nunca)", @@ -394,10 +394,10 @@ "download": "Descarregar mídia do evento", "enabled": "Botão ativado", "expand": "Expandir", - "frigate": "Frigate menu / Visualização padrão", "fullscreen": "Tela cheia", "icon": "Ícone", "image": "Imagem", + "iris": "", "live": "Ao vivo", "media_player": "Enviar para o reprodutor de mídia", "microphone": "", @@ -646,7 +646,6 @@ "no_camera_entity": "Não existe uma entidade câmera", "no_camera_entity_for_triggers": "Não existe camera para a acção", "no_camera_id": "Não foi possível determinar o ID da câmera para a câmera a seguir, pode ser necessário definir o parâmetro 'id' manualmente", - "no_camera_name": "Não foi possível determinar o nome da câmera da Frigate, especifique 'camera_entity' ou 'camera_name' para a câmera a seguir", "no_dashboard_or_view": "", "no_live_camera": "O parâmetro camera_entity deve ser definido e válido para este serviço ativo", "no_supported_camera": "", diff --git a/src/localize/localize.ts b/src/localize/localize.ts index 76b8361a..556f3f5e 100644 --- a/src/localize/localize.ts +++ b/src/localize/localize.ts @@ -14,7 +14,7 @@ const languages: Record = { // The language is calculated and stored once, then re-used to avoid needing to // repeat the lookups and to ensure minimal information needs to be plumbed // through on each localization call. -let frigateCardLanguage: string | undefined; +let advancedCameraCardLanguage: string | undefined; /** * Get the configured language. @@ -67,7 +67,7 @@ export const loadLanguages = async (hass: HomeAssistant): Promise => { } if (lang) { - frigateCardLanguage = lang; + advancedCameraCardLanguage = lang; } return true; }; @@ -85,7 +85,7 @@ export function localize(string: string, search = '', replace = ''): string { try { translated = string .split('.') - .reduce((o, i) => o[i], languages[frigateCardLanguage ?? DEFAULT_LANG]); + .reduce((o, i) => o[i], languages[advancedCameraCardLanguage ?? DEFAULT_LANG]); } catch (_) {} if (!translated) { diff --git a/src/patches/ha-camera-stream.ts b/src/patches/ha-camera-stream.ts index ff2c3fd5..3e2e7aa0 100644 --- a/src/patches/ha-camera-stream.ts +++ b/src/patches/ha-camera-stream.ts @@ -13,7 +13,11 @@ import { css, CSSResultGroup, html, nothing, PropertyValues, unsafeCSS } from 'l import { customElement } from 'lit/decorators.js'; import { query } from 'lit/decorators/query.js'; import liveHAComponentsStyle from '../scss/live-ha-components.scss'; -import { FrigateCardMediaPlayer, FullscreenElement, MediaLoadedInfo } from '../types.js'; +import { + AdvancedCameraCardMediaPlayer, + FullscreenElement, + MediaLoadedInfo, +} from '../types.js'; import { createMediaLoadedInfo, dispatchExistingMediaLoadedInfoAsEvent, @@ -36,16 +40,16 @@ customElements.whenDefined('ha-camera-stream').then(() => { const STREAM_TYPE_MJPEG = 'mjpeg'; type StreamType = STREAM_TYPE_HLS | STREAM_TYPE_WEB_RTC | STREAM_TYPE_MJPEG; - @customElement('frigate-card-ha-camera-stream') + @customElement('advanced-camera-card-ha-camera-stream') // eslint-disable-next-line @typescript-eslint/no-unused-vars - class FrigateCardHaCameraStream + class AdvancedCameraCardHaCameraStream extends customElements.get('ha-camera-stream') - implements FrigateCardMediaPlayer + implements AdvancedCameraCardMediaPlayer { // Due to an obscure behavior when this card is casted, this element needs // to use query rather than the ref directive to find the player. @query('.player:not(.hidden)') - protected _player: FrigateCardMediaPlayer; + protected _player: AdvancedCameraCardMediaPlayer; protected _mediaLoadedInfoPerStream: Record = {}; protected _mediaLoadedInfoDispatched: MediaLoadedInfo | null = null; @@ -133,7 +137,7 @@ customElements.whenDefined('ha-camera-stream').then(() => { } if (stream.type === STREAM_TYPE_HLS) { - return html` { .hass=${this.hass} .entityid=${this.stateObj.entity_id} .posterUrl=${this._posterUrl} - @frigate-card:media:loaded=${(ev) => + @advanced-camera-card:media:loaded=${(ev) => this._storeMediaLoadedInfoHandler(STREAM_TYPE_HLS, ev)} @streams=${this._handleHlsStreams} class="player ${stream.visible ? '' : 'hidden'}" - >`; + >`; } if (stream.type === STREAM_TYPE_WEB_RTC) { - return html` { .hass=${this.hass} .entityid=${this.stateObj.entity_id} .posterUrl=${this._posterUrl} - @frigate-card:media:loaded=${(ev) => + @advanced-camera-card:media:loaded=${(ev) => this._storeMediaLoadedInfoHandler(STREAM_TYPE_WEB_RTC, ev)} @streams=${this._handleWebRtcStreams} class="player ${stream.visible ? '' : 'hidden'}" - >`; + >`; } return nothing; @@ -208,6 +212,6 @@ customElements.whenDefined('ha-camera-stream').then(() => { declare global { interface HTMLElementTagNameMap { - 'frigate-card-ha-camera-stream': FrigateCardHaCameraStream; + 'advanced-camera-card-ha-camera-stream': AdvancedCameraCardHaCameraStream; } } diff --git a/src/patches/ha-hls-player.ts b/src/patches/ha-hls-player.ts index d7a897dd..840d1bc4 100644 --- a/src/patches/ha-hls-player.ts +++ b/src/patches/ha-hls-player.ts @@ -15,7 +15,7 @@ import { query } from 'lit/decorators/query.js'; import { dispatchLiveErrorEvent } from '../components-lib/live/utils/dispatch-live-error.js'; import { renderMessage } from '../components/message.js'; import liveHAComponentsStyle from '../scss/live-ha-components.scss'; -import { FrigateCardMediaPlayer, FullscreenElement } from '../types.js'; +import { AdvancedCameraCardMediaPlayer, FullscreenElement } from '../types.js'; import { mayHaveAudio } from '../utils/audio.js'; import { errorToConsole } from '../utils/basic.js'; import { @@ -32,11 +32,11 @@ import { import { screenshotMedia } from '../utils/screenshot.js'; customElements.whenDefined('ha-hls-player').then(() => { - @customElement('frigate-card-ha-hls-player') + @customElement('advanced-camera-card-ha-hls-player') // eslint-disable-next-line @typescript-eslint/no-unused-vars - class FrigateCardHaHlsPlayer + class AdvancedCameraCardHaHlsPlayer extends customElements.get('ha-hls-player') - implements FrigateCardMediaPlayer + implements AdvancedCameraCardMediaPlayer { // Due to an obscure behavior when this card is casted, this element needs // to use query rather than the ref directive to find the player. @@ -171,6 +171,6 @@ customElements.whenDefined('ha-hls-player').then(() => { declare global { interface HTMLElementTagNameMap { - 'frigate-card-ha-hls-player': FrigateCardHaHlsPlayer; + 'advanced-camera-card-ha-hls-player': AdvancedCameraCardHaHlsPlayer; } } diff --git a/src/patches/ha-web-rtc-player.ts b/src/patches/ha-web-rtc-player.ts index 839098e6..5a0cfbfd 100644 --- a/src/patches/ha-web-rtc-player.ts +++ b/src/patches/ha-web-rtc-player.ts @@ -16,7 +16,7 @@ import { ifDefined } from 'lit/directives/if-defined.js'; import { dispatchLiveErrorEvent } from '../components-lib/live/utils/dispatch-live-error.js'; import { renderMessage } from '../components/message.js'; import liveHAComponentsStyle from '../scss/live-ha-components.scss'; -import { FrigateCardMediaPlayer, FullscreenElement } from '../types.js'; +import { AdvancedCameraCardMediaPlayer, FullscreenElement } from '../types.js'; import { mayHaveAudio } from '../utils/audio.js'; import { dispatchMediaLoadedEvent, @@ -32,11 +32,11 @@ import { import { screenshotMedia } from '../utils/screenshot.js'; customElements.whenDefined('ha-web-rtc-player').then(() => { - @customElement('frigate-card-ha-web-rtc-player') + @customElement('advanced-camera-card-ha-web-rtc-player') // eslint-disable-next-line @typescript-eslint/no-unused-vars - class FrigateCardHaWebRtcPlayer + class AdvancedCameraCardHaWebRtcPlayer extends customElements.get('ha-web-rtc-player') - implements FrigateCardMediaPlayer + implements AdvancedCameraCardMediaPlayer { // Due to an obscure behavior when this card is casted, this element needs // to use query rather than the ref directive to find the player. @@ -166,6 +166,6 @@ customElements.whenDefined('ha-web-rtc-player').then(() => { declare global { interface HTMLElementTagNameMap { - 'frigate-card-ha-web-rtc-player': FrigateCardHaWebRtcPlayer; + 'advanced-camera-card-ha-web-rtc-player': AdvancedCameraCardHaWebRtcPlayer; } } diff --git a/src/scss/button.scss b/src/scss/button.scss index d2ac2a92..849a1545 100644 --- a/src/scss/button.scss +++ b/src/scss/button.scss @@ -1,6 +1,6 @@ ha-icon-button { - color: var(--frigate-card-button-color); - background-color: var(--frigate-card-button-background); + color: var(--advanced-camera-card-button-color); + background-color: var(--advanced-camera-card-button-background); border-radius: 50%; padding: 0px; diff --git a/src/scss/card.scss b/src/scss/card.scss index 678f4107..8f70e057 100644 --- a/src/scss/card.scss +++ b/src/scss/card.scss @@ -14,7 +14,7 @@ // keeping the background-color within the radius. border-radius: var(--ha-card-border-radius, 4px); - height: var(--frigate-card-height); + height: var(--advanced-camera-card-height); min-height: 100px; // Ensure all clicks at the top level work. @@ -23,16 +23,16 @@ // The standard HA header is 56 pixels tall, so that much off the top (header) // and bottom (to maintain center), before doing the calculation of // max-height. This matters on small mobile devices in landscape orientation. - --frigate-card-expand-max-height: calc((100vh - (2 * 56px)) * 0.85); - --frigate-card-expand-max-width: 85vw; - --frigate-card-expand-width: none; - --frigate-card-expand-height: none; - --frigate-card-expand-aspect-ratio: unset; + --advanced-camera-card-expand-max-height: calc((100vh - (2 * 56px)) * 0.85); + --advanced-camera-card-expand-max-width: 85vw; + --advanced-camera-card-expand-width: none; + --advanced-camera-card-expand-height: none; + --advanced-camera-card-expand-aspect-ratio: unset; - --frigate-card-height: auto; + --advanced-camera-card-height: auto; } -frigate-card-loading { +advanced-camera-card-loading { position: absolute; inset: 0; z-index: 1; @@ -47,13 +47,13 @@ frigate-card-loading { // If the "Kiosk Mode" (https://github.com/maykar/kiosk-mode) plugin is // installed, the --kiosk-header-height variable will be authoritative // instead. - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1762 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1762 height: calc(100vh - var(--kiosk-header-height, var(--header-height))); } :host([panel][casted]) { // Card always extends to the full height in panel mode when casting (there is // no header). - // See: https://github.com/dermotduffy/frigate-hass-card/issues/1746 + // See: https://github.com/dermotduffy/advanced-camera-card/issues/1746 height: 100%; } @@ -143,11 +143,11 @@ ha-card { } :host(:fullscreen) div.main, -:host(:fullscreen) frigate-card-menu { +:host(:fullscreen) advanced-camera-card-menu { @include fullscreen-no-rounded-corners; } :host(:-webkit-full-screen) div.main, -:host(:-webkit-full-screen) frigate-card-menu { +:host(:-webkit-full-screen) advanced-camera-card-menu { @include fullscreen-no-rounded-corners; } @@ -159,11 +159,11 @@ web-dialog { --dialog-padding: 0px; --dialog-container-padding: 0px; - --dialog-max-height: var(--frigate-card-expand-max-height); - --dialog-max-width: var(--frigate-card-expand-max-width); + --dialog-max-height: var(--advanced-camera-card-expand-max-height); + --dialog-max-width: var(--advanced-camera-card-expand-max-width); - --dialog-width: var(--frigate-card-expand-width); - --dialog-height: var(--frigate-card-expand-height); + --dialog-width: var(--advanced-camera-card-expand-width); + --dialog-height: var(--advanced-camera-card-expand-height); // Allow submenus to flow outside the edge of the dialog. --dialog-overflow-x: visible; @@ -174,7 +174,7 @@ web-dialog { } web-dialog::part(dialog) { - aspect-ratio: var(--frigate-card-expand-aspect-ratio); + aspect-ratio: var(--advanced-camera-card-expand-aspect-ratio); // Fixes to render the dialog correctly in Safari. border-radius: 0px; @@ -203,16 +203,18 @@ web-dialog::part(dialog) { * Menu hover styles *******************/ -frigate-card-menu { +advanced-camera-card-menu { z-index: 2; } -frigate-card-menu[data-style*='hover'] { +advanced-camera-card-menu[data-style*='hover'] { transition: opacity 0.5s ease; opacity: 0; } -frigate-card-menu[data-style*='hover']:hover, -ha-card:hover > frigate-card-overlay > frigate-card-menu[data-style='hover-card'] { +advanced-camera-card-menu[data-style*='hover']:hover, +ha-card:hover + > advanced-camera-card-overlay + > advanced-camera-card-menu[data-style='hover-card'] { opacity: 1; } @@ -220,15 +222,17 @@ ha-card:hover > frigate-card-overlay > frigate-card-menu[data-style='hover-card' * Status bar hover styles *************************/ -frigate-card-status-bar { +advanced-camera-card-status-bar { z-index: 1; } -frigate-card-status-bar[data-style*='hover'] { +advanced-camera-card-status-bar[data-style*='hover'] { transition: opacity 0.5s ease; opacity: 0; } -frigate-card-status-bar[data-style*='hover']:hover, -ha-card:hover > frigate-card-overlay > frigate-card-status-bar[data-style='hover-card'] { +advanced-camera-card-status-bar[data-style*='hover']:hover, +ha-card:hover + > advanced-camera-card-overlay + > advanced-camera-card-status-bar[data-style='hover-card'] { opacity: 1; } diff --git a/src/scss/const.scss b/src/scss/const.scss index 35264fd3..45373b6f 100644 --- a/src/scss/const.scss +++ b/src/scss/const.scss @@ -1,6 +1,6 @@ :host { - --frigate-card-thumbnail-size-max: 300px; - --frigate-card-thumbnail-details-width: calc( - var(--frigate-card-thumbnail-size) + 200px + --advanced-camera-card-thumbnail-size-max: 300px; + --advanced-camera-card-thumbnail-details-width: calc( + var(--advanced-camera-card-thumbnail-size) + 200px ); } diff --git a/src/scss/date-picker.scss b/src/scss/date-picker.scss index 92fc9d45..585a4569 100644 --- a/src/scss/date-picker.scss +++ b/src/scss/date-picker.scss @@ -42,7 +42,7 @@ input { } } -frigate-card-icon { +advanced-camera-card-icon { display: block; height: 100%; width: 100%; diff --git a/src/scss/drawer-inject.scss b/src/scss/drawer-inject.scss index 94db2960..f6e1ed1c 100644 --- a/src/scss/drawer-inject.scss +++ b/src/scss/drawer-inject.scss @@ -40,7 +40,7 @@ :host([location='right'][open]) #d { transform: none; - box-shadow: var(--frigate-card-css-box-shadow, 0px 0px 25px 0px black); + box-shadow: var(--advanced-camera-card-css-box-shadow, 0px 0px 25px 0px black); } #ifs { diff --git a/src/scss/drawer.scss b/src/scss/drawer.scss index 96395559..f4ef72b2 100644 --- a/src/scss/drawer.scss +++ b/src/scss/drawer.scss @@ -36,9 +36,9 @@ div.control-surround { visibility: visible; } -frigate-card-icon.control { - color: var(--frigate-card-button-color); - background-color: var(--frigate-card-button-background); +advanced-camera-card-icon.control { + color: var(--advanced-camera-card-button-color); + background-color: var(--advanced-camera-card-button-background); pointer-events: all; --mdc-icon-size: #{$drawer-icon-size}; @@ -48,19 +48,19 @@ frigate-card-icon.control { transition: opacity 0.5s ease; } -:host([open]) frigate-card-icon.control, -frigate-card-icon.control:hover { +:host([open]) advanced-camera-card-icon.control, +advanced-camera-card-icon.control:hover { // When the drawer is open or hovered make the button to close it more // prominent. opacity: 1; } -:host([location='left']) frigate-card-icon.control { +:host([location='left']) advanced-camera-card-icon.control { border-top-right-radius: $drawer-icon-size; border-bottom-right-radius: $drawer-icon-size; } -:host([location='right']) frigate-card-icon.control { +:host([location='right']) advanced-camera-card-icon.control { border-top-left-radius: $drawer-icon-size; border-bottom-left-radius: $drawer-icon-size; } diff --git a/src/scss/editor.scss b/src/scss/editor.scss index def0ed45..52f68cf4 100644 --- a/src/scss/editor.scss +++ b/src/scss/editor.scss @@ -70,7 +70,7 @@ div.upgrade span { color: var(--secondary-text-color, 'black'); } -.submenu-header frigate-card-icon { +.submenu-header advanced-camera-card-icon { margin-right: 15px; } diff --git a/src/scss/gallery-core.scss b/src/scss/gallery-core.scss index f6a230ec..a6f86243 100644 --- a/src/scss/gallery-core.scss +++ b/src/scss/gallery-core.scss @@ -10,15 +10,18 @@ // Hide scrollbar: Firefox scrollbar-width: none; - --frigate-card-gallery-gap: 3px; - --frigate-card-gallery-columns: 4; + --advanced-camera-card-gallery-gap: 3px; + --advanced-camera-card-gallery-columns: 4; } .grid { display: grid; - grid-template-columns: repeat(var(--frigate-card-gallery-columns), minmax(0, 1fr)); + grid-template-columns: repeat( + var(--advanced-camera-card-gallery-columns), + minmax(0, 1fr) + ); grid-auto-rows: min-content; - gap: var(--frigate-card-gallery-gap); + gap: var(--advanced-camera-card-gallery-gap); } // Hide scrollbar for Chrome, Safari and Opera @@ -26,14 +29,14 @@ display: none; } -frigate-card-thumbnail { +advanced-camera-card-thumbnail { height: 100%; - max-height: var(--frigate-card-thumbnail-size); + max-height: var(--advanced-camera-card-thumbnail-size); } -frigate-card-thumbnail:not([details]) { +advanced-camera-card-thumbnail:not([details]) { width: 100%; } -frigate-card-thumbnail.selected { +advanced-camera-card-thumbnail.selected { border: 4px solid var(--accent-color); // Because this is box-sizing: border-box, the border is effectively // 'padding'. To get the curved borders to line up between the thumbnail and @@ -41,11 +44,12 @@ frigate-card-thumbnail.selected { // image border radius. // Related: https://www.30secondsofcode.org/articles/s/css-nested-border-radius border-radius: calc( - var(--frigate-card-css-border-radius, var(--ha-card-border-radius, 4px)) + 4px + var(--advanced-camera-card-css-border-radius, var(--ha-card-border-radius, 4px)) + + 4px ); } -frigate-card-progress-indicator.top { +advanced-camera-card-progress-indicator.top { // The top loading progress indicator should span the whole width. grid-column: 1/-1; } diff --git a/src/scss/gallery.scss b/src/scss/gallery.scss index b650673d..1628a336 100644 --- a/src/scss/gallery.scss +++ b/src/scss/gallery.scss @@ -4,7 +4,7 @@ display: block; } -frigate-card-surround-basic { +advanced-camera-card-surround-basic { // The gallery will grow indefinitely in the `unconstrained` aspect ratio // modes, the surround height needs to be limited to something reasonable in // order for the media filter to still display somewhere the user can diff --git a/src/scss/key-assigner.scss b/src/scss/key-assigner.scss index dfb85002..80ff942c 100644 --- a/src/scss/key-assigner.scss +++ b/src/scss/key-assigner.scss @@ -12,11 +12,11 @@ } :host([assigning]) ha-button.assign span, -:host([assigning]) ha-button.assign frigate-card-icon { +:host([assigning]) ha-button.assign advanced-camera-card-icon { color: var(--warning-color); } -frigate-card-icon { +advanced-camera-card-icon { padding: 10px; } diff --git a/src/scss/live-carousel.scss b/src/scss/live-carousel.scss index a6b9f29a..8589935a 100644 --- a/src/scss/live-carousel.scss +++ b/src/scss/live-carousel.scss @@ -14,7 +14,7 @@ // when the carousel is part of a media-grid. Without this next/prev controls // will enlarge on hover, and the wheel-gestures plugin may block scrolling. // See matching in viewer-carousel.scss . -:host([unselected]) frigate-card-carousel { +:host([unselected]) advanced-camera-card-carousel { pointer-events: none; } diff --git a/src/scss/live-grid.scss b/src/scss/live-grid.scss index 8e860304..ae976b24 100644 --- a/src/scss/live-grid.scss +++ b/src/scss/live-grid.scss @@ -2,19 +2,19 @@ @keyframes warning-pulse { 0% { - border: solid 2px var(--frigate-card-triggered-color-1, rgba(0, 0, 0, 0)); + border: solid 2px var(--advanced-camera-card-triggered-color-1, rgba(0, 0, 0, 0)); } 50% { - border: solid 2px var(--frigate-card-triggered-color-2, var(--warning-color)); + border: solid 2px var(--advanced-camera-card-triggered-color-2, var(--warning-color)); } 100% { - border: solid 2px var(--frigate-card-triggered-color-1, rgba(0, 0, 0, 0)); + border: solid 2px var(--advanced-camera-card-triggered-color-1, rgba(0, 0, 0, 0)); } } -frigate-card-live-carousel[triggered] { +advanced-camera-card-live-carousel[triggered] { animation: warning-pulse 5s infinite; } -frigate-card-live-carousel[selected] { - --frigate-card-triggered-color-1: var(--primary-color); +advanced-camera-card-live-carousel[selected] { + --advanced-camera-card-triggered-color-1: var(--primary-color); } diff --git a/src/scss/live-provider.scss b/src/scss/live-provider.scss index 8fe39c01..71536ce1 100644 --- a/src/scss/live-provider.scss +++ b/src/scss/live-provider.scss @@ -11,7 +11,7 @@ display: none; } -frigate-card-icon { +advanced-camera-card-icon { position: absolute; top: 10px; right: 10px; diff --git a/src/scss/loading.scss b/src/scss/loading.scss index 8db11741..56d8bedb 100644 --- a/src/scss/loading.scss +++ b/src/scss/loading.scss @@ -9,8 +9,8 @@ pointer-events: none; - background-color: var(--frigate-card-loading-background-color); - color: var(--frigate-card-loading-foreground-color); + background-color: var(--advanced-camera-card-loading-background-color); + color: var(--advanced-camera-card-loading-foreground-color); transition: opacity 1.5s ease-in; opacity: 1; @@ -20,7 +20,7 @@ opacity: 0; } -frigate-card-icon { +advanced-camera-card-icon { margin-bottom: 20px; height: 25%; width: auto; diff --git a/src/scss/media-filter.scss b/src/scss/media-filter.scss index 77d1741b..b9650f09 100644 --- a/src/scss/media-filter.scss +++ b/src/scss/media-filter.scss @@ -19,7 +19,7 @@ display: none; } -frigate-card-select { +advanced-camera-card-select { padding: 5px; } @@ -27,21 +27,21 @@ div.when { display: inline-flex; align-items: flex-end; } -div.when frigate-card-select { +div.when advanced-camera-card-select { flex: 1; } -div.when frigate-card-date-picker { +div.when advanced-camera-card-date-picker { // Visually line up the date-picker icon with the bottom of the select // dropdown. padding-bottom: 5px; transition: width 0.5s ease-in-out; } -div.when frigate-card-date-picker { +div.when advanced-camera-card-date-picker { color: var(--secondary-color); } -div.when frigate-card-date-picker.selected { +div.when advanced-camera-card-date-picker.selected { color: var(--primary-color); } -div.when frigate-card-date-picker.hidden { +div.when advanced-camera-card-date-picker.hidden { width: 0px; } diff --git a/src/scss/media-grid.scss b/src/scss/media-grid.scss index 9efa5f6c..acc48f93 100644 --- a/src/scss/media-grid.scss +++ b/src/scss/media-grid.scss @@ -3,9 +3,9 @@ width: 100%; height: 100%; - --frigate-card-grid-border-size: 3px; - --frigate-card-grid-column-size: 100%; - --frigate-card-grid-selected-width-factor: 2; + --advanced-camera-card-grid-border-size: 3px; + --advanced-camera-card-grid-column-size: 100%; + --advanced-camera-card-grid-selected-width-factor: 2; // Allow the grid to scroll if necessary (e.g. fullscreen). overflow: auto; @@ -25,22 +25,22 @@ box-sizing: border-box; border-radius: var(--ha-card-border-radius, 4px); overflow: hidden; - width: var(--frigate-card-grid-column-size); + width: var(--advanced-camera-card-grid-column-size); // Unselected items included a transparent border to act as the effective // gutter between elements, and to ensure when the item is selected it does // not change in size (even border-box sizing appears to allow size to change // when the element has a non-fixed height). - border: var(--frigate-card-grid-border-size) solid transparent; + border: var(--advanced-camera-card-grid-border-size) solid transparent; } ::slotted([selected]) { - border: var(--frigate-card-grid-border-size) solid var(--primary-color); + border: var(--advanced-camera-card-grid-border-size) solid var(--primary-color); width: min( 100%, calc( - var(--frigate-card-grid-selected-width-factor) * - var(--frigate-card-grid-column-size) + var(--advanced-camera-card-grid-selected-width-factor) * + var(--advanced-camera-card-grid-column-size) ) ); diff --git a/src/scss/media-layout.scss b/src/scss/media-layout.scss index ec93509a..45846480 100644 --- a/src/scss/media-layout.scss +++ b/src/scss/media-layout.scss @@ -1,14 +1,14 @@ @mixin media-layout { - object-fit: var(--frigate-card-media-layout-fit, contain); - object-position: var(--frigate-card-media-layout-position-x, 50%) - var(--frigate-card-media-layout-position-y, 50%); + object-fit: var(--advanced-camera-card-media-layout-fit, contain); + object-position: var(--advanced-camera-card-media-layout-position-x, 50%) + var(--advanced-camera-card-media-layout-position-y, 50%); // This is supported on limited browsers: // See: https://caniuse.com/?search=object-view-box object-view-box: inset( - var(--frigate-card-media-layout-view-box-top, 0%) - var(--frigate-card-media-layout-view-box-right, 0%) - var(--frigate-card-media-layout-view-box-bottom, 0%) - var(--frigate-card-media-layout-view-box-left, 0%) + var(--advanced-camera-card-media-layout-view-box-top, 0%) + var(--advanced-camera-card-media-layout-view-box-right, 0%) + var(--advanced-camera-card-media-layout-view-box-bottom, 0%) + var(--advanced-camera-card-media-layout-view-box-left, 0%) ); } diff --git a/src/scss/menu.scss b/src/scss/menu.scss index bf2d2f93..d33f4449 100644 --- a/src/scss/menu.scss +++ b/src/scss/menu.scss @@ -1,8 +1,8 @@ @use './button.scss'; :host { - --frigate-card-menu-button-size: 40px; - --mdc-icon-button-size: var(--frigate-card-menu-button-size); + --advanced-camera-card-menu-button-size: 40px; + --mdc-icon-button-size: var(--advanced-camera-card-menu-button-size); --mdc-icon-size: calc(var(--mdc-icon-button-size) / 2); pointer-events: auto; @@ -102,8 +102,8 @@ div.opposing { ****************************/ :host(:not([data-style='outside'])[data-position='bottom']) div { - // If the menu has more content that allows, "wrap upwards" to keep the - // Frigate button in the same place. + // If the menu has more content that allows, "wrap upwards" to keep the Iris + // button in the same place. flex-wrap: wrap-reverse; } @@ -139,15 +139,15 @@ div.opposing { // permanent menu buttons use the menu background color instead of the button // background color. :host([data-style='hidden']:not([expanded])) ha-icon-button { - background: var(--frigate-card-menu-background); + background: var(--advanced-camera-card-menu-background); } -// Icons in the menu are expected to follow Frigate card theming unless they are +// Icons in the menu are expected to follow Advanced Camera Card theming unless they are // active (in which case we want to take advantage of the whatever styling is // appropriate, e.g. light icon partially lit). :host ha-icon-button { - --state-unavailable-color: var(--frigate-card-button-color); - --state-inactive-color: var(--frigate-card-button-color); + --state-unavailable-color: var(--advanced-camera-card-button-color); + --state-inactive-color: var(--advanced-camera-card-button-color); } // Further theme related styling is dynamically applied by `menu.ts`, see diff --git a/src/scss/message.scss b/src/scss/message.scss index 8e7d5411..d191d52d 100644 --- a/src/scss/message.scss +++ b/src/scss/message.scss @@ -15,16 +15,16 @@ height: 100%; width: 100%; - background: var(--frigate-card-message-background); - color: var(--frigate-card-message-color); + background: var(--advanced-camera-card-message-background); + color: var(--advanced-camera-card-message-color); } :host([overlay]) { position: absolute; inset: 5%; - background: var(--frigate-card-message-overlay-background); - color: var(--frigate-card-message-overlay-color); + background: var(--advanced-camera-card-message-overlay-background); + color: var(--advanced-camera-card-message-overlay-color); } div.wrapper { @@ -65,16 +65,16 @@ a { } :host(:not([overlay])) a { - color: var(--frigate-card-message-color); + color: var(--advanced-camera-card-message-color); } :host([overlay]) a { - color: var(--frigate-card-message-overlay-color); + color: var(--advanced-camera-card-message-overlay-color); } .message pre { margin-top: 20px; overflow-x: auto; - border: 1px dotted var(--frigate-card-divider-color); + border: 1px dotted var(--advanced-camera-card-divider-color); padding: 1em; } diff --git a/src/scss/next-previous-control.scss b/src/scss/next-previous-control.scss index 4a24a2d1..caa643ab 100644 --- a/src/scss/next-previous-control.scss +++ b/src/scss/next-previous-control.scss @@ -1,11 +1,13 @@ @use './button.scss'; :host { - --frigate-card-next-prev-size: 48px; - --frigate-card-next-prev-size-hover: calc(var(--frigate-card-next-prev-size) * 2); - --frigate-card-left-position: 45px; - --frigate-card-right-position: 45px; - --mdc-icon-button-size: var(--frigate-card-next-prev-size); + --advanced-camera-card-next-prev-size: 48px; + --advanced-camera-card-next-prev-size-hover: calc( + var(--advanced-camera-card-next-prev-size) * 2 + ); + --advanced-camera-card-left-position: 45px; + --advanced-camera-card-right-position: 45px; + --mdc-icon-button-size: var(--advanced-camera-card-next-prev-size); --mdc-icon-size: calc(var(--mdc-icon-button-size) / 2); } @@ -15,41 +17,49 @@ overflow: hidden; } .controls.left { - left: var(--frigate-card-left-position); + left: var(--advanced-camera-card-left-position); } .controls.right { - right: var(--frigate-card-right-position); + right: var(--advanced-camera-card-right-position); } .controls.icons { - top: calc(50% - (var(--frigate-card-next-prev-size) / 2)); + top: calc(50% - (var(--advanced-camera-card-next-prev-size) / 2)); } .controls.thumbnails { border-radius: 50%; - height: var(--frigate-card-next-prev-size); - top: calc(50% - (var(--frigate-card-next-prev-size) / 2)); - box-shadow: var(--frigate-card-css-box-shadow, 0px 0px 20px 5px black); + height: var(--advanced-camera-card-next-prev-size); + top: calc(50% - (var(--advanced-camera-card-next-prev-size) / 2)); + box-shadow: var(--advanced-camera-card-css-box-shadow, 0px 0px 20px 5px black); transition: all 0.2s ease-out; opacity: 0.8; aspect-ratio: 1 / 1; } .controls.thumbnails:hover { opacity: 1 !important; - height: var(--frigate-card-next-prev-size-hover); - top: calc(50% - (var(--frigate-card-next-prev-size-hover) / 2)); + height: var(--advanced-camera-card-next-prev-size-hover); + top: calc(50% - (var(--advanced-camera-card-next-prev-size-hover) / 2)); } .controls.left.thumbnails:hover { left: calc( - var(--frigate-card-left-position) - - (var(--frigate-card-next-prev-size-hover) - var(--frigate-card-next-prev-size)) / 2 + var(--advanced-camera-card-left-position) - + ( + var(--advanced-camera-card-next-prev-size-hover) - var( + --advanced-camera-card-next-prev-size + ) + ) / 2 ); } .controls.right.thumbnails:hover { right: calc( - var(--frigate-card-right-position) - - (var(--frigate-card-next-prev-size-hover) - var(--frigate-card-next-prev-size)) / 2 + var(--advanced-camera-card-right-position) - + ( + var(--advanced-camera-card-next-prev-size-hover) - var( + --advanced-camera-card-next-prev-size + ) + ) / 2 ); } diff --git a/src/scss/ptz.scss b/src/scss/ptz.scss index c3ad73f9..4af81493 100644 --- a/src/scss/ptz.scss +++ b/src/scss/ptz.scss @@ -6,7 +6,7 @@ width: fit-content; height: fit-content; - --frigate-card-ptz-icon-size: 24px; + --advanced-camera-card-ptz-icon-size: 24px; } :host([data-position$='-left']) { @@ -47,10 +47,10 @@ } :host([data-orientation='vertical']) .ptz div { - width: calc(var(--frigate-card-ptz-icon-size) * 3); + width: calc(var(--advanced-camera-card-ptz-icon-size) * 3); } :host([data-orientation='horizontal']) .ptz div { - height: calc(var(--frigate-card-ptz-icon-size) * 3); + height: calc(var(--advanced-camera-card-ptz-icon-size) * 3); } .ptz-move, @@ -61,19 +61,19 @@ } .ptz-move { - height: calc(var(--frigate-card-ptz-icon-size) * 3); - width: calc(var(--frigate-card-ptz-icon-size) * 3); + height: calc(var(--advanced-camera-card-ptz-icon-size) * 3); + width: calc(var(--advanced-camera-card-ptz-icon-size) * 3); border-radius: 50%; } :host([data-orientation='horizontal']) .ptz .ptz-zoom, :host([data-orientation='horizontal']) .ptz .ptz-home { - width: calc(var(--frigate-card-ptz-icon-size) * 1.5); + width: calc(var(--advanced-camera-card-ptz-icon-size) * 1.5); } :host([data-orientation='vertical']) .ptz .ptz-zoom, :host([data-orientation='vertical']) .ptz .ptz-home { - height: calc(var(--frigate-card-ptz-icon-size) * 1.5); + height: calc(var(--advanced-camera-card-ptz-icon-size) * 1.5); } .ptz-zoom, @@ -84,11 +84,11 @@ /*********** * PTZ Icons ***********/ -frigate-card-icon { +advanced-camera-card-icon { position: absolute; - --mdc-icon-size: var(--frigate-card-ptz-icon-size); + --mdc-icon-size: var(--advanced-camera-card-ptz-icon-size); } -frigate-card-icon:not(.disabled) { +advanced-camera-card-icon:not(.disabled) { cursor: pointer; } .disabled { diff --git a/src/scss/status.scss b/src/scss/status.scss index 8ee52e53..962cc6e5 100644 --- a/src/scss/status.scss +++ b/src/scss/status.scss @@ -1,7 +1,7 @@ @use './button.scss'; :host { - --mdc-icon-size: calc(var(--frigate-card-status-bar-height) / 2); + --mdc-icon-size: calc(var(--advanced-camera-card-status-bar-height) / 2); display: block; width: 100%; @@ -41,7 +41,7 @@ overflow: hidden; width: 100%; - height: var(--frigate-card-status-bar-height); + height: var(--advanced-camera-card-status-bar-height); } .item { @@ -67,7 +67,7 @@ } img.item, -frigate-card-icon.item { +advanced-camera-card-icon.item { display: block; // To ensure images render somewhat reasonably looking their height is kept to diff --git a/src/scss/submenu.scss b/src/scss/submenu.scss index 36e222f1..7f824c7e 100644 --- a/src/scss/submenu.scss +++ b/src/scss/submenu.scss @@ -9,12 +9,12 @@ mwc-list-item { } ha-icon-button { - // The main submenu icon is expected to follow Frigate card theming unless it + // The main submenu icon is expected to follow Advanced Camera Card theming unless it // is are active (in which case we want to take advantage of the whatever // styling is appropriate, e.g. light icon partially lit). - --state-unavailable-color: var(--frigate-card-button-color); - --state-inactive-color: var(--frigate-card-button-color); + --state-unavailable-color: var(--advanced-camera-card-button-color); + --state-inactive-color: var(--advanced-camera-card-button-color); - color: var(--frigate-card-menu-button-inactive-color); - background-color: var(--frigate-card-menu-button-background); + color: var(--advanced-camera-card-menu-button-inactive-color); + background-color: var(--advanced-camera-card-menu-button-background); } diff --git a/src/scss/themes/base.scss b/src/scss/themes/base.scss index 1f041179..ceb084b5 100644 --- a/src/scss/themes/base.scss +++ b/src/scss/themes/base.scss @@ -7,141 +7,169 @@ * General *********/ - --frigate-card-exterior: var(--secondary-background-color, black); - --frigate-card-background: var(--primary-background-color, white); + --advanced-camera-card-exterior: var(--secondary-background-color, black); + --advanced-camera-card-background: var(--primary-background-color, white); - --frigate-card-foreground-primary: var(--primary-color, black); + --advanced-camera-card-foreground-primary: var(--primary-color, black); - --frigate-card-active-color: var(--accent-color, orange); - --frigate-card-warning-color: var(--error-color, red); + --advanced-camera-card-active-color: var(--accent-color, orange); + --advanced-camera-card-warning-color: var(--error-color, red); - --frigate-card-text-color: var(--primary-text-color); - --frigate-card-divider-color: var(--divider-color); + --advanced-camera-card-text-color: var(--primary-text-color); + --advanced-camera-card-divider-color: var(--divider-color); - --frigate-card-control-background: var(--app-header-background-color); - --frigate-card-control-background-transparent: color-mix( + --advanced-camera-card-control-background: var(--app-header-background-color); + --advanced-camera-card-control-background-transparent: color-mix( in oklab, - var(--frigate-card-control-background), + var(--advanced-camera-card-control-background), transparent 60% ); - --frigate-card-control-foreground: var(--app-header-text-color); + --advanced-camera-card-control-foreground: var(--app-header-text-color); /********* * Buttons *********/ // The color of buttons (in general, menu or otherwise). - --frigate-card-button-color: var(--frigate-card-control-foreground); + --advanced-camera-card-button-color: var(--advanced-camera-card-control-foreground); // The color of button background "circles". - --frigate-card-button-background: var(--frigate-card-control-background-transparent); + --advanced-camera-card-button-background: var( + --advanced-camera-card-control-background-transparent + ); /****** * Menu ******/ // The color of an active icon in the menu. - --frigate-card-menu-button-active-color: var(--frigate-card-active-color); + --advanced-camera-card-menu-button-active-color: var( + --advanced-camera-card-active-color + ); // The color of an inactive icon in the menu. - --frigate-card-menu-button-inactive-color: var(--frigate-card-button-color); + --advanced-camera-card-menu-button-inactive-color: var( + --advanced-camera-card-button-color + ); // The color of a critical icon in the menu. - --frigate-card-menu-button-critical-color: var( + --advanced-camera-card-menu-button-critical-color: var( --error-color, - var(--frigate-card-warning-color) + var(--advanced-camera-card-warning-color) ); // The background color of the button "circles" in the menu. - --frigate-card-menu-button-background: color-mix( + --advanced-camera-card-menu-button-background: color-mix( in oklab, - var(--frigate-card-menu-button-inactive-color), + var(--advanced-camera-card-menu-button-inactive-color), transparent 60% ); // The menu background color. - --frigate-card-menu-background: var(--frigate-card-control-background-transparent); - --frigate-card-menu-style-outside-background: var(--frigate-card-control-background); + --advanced-camera-card-menu-background: var( + --advanced-camera-card-control-background-transparent + ); + --advanced-camera-card-menu-style-outside-background: var( + --advanced-camera-card-control-background + ); // May be overridden by the user to force a particular color. - --frigate-card-menu-override-background: unset; - --frigate-card-menu-override-button-inactive-color: unset; - --frigate-card-menu-override-button-background: unset; + --advanced-camera-card-menu-override-background: unset; + --advanced-camera-card-menu-override-button-inactive-color: unset; + --advanced-camera-card-menu-override-button-background: unset; // Examples of precise theming for the menu: - // --frigate-card-menu-position-left-alignment-left-style-overlay-background: pink; - // --frigate-card-menu-position-left-background: purple; - // --frigate-card-menu-style-overlay-background: pink; + // --advanced-camera-card-menu-position-left-alignment-left-style-overlay-background: pink; + // --advanced-camera-card-menu-position-left-background: purple; + // --advanced-camera-card-menu-style-overlay-background: pink; /************ * Status bar ************/ // May be overridden by the user to force a particular color. - --frigate-card-status-bar-override-background: unset; - --frigate-card-status-bar-override-color: unset; + --advanced-camera-card-status-bar-override-background: unset; + --advanced-camera-card-status-bar-override-color: unset; // The color of items on the status bar. - --frigate-card-status-bar-color: var(--frigate-card-control-foreground); + --advanced-camera-card-status-bar-color: var( + --advanced-camera-card-control-foreground + ); // The background color of the outerlay above/below the card. - --frigate-card-status-bar-background: var( - --frigate-card-control-background-transparent + --advanced-camera-card-status-bar-background: var( + --advanced-camera-card-control-background-transparent ); - --frigate-card-status-bar-style-outside-background: var( - --frigate-card-control-background + --advanced-camera-card-status-bar-style-outside-background: var( + --advanced-camera-card-control-background ); /************ * Thumbnails ************/ - --frigate-card-thumbnail-icon-color: var(--frigate-card-foreground-primary); - --frigate-card-thumbnail-border-color: var(--frigate-card-foreground-primary); - --frigate-card-thumbnail-background: var(--frigate-card-background); - --frigate-card-thumbnail-icon-favorite-color: color-mix( + --advanced-camera-card-thumbnail-icon-color: var( + --advanced-camera-card-foreground-primary + ); + --advanced-camera-card-thumbnail-border-color: var( + --advanced-camera-card-foreground-primary + ); + --advanced-camera-card-thumbnail-background: var(--advanced-camera-card-background); + --advanced-camera-card-thumbnail-icon-favorite-color: color-mix( in oklab, gold, transparent 25% ); - --frigate-card-thumbnail-text-color: var(--frigate-card-text-color); + --advanced-camera-card-thumbnail-text-color: var(--advanced-camera-card-text-color); /********** * Timeline **********/ - --frigate-card-timeline-text-color: var(--frigate-card-text-color); - --frigate-card-timeline-item-color: var(--frigate-card-foreground-primary); - --frigate-card-timeline-tool-color: var(--frigate-card-foreground-primary); - --frigate-card-timeline-background: var(--frigate-card-background); + --advanced-camera-card-timeline-text-color: var(--advanced-camera-card-text-color); + --advanced-camera-card-timeline-item-color: var( + --advanced-camera-card-foreground-primary + ); + --advanced-camera-card-timeline-tool-color: var( + --advanced-camera-card-foreground-primary + ); + --advanced-camera-card-timeline-background: var(--advanced-camera-card-background); // style for the 'background' style of item (i.e. recordings). - --frigate-card-timeline-background-item-color: color-mix( + --advanced-camera-card-timeline-background-item-color: color-mix( in oklab, - var(--frigate-card-timeline-item-color), + var(--advanced-camera-card-timeline-item-color), transparent 90% ); - --frigate-card-timeline-divider-color: var(--frigate-card-divider-color); - --frigate-card-timeline-target-bar-color: var(--frigate-card-active-color); + --advanced-camera-card-timeline-divider-color: var( + --advanced-camera-card-divider-color + ); + --advanced-camera-card-timeline-target-bar-color: var( + --advanced-camera-card-active-color + ); /******************* * Loading Indicator *******************/ - --frigate-card-loading-background-color: var( - --frigate-card-control-background-transparent + --advanced-camera-card-loading-background-color: var( + --advanced-camera-card-control-background-transparent + ); + --advanced-camera-card-loading-foreground-color: var( + --advanced-camera-card-control-foreground ); - --frigate-card-loading-foreground-color: var(--frigate-card-control-foreground); /********* * Message *********/ - --frigate-card-message-color: var(--frigate-card-text-color); - --frigate-card-message-background: var(--frigate-card-background); + --advanced-camera-card-message-color: var(--advanced-camera-card-text-color); + --advanced-camera-card-message-background: var(--advanced-camera-card-background); - --frigate-card-message-overlay-background: var( - --frigate-card-control-background-transparent + --advanced-camera-card-message-overlay-background: var( + --advanced-camera-card-control-background-transparent + ); + --advanced-camera-card-message-overlay-color: var( + --advanced-camera-card-control-foreground ); - --frigate-card-message-overlay-color: var(--frigate-card-control-foreground); } diff --git a/src/scss/themes/traditional.scss b/src/scss/themes/traditional.scss index b3b2fe2b..6d717c47 100644 --- a/src/scss/themes/traditional.scss +++ b/src/scss/themes/traditional.scss @@ -1,10 +1,10 @@ @mixin background-gradient($var, $angle) { #{$var}: linear-gradient( $angle, - var(--frigate-card-menu-background), - var(--frigate-card-menu-background), - var(--frigate-card-menu-background), - color-mix(in oklab, var(--frigate-card-menu-background), transparent 100%) + var(--advanced-camera-card-menu-background), + var(--advanced-camera-card-menu-background), + var(--advanced-camera-card-menu-background), + color-mix(in oklab, var(--advanced-camera-card-menu-background), transparent 100%) ); } @@ -13,59 +13,59 @@ * General *********/ - --frigate-card-control-background: black; + --advanced-camera-card-control-background: black; /****** * Menu ******/ - --frigate-card-menu-button-active-color: var(--primary-color, white); - --frigate-card-menu-button-background: rgba(0, 0, 0, 0.6); + --advanced-camera-card-menu-button-active-color: var(--primary-color, white); + --advanced-camera-card-menu-button-background: rgba(0, 0, 0, 0.6); @include background-gradient( - '--frigate-card-menu-position-left-alignment-top-background', + '--advanced-camera-card-menu-position-left-alignment-top-background', 180deg ); @include background-gradient( - '--frigate-card-menu-position-left-alignment-bottom-background', + '--advanced-camera-card-menu-position-left-alignment-bottom-background', 0deg ); @include background-gradient( - '--frigate-card-menu-position-right-alignment-top-background', + '--advanced-camera-card-menu-position-right-alignment-top-background', 180deg ); @include background-gradient( - '--frigate-card-menu-position-right-alignment-bottom-background', + '--advanced-camera-card-menu-position-right-alignment-bottom-background', 0deg ); @include background-gradient( - '--frigate-card-menu-position-top-alignment-left-background', + '--advanced-camera-card-menu-position-top-alignment-left-background', 90deg ); @include background-gradient( - '--frigate-card-menu-position-top-alignment-right-background', + '--advanced-camera-card-menu-position-top-alignment-right-background', 270deg ); @include background-gradient( - '--frigate-card-menu-position-bottom-alignment-left-background', + '--advanced-camera-card-menu-position-bottom-alignment-left-background', 90deg ); @include background-gradient( - '--frigate-card-menu-position-bottom-alignment-right-background', + '--advanced-camera-card-menu-position-bottom-alignment-right-background', 270deg ); - --frigate-card-menu-position-top-alignment-left-style-outside-background: var( + --advanced-camera-card-menu-position-top-alignment-left-style-outside-background: var( --secondary-background-color ); - --frigate-card-menu-position-top-alignment-right-style-outside-background: var( + --advanced-camera-card-menu-position-top-alignment-right-style-outside-background: var( --secondary-background-color ); - --frigate-card-menu-position-bottom-alignment-left-style-outside-background: var( + --advanced-camera-card-menu-position-bottom-alignment-left-style-outside-background: var( --secondary-background-color ); - --frigate-card-menu-position-bottom-alignment-right-style-outside-background: var( + --advanced-camera-card-menu-position-bottom-alignment-right-style-outside-background: var( --secondary-background-color ); @@ -73,27 +73,27 @@ * Status bar ************/ - --frigate-card-status-bar-color: white; + --advanced-camera-card-status-bar-color: white; @include background-gradient( - '--frigate-card-status-bar-position-top-background', + '--advanced-camera-card-status-bar-position-top-background', 90deg ); @include background-gradient( - '--frigate-card-status-bar-position-bottom-background', + '--advanced-camera-card-status-bar-position-bottom-background', 90deg ); - --frigate-card-status-bar-position-top-style-outside-background: var( + --advanced-camera-card-status-bar-position-top-style-outside-background: var( --secondary-background-color ); - --frigate-card-status-bar-position-bottom-style-outside-background: var( + --advanced-camera-card-status-bar-position-bottom-style-outside-background: var( --secondary-background-color ); - --frigate-card-status-bar-position-top-style-outside-color: var( + --advanced-camera-card-status-bar-position-top-style-outside-color: var( --secondary-text-color ); - --frigate-card-status-bar-position-bottom-style-outside-color: var( + --advanced-camera-card-status-bar-position-bottom-style-outside-color: var( --secondary-text-color ); } diff --git a/src/scss/thumbnail-carousel.scss b/src/scss/thumbnail-carousel.scss index d4c77eaa..846ad205 100644 --- a/src/scss/thumbnail-carousel.scss +++ b/src/scss/thumbnail-carousel.scss @@ -5,9 +5,9 @@ width: 100%; height: 100%; - background-color: var(--frigate-card-thumbnail-background); + background-color: var(--advanced-camera-card-thumbnail-background); - --frigate-card-carousel-thumbnail-opacity: 1; + --advanced-camera-card-carousel-thumbnail-opacity: 1; } :host([direction='vertical']) { @@ -21,19 +21,19 @@ .embla__slide { flex: 0 0 auto; - opacity: var(--frigate-card-carousel-thumbnail-opacity); + opacity: var(--advanced-camera-card-carousel-thumbnail-opacity); } .embla__slide.slide-selected { opacity: 1; } -frigate-card-thumbnail { - width: var(--frigate-card-thumbnail-size); - height: var(--frigate-card-thumbnail-size); +advanced-camera-card-thumbnail { + width: var(--advanced-camera-card-thumbnail-size); + height: var(--advanced-camera-card-thumbnail-size); // Do not let thumbnails span beyond the width of the carousel. max-width: 100%; } -frigate-card-thumbnail[details] { - width: var(--frigate-card-thumbnail-details-width); +advanced-camera-card-thumbnail[details] { + width: var(--advanced-camera-card-thumbnail-details-width); } diff --git a/src/scss/thumbnail-details.scss b/src/scss/thumbnail-details.scss index fcf9a537..40dc6b04 100644 --- a/src/scss/thumbnail-details.scss +++ b/src/scss/thumbnail-details.scss @@ -5,7 +5,7 @@ width: 100%; margin-left: 5px; padding: 5px; - color: var(--frigate-card-thumbnail-text-color); + color: var(--advanced-camera-card-thumbnail-text-color); overflow: hidden; column-gap: 5%; } diff --git a/src/scss/thumbnail-feature-text.scss b/src/scss/thumbnail-feature-text.scss index e4cec7b4..df978c72 100644 --- a/src/scss/thumbnail-feature-text.scss +++ b/src/scss/thumbnail-feature-text.scss @@ -1,14 +1,14 @@ :host { display: block; - max-width: var(--frigate-card-thumbnail-size); - max-height: var(--frigate-card-thumbnail-size); + max-width: var(--advanced-camera-card-thumbnail-size); + max-height: var(--advanced-camera-card-thumbnail-size); aspect-ratio: 1 / 1; border: 1px solid var(--secondary-color); background-color: var(--secondary-background-color); border-radius: var( - --frigate-card-css-border-radius, + --advanced-camera-card-css-border-radius, var(--ha-card-border-radius, 4px) ); @@ -21,7 +21,7 @@ position: relative; } -frigate-card-icon.background { +advanced-camera-card-icon.background { display: block; width: 100%; height: 100%; diff --git a/src/scss/thumbnail-feature-thumbnail.scss b/src/scss/thumbnail-feature-thumbnail.scss index cc75a31a..ccbd1f82 100644 --- a/src/scss/thumbnail-feature-thumbnail.scss +++ b/src/scss/thumbnail-feature-thumbnail.scss @@ -14,7 +14,7 @@ img { } img, -frigate-card-icon { +advanced-camera-card-icon { // Safari will occasionally not load thumbnails correctly with display block. display: inline-block; @@ -23,11 +23,11 @@ frigate-card-icon { margin: 0; border-radius: var( - --frigate-card-css-border-radius, + --advanced-camera-card-css-border-radius, var(--ha-card-border-radius, 4px) ); - width: var(--frigate-card-thumbnail-size); + width: var(--advanced-camera-card-thumbnail-size); max-height: 100%; aspect-ratio: 1 / 1; @@ -37,7 +37,7 @@ frigate-card-icon { object-fit: cover; } -frigate-card-icon { +advanced-camera-card-icon { --mdc-icon-size: 50%; color: var(--primary-text-color); display: flex; diff --git a/src/scss/thumbnail.scss b/src/scss/thumbnail.scss index 5054e93b..9d4b175d 100644 --- a/src/scss/thumbnail.scss +++ b/src/scss/thumbnail.scss @@ -15,26 +15,26 @@ } :host([details]) { - border: 1px solid var(--frigate-card-thumbnail-border-color); + border: 1px solid var(--advanced-camera-card-thumbnail-border-color); border-radius: var( - --frigate-card-css-border-radius, + --advanced-camera-card-css-border-radius, var(--ha-card-border-radius, 4px) ); padding: 2px; // When details are enabled, use a background color so that the details have // contrast with the background. - background-color: var(--frigate-card-thumbnail-background); + background-color: var(--advanced-camera-card-thumbnail-background); } :host(:hover) { transform: scale(1.04); } -frigate-card-icon { +advanced-camera-card-icon { position: absolute; border-radius: 50%; - color: var(--frigate-card-thumbnail-icon-color); + color: var(--advanced-camera-card-thumbnail-icon-color); cursor: pointer; @@ -42,29 +42,29 @@ frigate-card-icon { opacity 0.2s ease-in-out, color 0.2s ease-in-out; } -frigate-card-icon:hover { +advanced-camera-card-icon:hover { opacity: 1; } -frigate-card-icon.star { +advanced-camera-card-icon.star { top: 3px; left: 3px; } -frigate-card-icon.star.starred { - color: var(--frigate-card-thumbnail-icon-favorite-color); +advanced-camera-card-icon.star.starred { + color: var(--advanced-camera-card-thumbnail-icon-favorite-color); } -frigate-card-icon.timeline { +advanced-camera-card-icon.timeline { top: 3px; right: 3px; } -frigate-card-icon.download { +advanced-camera-card-icon.download { right: 3px; bottom: 3px; } -frigate-card-thumbnail-details-event, -frigate-card-thumbnail-details-recording { +advanced-camera-card-thumbnail-details-event, +advanced-camera-card-thumbnail-details-recording { flex: 1; } diff --git a/src/scss/timeline-core.scss b/src/scss/timeline-core.scss index 990c7149..acc9ff27 100644 --- a/src/scss/timeline-core.scss +++ b/src/scss/timeline-core.scss @@ -11,7 +11,7 @@ // So that absolute sidedrawer is relative to this host. position: relative; - background-color: var(--frigate-card-timeline-background); + background-color: var(--advanced-camera-card-timeline-background); } :host(:not([mini])) { @@ -19,12 +19,12 @@ height: 100%; } -frigate-card-thumbnail { - height: var(--frigate-card-thumbnail-size); - width: var(--frigate-card-thumbnail-size); +advanced-camera-card-thumbnail { + height: var(--advanced-camera-card-thumbnail-size); + width: var(--advanced-camera-card-thumbnail-size); } -frigate-card-thumbnail[details] { - width: var(--frigate-card-thumbnail-details-width); +advanced-camera-card-thumbnail[details] { + width: var(--advanced-camera-card-thumbnail-details-width); } div.timeline { @@ -32,7 +32,7 @@ div.timeline { } .vis-text { - color: var(--frigate-card-timeline-text-color) !important; + color: var(--advanced-camera-card-timeline-text-color) !important; } .vis-timeline { @@ -41,7 +41,7 @@ div.timeline { .vis-labelset .vis-label { // Group labels. - color: var(--frigate-card-timeline-text-color); + color: var(--advanced-camera-card-timeline-text-color); } :host([ribbon]:not([groups])) .vis-item:not(.vis-background) { @@ -55,10 +55,10 @@ div.timeline { height: 6px; } .vis-item { - border-color: var(--frigate-card-timeline-item-color); + border-color: var(--advanced-camera-card-timeline-item-color); background: none; - color: var(--frigate-card-timeline-text-color); - background-color: var(--frigate-card-timeline-item-color); + color: var(--advanced-camera-card-timeline-text-color); + background-color: var(--advanced-camera-card-timeline-item-color); transition: background-color ease-in-out 1s, @@ -66,15 +66,15 @@ div.timeline { box-shadow ease-in-out 1s; } .vis-item.vis-selected { - border-color: var(--frigate-card-active-color); - background-color: var(--frigate-card-active-color); + border-color: var(--advanced-camera-card-active-color); + background-color: var(--advanced-camera-card-active-color); box-shadow: var( - --frigate-card-css-box-shadow, - 0px 0px 5px 1px var(--frigate-card-active-color) + --advanced-camera-card-css-box-shadow, + 0px 0px 5px 1px var(--advanced-camera-card-active-color) ); } .vis-item.vis-background { - background-color: var(--frigate-card-timeline-background-item-color); + background-color: var(--advanced-camera-card-timeline-background-item-color); } // If there are no timeline groups shown (e.g. mini mode with a single camera), @@ -106,19 +106,19 @@ div.timeline { .vis-item.vis-cluster { border-style: dotted; - color: var(--frigate-card-timeline-text-color); - background-color: var(--frigate-card-timeline-background); + color: var(--advanced-camera-card-timeline-text-color); + background-color: var(--advanced-camera-card-timeline-background); box-shadow: var( - --frigate-card-css-box-shadow, - 0px 0px 5px 1px var(--frigate-card-timeline-item-color) + --advanced-camera-card-css-box-shadow, + 0px 0px 5px 1px var(--advanced-camera-card-timeline-item-color) ); } .vis-item.vis-range { - border-radius: var(--frigate-card-css-border-radius, unset); + border-radius: var(--advanced-camera-card-css-border-radius, unset); } .vis-time-axis .vis-grid.vis-minor { - border-color: var(--frigate-card-timeline-divider-color); + border-color: var(--advanced-camera-card-timeline-divider-color); } // Give an indication that the user can interact with the axes. @@ -129,13 +129,13 @@ div.timeline { :host([recordings]) .vis-text.vis-minor:hover { background-color: color-mix( in oklab, - var(--frigate-card-timeline-text-color), + var(--advanced-camera-card-timeline-text-color), transparent 80% ); } .vis-time-axis .vis-grid.vis-major { - border-color: var(--frigate-card-timeline-divider-color); + border-color: var(--advanced-camera-card-timeline-divider-color); } .vis-label { @@ -156,7 +156,7 @@ div.vis-tooltip { } .target_bar { - background-color: var(--frigate-card-timeline-target-bar-color); + background-color: var(--advanced-camera-card-timeline-target-bar-color); width: 2px; z-index: 20; @@ -165,8 +165,8 @@ div.vis-tooltip { pointer-events: none; } .target_bar .vis-custom-time-marker { - background-color: var(--frigate-card-timeline-background); - color: var(--frigate-card-timeline-text-color); + background-color: var(--advanced-camera-card-timeline-background); + color: var(--advanced-camera-card-timeline-text-color); bottom: 0px; top: unset; } @@ -186,7 +186,7 @@ div.vis-tooltip { position: absolute; right: 0px; bottom: 0px; - color: var(--frigate-card-timeline-tool-color); + color: var(--advanced-camera-card-timeline-tool-color); z-index: 10; } .timeline-tools * { diff --git a/src/scss/viewer-carousel.scss b/src/scss/viewer-carousel.scss index 5d4a4501..e0c4a889 100644 --- a/src/scss/viewer-carousel.scss +++ b/src/scss/viewer-carousel.scss @@ -10,12 +10,12 @@ // when the carousel is part of a media-grid. Without this next/prev controls // will enlarge on hover, and the wheel-gestures plugin may block scrolling. // See matching in live-carousel.scss . -:host([unselected]) frigate-card-carousel, +:host([unselected]) advanced-camera-card-carousel, :host([unselected]) .seek-warning { pointer-events: none; } -:host([unseekable]) frigate-card-carousel { +:host([unseekable]) advanced-camera-card-carousel { filter: brightness(50%); } :host([unseekable]) .seek-warning { diff --git a/src/scss/viewer-provider.scss b/src/scss/viewer-provider.scss index a9c6ce50..53663142 100644 --- a/src/scss/viewer-provider.scss +++ b/src/scss/viewer-provider.scss @@ -9,7 +9,7 @@ img, video, -frigate-card-ha-hls-player { +advanced-camera-card-ha-hls-player { display: block; width: 100%; height: 100%; @@ -17,7 +17,7 @@ frigate-card-ha-hls-player { @include media-layout.media-layout(); } -frigate-card-progress-indicator { +advanced-camera-card-progress-indicator { padding: 30px; box-sizing: border-box; } diff --git a/src/scss/viewer.scss b/src/scss/viewer.scss index 18ec3c39..aadbb614 100644 --- a/src/scss/viewer.scss +++ b/src/scss/viewer.scss @@ -6,7 +6,7 @@ gap: 5px; } -frigate-card-viewer-carousel { +advanced-camera-card-viewer-carousel { flex: 1; min-height: 0; } diff --git a/src/types.ts b/src/types.ts index 85471470..b8b1d5d4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,7 +10,7 @@ import { z } from 'zod'; export type ClipsOrSnapshots = 'clips' | 'snapshots'; export type ClipsOrSnapshotsOrAll = 'clips' | 'snapshots' | 'all'; -export class FrigateCardError extends Error { +export class AdvancedCameraCardError extends Error { context?: unknown; constructor(message: string, context?: unknown) { @@ -39,7 +39,7 @@ export interface MediaLoadedInfo { width: number; height: number; technology?: MediaTechnology[]; - player?: FrigateCardMediaPlayer; + player?: AdvancedCameraCardMediaPlayer; capabilities?: MediaLoadedCapabilities; } @@ -63,7 +63,7 @@ export type WebkitHTMLVideoElement = HTMLVideoElement & { export type FullscreenElement = HTMLElement; -export interface FrigateCardMediaPlayer { +export interface AdvancedCameraCardMediaPlayer { play(): Promise; pause(): Promise; mute(): Promise; diff --git a/src/utils/action.ts b/src/utils/action.ts index d26af9be..7eb15108 100644 --- a/src/utils/action.ts +++ b/src/utils/action.ts @@ -1,48 +1,51 @@ -import { ActionConfig, hasAction } from '@dermotduffy/custom-card-helpers'; +import { + ActionConfig, + hasAction as customCardHasAction, +} from '@dermotduffy/custom-card-helpers'; import { ZoomSettingsBase } from '../components-lib/zoom/types.js'; import { PTZAction } from '../config/ptz.js'; import { ActionPhase, ActionType, Actions, - FrigateCardCustomAction, - FrigateCardGeneralAction, - FrigateCardUserSpecifiedView, + AdvancedCameraCardCustomAction, + AdvancedCameraCardGeneralAction, + AdvancedCameraCardUserSpecifiedView, LogActionConfig, LogActionLevel, PTZActionConfig, PTZDigitialActionConfig, PTZMultiActionConfig, - frigateCardCustomActionSchema, + advancedCameraCardCustomActionSchema, } from '../config/types.js'; import { arrayify } from './basic.js'; /** - * Convert a generic Action to a FrigateCardCustomAction if it parses correctly. + * Convert a generic Action to a AdvancedCameraCardCustomAction if it parses correctly. * @param action The generic action configuration. - * @returns A FrigateCardCustomAction or null if it cannot be converted. + * @returns A AdvancedCameraCardCustomAction or null if it cannot be converted. */ export function convertActionToCardCustomAction( action: unknown, -): FrigateCardCustomAction | null { +): AdvancedCameraCardCustomAction | null { if (!action) { return null; } // Parse a custom event as other things could generate ll-custom events that - // are not related to Frigate Card. - const parseResult = frigateCardCustomActionSchema.safeParse(action); + // are not related to Advanced Camera Card. + const parseResult = advancedCameraCardCustomActionSchema.safeParse(action); return parseResult.success ? parseResult.data : null; } export function createGeneralAction( - action: FrigateCardGeneralAction | FrigateCardUserSpecifiedView, + action: AdvancedCameraCardGeneralAction | AdvancedCameraCardUserSpecifiedView, options?: { cardID?: string; }, -): FrigateCardCustomAction { +): AdvancedCameraCardCustomAction { return { action: 'fire-dom-event', - frigate_card_action: action, + advanced_camera_card_action: action, ...(options?.cardID && { card_id: options.cardID }), }; } @@ -53,10 +56,10 @@ export function createCameraAction( options?: { cardID?: string; }, -): FrigateCardCustomAction { +): AdvancedCameraCardCustomAction { return { action: 'fire-dom-event', - frigate_card_action: action, + advanced_camera_card_action: action, camera: camera, ...(options?.cardID && { card_id: options.cardID }), }; @@ -68,10 +71,10 @@ export function createMediaPlayerAction( options?: { cardID?: string; }, -): FrigateCardCustomAction { +): AdvancedCameraCardCustomAction { return { action: 'fire-dom-event', - frigate_card_action: 'media_player', + advanced_camera_card_action: 'media_player', media_player: mediaPlayer, media_player_action: mediaPlayerAction, ...(options?.cardID && { card_id: options.cardID }), @@ -83,10 +86,10 @@ export function createDisplayModeAction( options?: { cardID?: string; }, -): FrigateCardCustomAction { +): AdvancedCameraCardCustomAction { return { action: 'fire-dom-event', - frigate_card_action: 'display_mode_select', + advanced_camera_card_action: 'display_mode_select', display_mode: displayMode, ...(options?.cardID && { card_id: options.cardID }), }; @@ -97,10 +100,10 @@ export function createPTZControlsAction( options?: { cardID?: string; }, -): FrigateCardCustomAction { +): AdvancedCameraCardCustomAction { return { action: 'fire-dom-event', - frigate_card_action: 'ptz_controls', + advanced_camera_card_action: 'ptz_controls', enabled: enabled, ...(options?.cardID && { card_id: options.cardID }), }; @@ -115,7 +118,7 @@ export function createPTZAction(options?: { }): PTZActionConfig { return { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ...(options?.cardID && { card_id: options.cardID }), ...(options?.ptzAction && { ptz_action: options.ptzAction }), ...(options?.ptzPhase && { ptz_phase: options.ptzPhase }), @@ -133,7 +136,7 @@ export function createPTZDigitalAction(options?: { }): PTZDigitialActionConfig { return { action: 'fire-dom-event', - frigate_card_action: 'ptz_digital', + advanced_camera_card_action: 'ptz_digital', ...(options?.cardID && { card_id: options.cardID }), ...(options?.ptzAction && { ptz_action: options.ptzAction }), ...(options?.ptzPhase && { ptz_phase: options.ptzPhase }), @@ -151,7 +154,7 @@ export function createPTZMultiAction(options?: { }): PTZMultiActionConfig { return { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', ...(options?.cardID && { card_id: options.cardID }), ...(options?.ptzAction && { ptz_action: options.ptzAction }), ...(options?.ptzPhase && { ptz_phase: options.ptzPhase }), @@ -169,7 +172,7 @@ export function createLogAction( ): LogActionConfig { return { action: 'fire-dom-event', - frigate_card_action: 'log', + advanced_camera_card_action: 'log', message: message, level: options?.level ?? 'info', ...(options?.cardID && { card_id: options.cardID }), @@ -209,10 +212,12 @@ export function getActionConfigGivenAction( * @param config The action config in question. * @returns `true` if there's a real action defined, `false` otherwise. */ -export const frigateCardHasAction = (config?: ActionType | ActionType[]): boolean => { +export const hasAction = (config?: ActionType | ActionType[]): boolean => { // See note above on 'ActionConfig vs ActionType' for why this cast is // necessary and harmless. - return arrayify(config).some((item) => hasAction(item as ActionConfig | undefined)); + return arrayify(config).some((item) => + customCardHasAction(item as ActionConfig | undefined), + ); }; /** diff --git a/src/utils/basic.ts b/src/utils/basic.ts index e530849b..8c692ed9 100644 --- a/src/utils/basic.ts +++ b/src/utils/basic.ts @@ -9,23 +9,23 @@ import isEqualWith from 'lodash-es/isEqualWith'; import mergeWith from 'lodash-es/mergeWith'; import round from 'lodash-es/round'; import uniq from 'lodash-es/uniq'; -import { FrigateCardError } from '../types'; +import { AdvancedCameraCardError } from '../types'; export type ModifyInterface = Omit & R; /** - * Dispatch a Frigate Card event. + * Dispatch an Advanced Camera Card event. * @param target The target from which send the event. - * @param name The name of the Frigate card event to send. + * @param name The name of the Advanced Camera Card event to send. * @param detail An optional detail object to attach. */ -export function dispatchFrigateCardEvent( +export function dispatchAdvancedCameraCardEvent( target: EventTarget, name: string, detail?: T, ): void { target.dispatchEvent( - new CustomEvent(`frigate-card:${name}`, { + new CustomEvent(`advanced-camera-card:${name}`, { bubbles: true, composed: true, detail: detail, @@ -35,7 +35,7 @@ export function dispatchFrigateCardEvent( /** * Prettify a title by converting '_' to spaces and capitalizing words. - * @param input The input Frigate (camera/label/zone) name. + * @param input The input string. * @returns A prettified name. */ export function prettifyTitle(input: string): string; @@ -108,7 +108,7 @@ export function errorToConsole( e: Error | { message: unknown } | string, func: CallableFunction = console.warn, ): void { - if (e instanceof FrigateCardError && e.context) { + if (e instanceof AdvancedCameraCardError && e.context) { func(e, e.context); } else if (typeof e === 'object' && 'message' in e) { func(e.message); diff --git a/src/utils/camera.ts b/src/utils/camera.ts index 0a561890..ab5e952b 100644 --- a/src/utils/camera.ts +++ b/src/utils/camera.ts @@ -1,4 +1,4 @@ -import { CameraConfig, RawFrigateCardConfig } from '../config/types.js'; +import { CameraConfig, RawAdvancedCameraCardConfig } from '../config/types.js'; /** * Get a camera id. @@ -6,7 +6,7 @@ import { CameraConfig, RawFrigateCardConfig } from '../config/types.js'; * @returns A camera id. */ export function getCameraID( - config?: CameraConfig | RawFrigateCardConfig | null, + config?: CameraConfig | RawAdvancedCameraCardConfig | null, ): string { return ( (typeof config?.id === 'string' && config.id) || diff --git a/src/utils/diagnostics.ts b/src/utils/diagnostics.ts index 7c0f073d..3115fe6f 100644 --- a/src/utils/diagnostics.ts +++ b/src/utils/diagnostics.ts @@ -1,6 +1,6 @@ import { HomeAssistant } from '@dermotduffy/custom-card-helpers'; import pkg from '../../package.json'; -import { RawFrigateCardConfig } from '../config/types'; +import { RawAdvancedCameraCardConfig } from '../config/types'; import { getLanguage } from '../localize/localize'; import { getIntegrationManifest } from './ha/integration'; import { IntegrationManifest } from './ha/integration/types'; @@ -21,7 +21,7 @@ interface IntegrationDiagnostics { } export const getReleaseVersion = (): string => { - const releaseVersion: string = '__FRIGATE_CARD_RELEASE_VERSION__'; + const releaseVersion: string = '__ADVANCED_CAMERA_CARD_RELEASE_VERSION__'; /* istanbul ignore if: depends on rollup substitution -- @preserve */ if (releaseVersion === 'pkg') { @@ -45,7 +45,7 @@ interface Diagnostics { git: GitDiagnostics; ha_version?: string; - config?: RawFrigateCardConfig; + config?: RawAdvancedCameraCardConfig; custom_integrations: { frigate: IntegrationDiagnostics & { @@ -78,7 +78,7 @@ const getIntegrationDiagnostics = async ( export const getDiagnostics = async ( hass?: HomeAssistant, deviceRegistryManager?: DeviceRegistryManager, - rawConfig?: RawFrigateCardConfig, + rawConfig?: RawAdvancedCameraCardConfig, ): Promise => { // Get the Frigate devices in order to extract the Frigate integration and // server version numbers. diff --git a/src/utils/download.ts b/src/utils/download.ts index 5e9b77d2..f42a3b59 100644 --- a/src/utils/download.ts +++ b/src/utils/download.ts @@ -1,6 +1,6 @@ import { CameraManager } from '../camera-manager/manager'; import { localize } from '../localize/localize'; -import { ExtendedHomeAssistant, FrigateCardError } from '../types'; +import { AdvancedCameraCardError, ExtendedHomeAssistant } from '../types'; import { ViewMedia } from '../view/media'; import { errorToConsole } from './basic'; import { homeAssistantSignPath } from './ha'; @@ -32,7 +32,7 @@ export const downloadMedia = async ( ): Promise => { const download = await cameraManager.getMediaDownloadPath(media); if (!download) { - throw new FrigateCardError(localize('error.download_no_media')); + throw new AdvancedCameraCardError(localize('error.download_no_media')); } let finalURL = download.endpoint; @@ -45,7 +45,7 @@ export const downloadMedia = async ( } if (!response) { - throw new FrigateCardError(localize('error.download_sign_failed')); + throw new AdvancedCameraCardError(localize('error.download_sign_failed')); } finalURL = response; } diff --git a/src/utils/embla/carousel-controller.ts b/src/utils/embla/carousel-controller.ts index 6153300f..db98ce0c 100644 --- a/src/utils/embla/carousel-controller.ts +++ b/src/utils/embla/carousel-controller.ts @@ -3,7 +3,7 @@ import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures'; import { CreatePluginType, LoosePluginType } from 'embla-carousel/components/Plugins'; import isEqual from 'lodash-es/isEqual'; import { TransitionEffect } from '../../config/types'; -import { dispatchFrigateCardEvent, getChildrenFromElement } from '../basic.js'; +import { dispatchAdvancedCameraCardEvent, getChildrenFromElement } from '../basic.js'; import { TextDirection } from '../text-direction'; export interface CarouselSelected { @@ -99,10 +99,14 @@ export class CarouselController { // this is used. const newSlide = this.getSlide(index); if (newSlide) { - dispatchFrigateCardEvent(this._parent, 'carousel:force-select', { - index: index, - element: newSlide, - }); + dispatchAdvancedCameraCardEvent( + this._parent, + 'carousel:force-select', + { + index: index, + element: newSlide, + }, + ); } } @@ -168,7 +172,7 @@ export class CarouselController { const selectSlide = (): void => { const carouselSelected = getCarouselSelectedObject(); if (carouselSelected) { - dispatchFrigateCardEvent( + dispatchAdvancedCameraCardEvent( this._parent, 'carousel:select', carouselSelected, diff --git a/src/utils/embla/plugins/auto-media-loaded-info/auto-media-loaded-info.ts b/src/utils/embla/plugins/auto-media-loaded-info/auto-media-loaded-info.ts index d260b368..0d30feee 100644 --- a/src/utils/embla/plugins/auto-media-loaded-info/auto-media-loaded-info.ts +++ b/src/utils/embla/plugins/auto-media-loaded-info/auto-media-loaded-info.ts @@ -3,7 +3,7 @@ import { LooseOptionsType } from 'embla-carousel/components/Options'; import { CreatePluginType, LoosePluginType } from 'embla-carousel/components/Plugins'; import { MediaLoadedInfo } from '../../../../types'; import { - FrigateCardMediaLoadedEventTarget, + AdvancedCameraCardMediaLoadedEventTarget, dispatchExistingMediaLoadedInfoAsEvent, } from '../../../media-info'; @@ -41,7 +41,7 @@ type AutoMediaLoadedInfoType = CreatePluginType): void { diff --git a/src/utils/embla/plugins/auto-size/auto-size.ts b/src/utils/embla/plugins/auto-size/auto-size.ts index 5b139837..2d325f3b 100644 --- a/src/utils/embla/plugins/auto-size/auto-size.ts +++ b/src/utils/embla/plugins/auto-size/auto-size.ts @@ -61,7 +61,10 @@ function AutoSize(): AutoSizeType { // the size to large than the maxHeight is set). emblaApi .containerNode() - .addEventListener('frigate-card:media:loaded', debouncedSetContainerHeight); + .addEventListener( + 'advanced-camera-card:media:loaded', + debouncedSetContainerHeight, + ); emblaApi.on('settle', debouncedSetContainerHeight); } @@ -72,7 +75,10 @@ function AutoSize(): AutoSizeType { emblaApi .containerNode() - .removeEventListener('frigate-card:media:loaded', debouncedSetContainerHeight); + .removeEventListener( + 'advanced-camera-card:media:loaded', + debouncedSetContainerHeight, + ); emblaApi.off('settle', debouncedSetContainerHeight); } @@ -85,7 +91,7 @@ function AutoSize(): AutoSizeType { * occur for some users in some circumstances causing the carousel to * appear 'stuck'. * - Example bug when this reinitialization is not performed: - * https://github.com/dermotduffy/frigate-hass-card/issues/651 + * https://github.com/dermotduffy/advanced-camera-card/issues/651 */ const isContainerIntersectingNow = entries.some((entry) => entry.isIntersecting); diff --git a/src/utils/ha/registry/entity/types.ts b/src/utils/ha/registry/entity/types.ts index 702b6b04..1ab16af5 100644 --- a/src/utils/ha/registry/entity/types.ts +++ b/src/utils/ha/registry/entity/types.ts @@ -10,7 +10,7 @@ export const entitySchema = z.object({ translation_key: z.string().nullable(), // Technically the unique_id should be a string, but we want to tolerate // numeric unique_ids also in case they are used. See: - // https://github.com/dermotduffy/frigate-hass-card/issues/1016 + // https://github.com/dermotduffy/advanced-camera-card/issues/1016 unique_id: z.string().or(z.number()).optional(), }); export type Entity = z.infer; diff --git a/src/utils/ha/ws-request.ts b/src/utils/ha/ws-request.ts index 1a4bb569..c65ce724 100644 --- a/src/utils/ha/ws-request.ts +++ b/src/utils/ha/ws-request.ts @@ -2,7 +2,7 @@ import { HomeAssistant } from '@dermotduffy/custom-card-helpers'; import { MessageBase } from 'home-assistant-js-websocket'; import { ZodSchema } from 'zod'; import { localize } from '../../localize/localize'; -import { FrigateCardError } from '../../types'; +import { AdvancedCameraCardError } from '../../types'; import { getParseErrorKeys } from '../zod'; /** @@ -24,7 +24,7 @@ export async function homeAssistantWSRequest( response = await hass.callWS(request); } catch (e) { if (!(e instanceof Error)) { - throw new FrigateCardError(localize('error.failed_response'), { + throw new AdvancedCameraCardError(localize('error.failed_response'), { request: request, response: e, }); @@ -33,17 +33,17 @@ export async function homeAssistantWSRequest( } if (!response) { - throw new FrigateCardError(localize('error.empty_response'), { + throw new AdvancedCameraCardError(localize('error.empty_response'), { request: request, }); } - // Some endpoints on the integration pass through JSON directly from Frigate - // These end up wrapped in a string and must be unwrapped first + // Some endpoints in Home Assistant pass JSON directly though, these end up + // wrapped in a string and must be unwrapped first. const parseResult = passthrough ? schema.safeParse(JSON.parse(response)) : schema.safeParse(response); if (!parseResult.success) { - throw new FrigateCardError(localize('error.invalid_response'), { + throw new AdvancedCameraCardError(localize('error.invalid_response'), { request: request, response: response, invalid_keys: getParseErrorKeys(parseResult.error), diff --git a/src/utils/media-info.ts b/src/utils/media-info.ts index 41af8cfa..cf8f54bc 100644 --- a/src/utils/media-info.ts +++ b/src/utils/media-info.ts @@ -1,10 +1,10 @@ import { - FrigateCardMediaPlayer, + AdvancedCameraCardMediaPlayer, MediaLoadedCapabilities, MediaLoadedInfo, MediaTechnology, } from '../types.js'; -import { dispatchFrigateCardEvent } from './basic.js'; +import { dispatchAdvancedCameraCardEvent } from './basic.js'; const MEDIA_INFO_HEIGHT_CUTOFF = 50; const MEDIA_INFO_WIDTH_CUTOFF = MEDIA_INFO_HEIGHT_CUTOFF; @@ -17,7 +17,7 @@ const MEDIA_INFO_WIDTH_CUTOFF = MEDIA_INFO_HEIGHT_CUTOFF; export function createMediaLoadedInfo( source: Event | HTMLElement, options?: { - player?: FrigateCardMediaPlayer; + player?: AdvancedCameraCardMediaPlayer; capabilities?: MediaLoadedCapabilities; technology?: MediaTechnology[]; }, @@ -53,7 +53,7 @@ export function createMediaLoadedInfo( } /** - * Dispatch a Frigate card media loaded event. + * Dispatch an Advanced Camera Card media loaded event. * @param element The element to send the event. * @param source An event or HTMLElement that should be used as a source. */ @@ -61,7 +61,7 @@ export function dispatchMediaLoadedEvent( target: HTMLElement, source: Event | HTMLElement, options?: { - player?: FrigateCardMediaPlayer; + player?: AdvancedCameraCardMediaPlayer; capabilities?: MediaLoadedCapabilities; technology?: MediaTechnology[]; }, @@ -81,7 +81,11 @@ export function dispatchExistingMediaLoadedInfoAsEvent( target: EventTarget, MediaLoadedInfo: MediaLoadedInfo, ): void { - dispatchFrigateCardEvent(target, 'media:loaded', MediaLoadedInfo); + dispatchAdvancedCameraCardEvent( + target, + 'media:loaded', + MediaLoadedInfo, + ); } /** @@ -89,19 +93,19 @@ export function dispatchExistingMediaLoadedInfoAsEvent( * @param element The element to send the event. */ export function dispatchMediaUnloadedEvent(element: HTMLElement): void { - dispatchFrigateCardEvent(element, 'media:unloaded'); + dispatchAdvancedCameraCardEvent(element, 'media:unloaded'); } export function dispatchMediaVolumeChangeEvent(target: HTMLElement): void { - dispatchFrigateCardEvent(target, 'media:volumechange'); + dispatchAdvancedCameraCardEvent(target, 'media:volumechange'); } export function dispatchMediaPlayEvent(target: HTMLElement): void { - dispatchFrigateCardEvent(target, 'media:play'); + dispatchAdvancedCameraCardEvent(target, 'media:play'); } export function dispatchMediaPauseEvent(target: HTMLElement): void { - dispatchFrigateCardEvent(target, 'media:pause'); + dispatchAdvancedCameraCardEvent(target, 'media:pause'); } /** @@ -116,18 +120,18 @@ export function isValidMediaLoadedInfo(info: MediaLoadedInfo): boolean { } // Facilitates correct typing of event handlers. -export interface FrigateCardMediaLoadedEventTarget extends EventTarget { +export interface AdvancedCameraCardMediaLoadedEventTarget extends EventTarget { addEventListener( - event: 'frigate-card:media:loaded', + event: 'advanced-camera-card:media:loaded', listener: ( - this: FrigateCardMediaLoadedEventTarget, + this: AdvancedCameraCardMediaLoadedEventTarget, ev: CustomEvent, ) => void, options?: AddEventListenerOptions | boolean, ): void; addEventListener( - event: 'frigate-card:media:unloaded', - listener: (this: FrigateCardMediaLoadedEventTarget, ev: CustomEvent) => void, + event: 'advanced-camera-card:media:unloaded', + listener: (this: AdvancedCameraCardMediaLoadedEventTarget, ev: CustomEvent) => void, options?: AddEventListenerOptions | boolean, ): void; addEventListener( @@ -136,16 +140,16 @@ export interface FrigateCardMediaLoadedEventTarget extends EventTarget { options?: AddEventListenerOptions | boolean, ): void; removeEventListener( - event: 'frigate-card:media:loaded', + event: 'advanced-camera-card:media:loaded', listener: ( - this: FrigateCardMediaLoadedEventTarget, + this: AdvancedCameraCardMediaLoadedEventTarget, ev: CustomEvent, ) => void, options?: boolean | EventListenerOptions, ): void; removeEventListener( - event: 'frigate-card:media:unloaded', - listener: (this: FrigateCardMediaLoadedEventTarget, ev: CustomEvent) => void, + event: 'advanced-camera-card:media:unloaded', + listener: (this: AdvancedCameraCardMediaLoadedEventTarget, ev: CustomEvent) => void, options?: boolean | EventListenerOptions, ): void; removeEventListener( diff --git a/src/utils/media-layout.ts b/src/utils/media-layout.ts index 6128e10c..e7c52a0b 100644 --- a/src/utils/media-layout.ts +++ b/src/utils/media-layout.ts @@ -10,28 +10,35 @@ export const updateElementStyleFromMediaLayoutConfig = ( mediaLayoutConfig?: MediaLayoutConfig, ): void => { if (mediaLayoutConfig?.fit !== undefined) { - element.style.setProperty('--frigate-card-media-layout-fit', mediaLayoutConfig.fit); + element.style.setProperty( + '--advanced-camera-card-media-layout-fit', + mediaLayoutConfig.fit, + ); } else { - element.style.removeProperty('--frigate-card-media-layout-fit'); + element.style.removeProperty('--advanced-camera-card-media-layout-fit'); } for (const dimension of ['x', 'y']) { if (mediaLayoutConfig?.position?.[dimension] !== undefined) { element.style.setProperty( - `--frigate-card-media-layout-position-${dimension}`, + `--advanced-camera-card-media-layout-position-${dimension}`, `${mediaLayoutConfig.position[dimension]}%`, ); } else { - element.style.removeProperty(`--frigate-card-media-layout-position-${dimension}`); + element.style.removeProperty( + `--advanced-camera-card-media-layout-position-${dimension}`, + ); } } for (const dimension of ['top', 'bottom', 'left', 'right']) { if (mediaLayoutConfig?.view_box?.[dimension] !== undefined) { element.style.setProperty( - `--frigate-card-media-layout-view-box-${dimension}`, + `--advanced-camera-card-media-layout-view-box-${dimension}`, `${mediaLayoutConfig.view_box[dimension]}%`, ); } else { - element.style.removeProperty(`--frigate-card-media-layout-view-box-${dimension}`); + element.style.removeProperty( + `--advanced-camera-card-media-layout-view-box-${dimension}`, + ); } } }; diff --git a/src/utils/media.ts b/src/utils/media.ts index f63f07a9..252f7f6a 100644 --- a/src/utils/media.ts +++ b/src/utils/media.ts @@ -1,13 +1,13 @@ -import { FrigateCardMediaPlayer } from '../types'; +import { AdvancedCameraCardMediaPlayer } from '../types'; import { Timer } from './timer'; // The number of seconds to hide the video controls for after loading (in order // to give a cleaner UI appearance, see: -// https://github.com/dermotduffy/frigate-hass-card/issues/856 +// https://github.com/dermotduffy/advanced-camera-card/issues/856 export const MEDIA_LOAD_CONTROLS_HIDE_SECONDS = 2; const MEDIA_SEEK_CONTROLS_HIDE_SECONDS = 1; -export type FrigateCardHTMLVideoElement = HTMLVideoElement & { +export type AdvancedCameraCardHTMLVideoElement = HTMLVideoElement & { _controlsHideTimer?: Timer; _controlsOriginalValue?: boolean; }; @@ -19,7 +19,7 @@ export type FrigateCardHTMLVideoElement = HTMLVideoElement & { * @param value */ export const setControlsOnVideo = ( - video: FrigateCardHTMLVideoElement, + video: AdvancedCameraCardHTMLVideoElement, value: boolean, ): void => { if (video._controlsHideTimer) { @@ -33,11 +33,11 @@ export const setControlsOnVideo = ( /** * Temporarily hide media controls. * @param element Any HTMLElement that has a controls property (e.g. - * HTMLVideoElement, FrigateCardHaHlsPlayer) + * HTMLVideoElement, AdvancedCameraCardHaHlsPlayer) * @param seconds The number of seconds to hide the controls for. */ export const hideMediaControlsTemporarily = ( - video: FrigateCardHTMLVideoElement, + video: AdvancedCameraCardHTMLVideoElement, seconds = MEDIA_SEEK_CONTROLS_HIDE_SECONDS, ): void => { const oldValue = video._controlsOriginalValue ?? video.controls; @@ -48,7 +48,7 @@ export const hideMediaControlsTemporarily = ( // LitElement may change the src attribute of the video element during // rendering, so we need to ensure that the controls are reset on the 'old' // video. See: - // https://github.com/dermotduffy/frigate-hass-card/issues/1310 + // https://github.com/dermotduffy/advanced-camera-card/issues/1310 const resetIfReloaded = () => { setControlsOnVideo(video, oldValue); video.removeEventListener('loadstart', resetIfReloaded); @@ -61,12 +61,12 @@ export const hideMediaControlsTemporarily = ( }; /** - * @param player The Frigate Card Media Player object. + * @param player The Advanced Camera Card Media Player object. * @param video An underlying video or media player upon which to call play. */ export const playMediaMutingIfNecessary = async ( - player: FrigateCardMediaPlayer, - video?: HTMLVideoElement | FrigateCardMediaPlayer, + player: AdvancedCameraCardMediaPlayer, + video?: HTMLVideoElement | AdvancedCameraCardMediaPlayer, ): Promise => { // If the play call fails, and the media is not already muted, mute it first // and then try again. This works around some browsers that prevent diff --git a/src/utils/scroll.ts b/src/utils/scroll.ts index 08c2975b..6c6623bc 100644 --- a/src/utils/scroll.ts +++ b/src/utils/scroll.ts @@ -3,7 +3,7 @@ import { compute as computeScroll, Options } from 'compute-scroll-into-view'; // Alternative to the stock element.scrollIntoView that suppports limiting // scrolling to a boundary, rather than the entire browser root. // -// See: https://github.com/dermotduffy/frigate-hass-card/issues/1814 +// See: https://github.com/dermotduffy/advanced-camera-card/issues/1814 // See: https://github.com/w3c/csswg-drafts/issues/9452 export const scrollIntoView = (element: HTMLElement, options: Options) => { computeScroll(element, options).forEach(({ el, top, left }) => { diff --git a/src/view/view-to-cameras.ts b/src/view/view-to-cameras.ts index 2efbf40b..6d65c471 100644 --- a/src/view/view-to-cameras.ts +++ b/src/view/view-to-cameras.ts @@ -1,6 +1,6 @@ import { CameraManager } from '../camera-manager/manager'; import { CapabilitySearchOptions } from '../camera-manager/types'; -import { FrigateCardView } from '../config/types'; +import { AdvancedCameraCardView } from '../config/types'; /** * Get cameraIDs that are relevant for a given view name based on camera @@ -8,7 +8,7 @@ import { FrigateCardView } from '../config/types'; */ export const getCameraIDsForViewName = ( cameraManager: CameraManager, - viewName: FrigateCardView, + viewName: AdvancedCameraCardView, cameraID?: string, ): Set => { const capabilityMatchAnyMedia: CapabilitySearchOptions = { diff --git a/src/view/view.ts b/src/view/view.ts index 36c12024..ef68fb60 100644 --- a/src/view/view.ts +++ b/src/view/view.ts @@ -1,6 +1,6 @@ import merge from 'lodash-es/merge'; import { ViewContext } from 'view'; -import { FrigateCardView, ViewDisplayMode } from '../config/types.js'; +import { AdvancedCameraCardView, ViewDisplayMode } from '../config/types.js'; import { MediaQueries } from './media-queries'; import { MediaQueriesResults } from './media-queries-results'; @@ -13,7 +13,7 @@ declare module 'view' { } interface ViewEvolveParameters { - view?: FrigateCardView; + view?: AdvancedCameraCardView; camera?: string; query?: MediaQueries | null; queryResults?: MediaQueriesResults | null; @@ -22,7 +22,7 @@ interface ViewEvolveParameters { } export interface ViewParameters extends ViewEvolveParameters { - view: FrigateCardView; + view: AdvancedCameraCardView; camera: string; } @@ -34,7 +34,7 @@ export const mergeViewContext = ( }; export class View { - public view: FrigateCardView; + public view: AdvancedCameraCardView; public camera: string; public query: MediaQueries | null; public queryResults: MediaQueriesResults | null; @@ -117,7 +117,7 @@ export class View { /** * Determine if current view matches a named view. */ - public is(view: FrigateCardView): boolean { + public is(view: AdvancedCameraCardView): boolean { return this.view == view; } diff --git a/tests/camera-manager/frigate/engine-frigate.test.ts b/tests/camera-manager/frigate/engine-frigate.test.ts index 93d60a75..190886c3 100644 --- a/tests/camera-manager/frigate/engine-frigate.test.ts +++ b/tests/camera-manager/frigate/engine-frigate.test.ts @@ -6,17 +6,24 @@ import { FrigateRecordingViewMedia, } from '../../../src/camera-manager/frigate/media'; import { FrigateEvent, eventSchema } from '../../../src/camera-manager/frigate/types.js'; +import { StateWatcher } from '../../../src/card-controller/hass/state-watcher'; import { PTZAction } from '../../../src/config/ptz'; import { + AdvancedCameraCardView, CameraConfig, - FrigateCardView, - RawFrigateCardConfig, + RawAdvancedCameraCardConfig, } from '../../../src/config/types'; +import { + EntityRegistryManager, + createEntityRegistryCache, +} from '../../../src/utils/ha/registry/entity'; import { ViewMedia } from '../../../src/view/media'; import { TestViewMedia, createCameraConfig, createHASS } from '../../test-utils'; const createEngine = (): FrigateCameraManagerEngine => { return new FrigateCameraManagerEngine( + new EntityRegistryManager(createEntityRegistryCache()), + new StateWatcher(), new RecordingSegmentsCache(), new RequestCache(), ); @@ -75,7 +82,9 @@ const createSnapshotMedia = (): FrigateEventViewMedia => { ); }; -const createFrigateCameraConfig = (config?: RawFrigateCardConfig): CameraConfig => { +const createFrigateCameraConfig = ( + config?: RawAdvancedCameraCardConfig, +): CameraConfig => { return createCameraConfig({ frigate: { camera_name: 'camera-1', @@ -345,7 +354,7 @@ describe('getCameraEndpoints', () => { ['clips' as const], ['snapshot' as const], ['snapshots' as const], - ])('%s', (viewName: FrigateCardView) => { + ])('%s', (viewName: AdvancedCameraCardView) => { const endpoints = createEngine().getCameraEndpoints( createCameraConfig({ frigate: { @@ -369,7 +378,7 @@ describe('getCameraEndpoints', () => { it.each([['recording' as const], ['recordings' as const]])( '%s', - (viewName: FrigateCardView) => { + (viewName: AdvancedCameraCardView) => { const endpoints = createEngine().getCameraEndpoints( createCameraConfig({ frigate: { diff --git a/tests/camera-manager/generic/engine-generic.test.ts b/tests/camera-manager/generic/engine-generic.test.ts index 0c57fc86..212d4594 100644 --- a/tests/camera-manager/generic/engine-generic.test.ts +++ b/tests/camera-manager/generic/engine-generic.test.ts @@ -3,7 +3,7 @@ import { mock } from 'vitest-mock-extended'; import { GenericCameraManagerEngine } from '../../../src/camera-manager/generic/engine-generic'; import { Engine, QueryResultsType, QueryType } from '../../../src/camera-manager/types'; import { StateWatcherSubscriptionInterface } from '../../../src/card-controller/hass/state-watcher'; -import { CameraConfig, RawFrigateCardConfig } from '../../../src/config/types'; +import { CameraConfig, RawAdvancedCameraCardConfig } from '../../../src/config/types'; import { TestViewMedia, createCameraConfig, @@ -16,7 +16,9 @@ const createEngine = (): GenericCameraManagerEngine => { return new GenericCameraManagerEngine(mock()); }; -const createGenericCameraConfig = (config?: RawFrigateCardConfig): CameraConfig => { +const createGenericCameraConfig = ( + config?: RawAdvancedCameraCardConfig, +): CameraConfig => { return createCameraConfig(config); }; diff --git a/tests/camera-manager/manager.test.ts b/tests/camera-manager/manager.test.ts index 43909bff..fb80c74e 100644 --- a/tests/camera-manager/manager.test.ts +++ b/tests/camera-manager/manager.test.ts @@ -331,7 +331,7 @@ describe('CameraManager', async () => { expect(await manager.initializeCamerasFromConfig()).toBeFalsy(); expect(api.getMessageManager().setErrorIfHigherPriority).toBeCalledWith( new Error( - 'Duplicate Frigate camera id for the following camera, ' + + 'Duplicate camera id for the following camera, ' + "use the 'id' parameter to uniquely identify cameras", ), 'Camera initialization failed', @@ -997,7 +997,9 @@ describe('CameraManager', async () => { const result: CameraManagerCameraMetadata = { title: 'My Camera', - icon: 'mdi:camera', + icon: { + icon: 'mdi:camera', + }, }; vi.mocked(engine.getCameraMetadata).mockReturnValue(result); diff --git a/tests/card-controller/actions/actions-manager.test.ts b/tests/card-controller/actions/actions-manager.test.ts index 32bd5c82..ba8f127f 100644 --- a/tests/card-controller/actions/actions-manager.test.ts +++ b/tests/card-controller/actions/actions-manager.test.ts @@ -4,7 +4,7 @@ import { Interaction, InteractionName, } from '../../../src/card-controller/actions/actions-manager'; -import { FrigateCardView } from '../../../src/config/types'; +import { AdvancedCameraCardView } from '../../../src/config/types'; import { createLogAction } from '../../../src/utils/action'; import { createAction, @@ -114,7 +114,7 @@ describe('ActionsManager', () => { }, ], ['timeline' as const, {}], - ])('%s', (viewName: FrigateCardView, result: Record) => { + ])('%s', (viewName: AdvancedCameraCardView, result: Record) => { const api = createCardAPI(); vi.mocked(api.getViewManager().getView).mockReturnValue( createView({ view: viewName }), @@ -231,7 +231,7 @@ describe('ActionsManager', () => { const consoleSpy = vi.spyOn(global.console, 'info').mockReturnValue(undefined); await manager.handleActionExecutionRequestEvent( - new CustomEvent('frigate-card:action:execution-request', { + new CustomEvent('advanced-camera-card:action:execution-request', { detail: { action: createLogAction('Hello, world!') }, }), ); @@ -266,7 +266,7 @@ describe('ActionsManager', () => { const promise = manager.executeActions([ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion createAction({ - frigate_card_action: 'sleep', + advanced_camera_card_action: 'sleep', duration: { m: 1, }, diff --git a/tests/card-controller/actions/actions/camera-select.test.ts b/tests/card-controller/actions/actions/camera-select.test.ts index 57d30a19..d7c5acd2 100644 --- a/tests/card-controller/actions/actions/camera-select.test.ts +++ b/tests/card-controller/actions/actions/camera-select.test.ts @@ -12,7 +12,7 @@ describe('should handle camera_select action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'camera_select', + advanced_camera_card_action: 'camera_select', camera: 'camera', }, ); @@ -44,7 +44,7 @@ describe('should handle camera_select action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'camera_select', + advanced_camera_card_action: 'camera_select', camera: 'camera', }, ); @@ -83,7 +83,7 @@ describe('should handle camera_select action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'camera_select', + advanced_camera_card_action: 'camera_select', camera: 'camera', }, ); @@ -113,7 +113,7 @@ describe('should handle camera_select action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'camera_select', + advanced_camera_card_action: 'camera_select', triggered: true, }, ); @@ -143,7 +143,7 @@ describe('should handle camera_select action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'camera_select', + advanced_camera_card_action: 'camera_select', }, ); @@ -159,7 +159,7 @@ describe('should handle camera_select action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'camera_select', + advanced_camera_card_action: 'camera_select', camera: 'camera', }, ); diff --git a/tests/card-controller/actions/actions/camera-ui.test.ts b/tests/card-controller/actions/actions/camera-ui.test.ts index 09703fb1..287e3958 100644 --- a/tests/card-controller/actions/actions/camera-ui.test.ts +++ b/tests/card-controller/actions/actions/camera-ui.test.ts @@ -8,7 +8,7 @@ it('should handle camera_ui action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'camera_ui', + advanced_camera_card_action: 'camera_ui', }, ); diff --git a/tests/card-controller/actions/actions/default.test.ts b/tests/card-controller/actions/actions/default.test.ts index 2c62c811..0397d9cf 100644 --- a/tests/card-controller/actions/actions/default.test.ts +++ b/tests/card-controller/actions/actions/default.test.ts @@ -8,7 +8,7 @@ it('should handle default action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'default', + advanced_camera_card_action: 'default', }, ); diff --git a/tests/card-controller/actions/actions/display-mode-select.test.ts b/tests/card-controller/actions/actions/display-mode-select.test.ts index 7c7900e5..2056117f 100644 --- a/tests/card-controller/actions/actions/display-mode-select.test.ts +++ b/tests/card-controller/actions/actions/display-mode-select.test.ts @@ -8,7 +8,7 @@ it('should handle default action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'display_mode_select', + advanced_camera_card_action: 'display_mode_select', display_mode: 'grid', }, ); diff --git a/tests/card-controller/actions/actions/download.test.ts b/tests/card-controller/actions/actions/download.test.ts index 3e9b321d..9552d96d 100644 --- a/tests/card-controller/actions/actions/download.test.ts +++ b/tests/card-controller/actions/actions/download.test.ts @@ -8,7 +8,7 @@ it('should handle download action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'download', + advanced_camera_card_action: 'download', }, ); diff --git a/tests/card-controller/actions/actions/expand.test.ts b/tests/card-controller/actions/actions/expand.test.ts index 1a3ac265..7f4a602d 100644 --- a/tests/card-controller/actions/actions/expand.test.ts +++ b/tests/card-controller/actions/actions/expand.test.ts @@ -8,7 +8,7 @@ it('should handle expand action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'expand', + advanced_camera_card_action: 'expand', }, ); diff --git a/tests/card-controller/actions/actions/fullscreen.test.ts b/tests/card-controller/actions/actions/fullscreen.test.ts index d678b1a3..5b57e8f3 100644 --- a/tests/card-controller/actions/actions/fullscreen.test.ts +++ b/tests/card-controller/actions/actions/fullscreen.test.ts @@ -8,7 +8,7 @@ it('should handle fullscreen action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'fullscreen', + advanced_camera_card_action: 'fullscreen', }, ); diff --git a/tests/card-controller/actions/actions/log.test.ts b/tests/card-controller/actions/actions/log.test.ts index 889f32c8..32d68748 100644 --- a/tests/card-controller/actions/actions/log.test.ts +++ b/tests/card-controller/actions/actions/log.test.ts @@ -12,7 +12,7 @@ it('should handle log action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'log', + advanced_camera_card_action: 'log', message: 'Hello, world!', level: 'warn', }, diff --git a/tests/card-controller/actions/actions/media-player.test.ts b/tests/card-controller/actions/actions/media-player.test.ts index 49a539e9..ca0cd3d8 100644 --- a/tests/card-controller/actions/actions/media-player.test.ts +++ b/tests/card-controller/actions/actions/media-player.test.ts @@ -14,7 +14,7 @@ describe('should handle media_player action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'media_player', + advanced_camera_card_action: 'media_player', media_player_action: 'stop', media_player: 'this_is_a_media_player', }, @@ -38,7 +38,7 @@ describe('should handle media_player action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'media_player', + advanced_camera_card_action: 'media_player', media_player_action: 'play', media_player: 'this_is_a_media_player', }, @@ -65,7 +65,7 @@ describe('should handle media_player action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'media_player', + advanced_camera_card_action: 'media_player', media_player_action: 'play', media_player: 'this_is_a_media_player', }, @@ -91,7 +91,7 @@ describe('should handle media_player action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'media_player', + advanced_camera_card_action: 'media_player', media_player_action: 'play', media_player: 'this_is_a_media_player', }, diff --git a/tests/card-controller/actions/actions/menu-toggle.test.ts b/tests/card-controller/actions/actions/menu-toggle.test.ts index c9d1d276..36e69f7d 100644 --- a/tests/card-controller/actions/actions/menu-toggle.test.ts +++ b/tests/card-controller/actions/actions/menu-toggle.test.ts @@ -8,7 +8,7 @@ it('should handle menu toggle action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'menu_toggle', + advanced_camera_card_action: 'menu_toggle', }, ); diff --git a/tests/card-controller/actions/actions/microphone-connect.test.ts b/tests/card-controller/actions/actions/microphone-connect.test.ts index ad252ae7..3073db94 100644 --- a/tests/card-controller/actions/actions/microphone-connect.test.ts +++ b/tests/card-controller/actions/actions/microphone-connect.test.ts @@ -8,7 +8,7 @@ it('should handle microphone_connect action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'microphone_connect', + advanced_camera_card_action: 'microphone_connect', }, ); diff --git a/tests/card-controller/actions/actions/microphone-disconnect.test.ts b/tests/card-controller/actions/actions/microphone-disconnect.test.ts index fe0d20e1..0c85d488 100644 --- a/tests/card-controller/actions/actions/microphone-disconnect.test.ts +++ b/tests/card-controller/actions/actions/microphone-disconnect.test.ts @@ -8,7 +8,7 @@ it('should handle microphone_disconnect action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'microphone_disconnect', + advanced_camera_card_action: 'microphone_disconnect', }, ); diff --git a/tests/card-controller/actions/actions/microphone-mute.test.ts b/tests/card-controller/actions/actions/microphone-mute.test.ts index 6b343c48..7179a176 100644 --- a/tests/card-controller/actions/actions/microphone-mute.test.ts +++ b/tests/card-controller/actions/actions/microphone-mute.test.ts @@ -8,7 +8,7 @@ it('should handle microphone_mute action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'microphone_mute', + advanced_camera_card_action: 'microphone_mute', }, ); diff --git a/tests/card-controller/actions/actions/microphone-unmute.test.ts b/tests/card-controller/actions/actions/microphone-unmute.test.ts index dd53b923..680a7f79 100644 --- a/tests/card-controller/actions/actions/microphone-unmute.test.ts +++ b/tests/card-controller/actions/actions/microphone-unmute.test.ts @@ -8,7 +8,7 @@ it('should handle microphone_unmute action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'microphone_unmute', + advanced_camera_card_action: 'microphone_unmute', }, ); diff --git a/tests/card-controller/actions/actions/mute.test.ts b/tests/card-controller/actions/actions/mute.test.ts index 5ba80cce..31234251 100644 --- a/tests/card-controller/actions/actions/mute.test.ts +++ b/tests/card-controller/actions/actions/mute.test.ts @@ -1,12 +1,12 @@ import { expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; import { MuteAction } from '../../../../src/card-controller/actions/actions/mute'; -import { FrigateCardMediaPlayer } from '../../../../src/types'; +import { AdvancedCameraCardMediaPlayer } from '../../../../src/types'; import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils'; it('should handle mute action', async () => { const api = createCardAPI(); - const player = mock(); + const player = mock(); vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue( createMediaLoadedInfo({ player: player, @@ -16,7 +16,7 @@ it('should handle mute action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'mute', + advanced_camera_card_action: 'mute', }, ); diff --git a/tests/card-controller/actions/actions/pause.test.ts b/tests/card-controller/actions/actions/pause.test.ts index 733bf8a0..02b12d7b 100644 --- a/tests/card-controller/actions/actions/pause.test.ts +++ b/tests/card-controller/actions/actions/pause.test.ts @@ -1,12 +1,12 @@ import { expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; import { PauseAction } from '../../../../src/card-controller/actions/actions/pause'; -import { FrigateCardMediaPlayer } from '../../../../src/types'; +import { AdvancedCameraCardMediaPlayer } from '../../../../src/types'; import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils'; it('should handle pause action', async () => { const api = createCardAPI(); - const player = mock(); + const player = mock(); vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue( createMediaLoadedInfo({ player: player, @@ -16,7 +16,7 @@ it('should handle pause action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'pause', + advanced_camera_card_action: 'pause', }, ); diff --git a/tests/card-controller/actions/actions/play.test.ts b/tests/card-controller/actions/actions/play.test.ts index f0f3879e..3c35373f 100644 --- a/tests/card-controller/actions/actions/play.test.ts +++ b/tests/card-controller/actions/actions/play.test.ts @@ -1,12 +1,12 @@ import { expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; import { PlayAction } from '../../../../src/card-controller/actions/actions/play'; -import { FrigateCardMediaPlayer } from '../../../../src/types'; +import { AdvancedCameraCardMediaPlayer } from '../../../../src/types'; import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils'; it('should handle play action', async () => { const api = createCardAPI(); - const player = mock(); + const player = mock(); vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue( createMediaLoadedInfo({ player: player, @@ -16,7 +16,7 @@ it('should handle play action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'play', + advanced_camera_card_action: 'play', }, ); diff --git a/tests/card-controller/actions/actions/ptz-controls.test.ts b/tests/card-controller/actions/actions/ptz-controls.test.ts index 7ab107fb..9cc22469 100644 --- a/tests/card-controller/actions/actions/ptz-controls.test.ts +++ b/tests/card-controller/actions/actions/ptz-controls.test.ts @@ -8,7 +8,7 @@ it('should handle ptz_controls action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz_controls', + advanced_camera_card_action: 'ptz_controls', enabled: true, }, ); diff --git a/tests/card-controller/actions/actions/ptz-digital.test.ts b/tests/card-controller/actions/actions/ptz-digital.test.ts index 7652fbc3..05dc946f 100644 --- a/tests/card-controller/actions/actions/ptz-digital.test.ts +++ b/tests/card-controller/actions/actions/ptz-digital.test.ts @@ -33,7 +33,7 @@ describe('should handle ptz digital action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz_digital', + advanced_camera_card_action: 'ptz_digital', absolute: { zoom: 2, pan: { @@ -70,7 +70,7 @@ describe('should handle ptz digital action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz_digital', + advanced_camera_card_action: 'ptz_digital', }, ); @@ -93,7 +93,7 @@ describe('should handle ptz digital action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz_digital', + advanced_camera_card_action: 'ptz_digital', ptz_action: 'left', }, ); @@ -117,7 +117,7 @@ describe('should handle ptz digital action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz_digital', + advanced_camera_card_action: 'ptz_digital', ptz_action: 'left', }, ); @@ -330,7 +330,7 @@ describe('should handle ptz digital action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz_digital', + advanced_camera_card_action: 'ptz_digital', ptz_action: ptzAction, }, ); @@ -370,7 +370,7 @@ describe('should handle ptz digital action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz_digital', + advanced_camera_card_action: 'ptz_digital', ptz_action: 'right', ptz_phase: 'start', }, @@ -442,7 +442,7 @@ describe('should handle ptz digital action', () => { const startAction = new PTZDigitalAction(context, { action: 'fire-dom-event', - frigate_card_action: 'ptz_digital', + advanced_camera_card_action: 'ptz_digital', ptz_action: 'right', ptz_phase: 'start', }); @@ -466,7 +466,7 @@ describe('should handle ptz digital action', () => { const stopAction = new PTZDigitalAction(context, { action: 'fire-dom-event', - frigate_card_action: 'ptz_digital', + advanced_camera_card_action: 'ptz_digital', ptz_phase: 'stop', }); await stopAction.execute(api); diff --git a/tests/card-controller/actions/actions/ptz-multi.test.ts b/tests/card-controller/actions/actions/ptz-multi.test.ts index 92ee7590..70bcd810 100644 --- a/tests/card-controller/actions/actions/ptz-multi.test.ts +++ b/tests/card-controller/actions/actions/ptz-multi.test.ts @@ -32,7 +32,7 @@ describe('should handle ptz multi action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', ptz_action: 'left', ...(targetID && { target_id: targetID }), }, @@ -69,7 +69,7 @@ describe('should handle ptz multi action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', ptz_action: 'right', ...(targetID && { target_id: targetID }), }, @@ -108,7 +108,7 @@ describe('should handle ptz multi action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', ptz_action: 'right', }, ); @@ -137,7 +137,7 @@ describe('should handle ptz multi action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', ptz_action: 'right', }, ); diff --git a/tests/card-controller/actions/actions/ptz.test.ts b/tests/card-controller/actions/actions/ptz.test.ts index 4743089d..36a6f279 100644 --- a/tests/card-controller/actions/actions/ptz.test.ts +++ b/tests/card-controller/actions/actions/ptz.test.ts @@ -29,7 +29,7 @@ describe('should handle ptz action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'left', camera: 'camera.office', }, @@ -65,7 +65,7 @@ describe('should handle ptz action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'left', }, ); @@ -112,7 +112,7 @@ describe('should handle ptz action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'left', }, ); @@ -140,7 +140,7 @@ describe('should handle ptz action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'left', }, ); @@ -159,7 +159,7 @@ describe('should handle ptz action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'left', }, ); @@ -187,7 +187,7 @@ describe('should handle ptz action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', }, ); @@ -225,7 +225,7 @@ describe('should handle ptz action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', }, ); @@ -256,7 +256,7 @@ describe('should handle ptz action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'preset', ptz_preset: 'window', }, @@ -295,7 +295,7 @@ describe('should handle ptz action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'left', ptz_phase: 'start', }, @@ -343,7 +343,7 @@ describe('should handle ptz action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'left', }, ); @@ -388,7 +388,7 @@ describe('should handle ptz action', () => { const context = {}; const action = new PTZAction(context, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'left', }); @@ -432,7 +432,7 @@ describe('should handle ptz action', () => { const context = {}; const startAction = new PTZAction(context, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'left', ptz_phase: 'start', }); @@ -455,7 +455,7 @@ describe('should handle ptz action', () => { const stopAction = new PTZAction(context, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'left', ptz_phase: 'stop', }); @@ -486,7 +486,7 @@ describe('should handle ptz action', () => { const context = {}; const action = new PTZAction(context, { action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', ptz_action: 'left', ptz_phase: 'start', }); diff --git a/tests/card-controller/actions/actions/screenshot.test.ts b/tests/card-controller/actions/actions/screenshot.test.ts index 3e773aa0..4af61046 100644 --- a/tests/card-controller/actions/actions/screenshot.test.ts +++ b/tests/card-controller/actions/actions/screenshot.test.ts @@ -8,7 +8,7 @@ it('should handle screenshot action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'screenshot', + advanced_camera_card_action: 'screenshot', }, ); diff --git a/tests/card-controller/actions/actions/sleep.test.ts b/tests/card-controller/actions/actions/sleep.test.ts index b9e3efb7..25ab0ab1 100644 --- a/tests/card-controller/actions/actions/sleep.test.ts +++ b/tests/card-controller/actions/actions/sleep.test.ts @@ -15,7 +15,7 @@ it('should handle sleep action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'sleep', + advanced_camera_card_action: 'sleep', duration: { s: 5, ms: 200, diff --git a/tests/card-controller/actions/actions/status-bar.test.ts b/tests/card-controller/actions/actions/status-bar.test.ts index d4ec5c8c..a631229c 100644 --- a/tests/card-controller/actions/actions/status-bar.test.ts +++ b/tests/card-controller/actions/actions/status-bar.test.ts @@ -9,7 +9,7 @@ describe('should handle status bar action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'status_bar', + advanced_camera_card_action: 'status_bar', status_bar_action: 'reset', }, ); @@ -22,7 +22,7 @@ describe('should handle status bar action', () => { it('add', async () => { const api = createCardAPI(); const item = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item', }; @@ -30,7 +30,7 @@ describe('should handle status bar action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'status_bar', + advanced_camera_card_action: 'status_bar', status_bar_action: 'add', items: [item], }, @@ -44,7 +44,7 @@ describe('should handle status bar action', () => { it('remove', async () => { const api = createCardAPI(); const item = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item', }; @@ -52,7 +52,7 @@ describe('should handle status bar action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'status_bar', + advanced_camera_card_action: 'status_bar', status_bar_action: 'remove', items: [item], }, diff --git a/tests/card-controller/actions/actions/substream-off.test.ts b/tests/card-controller/actions/actions/substream-off.test.ts index 03b86187..37550519 100644 --- a/tests/card-controller/actions/actions/substream-off.test.ts +++ b/tests/card-controller/actions/actions/substream-off.test.ts @@ -9,7 +9,7 @@ it('should handle live_substream_off action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'live_substream_off', + advanced_camera_card_action: 'live_substream_off', }, ); diff --git a/tests/card-controller/actions/actions/substream-on.test.ts b/tests/card-controller/actions/actions/substream-on.test.ts index b37061f3..9369bc34 100644 --- a/tests/card-controller/actions/actions/substream-on.test.ts +++ b/tests/card-controller/actions/actions/substream-on.test.ts @@ -9,7 +9,7 @@ it('should handle live_substream_on action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'live_substream_on', + advanced_camera_card_action: 'live_substream_on', }, ); diff --git a/tests/card-controller/actions/actions/substream-select.test.ts b/tests/card-controller/actions/actions/substream-select.test.ts index a0f326f9..0d320ec3 100644 --- a/tests/card-controller/actions/actions/substream-select.test.ts +++ b/tests/card-controller/actions/actions/substream-select.test.ts @@ -9,7 +9,7 @@ it('should handle live_substream_select action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'live_substream_select', + advanced_camera_card_action: 'live_substream_select', camera: 'substream', }, ); diff --git a/tests/card-controller/actions/actions/unmute.test.ts b/tests/card-controller/actions/actions/unmute.test.ts index 34a59a46..a47a9801 100644 --- a/tests/card-controller/actions/actions/unmute.test.ts +++ b/tests/card-controller/actions/actions/unmute.test.ts @@ -1,12 +1,12 @@ import { expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; import { UnmuteAction } from '../../../../src/card-controller/actions/actions/unmute'; -import { FrigateCardMediaPlayer } from '../../../../src/types'; +import { AdvancedCameraCardMediaPlayer } from '../../../../src/types'; import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils'; it('should handle unmute action', async () => { const api = createCardAPI(); - const player = mock(); + const player = mock(); vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue( createMediaLoadedInfo({ player: player, @@ -16,7 +16,7 @@ it('should handle unmute action', async () => { {}, { action: 'fire-dom-event', - frigate_card_action: 'unmute', + advanced_camera_card_action: 'unmute', }, ); diff --git a/tests/card-controller/actions/actions/view.test.ts b/tests/card-controller/actions/actions/view.test.ts index 41215a63..2e290b5d 100644 --- a/tests/card-controller/actions/actions/view.test.ts +++ b/tests/card-controller/actions/actions/view.test.ts @@ -21,7 +21,7 @@ describe('should handle view action', () => { {}, { action: 'fire-dom-event', - frigate_card_action: viewName, + advanced_camera_card_action: viewName, }, ); diff --git a/tests/card-controller/actions/factory.test.ts b/tests/card-controller/actions/factory.test.ts index becc9e7f..9107ccf7 100644 --- a/tests/card-controller/actions/factory.test.ts +++ b/tests/card-controller/actions/factory.test.ts @@ -30,7 +30,7 @@ import { SubstreamSelectAction } from '../../../src/card-controller/actions/acti import { UnmuteAction } from '../../../src/card-controller/actions/actions/unmute'; import { ViewAction } from '../../../src/card-controller/actions/actions/view'; import { ActionFactory } from '../../../src/card-controller/actions/factory'; -import { FrigateCardCustomAction } from '../../../src/config/types'; +import { AdvancedCameraCardCustomAction } from '../../../src/config/types'; // @vitest-environment jsdom describe('ActionFactory', () => { @@ -39,7 +39,11 @@ describe('ActionFactory', () => { expect( factory.createAction( {}, - { action: 'fire-dom-event', frigate_card_action: 'clip', card_id: 'card_id' }, + { + action: 'fire-dom-event', + advanced_camera_card_action: 'clip', + card_id: 'card_id', + }, { cardID: 'different_card_id', }, @@ -48,7 +52,7 @@ describe('ActionFactory', () => { }); describe('generic', () => { - it('non frigate card action', () => { + it('non advanced camera card action', () => { const factory = new ActionFactory(); expect(factory.createAction({}, { action: 'fire-dom-event' })).toBeInstanceOf( GenericAction, @@ -65,81 +69,105 @@ describe('ActionFactory', () => { describe('actions', () => { it.each([ - [{ frigate_card_action: 'camera_select' as const }, CameraSelectAction], - [{ frigate_card_action: 'camera_ui' as const }, CameraUIAction], - [{ frigate_card_action: 'clip' as const }, ViewAction], - [{ frigate_card_action: 'clips' as const }, ViewAction], - [{ frigate_card_action: 'default' as const }, DefaultAction], - [{ frigate_card_action: 'diagnostics' as const }, ViewAction], + [{ advanced_camera_card_action: 'camera_select' as const }, CameraSelectAction], + [{ advanced_camera_card_action: 'camera_ui' as const }, CameraUIAction], + [{ advanced_camera_card_action: 'clip' as const }, ViewAction], + [{ advanced_camera_card_action: 'clips' as const }, ViewAction], + [{ advanced_camera_card_action: 'default' as const }, DefaultAction], + [{ advanced_camera_card_action: 'diagnostics' as const }, ViewAction], [ { - frigate_card_action: 'display_mode_select' as const, + advanced_camera_card_action: 'display_mode_select' as const, display_mode: 'single' as const, }, DisplayModeSelectAction, ], - [{ frigate_card_action: 'download' as const }, DownloadAction], - [{ frigate_card_action: 'expand' as const }, ExpandAction], - [{ frigate_card_action: 'fullscreen' as const }, FullscreenAction], - [{ frigate_card_action: 'image' as const }, ViewAction], - [{ frigate_card_action: 'live_substream_off' as const }, SubstreamOffAction], - [{ frigate_card_action: 'live_substream_on' as const }, SubstreamOnAction], + [{ advanced_camera_card_action: 'download' as const }, DownloadAction], + [{ advanced_camera_card_action: 'expand' as const }, ExpandAction], + [{ advanced_camera_card_action: 'fullscreen' as const }, FullscreenAction], + [{ advanced_camera_card_action: 'image' as const }, ViewAction], + [ + { advanced_camera_card_action: 'live_substream_off' as const }, + SubstreamOffAction, + ], + [{ advanced_camera_card_action: 'live_substream_on' as const }, SubstreamOnAction], [ { - frigate_card_action: 'live_substream_select' as const, + advanced_camera_card_action: 'live_substream_select' as const, camera: 'camera.office', }, SubstreamSelectAction, ], - [{ frigate_card_action: 'live' as const }, ViewAction], + [{ advanced_camera_card_action: 'live' as const }, ViewAction], [ - { frigate_card_action: 'log' as const, message: 'Hello, world!' as const }, + { + advanced_camera_card_action: 'log' as const, + message: 'Hello, world!' as const, + }, LogAction, ], [ { - frigate_card_action: 'media_player' as const, + advanced_camera_card_action: 'media_player' as const, media_player: 'media_player.foo' as const, media_player_action: 'play' as const, }, MediaPlayerAction, ], - [{ frigate_card_action: 'menu_toggle' as const }, MenuToggleAction], - [{ frigate_card_action: 'microphone_connect' as const }, MicrophoneConnectAction], + [{ advanced_camera_card_action: 'menu_toggle' as const }, MenuToggleAction], [ - { frigate_card_action: 'microphone_disconnect' as const }, + { advanced_camera_card_action: 'microphone_connect' as const }, + MicrophoneConnectAction, + ], + [ + { advanced_camera_card_action: 'microphone_disconnect' as const }, MicrophoneDisconnectAction, ], - [{ frigate_card_action: 'microphone_mute' as const }, MicrophoneMuteAction], - [{ frigate_card_action: 'microphone_unmute' as const }, MicrophoneUnmuteAction], - [{ frigate_card_action: 'mute' as const }, MuteAction], - [{ frigate_card_action: 'pause' as const }, PauseAction], - [{ frigate_card_action: 'play' as const }, PlayAction], - [{ frigate_card_action: 'ptz_digital' as const }, PTZDigitalAction], [ - { frigate_card_action: 'ptz_multi' as const, ptz_action: 'right' as const }, + { advanced_camera_card_action: 'microphone_mute' as const }, + MicrophoneMuteAction, + ], + [ + { advanced_camera_card_action: 'microphone_unmute' as const }, + MicrophoneUnmuteAction, + ], + [{ advanced_camera_card_action: 'mute' as const }, MuteAction], + [{ advanced_camera_card_action: 'pause' as const }, PauseAction], + [{ advanced_camera_card_action: 'play' as const }, PlayAction], + [{ advanced_camera_card_action: 'ptz_digital' as const }, PTZDigitalAction], + [ + { + advanced_camera_card_action: 'ptz_multi' as const, + ptz_action: 'right' as const, + }, PTZMultiAction, ], - [{ frigate_card_action: 'ptz' as const, ptz_action: 'right' as const }, PTZAction], - [{ frigate_card_action: 'recording' as const }, ViewAction], - [{ frigate_card_action: 'recordings' as const }, ViewAction], - [{ frigate_card_action: 'screenshot' as const }, ScreenshotAction], [ - { frigate_card_action: 'ptz_controls' as const, enabled: true }, + { advanced_camera_card_action: 'ptz' as const, ptz_action: 'right' as const }, + PTZAction, + ], + [{ advanced_camera_card_action: 'recording' as const }, ViewAction], + [{ advanced_camera_card_action: 'recordings' as const }, ViewAction], + [{ advanced_camera_card_action: 'screenshot' as const }, ScreenshotAction], + [ + { advanced_camera_card_action: 'ptz_controls' as const, enabled: true }, PTZControlsAction, ], - [{ frigate_card_action: 'sleep' as const }, SleepAction], - [{ frigate_card_action: 'snapshot' as const }, ViewAction], - [{ frigate_card_action: 'snapshots' as const }, ViewAction], - [{ frigate_card_action: 'timeline' as const }, ViewAction], - [{ frigate_card_action: 'unmute' as const }, UnmuteAction], + [{ advanced_camera_card_action: 'sleep' as const }, SleepAction], + [{ advanced_camera_card_action: 'snapshot' as const }, ViewAction], + [{ advanced_camera_card_action: 'snapshots' as const }, ViewAction], + [{ advanced_camera_card_action: 'timeline' as const }, ViewAction], + [{ advanced_camera_card_action: 'unmute' as const }, UnmuteAction], [ - { frigate_card_action: 'status_bar' as const, status_bar_action: 'reset' }, + { + advanced_camera_card_action: 'status_bar' as const, + status_bar_action: 'reset', + }, StatusBarAction, ], ])( - 'frigate_card_action: $frigate_card_action', - (action: Partial, classObject: object) => { + 'advanced_camera_card_action: $advanced_camera_card_action', + (action: Partial, classObject: object) => { const factory = new ActionFactory(); expect( factory.createAction({}, { action: 'fire-dom-event', ...action }), diff --git a/tests/card-controller/automations-manager.test.ts b/tests/card-controller/automations-manager.test.ts index 2f9d6d41..f90d904a 100644 --- a/tests/card-controller/automations-manager.test.ts +++ b/tests/card-controller/automations-manager.test.ts @@ -8,7 +8,7 @@ describe('AutomationsManager', () => { const actions = [ { action: 'fire-dom-event' as const, - frigate_card_action: 'clips', + advanced_camera_card_action: 'clips', }, ]; const conditions = [{ condition: 'fullscreen' as const, fullscreen: true }]; diff --git a/tests/card-controller/conditions-manager.test.ts b/tests/card-controller/conditions-manager.test.ts index 5243331d..f32d44b0 100644 --- a/tests/card-controller/conditions-manager.test.ts +++ b/tests/card-controller/conditions-manager.test.ts @@ -7,7 +7,7 @@ import { getOverriddenConfig, } from '../../src/card-controller/conditions-manager'; import { MicrophoneState } from '../../src/card-controller/types'; -import { FrigateCardCondition } from '../../src/config/types'; +import { AdvancedCameraCardCondition } from '../../src/config/types'; import { createCardAPI, createCondition, @@ -26,7 +26,7 @@ describe('ConditionEvaluateRequestEvent', () => { composed: true, }); - expect(event.type).toBe('frigate-card:conditions:evaluate'); + expect(event.type).toBe('advanced-camera-card:conditions:evaluate'); expect(event.conditions).toBe(conditions); expect(event.bubbles).toBeTruthy(); expect(event.composed).toBeTruthy(); @@ -45,7 +45,7 @@ describe('evaluateConditionViaEvent', () => { expect(ev.conditions).toBe(conditions); ev.evaluation = true; }); - element.addEventListener('frigate-card:conditions:evaluate', handler); + element.addEventListener('advanced-camera-card:conditions:evaluate', handler); expect(evaluateConditionViaEvent(element, conditions)).toBeTruthy(); expect(handler).toBeCalled(); @@ -57,7 +57,7 @@ describe('evaluateConditionViaEvent', () => { expect(ev.conditions).toBe(conditions); ev.evaluation = false; }); - element.addEventListener('frigate-card:conditions:evaluate', handler); + element.addEventListener('advanced-camera-card:conditions:evaluate', handler); expect(evaluateConditionViaEvent(element, conditions)).toBeFalsy(); expect(handler).toBeCalled(); @@ -66,7 +66,7 @@ describe('evaluateConditionViaEvent', () => { const element = document.createElement('div'); const conditions = [createCondition({ condition: 'fullscreen', fullscreen: true })]; const handler = vi.fn(); - element.addEventListener('frigate-card:conditions:evaluate', handler); + element.addEventListener('advanced-camera-card:conditions:evaluate', handler); expect(evaluateConditionViaEvent(element, conditions)).toBeFalsy(); expect(handler).toBeCalled(); @@ -464,7 +464,7 @@ describe('ConditionsManager', () => { vi.restoreAllMocks(); }); - const createSuitableConfig = (conditions: FrigateCardCondition[]) => { + const createSuitableConfig = (conditions: AdvancedCameraCardCondition[]) => { return createConfig({ overrides: [ { @@ -790,11 +790,11 @@ describe('ConditionsManager', () => { describe('with screen condition', () => { const mediaQueryConfig = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], elements: [ { - type: 'custom:frigate-card-conditional', + type: 'custom:advanced-camera-card-conditional', conditions: [ { condition: 'fullscreen' as const, @@ -805,7 +805,7 @@ describe('ConditionsManager', () => { { type: 'custom:nested-unknown-object', unknown_key: { - type: 'custom:frigate-card-conditional', + type: 'custom:advanced-camera-card-conditional', conditions: [ { condition: 'screen' as const, diff --git a/tests/card-controller/config/config-manager.test.ts b/tests/card-controller/config/config-manager.test.ts index c9020f6d..8254f415 100644 --- a/tests/card-controller/config/config-manager.test.ts +++ b/tests/card-controller/config/config-manager.test.ts @@ -3,7 +3,7 @@ import { ZodError } from 'zod'; import { getOverriddenConfig } from '../../../src/card-controller/conditions-manager'; import { ConfigManager } from '../../../src/card-controller/config/config-manager'; import { InitializationAspect } from '../../../src/card-controller/initialization-manager'; -import { frigateCardConfigSchema } from '../../../src/config/types'; +import { advancedCameraCardConfigSchema } from '../../../src/config/types'; import { createCardAPI, createConfig, flushPromises } from '../../test-utils'; vi.mock('../../../src/card-controller/conditions-manager.js'); @@ -20,7 +20,7 @@ describe('ConfigManager', () => { }); it('invalid configuration', () => { - const spy = vi.spyOn(frigateCardConfigSchema, 'safeParse').mockReturnValue({ + const spy = vi.spyOn(advancedCameraCardConfigSchema, 'safeParse').mockReturnValue({ success: false, error: new ZodError([]), }); @@ -44,18 +44,14 @@ describe('ConfigManager', () => { const manager = new ConfigManager(createCardAPI()); expect(() => manager.setConfig({ - cameras: [ - { - frigate: { - label: 'foo', - }, - }, - ], + // This key needs to be upgradeable in `management.ts` . + type: 'custom:frigate-card', + cameras: 'WILL_NOT_PARSE', }), ).toThrowError( 'An automated card configuration upgrade is ' + 'available, please visit the visual card editor. ' + - 'Invalid configuration: [\n "type"\n]', + 'Invalid configuration: [\n "cameras"\n]', ); }); }); @@ -72,7 +68,7 @@ describe('ConfigManager', () => { const api = createCardAPI(); const manager = new ConfigManager(api); const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], }; @@ -105,7 +101,7 @@ describe('ConfigManager', () => { it('should apply profiles', () => { const manager = new ConfigManager(createCardAPI()); const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], profiles: ['low-performance'], }; @@ -120,7 +116,7 @@ describe('ConfigManager', () => { const api = createCardAPI(); const manager = new ConfigManager(api); const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], }; @@ -137,7 +133,7 @@ describe('ConfigManager', () => { const api = createCardAPI(); const manager = new ConfigManager(api); const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], debug: { logging: true, @@ -183,7 +179,7 @@ describe('ConfigManager', () => { const api = createCardAPI(); const manager = new ConfigManager(api); const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], }; vi.mocked(getOverriddenConfig).mockReturnValue(config); @@ -201,14 +197,14 @@ describe('ConfigManager', () => { const api = createCardAPI(); const manager = new ConfigManager(api); const config_1 = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], }; manager.setConfig(config_1); vi.mocked(api.getStyleManager().updateFromConfig).mockClear(); const config_2 = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.kitchen' }], }; vi.mocked(getOverriddenConfig).mockReturnValue(config_2); @@ -223,7 +219,7 @@ describe('ConfigManager', () => { const api = createCardAPI(); const manager = new ConfigManager(api); manager.setConfig({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], }); @@ -242,7 +238,7 @@ describe('ConfigManager', () => { const api = createCardAPI(); const manager = new ConfigManager(api); const config_1 = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], }; vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1)); @@ -253,7 +249,7 @@ describe('ConfigManager', () => { ); const config_2 = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.kitchen' }], }; vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_2)); @@ -268,7 +264,7 @@ describe('ConfigManager', () => { const api = createCardAPI(); const manager = new ConfigManager(api); const config_1 = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], }; vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config_1)); @@ -296,7 +292,7 @@ describe('ConfigManager', () => { const api = createCardAPI(); const manager = new ConfigManager(api); const config_1 = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], live: { microphone: { @@ -332,7 +328,7 @@ describe('ConfigManager', () => { const api = createCardAPI(); const manager = new ConfigManager(api); const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], }; vi.mocked(getOverriddenConfig).mockReturnValue(createConfig(config)); diff --git a/tests/card-controller/config/load-automations.test.ts b/tests/card-controller/config/load-automations.test.ts index e246499d..6fd7d6f5 100644 --- a/tests/card-controller/config/load-automations.test.ts +++ b/tests/card-controller/config/load-automations.test.ts @@ -17,7 +17,7 @@ describe('setAutomationsFromConfig', () => { actions: [ { action: 'fire-dom-event' as const, - frigate_card_action: 'clips', + advanced_camera_card_action: 'clips', }, ], conditions: [{ condition: 'fullscreen' as const, fullscreen: true }], diff --git a/tests/card-controller/config/load-keyboard-shortcuts.test.ts b/tests/card-controller/config/load-keyboard-shortcuts.test.ts index 282fd433..bfa8dc1e 100644 --- a/tests/card-controller/config/load-keyboard-shortcuts.test.ts +++ b/tests/card-controller/config/load-keyboard-shortcuts.test.ts @@ -67,7 +67,7 @@ describe('setKeyboardShortcutsFromConfig', () => { actions: [ { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', ptz_action: ptzAction, ptz_phase: 'start', }, @@ -89,7 +89,7 @@ describe('setKeyboardShortcutsFromConfig', () => { actions: [ { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', ptz_action: ptzAction, ptz_phase: 'stop', }, @@ -119,7 +119,7 @@ describe('setKeyboardShortcutsFromConfig', () => { actions: [ { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', }, ], conditions: [ diff --git a/tests/card-controller/controller.test.ts b/tests/card-controller/controller.test.ts index e293ab32..27fa84c4 100644 --- a/tests/card-controller/controller.test.ts +++ b/tests/card-controller/controller.test.ts @@ -27,7 +27,7 @@ import { StatusBarItemManager } from '../../src/card-controller/status-bar-item- import { StyleManager } from '../../src/card-controller/style-manager'; import { TriggersManager } from '../../src/card-controller/triggers-manager'; import { ViewManager } from '../../src/card-controller/view/view-manager'; -import { FrigateCardEditor } from '../../src/editor'; +import { AdvancedCameraCardEditor } from '../../src/editor'; import { DeviceRegistryManager } from '../../src/utils/ha/registry/device'; import { EntityRegistryManager } from '../../src/utils/ha/registry/entity'; import { ResolvedMediaCache } from '../../src/utils/ha/resolved-media'; @@ -142,7 +142,9 @@ describe('CardController', () => { }); it('getConfigElement', async () => { - expect((await CardController.getConfigElement()) instanceof FrigateCardEditor); + expect( + (await CardController.getConfigElement()) instanceof AdvancedCameraCardEditor, + ); }); it('getConfigManager', () => { diff --git a/tests/card-controller/default-manager.test.ts b/tests/card-controller/default-manager.test.ts index ab4b4748..5b110d44 100644 --- a/tests/card-controller/default-manager.test.ts +++ b/tests/card-controller/default-manager.test.ts @@ -189,7 +189,7 @@ describe('DefaultManager', () => { actions: [ { action: 'fire-dom-event', - frigate_card_action: 'default', + advanced_camera_card_action: 'default', }, ], conditions: [ diff --git a/tests/card-controller/download-manager.test.ts b/tests/card-controller/download-manager.test.ts index 59427a12..07282f8a 100644 --- a/tests/card-controller/download-manager.test.ts +++ b/tests/card-controller/download-manager.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; -import { FrigateCardMediaPlayer } from '../../src/types'; import { DownloadManager } from '../../src/card-controller/download-manager'; +import { AdvancedCameraCardMediaPlayer } from '../../src/types'; import { downloadMedia, downloadURL } from '../../src/utils/download.js'; import { createCardAPI, @@ -62,7 +62,7 @@ describe('DownloadManager.downloadScreenshot', () => { it('with url', async () => { const api = createCardAPI(); - const player = mock(); + const player = mock(); player.getScreenshotURL.mockResolvedValue('http://screenshot'); vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue( @@ -78,7 +78,7 @@ describe('DownloadManager.downloadScreenshot', () => { it('without url', async () => { const api = createCardAPI(); - const player = mock(); + const player = mock(); player.getScreenshotURL.mockResolvedValue(null); vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue( diff --git a/tests/card-controller/fullscreen/webkit/index.test.ts b/tests/card-controller/fullscreen/webkit/index.test.ts index 97ecba06..f8d91897 100644 --- a/tests/card-controller/fullscreen/webkit/index.test.ts +++ b/tests/card-controller/fullscreen/webkit/index.test.ts @@ -2,7 +2,10 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; import { CardController } from '../../../../src/card-controller/controller'; import { WebkitFullScreenProvider } from '../../../../src/card-controller/fullscreen/webkit'; -import { FrigateCardMediaPlayer, WebkitHTMLVideoElement } from '../../../../src/types'; +import { + AdvancedCameraCardMediaPlayer, + WebkitHTMLVideoElement, +} from '../../../../src/types'; import { createCardAPI, createMediaLoadedInfo } from '../../../test-utils'; const createWebkitVideoElement = (): HTMLVideoElement & @@ -11,7 +14,7 @@ const createWebkitVideoElement = (): HTMLVideoElement & }; const setElement = (api: CardController, element: HTMLElement): void => { - const player = mock(); + const player = mock(); player.getFullscreenElement.mockReturnValue(element); vi.mocked(api.getMediaLoadedInfoManager().get).mockReturnValue( @@ -158,7 +161,7 @@ describe('WebkitFullScreenProvider', () => { ).mock.calls[0][0]; const element_1 = createWebkitVideoElement(); - const player_1 = mock(); + const player_1 = mock(); player_1.getFullscreenElement.mockReturnValue(element_1); const mediaLoadedInfo_1 = createMediaLoadedInfo({ player: player_1 }); @@ -169,7 +172,7 @@ describe('WebkitFullScreenProvider', () => { expect(handler).toBeCalledTimes(1); const element_2 = createWebkitVideoElement(); - const player_2 = mock(); + const player_2 = mock(); player_2.getFullscreenElement.mockReturnValue(element_2); const mediaLoadedInfo_2 = createMediaLoadedInfo({ player: player_2 }); @@ -219,7 +222,7 @@ describe('WebkitFullScreenProvider', () => { setElement(api, element); - const player = mock(); + const player = mock(); player.getFullscreenElement.mockReturnValue(element); const mediaLoadedInfo = createMediaLoadedInfo({ player }); diff --git a/tests/card-controller/message-manager.test.ts b/tests/card-controller/message-manager.test.ts index ffedd7f6..69f4c9ea 100644 --- a/tests/card-controller/message-manager.test.ts +++ b/tests/card-controller/message-manager.test.ts @@ -1,6 +1,6 @@ import { afterAll, describe, expect, it, vi } from 'vitest'; -import { FrigateCardError, Message } from '../../src/types'; import { MessageManager } from '../../src/card-controller/message-manager'; +import { AdvancedCameraCardError, Message } from '../../src/types'; import { createCardAPI } from '../test-utils'; const createMessage = (options?: Partial): Message => { @@ -111,7 +111,7 @@ describe('MessageManager', () => { expect(manager.getMessage()).toBe(connectionMessage); }); - it('should set FrigateCardError object', () => { + it('should set AdvancedCameraCardError object', () => { const consoleSpy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined); const api = createCardAPI(); @@ -119,11 +119,11 @@ describe('MessageManager', () => { const context = { foo: 'bar' }; manager.setErrorIfHigherPriority( - new FrigateCardError('frigate card message', context), + new AdvancedCameraCardError('advanced camera card message', context), ); expect(manager.hasMessage()).toBeTruthy(); expect(manager.getMessage()).toEqual({ - message: 'frigate card message', + message: 'advanced camera card message', type: 'error', context: context, }); diff --git a/tests/card-controller/query-string-manager.test.ts b/tests/card-controller/query-string-manager.test.ts index 5b7dcfb0..e2bd9a30 100644 --- a/tests/card-controller/query-string-manager.test.ts +++ b/tests/card-controller/query-string-manager.test.ts @@ -42,7 +42,7 @@ describe('QueryStringManager', () => { ['snapshots' as const], ['timeline' as const], ])('%s', async (viewName: string) => { - setQueryString(`?frigate-card-action.id.${viewName}=`); + setQueryString(`?advanced-camera-card-action.id.${viewName}=`); const api = createCardAPI(); // View actions do not need the card to have been updated. @@ -68,7 +68,7 @@ describe('QueryStringManager', () => { ['expand' as const], ['menu_toggle' as const], ])('%s', async (action: string) => { - setQueryString(`?frigate-card-action.id.${action}=`); + setQueryString(`?advanced-camera-card-action.id.${action}=`); const api = createCardAPI(); vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true); const manager = new QueryStringManager(api); @@ -80,14 +80,14 @@ describe('QueryStringManager', () => { { action: 'fire-dom-event', card_id: 'id', - frigate_card_action: action, + advanced_camera_card_action: action, }, ]); }); }); it('should execute view default action', async () => { - setQueryString('?frigate-card-action.id.default='); + setQueryString('?advanced-camera-card-action.id.default='); const api = createCardAPI(); // View actions do not need the card to have been updated. vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(false); @@ -104,7 +104,7 @@ describe('QueryStringManager', () => { }); it('should execute camera_select action', async () => { - setQueryString('?frigate-card-action.id.camera_select=camera.office'); + setQueryString('?advanced-camera-card-action.id.camera_select=camera.office'); const api = createCardAPI(); vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true); const manager = new QueryStringManager(api); @@ -123,7 +123,9 @@ describe('QueryStringManager', () => { }); it('should execute live_substream_select action', async () => { - setQueryString('?frigate-card-action.id.live_substream_select=camera.office_hd'); + setQueryString( + '?advanced-camera-card-action.id.live_substream_select=camera.office_hd', + ); const api = createCardAPI(); vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true); const manager = new QueryStringManager(api); @@ -145,7 +147,7 @@ describe('QueryStringManager', () => { it.each([['camera_select' as const], ['live_substream_select' as const]])( '%s', async (action: string) => { - setQueryString(`?frigate-card-action.id.${action}=`); + setQueryString(`?advanced-camera-card-action.id.${action}=`); const api = createCardAPI(); vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true); const manager = new QueryStringManager(api); @@ -163,7 +165,7 @@ describe('QueryStringManager', () => { it('should handle unknown action', async () => { const consoleSpy = vi.spyOn(global.console, 'warn').mockReturnValue(undefined); - setQueryString('?frigate-card-action.id.not_an_action=value'); + setQueryString('?advanced-camera-card-action.id.not_an_action=value'); const api = createCardAPI(); vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true); const manager = new QueryStringManager(api); @@ -190,7 +192,7 @@ describe('QueryStringManager', () => { ['snapshots' as const], ['timeline' as const], ])('%s', async (viewName: string) => { - setQueryString(`?frigate-card-action.id.${viewName}=`); + setQueryString(`?advanced-camera-card-action.id.${viewName}=`); const api = createCardAPI(); vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true); const manager = new QueryStringManager(api); @@ -210,10 +212,10 @@ describe('QueryStringManager', () => { describe('should handle conflicting but valid actions', () => { it('view and default with camera and substream specified', async () => { setQueryString( - '?frigate-card-action.id.clips=' + - '&frigate-card-action.id.live_substream_select=camera.kitchen_hd' + - '&frigate-card-action.id.default=' + - '&frigate-card-action.id.camera_select=camera.kitchen', + '?advanced-camera-card-action.id.clips=' + + '&advanced-camera-card-action.id.live_substream_select=camera.kitchen_hd' + + '&advanced-camera-card-action.id.default=' + + '&advanced-camera-card-action.id.camera_select=camera.kitchen', ); const api = createCardAPI(); vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true); @@ -232,8 +234,8 @@ describe('QueryStringManager', () => { it('multiple cameras specified', async () => { setQueryString( - '?frigate-card-action.id.camera_select=camera.kitchen' + - '&frigate-card-action.id.camera_select=camera.office', + '?advanced-camera-card-action.id.camera_select=camera.kitchen' + + '&advanced-camera-card-action.id.camera_select=camera.office', ); const api = createCardAPI(); vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true); @@ -250,7 +252,9 @@ describe('QueryStringManager', () => { }); it('should only execute when needed', async () => { - setQueryString('?frigate-card-action.id.live_substream_select=camera.office_hd'); + setQueryString( + '?advanced-camera-card-action.id.live_substream_select=camera.office_hd', + ); const api = createCardAPI(); vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(true); const manager = new QueryStringManager(api); @@ -271,4 +275,23 @@ describe('QueryStringManager', () => { expect(manager.hasViewRelatedActionsToRun()).toBeFalsy(); expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledTimes(2); }); + + it('should execute actions with old frigate-card-action key', async () => { + setQueryString(`?frigate-card-action.id.clips=`); + const api = createCardAPI(); + + // View actions do not need the card to have been updated. + vi.mocked(api.getCardElementManager().hasUpdated).mockReturnValue(false); + const manager = new QueryStringManager(api); + + expect(manager.hasViewRelatedActionsToRun()).toBeTruthy(); + await manager.executeIfNecessary(); + expect(manager.hasViewRelatedActionsToRun()).toBeFalsy(); + + expect(api.getViewManager().setViewByParametersWithNewQuery).toBeCalledWith({ + params: { + view: 'clips', + }, + }); + }); }); diff --git a/tests/card-controller/status-bar-item-manager.test.ts b/tests/card-controller/status-bar-item-manager.test.ts index 58f582f8..7ccc9769 100644 --- a/tests/card-controller/status-bar-item-manager.test.ts +++ b/tests/card-controller/status-bar-item-manager.test.ts @@ -12,7 +12,7 @@ import { describe('StatusBarItemManager', () => { const testItem: StatusBarString = { - type: 'custom:frigate-card-status-bar-string' as const, + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'test', }; @@ -68,7 +68,7 @@ describe('StatusBarItemManager', () => { view: createView({ view: 'live', camera: 'camera-1' }), }), ).toContainEqual({ - type: 'custom:frigate-card-status-bar-string' as const, + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Camera Title', expand: true, sufficient: true, @@ -108,7 +108,7 @@ describe('StatusBarItemManager', () => { view: view, }), ).toContainEqual({ - type: 'custom:frigate-card-status-bar-string' as const, + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'Media Title', expand: true, sufficient: true, @@ -180,7 +180,7 @@ describe('StatusBarItemManager', () => { mediaLoadedInfo: { width, height }, }), ).toContainEqual({ - type: 'custom:frigate-card-status-bar-string' as const, + type: 'custom:advanced-camera-card-status-bar-string' as const, string: expectedName, }); }, @@ -196,7 +196,7 @@ describe('StatusBarItemManager', () => { mediaLoadedInfo: { width: 640, height: 480, technology: ['webrtc'] }, }), ).toContainEqual({ - type: 'custom:frigate-card-status-bar-icon' as const, + type: 'custom:advanced-camera-card-status-bar-icon' as const, icon: 'mdi:webrtc', }); }); @@ -209,7 +209,7 @@ describe('StatusBarItemManager', () => { mediaLoadedInfo: { width: 640, height: 480, technology: ['hls'] }, }), ).toContainEqual({ - type: 'custom:frigate-card-status-bar-string' as const, + type: 'custom:advanced-camera-card-status-bar-string' as const, string: 'HLS', }); }); @@ -237,7 +237,7 @@ describe('StatusBarItemManager', () => { view: createView({ view: 'live', camera: 'camera-1' }), }), ).toContainEqual({ - type: 'custom:frigate-card-status-bar-icon' as const, + type: 'custom:advanced-camera-card-status-bar-icon' as const, icon: 'ENGINE_ICON', }); }); diff --git a/tests/card-controller/style-manager.test.ts b/tests/card-controller/style-manager.test.ts index c908a12c..2b6ac8b2 100644 --- a/tests/card-controller/style-manager.test.ts +++ b/tests/card-controller/style-manager.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { StyleManager } from '../../src/card-controller/style-manager'; -import { FrigateCardView, ThemeName } from '../../src/config/types'; +import { AdvancedCameraCardView, ThemeName } from '../../src/config/types'; import { createCardAPI, createConfig, createView } from '../test-utils'; // @vitest-environment jsdom @@ -57,15 +57,15 @@ describe('StyleManager', () => { manager.setExpandedMode(); - expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe( - 'unset', - ); - expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe( - 'var(--frigate-card-expand-max-width)', - ); - expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe( - 'var(--frigate-card-expand-max-height)', + expect( + element.style.getPropertyValue('--advanced-camera-card-expand-aspect-ratio'), + ).toBe('unset'); + expect(element.style.getPropertyValue('--advanced-camera-card-expand-width')).toBe( + 'var(--advanced-camera-card-expand-max-width)', ); + expect( + element.style.getPropertyValue('--advanced-camera-card-expand-height'), + ).toBe('var(--advanced-camera-card-expand-max-height)'); }); it('with view but without media', () => { @@ -79,13 +79,15 @@ describe('StyleManager', () => { manager.setExpandedMode(); - expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe( - 'unset', - ); - expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe('none'); - expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe( + expect( + element.style.getPropertyValue('--advanced-camera-card-expand-aspect-ratio'), + ).toBe('unset'); + expect(element.style.getPropertyValue('--advanced-camera-card-expand-width')).toBe( 'none', ); + expect( + element.style.getPropertyValue('--advanced-camera-card-expand-height'), + ).toBe('none'); }); it('with view and media', () => { @@ -102,13 +104,15 @@ describe('StyleManager', () => { manager.setExpandedMode(); - expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe( - '800 / 600', - ); - expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe('none'); - expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe( + expect( + element.style.getPropertyValue('--advanced-camera-card-expand-aspect-ratio'), + ).toBe('800 / 600'); + expect(element.style.getPropertyValue('--advanced-camera-card-expand-width')).toBe( 'none', ); + expect( + element.style.getPropertyValue('--advanced-camera-card-expand-height'), + ).toBe('none'); }); it('with view and grid display mode', () => { @@ -125,15 +129,15 @@ describe('StyleManager', () => { manager.setExpandedMode(); - expect(element.style.getPropertyValue('--frigate-card-expand-aspect-ratio')).toBe( - '800 / 600', - ); - expect(element.style.getPropertyValue('--frigate-card-expand-width')).toBe( - 'var(--frigate-card-expand-max-width)', - ); - expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBe( - 'var(--frigate-card-expand-max-height)', + expect( + element.style.getPropertyValue('--advanced-camera-card-expand-aspect-ratio'), + ).toBe('800 / 600'); + expect(element.style.getPropertyValue('--advanced-camera-card-expand-width')).toBe( + 'var(--advanced-camera-card-expand-max-width)', ); + expect( + element.style.getPropertyValue('--advanced-camera-card-expand-height'), + ).toBe('var(--advanced-camera-card-expand-max-height)'); }); }); @@ -146,8 +150,12 @@ describe('StyleManager', () => { manager.updateFromConfig(); - expect(element.style.getPropertyValue('--frigate-card-max-height')).toBeFalsy(); - expect(element.style.getPropertyValue('--frigate-card-expand-height')).toBeFalsy(); + expect( + element.style.getPropertyValue('--advanced-camera-card-max-height'), + ).toBeFalsy(); + expect( + element.style.getPropertyValue('--advanced-camera-card-expand-height'), + ).toBeFalsy(); }); it('with a config', () => { @@ -165,7 +173,9 @@ describe('StyleManager', () => { manager.updateFromConfig(); - expect(element.style.getPropertyValue('--frigate-card-height')).toBe('800px'); + expect(element.style.getPropertyValue('--advanced-camera-card-height')).toBe( + '800px', + ); }); }); @@ -180,10 +190,10 @@ describe('StyleManager', () => { manager.updateFromConfig(); expect( - element.style.getPropertyValue('--frigate-card-css-box-shadow'), + element.style.getPropertyValue('--advanced-camera-card-css-box-shadow'), ).toBeFalsy(); expect( - element.style.getPropertyValue('--frigate-card-css-border-radius'), + element.style.getPropertyValue('--advanced-camera-card-css-border-radius'), ).toBeFalsy(); }); @@ -205,11 +215,11 @@ describe('StyleManager', () => { manager.updateFromConfig(); - expect(element.style.getPropertyValue('--frigate-card-css-box-shadow')).toEqual( - 'none', - ); expect( - element.style.getPropertyValue('--frigate-card-css-border-radius'), + element.style.getPropertyValue('--advanced-camera-card-css-box-shadow'), + ).toEqual('none'); + expect( + element.style.getPropertyValue('--advanced-camera-card-css-border-radius'), ).toBeFalsy(); }); }); @@ -277,7 +287,7 @@ describe('StyleManager', () => { ['recording' as const], ['snapshot' as const], ['timeline' as const], - ])('%s', (viewName: FrigateCardView) => { + ])('%s', (viewName: AdvancedCameraCardView) => { const api = createCardAPI(); const view = createView({ view: viewName }); vi.mocked(api.getViewManager().getView).mockReturnValue(view); @@ -297,7 +307,7 @@ describe('StyleManager', () => { describe('should be enforced when dynamic in certain views', () => { it.each([['clips' as const], ['recordings' as const], ['snapshots' as const]])( '%s', - (viewName: FrigateCardView) => { + (viewName: AdvancedCameraCardView) => { const api = createCardAPI(); const view = createView({ view: viewName }); vi.mocked(api.getViewManager().getView).mockReturnValue(view); @@ -318,7 +328,7 @@ describe('StyleManager', () => { describe('should use media dimensions in dynamic', () => { it.each([['clips' as const], ['recordings' as const], ['snapshots' as const]])( '%s', - (viewName: FrigateCardView) => { + (viewName: AdvancedCameraCardView) => { const api = createCardAPI(); const view = createView({ view: viewName }); vi.mocked(api.getViewManager().getView).mockReturnValue(view); diff --git a/tests/card-controller/triggers-manager.test.ts b/tests/card-controller/triggers-manager.test.ts index 0ea09bec..b6258ad8 100644 --- a/tests/card-controller/triggers-manager.test.ts +++ b/tests/card-controller/triggers-manager.test.ts @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { CardController } from '../../src/card-controller/controller'; import { TriggersManager } from '../../src/card-controller/triggers-manager'; import { - FrigateCardView, + AdvancedCameraCardView, TriggersOptions, triggersSchema, } from '../../src/config/types'; @@ -36,7 +36,7 @@ const baseTriggersConfig: TriggersOptions = { // function reduces it. const createTriggerAPI = (options?: { config?: Partial; - default?: FrigateCardView; + default?: AdvancedCameraCardView; interaction?: boolean; }): CardController => { const api = createCardAPI(); diff --git a/tests/card-controller/view/factory.test.ts b/tests/card-controller/view/factory.test.ts index df690345..894dc00e 100644 --- a/tests/card-controller/view/factory.test.ts +++ b/tests/card-controller/view/factory.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; import { ViewFactory } from '../../../src/card-controller/view/factory'; import { ViewModifier } from '../../../src/card-controller/view/types'; -import { FrigateCardView, ViewDisplayMode } from '../../../src/config/types'; +import { AdvancedCameraCardView, ViewDisplayMode } from '../../../src/config/types'; import { View } from '../../../src/view/view'; import { createCameraManager, @@ -374,7 +374,7 @@ describe('getViewByParameters', () => { ['recording' as const], ['snapshot' as const], ['live' as const], - ])('%s', (viewName: FrigateCardView) => { + ])('%s', (viewName: AdvancedCameraCardView) => { const api = createPopulatedAPI({ media_viewer: { display: displayConfig, diff --git a/tests/card-controller/view/modifiers/substream-on.test.ts b/tests/card-controller/view/modifiers/substream-on.test.ts index ea82b18a..52d95240 100644 --- a/tests/card-controller/view/modifiers/substream-on.test.ts +++ b/tests/card-controller/view/modifiers/substream-on.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; import { CardController } from '../../../../src/card-controller/controller'; import { SubstreamOnViewModifier } from '../../../../src/card-controller/view/modifiers/substream-on'; -import { RawFrigateCardConfig } from '../../../../src/config/types'; +import { RawAdvancedCameraCardConfig } from '../../../../src/config/types'; import { getStreamCameraID, hasSubstream, @@ -17,7 +17,9 @@ import { createView, } from '../../../test-utils'; -const createAPIWithSubstreams = (config?: RawFrigateCardConfig): CardController => { +const createAPIWithSubstreams = ( + config?: RawAdvancedCameraCardConfig, +): CardController => { const api = createCardAPI(); vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager()); vi.mocked(api.getCameraManager().getStore).mockReturnValue( diff --git a/tests/card-controller/view/test-utils.ts b/tests/card-controller/view/test-utils.ts index 926208a5..8d67dfc5 100644 --- a/tests/card-controller/view/test-utils.ts +++ b/tests/card-controller/view/test-utils.ts @@ -1,6 +1,6 @@ import { vi } from 'vitest'; import { CardController } from '../../../src/card-controller/controller'; -import { RawFrigateCardConfig } from '../../../src/config/types'; +import { RawAdvancedCameraCardConfig } from '../../../src/config/types'; import { createCameraManager, createCapabilities, @@ -9,7 +9,9 @@ import { createStore, } from '../../test-utils'; -export const createPopulatedAPI = (config?: RawFrigateCardConfig): CardController => { +export const createPopulatedAPI = ( + config?: RawAdvancedCameraCardConfig, +): CardController => { const api = createCardAPI(); vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager()); vi.mocked(api.getCameraManager().getStore).mockReturnValue( diff --git a/tests/card-controller/view/view-manager.test.ts b/tests/card-controller/view/view-manager.test.ts index 0cb2461c..3086e32f 100644 --- a/tests/card-controller/view/view-manager.test.ts +++ b/tests/card-controller/view/view-manager.test.ts @@ -1,11 +1,20 @@ import { ViewContext } from 'view'; import { describe, expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; +import { QueryType } from '../../../src/camera-manager/types'; import { ViewFactory } from '../../../src/card-controller/view/factory'; +import { SetQueryViewModifier } from '../../../src/card-controller/view/modifiers/set-query'; +import { + QueryExecutorOptions, + ViewModifier, +} from '../../../src/card-controller/view/types'; import { ViewManager } from '../../../src/card-controller/view/view-manager'; -import { FrigateCardView } from '../../../src/config/types'; +import { ViewQueryExecutor } from '../../../src/card-controller/view/view-query-executor'; +import { AdvancedCameraCardView } from '../../../src/config/types'; import { ViewMedia } from '../../../src/view/media'; +import { EventMediaQueries } from '../../../src/view/media-queries'; import { MediaQueriesResults } from '../../../src/view/media-queries-results'; +import { View } from '../../../src/view/view'; import { createCameraManager, createCapabilities, @@ -13,15 +22,6 @@ import { createStore, createView, } from '../../test-utils'; -import { ViewQueryExecutor } from '../../../src/card-controller/view/view-query-executor'; -import { EventMediaQueries } from '../../../src/view/media-queries'; -import { QueryType } from '../../../src/camera-manager/types'; -import { SetQueryViewModifier } from '../../../src/card-controller/view/modifiers/set-query'; -import { View } from '../../../src/view/view'; -import { - QueryExecutorOptions, - ViewModifier, -} from '../../../src/card-controller/view/types'; describe('should act correctly when view is set', () => { it('basic view', () => { @@ -279,7 +279,7 @@ describe('isViewSupportedByCamera', () => { ['recordings' as const, false], ['timeline' as const, false], ['media' as const, false], - ])('%s', (viewName: FrigateCardView, expected: boolean) => { + ])('%s', (viewName: AdvancedCameraCardView, expected: boolean) => { const api = createCardAPI(); vi.mocked(api.getCameraManager).mockReturnValue(createCameraManager()); vi.mocked(api.getCameraManager().getStore).mockReturnValue( diff --git a/tests/card-controller/view/view-query-executor.test.ts b/tests/card-controller/view/view-query-executor.test.ts index 9abbc384..6936ce9e 100644 --- a/tests/card-controller/view/view-query-executor.test.ts +++ b/tests/card-controller/view/view-query-executor.test.ts @@ -3,7 +3,7 @@ import { mock } from 'vitest-mock-extended'; import { applyViewModifiers } from '../../../src/card-controller/view/modifiers'; import { QueryExecutor } from '../../../src/card-controller/view/query-executor'; import { ViewQueryExecutor } from '../../../src/card-controller/view/view-query-executor'; -import { FrigateCardView } from '../../../src/config/types'; +import { AdvancedCameraCardView } from '../../../src/config/types'; import { EventMediaQueries, RecordingMediaQueries, @@ -271,7 +271,10 @@ describe('ViewQueryExecutor', () => { ['snapshots' as const, 'snapshots' as const], ])( '%s', - async (viewName: FrigateCardView, eventsMediaType: 'clips' | 'snapshots') => { + async ( + viewName: AdvancedCameraCardView, + eventsMediaType: 'clips' | 'snapshots', + ) => { const executor = mock(); const query = new EventMediaQueries(); const queryResults = new MediaQueriesResults(); @@ -310,7 +313,7 @@ describe('ViewQueryExecutor', () => { describe('with a recordings-based view', () => { it.each([['recording' as const], ['recordings' as const]])( '%s', - async (viewName: FrigateCardView) => { + async (viewName: AdvancedCameraCardView) => { const executor = mock(); const query = new RecordingMediaQueries(); const queryResults = new MediaQueriesResults(); diff --git a/tests/components-lib/live/live-controller.test.ts b/tests/components-lib/live/live-controller.test.ts index 00647a21..bf5f2d2e 100644 --- a/tests/components-lib/live/live-controller.test.ts +++ b/tests/components-lib/live/live-controller.test.ts @@ -26,7 +26,7 @@ describe('LiveController', () => { const host = createLitElement(); const parent = createParent({ children: [host] }); const eventListener = vi.fn(); - parent.addEventListener('frigate-card:media:loaded', eventListener); + parent.addEventListener('advanced-camera-card:media:loaded', eventListener); const controller = new LiveController(host); expect(host.addController).toBeCalled(); @@ -70,7 +70,7 @@ describe('LiveController', () => { const host = createLitElement(); const parent = createParent({ children: [host] }); const eventListener = vi.fn(); - parent.addEventListener('frigate-card:media:loaded', eventListener); + parent.addEventListener('advanced-camera-card:media:loaded', eventListener); const controller = new LiveController(host); const mediaLoadedInfo = createMediaLoadedInfo(); diff --git a/tests/components-lib/live/utils/dispatch-live-error.test.ts b/tests/components-lib/live/utils/dispatch-live-error.test.ts index 18993cf4..4553044c 100644 --- a/tests/components-lib/live/utils/dispatch-live-error.test.ts +++ b/tests/components-lib/live/utils/dispatch-live-error.test.ts @@ -5,7 +5,7 @@ import { dispatchLiveErrorEvent } from '../../../../src/components-lib/live/util it('should dispatch live error event', () => { const element = document.createElement('div'); const handler = vi.fn(); - element.addEventListener('frigate-card:live:error', handler); + element.addEventListener('advanced-camera-card:live:error', handler); dispatchLiveErrorEvent(element); expect(handler).toBeCalled(); diff --git a/tests/components-lib/media-actions-controller.test.ts b/tests/components-lib/media-actions-controller.test.ts index 2e39c960..424c5758 100644 --- a/tests/components-lib/media-actions-controller.test.ts +++ b/tests/components-lib/media-actions-controller.test.ts @@ -4,7 +4,7 @@ import { MediaActionsController, MediaActionsControllerOptions, } from '../../src/components-lib/media-actions-controller'; -import { FrigateCardMediaPlayer } from '../../src/types'; +import { AdvancedCameraCardMediaPlayer } from '../../src/types'; import { IntersectionObserverMock, MutationObserverMock, @@ -18,11 +18,11 @@ import { callVisibilityHandler, createTestSlideNodes } from '../utils/embla/test const getPlayer = ( element: HTMLElement, selector: string, -): (HTMLElement & FrigateCardMediaPlayer) | null => { +): (HTMLElement & AdvancedCameraCardMediaPlayer) | null => { return element.querySelector(selector); }; -const createPlayer = (): HTMLElement & FrigateCardMediaPlayer => { +const createPlayer = (): HTMLElement & AdvancedCameraCardMediaPlayer => { const player = document.createElement('video'); player['play'] = vi.fn(); @@ -35,7 +35,7 @@ const createPlayer = (): HTMLElement & FrigateCardMediaPlayer => { player['setControls'] = vi.fn(); player['isPaused'] = vi.fn(); - return player as unknown as HTMLElement & FrigateCardMediaPlayer; + return player as unknown as HTMLElement & AdvancedCameraCardMediaPlayer; }; const createPlayerSlideNodes = (n = 10): HTMLElement[] => { @@ -231,7 +231,7 @@ describe('MediaActionsController', () => { controller.unsetTarget(); getPlayer(children[0], 'video')?.dispatchEvent( - new Event('frigate-card:media:loaded'), + new Event('advanced-camera-card:media:loaded'), ); await flushPromises(); @@ -255,7 +255,7 @@ describe('MediaActionsController', () => { expect(getPlayer(children[0], 'video')?.play).toBeCalledTimes(1); getPlayer(children[0], 'video')?.dispatchEvent( - new Event('frigate-card:media:loaded'), + new Event('advanced-camera-card:media:loaded'), ); await flushPromises(); @@ -277,7 +277,7 @@ describe('MediaActionsController', () => { expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(1); getPlayer(children[0], 'video')?.dispatchEvent( - new Event('frigate-card:media:loaded'), + new Event('advanced-camera-card:media:loaded'), ); await flushPromises(); @@ -299,7 +299,7 @@ describe('MediaActionsController', () => { await controller.setTarget(0, true); getPlayer(children[9], 'video')?.dispatchEvent( - new Event('frigate-card:media:loaded'), + new Event('advanced-camera-card:media:loaded'), ); await flushPromises(); @@ -325,7 +325,7 @@ describe('MediaActionsController', () => { expect(getPlayer(children[0], 'video')?.unmute).toBeCalledTimes(1); getPlayer(children[0], 'video')?.dispatchEvent( - new Event('frigate-card:media:loaded'), + new Event('advanced-camera-card:media:loaded'), ); await flushPromises(); diff --git a/tests/components-lib/media-grid-controller.test.ts b/tests/components-lib/media-grid-controller.test.ts index bac8bad1..0a902138 100644 --- a/tests/components-lib/media-grid-controller.test.ts +++ b/tests/components-lib/media-grid-controller.test.ts @@ -174,7 +174,7 @@ describe('MediaGridController', () => { const controller = createController(slot); const mediaLoadedInfoHandler = vi.fn(); - host.addEventListener('frigate-card:media:loaded', mediaLoadedInfoHandler); + host.addEventListener('advanced-camera-card:media:loaded', mediaLoadedInfoHandler); dispatchExistingMediaLoadedInfoAsEvent(children[0], mediaLoadedInfo); controller.selectCell('0'); @@ -194,7 +194,7 @@ describe('MediaGridController', () => { controller.selectCell('0'); const mediaLoadedInfoHandler = vi.fn(); - host.addEventListener('frigate-card:media:loaded', mediaLoadedInfoHandler); + host.addEventListener('advanced-camera-card:media:loaded', mediaLoadedInfoHandler); dispatchExistingMediaLoadedInfoAsEvent(children[0], mediaLoadedInfo); expect(mediaLoadedInfoHandler).toBeCalledWith( @@ -213,7 +213,7 @@ describe('MediaGridController', () => { controller.selectCell('1'); const mediaLoadedInfoHandler = vi.fn(); - host.addEventListener('frigate-card:media:loaded', mediaLoadedInfoHandler); + host.addEventListener('advanced-camera-card:media:loaded', mediaLoadedInfoHandler); dispatchExistingMediaLoadedInfoAsEvent(children[0], mediaLoadedInfo); // Another element is selected, so the event should not have propagated. @@ -228,8 +228,11 @@ describe('MediaGridController', () => { const unselectedHandler = vi.fn(); const unloadMediaHandler = vi.fn(); - host.addEventListener('frigate-card:media-grid:unselected', unselectedHandler); - host.addEventListener('frigate-card:media:unloaded', unloadMediaHandler); + host.addEventListener( + 'advanced-camera-card:media-grid:unselected', + unselectedHandler, + ); + host.addEventListener('advanced-camera-card:media:unloaded', unloadMediaHandler); controller.selectCell('0'); expect(controller.getSelected()).toBe('0'); @@ -401,9 +404,9 @@ describe('MediaGridController', () => { columnWidth: 245, }), ); - expect(parent.style.getPropertyValue('--frigate-card-grid-column-size')).toBe( - '245px', - ); + expect( + parent.style.getPropertyValue('--advanced-camera-card-grid-column-size'), + ).toBe('245px'); }); it('should respect exact columns', () => { @@ -420,9 +423,9 @@ describe('MediaGridController', () => { columnWidth: 1499, }), ); - expect(parent.style.getPropertyValue('--frigate-card-grid-column-size')).toBe( - '1499px', - ); + expect( + parent.style.getPropertyValue('--advanced-camera-card-grid-column-size'), + ).toBe('1499px'); }); it('should respect selected width factor', () => { @@ -430,13 +433,13 @@ describe('MediaGridController', () => { const controller = createController(parent); controller.setDisplayConfig({ mode: 'grid', grid_selected_width_factor: 3 }); expect( - parent.style.getPropertyValue('--frigate-card-grid-selected-width-factor'), + parent.style.getPropertyValue('--advanced-camera-card-grid-selected-width-factor'), ).toBe('3'); // Setting the same config again should do nothing. controller.setDisplayConfig({ mode: 'grid', grid_selected_width_factor: 3 }); expect( - parent.style.getPropertyValue('--frigate-card-grid-selected-width-factor'), + parent.style.getPropertyValue('--advanced-camera-card-grid-selected-width-factor'), ).toBe('3'); }); @@ -490,9 +493,9 @@ describe('MediaGridController', () => { columnWidth: 245, }), ); - expect(parent.style.getPropertyValue('--frigate-card-grid-column-size')).toBe( - '245px', - ); + expect( + parent.style.getPropertyValue('--advanced-camera-card-grid-column-size'), + ).toBe('245px'); // Clear mock state. vi.mocked(Masonry).mockClear(); @@ -509,9 +512,9 @@ describe('MediaGridController', () => { columnWidth: 599, }), ); - expect(parent.style.getPropertyValue('--frigate-card-grid-column-size')).toBe( - '599px', - ); + expect( + parent.style.getPropertyValue('--advanced-camera-card-grid-column-size'), + ).toBe('599px'); expect(masonry.layout).toBeCalled(); // Clear mock state. diff --git a/tests/components-lib/menu-button-controller.test.ts b/tests/components-lib/menu-button-controller.test.ts index 065ed454..36841e51 100644 --- a/tests/components-lib/menu-button-controller.test.ts +++ b/tests/components-lib/menu-button-controller.test.ts @@ -14,12 +14,12 @@ import { MenuButtonControllerOptions, } from '../../src/components-lib/menu-button-controller'; import { - FrigateCardConfig, - FrigateCardView, + AdvancedCameraCardConfig, + AdvancedCameraCardView, MenuItem, ViewDisplayMode, } from '../../src/config/types'; -import { FrigateCardMediaPlayer } from '../../src/types'; +import { AdvancedCameraCardMediaPlayer } from '../../src/types'; import { createGeneralAction } from '../../src/utils/action'; import { ViewMedia } from '../../src/view/media'; import { MediaQueriesResults } from '../../src/view/media-queries-results'; @@ -45,7 +45,7 @@ const calculateButtons = ( controller: MenuButtonController, options?: MenuButtonControllerOptions & { hass?: HomeAssistant; - config?: FrigateCardConfig; + config?: AdvancedCameraCardConfig; cameraManager?: CameraManager; view?: View | null; viewManager?: ViewManager; @@ -72,7 +72,7 @@ const calculateButtons = ( describe('MenuButtonController', () => { let controller: MenuButtonController; const dynamicButton: MenuItem = { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:alpha-a-circle', title: 'Dynamic button', }; @@ -82,17 +82,17 @@ describe('MenuButtonController', () => { controller = new MenuButtonController(); }); - describe('should have default menu button', () => { + describe('should have iris menu button', () => { it('with hidden menu style', () => { const buttons = calculateButtons(controller); expect(buttons).toContainEqual({ - icon: 'frigate', + icon: 'iris', enabled: true, permanent: true, priority: 50, - type: 'custom:frigate-card-menu-icon', - title: 'Frigate menu / Default view', + type: 'custom:advanced-camera-card-menu-icon', + title: 'Iris / Default View / Unhide menu', tap_action: createGeneralAction('menu_toggle'), hold_action: createGeneralAction('diagnostics'), }); @@ -104,12 +104,12 @@ describe('MenuButtonController', () => { }); expect(buttons).toContainEqual({ - icon: 'frigate', + icon: 'iris', enabled: true, permanent: true, priority: 50, - type: 'custom:frigate-card-menu-icon', - title: 'Frigate menu / Default view', + type: 'custom:advanced-camera-card-menu-icon', + title: 'Iris / Default View / Unhide menu', tap_action: createGeneralAction('default'), hold_action: createGeneralAction('diagnostics'), }); @@ -138,7 +138,7 @@ describe('MenuButtonController', () => { icon: 'mdi:video-switch', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-submenu', + type: 'custom:advanced-camera-card-menu-submenu', title: 'Cameras', items: [ { @@ -150,7 +150,7 @@ describe('MenuButtonController', () => { selected: true, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'camera_select', + advanced_camera_card_action: 'camera_select', camera: 'camera-1', }, }, @@ -163,7 +163,7 @@ describe('MenuButtonController', () => { selected: false, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'camera_select', + advanced_camera_card_action: 'camera_select', camera: 'camera-2', }, }, @@ -252,10 +252,10 @@ describe('MenuButtonController', () => { title: 'Substream(s)', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', tap_action: { action: 'fire-dom-event', - frigate_card_action: 'live_substream_on', + advanced_camera_card_action: 'live_substream_on', }, }); }); @@ -289,14 +289,14 @@ describe('MenuButtonController', () => { expect(buttons).toContainEqual({ icon: 'mdi:video-input-component', - style: { color: 'var(--frigate-card-menu-button-active-color)' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, title: 'Substream(s)', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', tap_action: { action: 'fire-dom-event', - frigate_card_action: 'live_substream_off', + advanced_camera_card_action: 'live_substream_off', }, }); }); @@ -354,7 +354,7 @@ describe('MenuButtonController', () => { style: {}, enabled: true, priority: 50, - type: 'custom:frigate-card-menu-submenu', + type: 'custom:advanced-camera-card-menu-submenu', items: [ { enabled: true, @@ -365,7 +365,7 @@ describe('MenuButtonController', () => { selected: true, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'live_substream_select', + advanced_camera_card_action: 'live_substream_select', camera: 'camera-1', }, }, @@ -378,7 +378,7 @@ describe('MenuButtonController', () => { selected: false, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'live_substream_select', + advanced_camera_card_action: 'live_substream_select', camera: 'camera-2', }, }, @@ -391,7 +391,7 @@ describe('MenuButtonController', () => { selected: false, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'live_substream_select', + advanced_camera_card_action: 'live_substream_select', camera: 'camera-3', }, }, @@ -443,10 +443,10 @@ describe('MenuButtonController', () => { expect(buttons).toContainEqual({ icon: 'mdi:video-input-component', title: 'Substream(s)', - style: { color: 'var(--frigate-card-menu-button-active-color)' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, enabled: true, priority: 50, - type: 'custom:frigate-card-menu-submenu', + type: 'custom:advanced-camera-card-menu-submenu', items: [ { enabled: true, @@ -457,7 +457,7 @@ describe('MenuButtonController', () => { selected: false, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'live_substream_select', + advanced_camera_card_action: 'live_substream_select', camera: 'camera-1', }, }, @@ -472,7 +472,7 @@ describe('MenuButtonController', () => { selected: true, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'live_substream_select', + advanced_camera_card_action: 'live_substream_select', camera: 'camera-2', }, }, @@ -485,7 +485,7 @@ describe('MenuButtonController', () => { selected: false, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'live_substream_select', + advanced_camera_card_action: 'live_substream_select', camera: 'camera-3', }, }, @@ -507,10 +507,10 @@ describe('MenuButtonController', () => { icon: 'mdi:cctv', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Live view', - style: { color: 'var(--frigate-card-menu-button-active-color)' }, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'live' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'live' }, }); }); @@ -526,10 +526,10 @@ describe('MenuButtonController', () => { icon: 'mdi:cctv', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Live view', style: {}, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'live' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'live' }, }); }); @@ -561,11 +561,11 @@ describe('MenuButtonController', () => { icon: 'mdi:filmstrip', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Clips gallery', - style: { color: 'var(--frigate-card-menu-button-active-color)' }, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'clips' }, - hold_action: { action: 'fire-dom-event', frigate_card_action: 'clip' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'clips' }, + hold_action: { action: 'fire-dom-event', advanced_camera_card_action: 'clip' }, }); }); @@ -580,11 +580,11 @@ describe('MenuButtonController', () => { icon: 'mdi:filmstrip', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Clips gallery', style: {}, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'clips' }, - hold_action: { action: 'fire-dom-event', frigate_card_action: 'clip' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'clips' }, + hold_action: { action: 'fire-dom-event', advanced_camera_card_action: 'clip' }, }); }); @@ -614,11 +614,17 @@ describe('MenuButtonController', () => { icon: 'mdi:camera', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Snapshots gallery', - style: { color: 'var(--frigate-card-menu-button-active-color)' }, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'snapshots' }, - hold_action: { action: 'fire-dom-event', frigate_card_action: 'snapshot' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, + tap_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'snapshots', + }, + hold_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'snapshot', + }, }); }); @@ -633,11 +639,17 @@ describe('MenuButtonController', () => { icon: 'mdi:camera', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Snapshots gallery', style: {}, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'snapshots' }, - hold_action: { action: 'fire-dom-event', frigate_card_action: 'snapshot' }, + tap_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'snapshots', + }, + hold_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'snapshot', + }, }); }); @@ -669,11 +681,17 @@ describe('MenuButtonController', () => { icon: 'mdi:album', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Recordings gallery', - style: { color: 'var(--frigate-card-menu-button-active-color)' }, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'recordings' }, - hold_action: { action: 'fire-dom-event', frigate_card_action: 'recording' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, + tap_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'recordings', + }, + hold_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'recording', + }, }); }); @@ -688,11 +706,17 @@ describe('MenuButtonController', () => { icon: 'mdi:album', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Recordings gallery', style: {}, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'recordings' }, - hold_action: { action: 'fire-dom-event', frigate_card_action: 'recording' }, + tap_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'recordings', + }, + hold_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'recording', + }, }); }); @@ -725,10 +749,10 @@ describe('MenuButtonController', () => { icon: 'mdi:image', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Static image', - style: { color: 'var(--frigate-card-menu-button-active-color)' }, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'image' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'image' }, }); }); @@ -745,10 +769,10 @@ describe('MenuButtonController', () => { icon: 'mdi:image', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Static image', style: {}, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'image' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'image' }, }); }); @@ -780,10 +804,13 @@ describe('MenuButtonController', () => { icon: 'mdi:chart-gantt', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Timeline view', - style: { color: 'var(--frigate-card-menu-button-active-color)' }, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'timeline' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, + tap_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'timeline', + }, }); }); @@ -799,10 +826,13 @@ describe('MenuButtonController', () => { icon: 'mdi:chart-gantt', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Timeline view', style: {}, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'timeline' }, + tap_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'timeline', + }, }); }); @@ -844,9 +874,12 @@ describe('MenuButtonController', () => { icon: 'mdi:download', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Download', - tap_action: { action: 'fire-dom-event', frigate_card_action: 'download' }, + tap_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'download', + }, }); }); @@ -909,9 +942,9 @@ describe('MenuButtonController', () => { icon: 'mdi:web', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Camera user interface', - tap_action: { action: 'fire-dom-event', frigate_card_action: 'camera_ui' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'camera_ui' }, }); }); @@ -935,19 +968,19 @@ describe('MenuButtonController', () => { icon: 'mdi:microphone', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Microphone', style: { animation: 'pulse 3s infinite', - color: 'var(--frigate-card-menu-button-critical-color)', + color: 'var(--advanced-camera-card-menu-button-critical-color)', }, start_tap_action: { action: 'fire-dom-event', - frigate_card_action: 'microphone_unmute', + advanced_camera_card_action: 'microphone_unmute', }, end_tap_action: { action: 'fire-dom-event', - frigate_card_action: 'microphone_mute', + advanced_camera_card_action: 'microphone_mute', }, }); }); @@ -989,7 +1022,7 @@ describe('MenuButtonController', () => { icon: 'mdi:microphone-message-off', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Microphone', style: {}, }); @@ -1014,16 +1047,16 @@ describe('MenuButtonController', () => { icon: 'mdi:microphone-off', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Microphone', style: {}, start_tap_action: { action: 'fire-dom-event', - frigate_card_action: 'microphone_unmute', + advanced_camera_card_action: 'microphone_unmute', }, end_tap_action: { action: 'fire-dom-event', - frigate_card_action: 'microphone_mute', + advanced_camera_card_action: 'microphone_mute', }, }); }); @@ -1047,7 +1080,7 @@ describe('MenuButtonController', () => { icon: 'mdi:microphone-message-off', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Microphone', style: {}, }); @@ -1075,12 +1108,12 @@ describe('MenuButtonController', () => { icon: 'mdi:microphone-off', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Microphone', style: {}, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'microphone_unmute', + advanced_camera_card_action: 'microphone_unmute', }, }); }); @@ -1107,15 +1140,15 @@ describe('MenuButtonController', () => { icon: 'mdi:microphone', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Microphone', style: { animation: 'pulse 3s infinite', - color: 'var(--frigate-card-menu-button-critical-color)', + color: 'var(--advanced-camera-card-menu-button-critical-color)', }, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'microphone_mute', + advanced_camera_card_action: 'microphone_mute', }, }); }); @@ -1140,9 +1173,12 @@ describe('MenuButtonController', () => { icon: 'mdi:fullscreen', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Fullscreen', - tap_action: { action: 'fire-dom-event', frigate_card_action: 'fullscreen' }, + tap_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'fullscreen', + }, style: {}, }); }); @@ -1159,10 +1195,13 @@ describe('MenuButtonController', () => { icon: 'mdi:fullscreen-exit', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Fullscreen', - tap_action: { action: 'fire-dom-event', frigate_card_action: 'fullscreen' }, - style: { color: 'var(--frigate-card-menu-button-active-color)' }, + tap_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'fullscreen', + }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, }); }); @@ -1188,9 +1227,9 @@ describe('MenuButtonController', () => { icon: 'mdi:arrow-expand-all', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Expand', - tap_action: { action: 'fire-dom-event', frigate_card_action: 'expand' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'expand' }, style: {}, }); }); @@ -1202,10 +1241,10 @@ describe('MenuButtonController', () => { icon: 'mdi:arrow-collapse-all', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Expand', - tap_action: { action: 'fire-dom-event', frigate_card_action: 'expand' }, - style: { color: 'var(--frigate-card-menu-button-active-color)' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'expand' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, }); }); }); @@ -1240,7 +1279,7 @@ describe('MenuButtonController', () => { icon: 'mdi:cast', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-submenu', + type: 'custom:advanced-camera-card-menu-submenu', title: 'Send to media player', items: [ { @@ -1252,13 +1291,13 @@ describe('MenuButtonController', () => { disabled: false, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'media_player', + advanced_camera_card_action: 'media_player', media_player: 'media_player.tv', media_player_action: 'play', }, hold_action: { action: 'fire-dom-event', - frigate_card_action: 'media_player', + advanced_camera_card_action: 'media_player', media_player: 'media_player.tv', media_player_action: 'stop', }, @@ -1293,7 +1332,7 @@ describe('MenuButtonController', () => { icon: 'mdi:cast', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-submenu', + type: 'custom:advanced-camera-card-menu-submenu', title: 'Send to media player', items: [ { @@ -1310,7 +1349,7 @@ describe('MenuButtonController', () => { }); it('should have pause button', () => { - const player = mock(); + const player = mock(); const buttons = calculateButtons(controller, { currentMediaLoadedInfo: createMediaLoadedInfo({ capabilities: { @@ -1324,14 +1363,14 @@ describe('MenuButtonController', () => { icon: 'mdi:pause', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Play / Pause', - tap_action: { action: 'fire-dom-event', frigate_card_action: 'pause' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'pause' }, }); }); it('should have play button', () => { - const player = mock(); + const player = mock(); player.isPaused.mockReturnValue(true); const buttons = calculateButtons(controller, { currentMediaLoadedInfo: createMediaLoadedInfo({ @@ -1346,14 +1385,14 @@ describe('MenuButtonController', () => { icon: 'mdi:play', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Play / Pause', - tap_action: { action: 'fire-dom-event', frigate_card_action: 'play' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'play' }, }); }); it('should have mute button', () => { - const player = mock(); + const player = mock(); const buttons = calculateButtons(controller, { currentMediaLoadedInfo: createMediaLoadedInfo({ capabilities: { @@ -1367,14 +1406,14 @@ describe('MenuButtonController', () => { icon: 'mdi:volume-high', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Mute / Unmute', - tap_action: { action: 'fire-dom-event', frigate_card_action: 'mute' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'mute' }, }); }); it('should have unmute button', () => { - const player = mock(); + const player = mock(); player.isMuted.mockReturnValue(true); const buttons = calculateButtons(controller, { currentMediaLoadedInfo: createMediaLoadedInfo({ @@ -1389,16 +1428,16 @@ describe('MenuButtonController', () => { icon: 'mdi:volume-off', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Mute / Unmute', - tap_action: { action: 'fire-dom-event', frigate_card_action: 'unmute' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'unmute' }, }); }); it('should have screenshot button', () => { const buttons = calculateButtons(controller, { currentMediaLoadedInfo: createMediaLoadedInfo({ - player: mock(), + player: mock(), }), }); @@ -1406,9 +1445,12 @@ describe('MenuButtonController', () => { icon: 'mdi:monitor-screenshot', enabled: false, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: 'Screenshot', - tap_action: { action: 'fire-dom-event', frigate_card_action: 'screenshot' }, + tap_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'screenshot', + }, }); }); @@ -1435,18 +1477,18 @@ describe('MenuButtonController', () => { icon: displayMode === 'single' ? 'mdi:grid' : 'mdi:grid-off', enabled: true, priority: 50, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', title: displayMode === 'grid' ? 'Show single media viewer' : 'Show media viewer for each camera in a grid', style: displayMode === 'grid' - ? { color: 'var(--frigate-card-menu-button-active-color)' } + ? { color: 'var(--advanced-camera-card-menu-button-active-color)' } : {}, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'display_mode_select', + advanced_camera_card_action: 'display_mode_select', display_mode: displayMode === 'single' ? 'grid' : 'single', }, }); @@ -1517,15 +1559,15 @@ describe('MenuButtonController', () => { icon: 'mdi:pan', priority: 50, style: { - color: 'var(--frigate-card-menu-button-active-color)', + color: 'var(--advanced-camera-card-menu-button-active-color)', }, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'ptz_controls', + advanced_camera_card_action: 'ptz_controls', enabled: false, }, title: 'Show PTZ controls', - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', }); }); @@ -1553,11 +1595,11 @@ describe('MenuButtonController', () => { style: {}, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'ptz_controls', + advanced_camera_card_action: 'ptz_controls', enabled: true, }, title: 'Show PTZ controls', - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', }); }); @@ -1583,15 +1625,15 @@ describe('MenuButtonController', () => { icon: 'mdi:pan', priority: 50, style: { - color: 'var(--frigate-card-menu-button-active-color)', + color: 'var(--advanced-camera-card-menu-button-active-color)', }, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'ptz_controls', + advanced_camera_card_action: 'ptz_controls', enabled: false, }, title: 'Show PTZ controls', - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', }); }); @@ -1624,15 +1666,15 @@ describe('MenuButtonController', () => { icon: 'mdi:pan', priority: 50, style: { - color: 'var(--frigate-card-menu-button-active-color)', + color: 'var(--advanced-camera-card-menu-button-active-color)', }, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'ptz_controls', + advanced_camera_card_action: 'ptz_controls', enabled: false, }, title: 'Show PTZ controls', - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', }); }); }); @@ -1648,7 +1690,7 @@ describe('MenuButtonController', () => { ])( '%s view with isDefault %s', ( - viewName: FrigateCardView, + viewName: AdvancedCameraCardView, isDefault: boolean | undefined, expectedResult: boolean, ) => { @@ -1695,11 +1737,11 @@ describe('MenuButtonController', () => { priority: 50, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', target_id: targetID, }, title: 'PTZ Home', - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', }); } else { expect(buttons).not.toContainEqual( @@ -1747,7 +1789,7 @@ describe('MenuButtonController', () => { }); }); - it('with non-Frigate fire-dom-event action', () => { + it('with non-advanced-camera-card fire-dom-event action', () => { const button: MenuItem = { ...dynamicButton, tap_action: { action: 'fire-dom-event' }, @@ -1761,37 +1803,40 @@ describe('MenuButtonController', () => { }); }); - it('with Frigate view action', () => { + it('with advanced camera card view action', () => { const button: MenuItem = { ...dynamicButton, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'clips' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'clips' }, }; const view = createView({ view: 'clips' }); controller.addDynamicMenuButton(button); expect(calculateButtons(controller, { view: view })).toContainEqual({ ...button, - style: { color: 'var(--frigate-card-menu-button-active-color)' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, }); }); - it('with Frigate default action', () => { + it('with advanced camera card default action', () => { const button: MenuItem = { ...dynamicButton, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'default' }, + tap_action: { action: 'fire-dom-event', advanced_camera_card_action: 'default' }, }; controller.addDynamicMenuButton(button); expect(calculateButtons(controller)).toContainEqual({ ...button, - style: { color: 'var(--frigate-card-menu-button-active-color)' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, }); }); it('with fullscreen action', () => { const button: MenuItem = { ...dynamicButton, - tap_action: { action: 'fire-dom-event', frigate_card_action: 'fullscreen' }, + tap_action: { + action: 'fire-dom-event', + advanced_camera_card_action: 'fullscreen', + }, }; controller.addDynamicMenuButton(button); const fullscreenManager = mock(); @@ -1800,7 +1845,7 @@ describe('MenuButtonController', () => { expect(calculateButtons(controller, { fullscreenManager })).toContainEqual({ ...button, - style: { color: 'var(--frigate-card-menu-button-active-color)' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, }); }); @@ -1809,7 +1854,7 @@ describe('MenuButtonController', () => { ...dynamicButton, tap_action: { action: 'fire-dom-event', - frigate_card_action: 'camera_select', + advanced_camera_card_action: 'camera_select', camera: 'foo', }, }; @@ -1818,7 +1863,7 @@ describe('MenuButtonController', () => { expect(calculateButtons(controller, { view: view })).toContainEqual({ ...button, - style: { color: 'var(--frigate-card-menu-button-active-color)' }, + style: { color: 'var(--advanced-camera-card-menu-button-active-color)' }, }); }); @@ -1827,7 +1872,7 @@ describe('MenuButtonController', () => { ...dynamicButton, tap_action: [ { action: 'fire-dom-event' }, - { action: 'fire-dom-event', frigate_card_action: 'clips' }, + { action: 'fire-dom-event', advanced_camera_card_action: 'clips' }, ], }; const view = createView({ camera: 'clips' }); diff --git a/tests/components-lib/menu-controller.test.ts b/tests/components-lib/menu-controller.test.ts index f22ad20f..58cd8f2d 100644 --- a/tests/components-lib/menu-controller.test.ts +++ b/tests/components-lib/menu-controller.test.ts @@ -18,7 +18,7 @@ describe('MenuController', () => { }; const menuToggleAction = { action: 'fire-dom-event' as const, - frigate_card_action: 'menu_toggle' as const, + advanced_camera_card_action: 'menu_toggle' as const, }; const tapActionConfig = { camera_entity: 'foo', @@ -46,7 +46,9 @@ describe('MenuController', () => { controller.setMenuConfig(config); expect(controller.getMenuConfig()).toBe(config); - expect(host.style.getPropertyValue('--frigate-card-menu-button-size')).toBe('21px'); + expect(host.style.getPropertyValue('--advanced-camera-card-menu-button-size')).toBe( + '21px', + ); expect(host.getAttribute('data-style')).toBe('hover'); expect(host.getAttribute('data-position')).toBe('left'); expect(host.getAttribute('data-alignment')).toBe('top'); @@ -79,24 +81,24 @@ describe('MenuController', () => { controller.setButtons([ { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:cow', priority: 20, alignment: 'matching', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:goat', alignment: 'matching', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:chicken', priority: 40, alignment: 'matching', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:horse', priority: 40, alignment: 'matching', @@ -105,7 +107,7 @@ describe('MenuController', () => { permanent: true, }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:sheep', priority: 30, alignment: 'matching', @@ -114,13 +116,13 @@ describe('MenuController', () => { expect(controller.getButtons('matching')).toEqual([ { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:chicken', priority: 40, alignment: 'matching', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:horse', priority: 40, alignment: 'matching', @@ -130,16 +132,16 @@ describe('MenuController', () => { alignment: 'matching', icon: 'mdi:sheep', priority: 30, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', }, { alignment: 'matching', icon: 'mdi:cow', priority: 20, - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:goat', alignment: 'matching', }, @@ -156,19 +158,19 @@ describe('MenuController', () => { controller.setExpanded(true); controller.setButtons([ { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:cow', priority: 99, alignment: 'matching', }, { - type: 'custom:frigate-card-menu-icon', - icon: 'frigate', + type: 'custom:advanced-camera-card-menu-icon', + icon: 'iris', alignment: 'matching', permanent: true, }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:sheep', priority: 100, alignment: 'matching', @@ -177,19 +179,19 @@ describe('MenuController', () => { expect(controller.getButtons('matching')).toEqual([ { - type: 'custom:frigate-card-menu-icon', - icon: 'frigate', + type: 'custom:advanced-camera-card-menu-icon', + icon: 'iris', alignment: 'matching', permanent: true, }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:sheep', priority: 100, alignment: 'matching', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:cow', priority: 99, alignment: 'matching', @@ -207,25 +209,25 @@ describe('MenuController', () => { controller.setExpanded(false); controller.setButtons([ { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:cow', priority: 100, alignment: 'matching', }, { - type: 'custom:frigate-card-menu-icon', - icon: 'frigate', + type: 'custom:advanced-camera-card-menu-icon', + icon: 'iris', alignment: 'matching', permanent: true, }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:sheep', priority: 100, alignment: 'matching', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:cow', alignment: 'matching', priority: 100, @@ -235,15 +237,15 @@ describe('MenuController', () => { expect(controller.getButtons('matching')).toEqual([ { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:cow', alignment: 'matching', priority: 100, permanent: true, }, { - type: 'custom:frigate-card-menu-icon', - icon: 'frigate', + type: 'custom:advanced-camera-card-menu-icon', + icon: 'iris', alignment: 'matching', permanent: true, }, @@ -256,29 +258,29 @@ describe('MenuController', () => { const controller = new MenuController(createLitElement()); controller.setButtons([ { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:cow', alignment: 'opposing', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:sheep', alignment: 'matching', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:cow', }, ]); expect(controller.getButtons('matching')).toEqual([ { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:sheep', alignment: 'matching', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:cow', }, ]); @@ -288,16 +290,16 @@ describe('MenuController', () => { const controller = new MenuController(createLitElement()); controller.setButtons([ { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:cow', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:sheep', enabled: false, }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:goat', enabled: true, }, @@ -305,11 +307,11 @@ describe('MenuController', () => { expect(controller.getButtons('matching')).toEqual([ { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:cow', }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:goat', enabled: true, }, @@ -326,20 +328,20 @@ describe('MenuController', () => { controller.setButtons([ { - type: 'custom:frigate-card-menu-icon', - icon: 'frigate', + type: 'custom:advanced-camera-card-menu-icon', + icon: 'iris', permanent: true, }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:sheep', }, ]); expect(controller.getButtons('matching')).toEqual([ { - type: 'custom:frigate-card-menu-icon', - icon: 'frigate', + type: 'custom:advanced-camera-card-menu-icon', + icon: 'iris', permanent: true, }, ]); @@ -348,12 +350,12 @@ describe('MenuController', () => { expect(controller.getButtons('matching')).toEqual([ { - type: 'custom:frigate-card-menu-icon', - icon: 'frigate', + type: 'custom:advanced-camera-card-menu-icon', + icon: 'iris', permanent: true, }, { - type: 'custom:frigate-card-menu-icon', + type: 'custom:advanced-camera-card-menu-icon', icon: 'mdi:sheep', }, ]); @@ -370,7 +372,7 @@ describe('MenuController', () => { it('should execute simple action in non-hidden menu', () => { const host = createLitElement(); const handler = vi.fn(); - host.addEventListener('frigate-card:action:execution-request', handler); + host.addEventListener('advanced-camera-card:action:execution-request', handler); const controller = new MenuController(host); @@ -386,7 +388,7 @@ describe('MenuController', () => { it('should execute simple action in with config in event', () => { const host = createLitElement(); const handler = vi.fn(); - host.addEventListener('frigate-card:action:execution-request', handler); + host.addEventListener('advanced-camera-card:action:execution-request', handler); const controller = new MenuController(host); @@ -401,7 +403,7 @@ describe('MenuController', () => { it('should execute simple array of actions in non-hidden menu', () => { const host = createLitElement(); const handler = vi.fn(); - host.addEventListener('frigate-card:action:execution-request', handler); + host.addEventListener('advanced-camera-card:action:execution-request', handler); const controller = new MenuController(host); diff --git a/tests/components-lib/message/dispatch.test.ts b/tests/components-lib/message/dispatch.test.ts index c81e4299..c6959f19 100644 --- a/tests/components-lib/message/dispatch.test.ts +++ b/tests/components-lib/message/dispatch.test.ts @@ -1,14 +1,14 @@ import { expect, it, vi } from 'vitest'; -import { dispatchFrigateCardErrorEvent } from '../../../src/components-lib/message/dispatch'; -import { FrigateCardError } from '../../../src/types'; +import { dispatchAdvancedCameraCardErrorEvent } from '../../../src/components-lib/message/dispatch'; +import { AdvancedCameraCardError } from '../../../src/types'; // @vitest-environment jsdom it('should ignore non-error', () => { const element = document.createElement('div'); const handler = vi.fn(); - element.addEventListener('frigate-card:message', handler); + element.addEventListener('advanced-camera-card:message', handler); - dispatchFrigateCardErrorEvent(element, 'NOT_FRIGATE_EVENT'); + dispatchAdvancedCameraCardErrorEvent(element, 'NOT_ADVANCED_CAMERA_CARD_EVENT'); expect(handler).not.toBeCalled(); }); @@ -16,9 +16,9 @@ it('should ignore non-error', () => { it('should dispatch error', () => { const element = document.createElement('div'); const handler = vi.fn(); - element.addEventListener('frigate-card:message', handler); + element.addEventListener('advanced-camera-card:message', handler); - dispatchFrigateCardErrorEvent(element, new Error('ERROR')); + dispatchAdvancedCameraCardErrorEvent(element, new Error('ERROR')); expect(handler).toBeCalledWith( expect.objectContaining({ @@ -32,9 +32,12 @@ it('should dispatch error', () => { it('should dispatch error with context', () => { const element = document.createElement('div'); const handler = vi.fn(); - element.addEventListener('frigate-card:message', handler); + element.addEventListener('advanced-camera-card:message', handler); - dispatchFrigateCardErrorEvent(element, new FrigateCardError('ERROR', 'CONTEXT')); + dispatchAdvancedCameraCardErrorEvent( + element, + new AdvancedCameraCardError('ERROR', 'CONTEXT'), + ); expect(handler).toBeCalledWith( expect.objectContaining({ diff --git a/tests/components-lib/ptz/ptz-controller.test.ts b/tests/components-lib/ptz/ptz-controller.test.ts index 5d5f9519..341ca2f4 100644 --- a/tests/components-lib/ptz/ptz-controller.test.ts +++ b/tests/components-lib/ptz/ptz-controller.test.ts @@ -172,13 +172,13 @@ describe('PTZController', () => { expect(controller.getPTZActions()?.[actionName]).toEqual({ start_tap_action: { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', ptz_action: actionName, ptz_phase: 'start', }, end_tap_action: { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', ptz_action: actionName, ptz_phase: 'stop', }, @@ -211,7 +211,7 @@ describe('PTZController', () => { expect(controller.getPTZActions()['home']).toEqual({ tap_action: { action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', }, }); }); @@ -229,7 +229,7 @@ describe('PTZController', () => { const element = document.createElement('div'); const handler = vi.fn(); - element.addEventListener('frigate-card:action:execution-request', handler); + element.addEventListener('advanced-camera-card:action:execution-request', handler); const controller = new PTZController(element); controller.setCamera(); @@ -251,7 +251,7 @@ describe('PTZController', () => { it('should not call action without actions config', () => { const element = document.createElement('div'); const handler = vi.fn(); - element.addEventListener('frigate-card:action:execution-request', handler); + element.addEventListener('advanced-camera-card:action:execution-request', handler); const controller = new PTZController(element); controller.setCamera(); @@ -265,7 +265,7 @@ describe('PTZController', () => { it('should not call action without hass', () => { const element = document.createElement('div'); const handler = vi.fn(); - element.addEventListener('frigate-card:action:execution-request', handler); + element.addEventListener('advanced-camera-card:action:execution-request', handler); const controller = new PTZController(element); controller.setCamera(); diff --git a/tests/components-lib/status-bar-controller.test.ts b/tests/components-lib/status-bar-controller.test.ts index c547219d..198ab32c 100644 --- a/tests/components-lib/status-bar-controller.test.ts +++ b/tests/components-lib/status-bar-controller.test.ts @@ -22,9 +22,9 @@ describe('StatusBarController', () => { controller.setConfig(config); expect(controller.getConfig()).toEqual(config); - expect(host.style.getPropertyValue('--frigate-card-status-bar-height')).toBe( - '50px', - ); + expect( + host.style.getPropertyValue('--advanced-camera-card-status-bar-height'), + ).toBe('50px'); expect(host.getAttribute('data-style')).toBe('hover'); expect(host.getAttribute('data-position')).toBe('top'); expect(host.requestUpdate).toHaveBeenCalled(); @@ -79,7 +79,7 @@ describe('StatusBarController', () => { const controller = new StatusBarController(host); const items = [ { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Test', }, ]; @@ -92,22 +92,22 @@ describe('StatusBarController', () => { const host = createLitElement(); const controller = new StatusBarController(host); const item1 = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 1', priority: 40, }; const item2 = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 2', priority: 10, }; const item3 = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 3', priority: 60, }; const item4 = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 4', priority: undefined, }; @@ -120,12 +120,12 @@ describe('StatusBarController', () => { const host = createLitElement(); const controller = new StatusBarController(host); const item1 = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 1', priority: 100, }; const exclusiveItem = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 2', priority: 1, exclusive: true, @@ -140,12 +140,12 @@ describe('StatusBarController', () => { const host = createLitElement(); const controller = new StatusBarController(host); const insufficientItem = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 1', sufficient: false, }; const sufficientItem = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 2', sufficient: true, }; @@ -159,12 +159,12 @@ describe('StatusBarController', () => { const host = createLitElement(); const controller = new StatusBarController(host); const insufficientItem = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 1', sufficient: false, }; const sufficientItem = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 2', sufficient: false, }; @@ -188,7 +188,7 @@ describe('StatusBarController', () => { ); const sufficientItem = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 1', priority: 100, sufficient: true, @@ -210,7 +210,7 @@ describe('StatusBarController', () => { ); const insufficientItem = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 1', priority: 100, sufficient: false, @@ -231,19 +231,19 @@ describe('StatusBarController', () => { ); const sufficientString = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'String', priority: 100, sufficient: true, }; const sufficientIcon = { - type: 'custom:frigate-card-status-bar-icon', + type: 'custom:advanced-camera-card-status-bar-icon', icon: 'Icon', priority: 100, sufficient: true, }; const sufficientImage = { - type: 'custom:frigate-card-status-bar-image', + type: 'custom:advanced-camera-card-status-bar-image', image: 'Image', priority: 100, sufficient: true, @@ -275,7 +275,7 @@ describe('StatusBarController', () => { ); const sufficientItem = { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item 1', priority: 100, sufficient: true, @@ -297,7 +297,7 @@ describe('StatusBarController', () => { it('should bail without action', () => { const host = createLitElement(); const handler = vi.fn(); - host.addEventListener('frigate-card:action:execution-request', handler); + host.addEventListener('advanced-camera-card:action:execution-request', handler); const controller = new StatusBarController(host); controller.actionHandler(createInteractionEvent('tap')); @@ -307,7 +307,7 @@ describe('StatusBarController', () => { it('should request action execution', () => { const host = createLitElement(); const handler = vi.fn(); - host.addEventListener('frigate-card:action:execution-request', handler); + host.addEventListener('advanced-camera-card:action:execution-request', handler); const controller = new StatusBarController(host); diff --git a/tests/components-lib/zoom/zoom-controller.test.ts b/tests/components-lib/zoom/zoom-controller.test.ts index ed4a3689..1ff1c8f3 100644 --- a/tests/components-lib/zoom/zoom-controller.test.ts +++ b/tests/components-lib/zoom/zoom-controller.test.ts @@ -231,8 +231,8 @@ describe('ZoomController', () => { const element = document.createElement('div'); const zoomedFunc = vi.fn(); const unzoomedFunc = vi.fn(); - element.addEventListener('frigate-card:zoom:zoomed', zoomedFunc); - element.addEventListener('frigate-card:zoom:unzoomed', unzoomedFunc); + element.addEventListener('advanced-camera-card:zoom:zoomed', zoomedFunc); + element.addEventListener('advanced-camera-card:zoom:unzoomed', unzoomedFunc); vi.mocked(Panzoom).mockReturnValueOnce(createMockPanZoom()); createAndRegisterZoom(element); @@ -267,8 +267,8 @@ describe('ZoomController', () => { const element = document.createElement('div'); const zoomedFunc = vi.fn(); const unzoomedFunc = vi.fn(); - element.addEventListener('frigate-card:zoom:zoomed', zoomedFunc); - element.addEventListener('frigate-card:zoom:unzoomed', unzoomedFunc); + element.addEventListener('advanced-camera-card:zoom:zoomed', zoomedFunc); + element.addEventListener('advanced-camera-card:zoom:unzoomed', unzoomedFunc); vi.mocked(Panzoom).mockReturnValueOnce(createMockPanZoom()); createAndRegisterZoom(element); @@ -312,7 +312,7 @@ describe('ZoomController', () => { setElementToDefaultCardSize(element); const changeFunc = vi.fn(); - element.addEventListener('frigate-card:zoom:change', changeFunc); + element.addEventListener('advanced-camera-card:zoom:change', changeFunc); vi.mocked(Panzoom).mockReturnValueOnce(createMockPanZoom()); createAndRegisterZoom(element); @@ -357,7 +357,7 @@ describe('ZoomController', () => { setElementToDefaultCardSize(element); const changeFunc = vi.fn(); - element.addEventListener('frigate-card:zoom:change', changeFunc); + element.addEventListener('advanced-camera-card:zoom:change', changeFunc); vi.mocked(Panzoom).mockReturnValueOnce(createMockPanZoom()); const controller = createAndRegisterZoom(element); @@ -403,7 +403,7 @@ describe('ZoomController', () => { setElementToDefaultCardSize(element); const changeFunc = vi.fn(); - element.addEventListener('frigate-card:zoom:change', changeFunc); + element.addEventListener('advanced-camera-card:zoom:change', changeFunc); vi.mocked(Panzoom).mockReturnValueOnce(createMockPanZoom()); const controller = createAndRegisterZoom(element); diff --git a/tests/components-lib/zoom/zoom-view-context.test.ts b/tests/components-lib/zoom/zoom-view-context.test.ts index cf2320ba..7bd630fb 100644 --- a/tests/components-lib/zoom/zoom-view-context.test.ts +++ b/tests/components-lib/zoom/zoom-view-context.test.ts @@ -61,7 +61,7 @@ it('handleZoomSettingsObservedEvent', () => { const viewManager = mock(); handleZoomSettingsObservedEvent( - new CustomEvent('frigate-card:zoom:change', { + new CustomEvent('advanced-camera-card:zoom:change', { detail: { pan: { x: 1, y: 2 }, zoom: 3, diff --git a/tests/config/management.test.ts b/tests/config/management.test.ts index 91976993..9d202374 100644 --- a/tests/config/management.test.ts +++ b/tests/config/management.test.ts @@ -24,8 +24,8 @@ import { import { PTZControlAction } from '../../src/config/ptz'; import { Actions, - RawFrigateCardConfig, - frigateCardConfigSchema, + RawAdvancedCameraCardConfig, + advancedCameraCardConfigSchema, } from '../../src/config/types'; import { getParseErrorPaths } from '../../src/utils/zod'; @@ -100,7 +100,7 @@ describe('upgrade functions', () => { expect( // Upgrade example: rename of service_data to data. isConfigUpgradeable({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -271,7 +271,10 @@ describe('upgrade functions', () => { c: 10, overrides: [ { - overrides: { + merge: { + c: 10, + }, + set: { c: 10, }, }, @@ -279,7 +282,7 @@ describe('upgrade functions', () => { }; expect(upgradeMoveToWithOverrides('c', 'd')(config)).toBeTruthy(); - expect(config).toEqual({ d: 10, overrides: [{ overrides: { d: 10 } }] }); + expect(config).toEqual({ d: 10, overrides: [{ merge: { d: 10 }, set: { d: 10 } }] }); }); it('should upgrade config and overrides in-place', () => { @@ -287,7 +290,7 @@ describe('upgrade functions', () => { c: 10, overrides: [ { - overrides: { + merge: { c: 10, }, }, @@ -295,7 +298,7 @@ describe('upgrade functions', () => { }; expect(upgradeWithOverrides('c', (val) => String(val))(config)).toBeTruthy(); - expect(config).toEqual({ c: '10', overrides: [{ overrides: { c: '10' } }] }); + expect(config).toEqual({ c: '10', overrides: [{ merge: { c: '10' } }] }); }); describe('should upgrade array', () => { @@ -341,6 +344,21 @@ describe('upgrade functions', () => { expect(upgradeObjectRecursively((_val) => false)(config)).toBeFalsy(); expect(config).toEqual({ c: 10, d: 10 }); }); + + it('getting sub-objects', () => { + const config = { c: 10, target_obj: { target_val: 10 } }; + expect( + upgradeObjectRecursively( + (val: RawAdvancedCameraCardConfig) => { + (val['target_val'] as number)++; + return true; + }, + (obj) => obj['target_obj'] as RawAdvancedCameraCardConfig, + )(config), + ).toBeTruthy(); + expect(config).toEqual({ c: 10, target_obj: { target_val: 11 } }); + }); + it('iterating into arrays', () => { const config = { values: [{ c: 10 }, { d: 10 }, 'random'] }; expect( @@ -360,7 +378,7 @@ describe('upgrade functions', () => { }); it('should have upgrades with bad input data', () => { - expect(upgradeConfig(3 as unknown as RawFrigateCardConfig)).toBeFalsy(); + expect(upgradeConfig(3 as unknown as RawAdvancedCameraCardConfig)).toBeFalsy(); }); it('should delete properties', () => { @@ -371,12 +389,12 @@ describe('upgrade functions', () => { }); describe('should handle version specific upgrades', () => { - const postUpgradeChecks = (config: RawFrigateCardConfig): void => { + const postUpgradeChecks = (config: RawAdvancedCameraCardConfig): void => { // Should be no additional upgrades. expect(upgradeConfig(config)).toBeFalsy(); // Result should be parseable. - const result = frigateCardConfigSchema.safeParse(config); + const result = advancedCameraCardConfigSchema.safeParse(config); if (!result.success) { expect( result.success, @@ -389,312 +407,11 @@ describe('should handle version specific upgrades', () => { } }; - describe('v4.1.0', () => { - describe('should rename mediaLoaded to media_loaded', () => { - it('elements', () => { - const config = { - type: 'custom:frigate-card', - cameras: [{ camera_entity: 'camera.office' }], - elements: [ - { - type: 'custom:random', - conditions: { - mediaLoaded: true, - }, - }, - { - type: 'custom:random2', - conditions: 'not an object', - }, - ], - }; - expect(upgradeConfig(config)).toBeTruthy(); - expect(config).toEqual({ - type: 'custom:frigate-card', - cameras: [{ camera_entity: 'camera.office' }], - elements: [ - { - type: 'custom:random', - conditions: [ - { - condition: 'media_loaded' as const, - media_loaded: true, - }, - ], - }, - { - type: 'custom:random2', - conditions: 'not an object', - }, - ], - }); - postUpgradeChecks(config); - }); - - it('overrides', () => { - const config = { - type: 'custom:frigate-card', - cameras: [{ camera_entity: 'camera.office' }], - overrides: [ - { - conditions: { - mediaLoaded: true, - }, - overrides: { - view: { - default: 'clips', - }, - }, - }, - ], - }; - expect(upgradeConfig(config)).toBeTruthy(); - expect(config).toEqual({ - type: 'custom:frigate-card', - cameras: [{ camera_entity: 'camera.office' }], - overrides: [ - { - conditions: [ - { - condition: 'media_loaded' as const, - media_loaded: true, - }, - ], - merge: { - view: { - default: 'clips', - }, - }, - }, - ], - }); - postUpgradeChecks(config); - }); - }); - - it('should rename event_gallery to media_gallery', () => { - const config = { - type: 'custom:frigate-card', - cameras: [{ camera_entity: 'camera.office' }], - event_gallery: { - foo: 'bar', - }, - }; - expect(upgradeConfig(config)).toBeTruthy(); - expect(config).toEqual({ - type: 'custom:frigate-card', - cameras: [{ camera_entity: 'camera.office' }], - media_gallery: { - foo: 'bar', - }, - }); - postUpgradeChecks(config); - }); - - it('should rename menu.buttons.frigate_ui to menu.buttons.camera_ui', () => { - const config = { - type: 'custom:frigate-card', - cameras: [{ camera_entity: 'camera.office' }], - menu: { - buttons: { - frigate_ui: { - foo: 'bar', - }, - }, - }, - overrides: [ - { - conditions: [ - { - condition: 'view', - views: ['clips'], - }, - ], - overrides: { - menu: { - buttons: { - frigate_ui: { - foo: 'bar', - }, - }, - }, - }, - }, - ], - }; - expect(upgradeConfig(config)).toBeTruthy(); - expect(config).toEqual({ - type: 'custom:frigate-card', - cameras: [{ camera_entity: 'camera.office' }], - menu: { - buttons: { - camera_ui: { - foo: 'bar', - }, - }, - }, - overrides: [ - { - conditions: [ - { - condition: 'view', - views: ['clips'], - }, - ], - merge: { - menu: { - buttons: { - camera_ui: { - foo: 'bar', - }, - }, - }, - }, - }, - ], - }); - postUpgradeChecks(config); - }); - - it('should rename frigate ui actions', () => { - const config = { - type: 'custom:frigate-card', - cameras: [{ camera_entity: 'camera.office' }], - elements: [ - { - type: 'icon', - icon: 'mdi:cow', - tap_action: { - action: 'custom:frigate-card-action', - frigate_card_action: 'frigate_ui', - }, - }, - ], - view: { - actions: { - double_tap_action: { - action: 'custom:frigate-card-action', - frigate_card_action: 'frigate_ui', - }, - }, - }, - }; - expect(upgradeConfig(config)).toBeTruthy(); - expect(config).toEqual({ - type: 'custom:frigate-card', - cameras: [{ camera_entity: 'camera.office' }], - elements: [ - { - type: 'icon', - icon: 'mdi:cow', - tap_action: { - action: 'custom:frigate-card-action', - frigate_card_action: 'camera_ui', - }, - }, - ], - view: { - actions: { - double_tap_action: { - action: 'custom:frigate-card-action', - frigate_card_action: 'camera_ui', - }, - }, - }, - }); - postUpgradeChecks(config); - }); - - describe('should rename frigate-jsmpeg provider to jsmpeg', () => { - it('in cameras', () => { - const config = { - type: 'custom:frigate-card', - cameras: [{ live_provider: 'ha' }, { live_provider: 'frigate-jsmpeg' }], - }; - expect(upgradeConfig(config)).toBeTruthy(); - expect(config).toEqual({ - type: 'custom:frigate-card', - cameras: [{ live_provider: 'ha' }, { live_provider: 'jsmpeg' }], - }); - postUpgradeChecks(config); - }); - }); - - describe('should move live object into cameras_global', () => { - it.each([['image' as const], ['jsmpeg' as const], ['webrtc_card' as const]])( - '%s', - (objName: string) => { - const config = { - type: 'custom:frigate-card', - cameras: [{}], - live: { - [objName]: { - foo: 'bar', - }, - }, - }; - expect(upgradeConfig(config)).toBeTruthy(); - expect(config).toEqual({ - type: 'custom:frigate-card', - cameras: [{}], - cameras_global: { - [objName]: { - foo: 'bar', - }, - }, - live: {}, - }); - postUpgradeChecks(config); - }, - ); - }); - - describe('should convert to array and rename', () => { - it.each([['zone' as const], ['label' as const]])(`%s`, (objName: string) => { - const config = { - type: 'custom:frigate-card', - cameras: [ - { live_provider: 'ha', frigate: { [objName]: 'foo' } }, - { live_provider: 'jsmpeg' }, - ], - }; - expect(upgradeConfig(config)).toBeTruthy(); - expect(config).toEqual({ - type: 'custom:frigate-card', - cameras: [ - { live_provider: 'ha', frigate: { [objName + 's']: ['foo'] } }, - { live_provider: 'jsmpeg' }, - ], - }); - postUpgradeChecks(config); - }); - }); - - it('should convert and rename frigate.label to frigate.labels', () => { - const config = { - type: 'custom:frigate-card', - cameras: [ - { live_provider: 'ha', frigate: { zone: 'foo' } }, - { live_provider: 'jsmpeg' }, - ], - }; - expect(upgradeConfig(config)).toBeTruthy(); - expect(config).toEqual({ - type: 'custom:frigate-card', - cameras: [ - { live_provider: 'ha', frigate: { zones: ['foo'] } }, - { live_provider: 'jsmpeg' }, - ], - }); - postUpgradeChecks(config); - }); - }); - describe('v5.2.0 -> v6.0.0', () => { describe('should rename service_data to data', () => { it('positive case', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -743,7 +460,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -796,7 +513,7 @@ describe('should handle version specific upgrades', () => { }); it('negative case', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], view: { default: 'live', @@ -804,7 +521,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeFalsy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], view: { default: 'live', @@ -817,11 +534,11 @@ describe('should handle version specific upgrades', () => { describe('should move PTZ elements to live', () => { it('case with 1 element', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { - type: 'custom:frigate-card-ptz', + type: 'custom:advanced-camera-card-ptz', orientation: 'vertical', style: { right: '20px', @@ -866,18 +583,18 @@ describe('should handle version specific upgrades', () => { }, }, }, - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', }); postUpgradeChecks(config); }); it('case with >1 element', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { - type: 'custom:frigate-card-ptz', + type: 'custom:advanced-camera-card-ptz', orientation: 'vertical', style: { right: '20px', @@ -940,25 +657,25 @@ describe('should handle version specific upgrades', () => { }, }, }, - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', }); postUpgradeChecks(config); }); it('case with custom conditional element with 2 PTZ but nothing else', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { - type: 'custom:frigate-card-conditional', + type: 'custom:advanced-camera-card-conditional', conditions: { fullscreen: true, media_loaded: true, }, elements: [ { - type: 'custom:frigate-card-ptz', + type: 'custom:advanced-camera-card-ptz', orientation: 'vertical', style: { right: '20px', @@ -976,7 +693,7 @@ describe('should handle version specific upgrades', () => { }, }, { - type: 'custom:frigate-card-ptz', + type: 'custom:advanced-camera-card-ptz', orientation: 'vertical', style: { right: '20px', @@ -1023,18 +740,18 @@ describe('should handle version specific upgrades', () => { }, }, }, - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', }); postUpgradeChecks(config); }); it('case with custom conditional element with 1 PTZ and another element', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { - type: 'custom:frigate-card-conditional', + type: 'custom:advanced-camera-card-conditional', conditions: { fullscreen: true, media_loaded: true, @@ -1049,7 +766,7 @@ describe('should handle version specific upgrades', () => { }, }, { - type: 'custom:frigate-card-ptz', + type: 'custom:advanced-camera-card-ptz', orientation: 'vertical', style: { right: '20px', @@ -1098,7 +815,7 @@ describe('should handle version specific upgrades', () => { }, elements: [ { - type: 'custom:frigate-card-conditional', + type: 'custom:advanced-camera-card-conditional', conditions: [ { condition: 'fullscreen' as const, @@ -1121,14 +838,14 @@ describe('should handle version specific upgrades', () => { ], }, ], - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', }); postUpgradeChecks(config); }); it('case with stock conditional element with 1 PTZ', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -1136,7 +853,7 @@ describe('should handle version specific upgrades', () => { conditions: [{ entity: 'light.office', state: 'on' }], elements: [ { - type: 'custom:frigate-card-ptz', + type: 'custom:advanced-camera-card-ptz', orientation: 'vertical', style: { right: '20px', @@ -1183,14 +900,14 @@ describe('should handle version specific upgrades', () => { }, }, }, - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', }); postUpgradeChecks(config); }); it('case when live.controls.ptz already exists', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], live: { controls: { @@ -1215,7 +932,7 @@ describe('should handle version specific upgrades', () => { }, elements: [ { - type: 'custom:frigate-card-ptz', + type: 'custom:advanced-camera-card-ptz', orientation: 'vertical', style: { right: '20px', @@ -1260,7 +977,7 @@ describe('should handle version specific upgrades', () => { }, }, }, - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', }); postUpgradeChecks(config); }); @@ -1268,7 +985,7 @@ describe('should handle version specific upgrades', () => { it('should move view.timeout_seconds', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], view: { timeout_seconds: 200, @@ -1276,7 +993,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], view: { interaction_seconds: 200, @@ -1290,7 +1007,7 @@ describe('should handle version specific upgrades', () => { describe('lazy_unload', () => { it('all', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { lazy_unload: 'all', @@ -1298,7 +1015,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { lazy_unload: ['unselected', 'hidden'], @@ -1308,7 +1025,7 @@ describe('should handle version specific upgrades', () => { }); it('never', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { lazy_unload: 'never', @@ -1316,7 +1033,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: {}, }); @@ -1324,7 +1041,7 @@ describe('should handle version specific upgrades', () => { }); it('other value', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { lazy_unload: 'unselected', @@ -1332,7 +1049,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { lazy_unload: ['unselected'], @@ -1345,7 +1062,7 @@ describe('should handle version specific upgrades', () => { describe('auto_play', () => { it('all', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_play: 'all', @@ -1353,7 +1070,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: {}, }); @@ -1361,7 +1078,7 @@ describe('should handle version specific upgrades', () => { }); it('never', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_play: 'never', @@ -1369,7 +1086,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_play: [], @@ -1379,7 +1096,7 @@ describe('should handle version specific upgrades', () => { }); it('other value', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_play: 'selected', @@ -1387,7 +1104,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_play: ['selected'], @@ -1399,7 +1116,7 @@ describe('should handle version specific upgrades', () => { describe('auto_pause', () => { it('all', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_pause: 'all', @@ -1407,7 +1124,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_pause: ['unselected', 'hidden'], @@ -1417,7 +1134,7 @@ describe('should handle version specific upgrades', () => { }); it('never', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_pause: 'never', @@ -1425,7 +1142,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: {}, }); @@ -1433,7 +1150,7 @@ describe('should handle version specific upgrades', () => { }); it('other value', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_pause: 'unselected', @@ -1441,7 +1158,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_pause: ['unselected'], @@ -1453,7 +1170,7 @@ describe('should handle version specific upgrades', () => { describe('auto_mute', () => { it('all', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_mute: 'all', @@ -1461,7 +1178,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: {}, }); @@ -1469,7 +1186,7 @@ describe('should handle version specific upgrades', () => { }); it('never', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_mute: 'never', @@ -1477,7 +1194,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_mute: [], @@ -1487,7 +1204,7 @@ describe('should handle version specific upgrades', () => { }); it('other value', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_mute: 'unselected', @@ -1495,7 +1212,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_mute: ['unselected'], @@ -1507,7 +1224,7 @@ describe('should handle version specific upgrades', () => { describe('auto_unmute', () => { it('all', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_unmute: 'all', @@ -1515,7 +1232,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_unmute: ['selected', 'visible', 'microphone'], @@ -1525,7 +1242,7 @@ describe('should handle version specific upgrades', () => { }); it('never', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_unmute: 'never', @@ -1533,7 +1250,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: {}, }); @@ -1541,7 +1258,7 @@ describe('should handle version specific upgrades', () => { }); it('other value', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_unmute: 'selected', @@ -1549,7 +1266,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { auto_unmute: ['selected'], @@ -1564,7 +1281,7 @@ describe('should handle version specific upgrades', () => { describe('auto_play', () => { it('all', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_play: 'all', @@ -1572,7 +1289,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: {}, }); @@ -1580,7 +1297,7 @@ describe('should handle version specific upgrades', () => { }); it('never', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_play: 'never', @@ -1588,7 +1305,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_play: [], @@ -1598,7 +1315,7 @@ describe('should handle version specific upgrades', () => { }); it('other value', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_play: 'selected', @@ -1606,7 +1323,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_play: ['selected'], @@ -1618,7 +1335,7 @@ describe('should handle version specific upgrades', () => { describe('auto_pause', () => { it('all', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_pause: 'all', @@ -1626,7 +1343,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: {}, }); @@ -1634,7 +1351,7 @@ describe('should handle version specific upgrades', () => { }); it('never', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_pause: 'never', @@ -1642,7 +1359,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_pause: [], @@ -1652,7 +1369,7 @@ describe('should handle version specific upgrades', () => { }); it('other value', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_pause: 'unselected', @@ -1660,7 +1377,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_pause: ['unselected'], @@ -1672,7 +1389,7 @@ describe('should handle version specific upgrades', () => { describe('auto_mute', () => { it('all', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_mute: 'all', @@ -1680,7 +1397,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: {}, }); @@ -1688,7 +1405,7 @@ describe('should handle version specific upgrades', () => { }); it('never', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_mute: 'never', @@ -1696,7 +1413,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_mute: [], @@ -1706,7 +1423,7 @@ describe('should handle version specific upgrades', () => { }); it('other value', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_mute: 'unselected', @@ -1714,7 +1431,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_mute: ['unselected'], @@ -1726,7 +1443,7 @@ describe('should handle version specific upgrades', () => { describe('auto_unmute', () => { it('all', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_unmute: 'all', @@ -1734,7 +1451,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_unmute: ['selected', 'visible'], @@ -1744,7 +1461,7 @@ describe('should handle version specific upgrades', () => { }); it('never', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_unmute: 'never', @@ -1752,7 +1469,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: {}, }); @@ -1760,7 +1477,7 @@ describe('should handle version specific upgrades', () => { }); it('other value', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_unmute: 'selected', @@ -1768,7 +1485,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { auto_unmute: ['selected'], @@ -1785,7 +1502,7 @@ describe('should handle version specific upgrades', () => { '%s', (mediaEventType: string) => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -1797,7 +1514,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -1817,7 +1534,7 @@ describe('should handle version specific upgrades', () => { '%s', (mediaEventType: string) => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], timeline: { media: mediaEventType, @@ -1825,7 +1542,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], timeline: { events_media_type: mediaEventType, @@ -1841,7 +1558,7 @@ describe('should handle version specific upgrades', () => { '%s', (mediaEventType: string) => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -1853,7 +1570,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -1873,7 +1590,7 @@ describe('should handle version specific upgrades', () => { '%s', (mediaEventType: string) => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { controls: { @@ -1885,7 +1602,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], media_viewer: { controls: { @@ -1904,7 +1621,7 @@ describe('should handle version specific upgrades', () => { describe('should move and transform untrigger_reset', () => { it('when true', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], view: { scan: { @@ -1914,7 +1631,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], view: { triggers: { @@ -1929,7 +1646,7 @@ describe('should handle version specific upgrades', () => { it('when false', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], view: { scan: { @@ -1939,7 +1656,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], view: { triggers: {}, @@ -1952,7 +1669,7 @@ describe('should handle version specific upgrades', () => { describe('should rename view.scan.enabled to a trigger action', () => { it('when true', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], view: { scan: { @@ -1962,7 +1679,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], view: { triggers: { @@ -1978,7 +1695,7 @@ describe('should handle version specific upgrades', () => { it('when false', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], view: { scan: { @@ -1988,7 +1705,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], view: { triggers: {}, @@ -2002,7 +1719,7 @@ describe('should handle version specific upgrades', () => { describe('should handle media layout changes', () => { it('from live.layout to camera_global.dimensions', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { layout: { @@ -2016,7 +1733,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: {}, cameras_global: { @@ -2039,7 +1756,7 @@ describe('should handle version specific upgrades', () => { '%s', (section: string) => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], [section]: { layout: { @@ -2053,7 +1770,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], [section]: {}, }); @@ -2067,7 +1784,7 @@ describe('should handle version specific upgrades', () => { describe('with view condition', () => { it('elements', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -2084,7 +1801,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -2107,7 +1824,7 @@ describe('should handle version specific upgrades', () => { it('overrides', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], overrides: [ { @@ -2125,7 +1842,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], overrides: [ { @@ -2148,7 +1865,7 @@ describe('should handle version specific upgrades', () => { it('automations', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], automations: [ { @@ -2157,8 +1874,8 @@ describe('should handle version specific upgrades', () => { }, actions: [ { - action: 'custom:frigate-card-action' as const, - frigate_card_action: 'live_substream_on' as const, + action: 'custom:advanced-camera-card-action' as const, + advanced_camera_card_action: 'live_substream_on' as const, }, ], }, @@ -2167,7 +1884,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], automations: [ { @@ -2179,8 +1896,8 @@ describe('should handle version specific upgrades', () => { ], actions: [ { - action: 'custom:frigate-card-action' as const, - frigate_card_action: 'live_substream_on' as const, + action: 'custom:advanced-camera-card-action' as const, + advanced_camera_card_action: 'live_substream_on' as const, }, ], }, @@ -2193,7 +1910,7 @@ describe('should handle version specific upgrades', () => { describe('with camera condition', () => { it('elements', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -2210,7 +1927,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -2233,7 +1950,7 @@ describe('should handle version specific upgrades', () => { it('overrides', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], overrides: [ { @@ -2251,7 +1968,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], overrides: [ { @@ -2274,7 +1991,7 @@ describe('should handle version specific upgrades', () => { it('automations', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], automations: [ { @@ -2283,8 +2000,8 @@ describe('should handle version specific upgrades', () => { }, actions: [ { - action: 'custom:frigate-card-action' as const, - frigate_card_action: 'live_substream_on' as const, + action: 'custom:advanced-camera-card-action' as const, + advanced_camera_card_action: 'live_substream_on' as const, }, ], }, @@ -2293,7 +2010,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], automations: [ { @@ -2305,8 +2022,8 @@ describe('should handle version specific upgrades', () => { ], actions: [ { - action: 'custom:frigate-card-action' as const, - frigate_card_action: 'live_substream_on' as const, + action: 'custom:advanced-camera-card-action' as const, + advanced_camera_card_action: 'live_substream_on' as const, }, ], }, @@ -2324,7 +2041,7 @@ describe('should handle version specific upgrades', () => { ])('%s', (condition: string) => { it('elements', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -2341,7 +2058,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -2364,7 +2081,7 @@ describe('should handle version specific upgrades', () => { it('overrides', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], overrides: [ { @@ -2382,7 +2099,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], overrides: [ { @@ -2405,7 +2122,7 @@ describe('should handle version specific upgrades', () => { it('automations', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], automations: [ { @@ -2414,8 +2131,8 @@ describe('should handle version specific upgrades', () => { }, actions: [ { - action: 'custom:frigate-card-action' as const, - frigate_card_action: 'live_substream_on' as const, + action: 'custom:advanced-camera-card-action' as const, + advanced_camera_card_action: 'live_substream_on' as const, }, ], }, @@ -2424,7 +2141,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], automations: [ { @@ -2436,8 +2153,8 @@ describe('should handle version specific upgrades', () => { ], actions: [ { - action: 'custom:frigate-card-action' as const, - frigate_card_action: 'live_substream_on' as const, + action: 'custom:advanced-camera-card-action' as const, + advanced_camera_card_action: 'live_substream_on' as const, }, ], }, @@ -2451,7 +2168,7 @@ describe('should handle version specific upgrades', () => { describe('with state condition', () => { it('elements', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -2478,7 +2195,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -2507,7 +2224,7 @@ describe('should handle version specific upgrades', () => { it('overrides', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], overrides: [ { @@ -2535,7 +2252,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], overrides: [ { @@ -2564,7 +2281,7 @@ describe('should handle version specific upgrades', () => { it('automations', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], automations: [ { @@ -2583,8 +2300,8 @@ describe('should handle version specific upgrades', () => { }, actions: [ { - action: 'custom:frigate-card-action' as const, - frigate_card_action: 'live_substream_on' as const, + action: 'custom:advanced-camera-card-action' as const, + advanced_camera_card_action: 'live_substream_on' as const, }, ], }, @@ -2593,7 +2310,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], automations: [ { @@ -2611,8 +2328,8 @@ describe('should handle version specific upgrades', () => { ], actions: [ { - action: 'custom:frigate-card-action' as const, - frigate_card_action: 'live_substream_on' as const, + action: 'custom:advanced-camera-card-action' as const, + advanced_camera_card_action: 'live_substream_on' as const, }, ], }, @@ -2625,7 +2342,7 @@ describe('should handle version specific upgrades', () => { describe('with media query condition', () => { it('elements', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -2642,7 +2359,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -2665,7 +2382,7 @@ describe('should handle version specific upgrades', () => { it('overrides', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], overrides: [ { @@ -2683,7 +2400,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], overrides: [ { @@ -2706,7 +2423,7 @@ describe('should handle version specific upgrades', () => { it('automations', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], automations: [ { @@ -2715,8 +2432,8 @@ describe('should handle version specific upgrades', () => { }, actions: [ { - action: 'custom:frigate-card-action' as const, - frigate_card_action: 'live_substream_on' as const, + action: 'custom:advanced-camera-card-action' as const, + advanced_camera_card_action: 'live_substream_on' as const, }, ], }, @@ -2725,7 +2442,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], automations: [ { @@ -2737,8 +2454,8 @@ describe('should handle version specific upgrades', () => { ], actions: [ { - action: 'custom:frigate-card-action' as const, - frigate_card_action: 'live_substream_on' as const, + action: 'custom:advanced-camera-card-action' as const, + advanced_camera_card_action: 'live_substream_on' as const, }, ], }, @@ -2752,7 +2469,7 @@ describe('should handle version specific upgrades', () => { describe('from hide to substream capability disable', () => { it('overrides', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [ { camera_entity: 'camera.office', hide: true }, { camera_entity: 'camera.sitting_room', hide: false }, @@ -2761,7 +2478,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [ { camera_entity: 'camera.office', @@ -2776,7 +2493,7 @@ describe('should handle version specific upgrades', () => { describe('from performance profile to generic profile', () => { it('low performance', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], performance: { profile: 'low', @@ -2785,7 +2502,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], profiles: ['low-performance'], performance: {}, @@ -2795,7 +2512,7 @@ describe('should handle version specific upgrades', () => { it('high performance', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], performance: { profile: 'high', @@ -2804,7 +2521,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], performance: {}, }); @@ -2815,7 +2532,7 @@ describe('should handle version specific upgrades', () => { it('from overrides to merge', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], overrides: [ { @@ -2836,7 +2553,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], overrides: [ { @@ -2880,7 +2597,7 @@ describe('should handle version specific upgrades', () => { it('with action_ format', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -2914,7 +2631,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], cameras_global: { ptz: { @@ -2957,7 +2674,7 @@ describe('should handle version specific upgrades', () => { }); const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -2989,7 +2706,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], cameras_global: { ptz: { @@ -3027,7 +2744,7 @@ describe('should handle version specific upgrades', () => { it('with invalid ptz type', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -3041,7 +2758,7 @@ describe('should handle version specific upgrades', () => { it('with nothing to transform', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -3057,7 +2774,7 @@ describe('should handle version specific upgrades', () => { it('without tap_action', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -3086,7 +2803,7 @@ describe('should handle version specific upgrades', () => { it('without pre-existing presets', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -3120,7 +2837,7 @@ describe('should handle version specific upgrades', () => { it('view.update_cycle_camera -> view.default_cycle_camera', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], view: { update_cycle_camera: true, @@ -3137,7 +2854,7 @@ describe('should handle version specific upgrades', () => { describe('view.update_force -> view.default_reset.interaction_mode', () => { it('should convert to all when true', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], view: { update_force: true, @@ -3155,7 +2872,7 @@ describe('should handle version specific upgrades', () => { it('should remove when false', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], view: { update_force: false, @@ -3170,7 +2887,7 @@ describe('should handle version specific upgrades', () => { it('view.update_seconds', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], view: { update_seconds: 42, @@ -3188,7 +2905,7 @@ describe('should handle version specific upgrades', () => { it('view.update_entities', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], view: { update_entities: ['binary_sensor.foo', 'camera.bar'], @@ -3207,7 +2924,7 @@ describe('should handle version specific upgrades', () => { describe('title controls to status bar', () => { it('when mode is none', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -3227,7 +2944,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: {} }, media_viewer: { controls: {} }, @@ -3244,7 +2961,7 @@ describe('should handle version specific upgrades', () => { '%s', (mode: unknown) => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -3264,7 +2981,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: {} }, media_viewer: { controls: {} }, @@ -3281,7 +2998,7 @@ describe('should handle version specific upgrades', () => { [`popup-${position}-right` as const], ])('%s', (mode: string) => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { @@ -3301,7 +3018,7 @@ describe('should handle version specific upgrades', () => { expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: {} }, media_viewer: { controls: {} }, @@ -3317,7 +3034,7 @@ describe('should handle version specific upgrades', () => { it('rename call-service -> perform-action', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -3343,7 +3060,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], elements: [ { @@ -3372,7 +3089,7 @@ describe('should handle version specific upgrades', () => { it('rename dimensions.max_height -> dimensions.height', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], dimensions: { max_height: '500px', @@ -3380,7 +3097,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], dimensions: { height: '500px', @@ -3391,7 +3108,7 @@ describe('should handle version specific upgrades', () => { it('delete dimensions.min_height', () => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], dimensions: { min_height: '100px', @@ -3399,7 +3116,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], dimensions: {}, }); @@ -3415,7 +3132,7 @@ describe('should handle version specific upgrades', () => { ['off' as const, false], ])('%s', (darkMode: 'on' | 'off' | 'auto', expected: boolean) => { const config = { - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], view: { dark_mode: darkMode, @@ -3423,7 +3140,7 @@ describe('should handle version specific upgrades', () => { }; expect(upgradeConfig(config)).toBeTruthy(); expect(config).toEqual({ - type: 'custom:frigate-card', + type: 'custom:advanced-camera-card', cameras: [{ camera_entity: 'camera.office' }], view: { dim: expected, @@ -3433,4 +3150,445 @@ describe('should handle version specific upgrades', () => { }); }); }); + + describe('v7.0.0+', () => { + it('custom:frigate-card -> custom:advanced-camera-card', () => { + const config = { + type: 'custom:frigate-card', + cameras: [{}], + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{}], + }); + postUpgradeChecks(config); + }); + + it('custom:frigate-card-action -> custom:advanced-camera-card-action', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'icon', + icon: 'mdi:cow', + tap_action: { + action: 'custom:frigate-card-action', + advanced_camera_card_action: 'camera_ui', + }, + }, + ], + view: { + actions: { + double_tap_action: { + action: 'custom:frigate-card-action', + advanced_camera_card_action: 'camera_ui', + }, + }, + }, + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'icon', + icon: 'mdi:cow', + tap_action: { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'camera_ui', + }, + }, + ], + view: { + actions: { + double_tap_action: { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'camera_ui', + }, + }, + }, + }); + postUpgradeChecks(config); + }); + + it('custom:frigate-card-menu-icon -> custom:advanced-camera-card-menu-icon', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:frigate-card-menu-icon', + icon: 'mdi:cow', + }, + ], + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:advanced-camera-card-menu-icon', + icon: 'mdi:cow', + }, + ], + }); + postUpgradeChecks(config); + }); + + it('custom:frigate-card-menu-state-icon -> custom:advanced-camera-card-menu-state-icon', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:frigate-card-menu-state-icon', + entity: 'binary_sensor.office', + icon: 'mdi:cow', + }, + ], + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:advanced-camera-card-menu-state-icon', + entity: 'binary_sensor.office', + icon: 'mdi:cow', + }, + ], + }); + postUpgradeChecks(config); + }); + + it('custom:frigate-card-menu-submenu -> custom:advanced-camera-card-menu-submenu', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:frigate-card-menu-submenu', + icon: 'mdi:cow', + items: [], + }, + ], + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:advanced-camera-card-menu-submenu', + icon: 'mdi:cow', + items: [], + }, + ], + }); + postUpgradeChecks(config); + }); + + it('custom:frigate-card-status-bar-icon -> custom:advanced-camera-card-status-bar-icon', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:frigate-card-status-bar-icon', + icon: 'mdi:cow', + }, + ], + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:advanced-camera-card-status-bar-icon', + icon: 'mdi:cow', + }, + ], + }); + postUpgradeChecks(config); + }); + + it('custom:frigate-card-status-bar-image -> custom:advanced-camera-card-status-bar-image', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:frigate-card-status-bar-image', + image: 'image', + }, + ], + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:advanced-camera-card-status-bar-image', + image: 'image', + }, + ], + }); + postUpgradeChecks(config); + }); + + it('custom:frigate-card-status-bar-string -> custom:advanced-camera-card-status-bar-string', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:frigate-card-status-bar-string', + string: 'string', + }, + ], + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:advanced-camera-card-status-bar-string', + string: 'string', + }, + ], + }); + postUpgradeChecks(config); + }); + + it('custom:frigate-card-conditional -> custom:advanced-camera-card-conditional', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:frigate-card-conditional', + conditions: [], + elements: [], + }, + ], + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'custom:advanced-camera-card-conditional', + conditions: [], + elements: [], + }, + ], + }); + postUpgradeChecks(config); + }); + + it('frigate_card_action -> advanced_camera_card_action', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'icon', + icon: 'mdi:cow', + tap_action: { + action: 'custom:advanced-camera-card-action', + frigate_card_action: 'camera_ui', + }, + }, + ], + view: { + actions: { + double_tap_action: { + action: 'custom:advanced-camera-card-action', + frigate_card_action: 'camera_ui', + }, + }, + }, + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + elements: [ + { + type: 'icon', + icon: 'mdi:cow', + tap_action: { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'camera_ui', + }, + }, + ], + view: { + actions: { + double_tap_action: { + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'camera_ui', + }, + }, + }, + }); + postUpgradeChecks(config); + }); + + describe('frigate card style overrides -> advanced camera card style overrides', () => { + it('valid style overrides', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + view: { + theme: { + overrides: { + '--zero': 'zero', + '--frigate-card-one': 'one', + }, + }, + }, + overrides: [ + { + conditions: [ + { + condition: 'media_loaded' as const, + media_loaded: true, + }, + ], + merge: { + view: { + theme: { + overrides: { + '--frigate-card-two': 'two', + }, + }, + }, + }, + }, + ], + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + view: { + theme: { + overrides: { + '--zero': 'zero', + '--advanced-camera-card-one': 'one', + }, + }, + }, + overrides: [ + { + conditions: [ + { + condition: 'media_loaded' as const, + media_loaded: true, + }, + ], + merge: { + view: { + theme: { + overrides: { + '--advanced-camera-card-two': 'two', + }, + }, + }, + }, + }, + ], + }); + postUpgradeChecks(config); + }); + + it('invalid style overrides', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + view: { + theme: { + overrides: ['should', 'not', 'be', 'an', 'array'], + }, + }, + }; + expect(upgradeConfig(config)).toBeFalsy(); + }); + }); + + it('frigate card button -> iris button', () => { + const config = { + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + menu: { + buttons: { + frigate: { + icon: 'mdi:cow', + }, + }, + }, + overrides: [ + { + conditions: [ + { + condition: 'media_loaded' as const, + media_loaded: true, + }, + ], + merge: { + menu: { + buttons: { + frigate: { + icon: 'mdi:cow', + }, + }, + }, + }, + }, + ], + }; + expect(upgradeConfig(config)).toBeTruthy(); + expect(config).toEqual({ + type: 'custom:advanced-camera-card', + cameras: [{ camera_entity: 'camera.office' }], + menu: { + buttons: { + iris: { + icon: 'mdi:cow', + }, + }, + }, + overrides: [ + { + conditions: [ + { + condition: 'media_loaded' as const, + media_loaded: true, + }, + ], + merge: { + menu: { + buttons: { + iris: { + icon: 'mdi:cow', + }, + }, + }, + }, + }, + ], + }); + postUpgradeChecks(config); + }); + }); }); diff --git a/tests/config/profiles/casting.test.ts b/tests/config/profiles/casting.test.ts index d4a77515..aabb8a5b 100644 --- a/tests/config/profiles/casting.test.ts +++ b/tests/config/profiles/casting.test.ts @@ -1,8 +1,8 @@ import { expect, it } from 'vitest'; import { setProfiles } from '../../../src/config/profiles'; -import { frigateCardConfigSchema } from '../../../src/config/types'; -import { createRawConfig } from '../../test-utils'; import { CASTING_PROFILE } from '../../../src/config/profiles/casting'; +import { advancedCameraCardConfigSchema } from '../../../src/config/types'; +import { createRawConfig } from '../../test-utils'; it('should contain expected defaults', () => { expect(CASTING_PROFILE).toEqual({ @@ -23,11 +23,11 @@ it('should contain expected defaults', () => { it('should be parseable after application', () => { const rawInputConfig = createRawConfig(); - const parsedConfig = frigateCardConfigSchema.parse(rawInputConfig); + const parsedConfig = advancedCameraCardConfigSchema.parse(rawInputConfig); setProfiles(rawInputConfig, parsedConfig, ['casting']); // Reparse the config to ensure the profile did not introduce errors. - const parseResult = frigateCardConfigSchema.safeParse(parsedConfig); + const parseResult = advancedCameraCardConfigSchema.safeParse(parsedConfig); expect(parseResult.success, parseResult.error?.toString()).toBeTruthy(); }); diff --git a/tests/config/profiles/index.test.ts b/tests/config/profiles/index.test.ts index c34d1c08..230a50ed 100644 --- a/tests/config/profiles/index.test.ts +++ b/tests/config/profiles/index.test.ts @@ -24,7 +24,7 @@ describe('setProfiles', () => { it('should handle profiles', () => { const input = { - type: 'frigate-hass-card', + type: 'custom:advanced-camera-card', cameras: [{}], live: { controls: { diff --git a/tests/config/profiles/low-performance.test.ts b/tests/config/profiles/low-performance.test.ts index ece5088b..3056f47d 100644 --- a/tests/config/profiles/low-performance.test.ts +++ b/tests/config/profiles/low-performance.test.ts @@ -1,7 +1,7 @@ import { expect, it } from 'vitest'; import { setProfiles } from '../../../src/config/profiles'; import { LOW_PERFORMANCE_PROFILE } from '../../../src/config/profiles/low-performance'; -import { frigateCardConfigSchema } from '../../../src/config/types'; +import { advancedCameraCardConfigSchema } from '../../../src/config/types'; import { createRawConfig } from '../../test-utils'; it('should contain expected defaults', () => { @@ -37,7 +37,7 @@ it('should contain expected defaults', () => { 'media_viewer.draggable': false, 'media_viewer.snapshot_click_plays_clip': false, 'media_viewer.transition_effect': 'none', - 'menu.buttons.frigate.enabled': false, + 'menu.buttons.iris.enabled': false, 'menu.buttons.media_player.enabled': false, 'menu.buttons.timeline.enabled': false, 'menu.style': 'outside', @@ -60,11 +60,11 @@ it('should contain expected defaults', () => { it('should be parseable after application', () => { const rawInputConfig = createRawConfig(); - const parsedConfig = frigateCardConfigSchema.parse(rawInputConfig); + const parsedConfig = advancedCameraCardConfigSchema.parse(rawInputConfig); setProfiles(rawInputConfig, parsedConfig, ['low-performance']); // Reparse the config to ensure the profile did not introduce errors. - const parseResult = frigateCardConfigSchema.safeParse(parsedConfig); + const parseResult = advancedCameraCardConfigSchema.safeParse(parsedConfig); expect(parseResult.success, parseResult.error?.toString()).toBeTruthy(); }); diff --git a/tests/config/profiles/scrubbing.test.ts b/tests/config/profiles/scrubbing.test.ts index de2a92b5..e9939e48 100644 --- a/tests/config/profiles/scrubbing.test.ts +++ b/tests/config/profiles/scrubbing.test.ts @@ -1,7 +1,7 @@ import { expect, it } from 'vitest'; -import { SCRUBBING_PROFILE } from '../../../src/config/profiles/scrubbing'; import { setProfiles } from '../../../src/config/profiles'; -import { frigateCardConfigSchema } from '../../../src/config/types'; +import { SCRUBBING_PROFILE } from '../../../src/config/profiles/scrubbing'; +import { advancedCameraCardConfigSchema } from '../../../src/config/types'; import { createRawConfig } from '../../test-utils'; it('should contain expected defaults', () => { @@ -17,11 +17,11 @@ it('should contain expected defaults', () => { it('should be parseable after application', () => { const rawInputConfig = createRawConfig(); - const parsedConfig = frigateCardConfigSchema.parse(rawInputConfig); + const parsedConfig = advancedCameraCardConfigSchema.parse(rawInputConfig); setProfiles(rawInputConfig, parsedConfig, ['low-performance']); // Reparse the config to ensure the profile did not introduce errors. - const parseResult = frigateCardConfigSchema.safeParse(parsedConfig); + const parseResult = advancedCameraCardConfigSchema.safeParse(parsedConfig); expect(parseResult.success, parseResult.error?.toString()).toBeTruthy(); }); diff --git a/tests/config/types.test.ts b/tests/config/types.test.ts index cbb9964b..cccd51ff 100644 --- a/tests/config/types.test.ts +++ b/tests/config/types.test.ts @@ -4,9 +4,9 @@ import { conditionalSchema, customSchema, dimensionsConfigSchema, - frigateCardConditionSchema, - frigateCardCustomActionsBaseSchema, - frigateCardCustomActionSchema, + advancedCameraCardConditionSchema, + advancedCameraCardCustomActionsBaseSchema, + advancedCameraCardCustomActionSchema, } from '../../src/config/types'; import { createConfig } from '../test-utils'; @@ -217,10 +217,6 @@ describe('config defaults', () => { enabled: false, priority: 50, }, - frigate: { - enabled: true, - priority: 50, - }, fullscreen: { enabled: true, priority: 50, @@ -229,6 +225,10 @@ describe('config defaults', () => { enabled: false, priority: 50, }, + iris: { + enabled: true, + priority: 50, + }, live: { enabled: true, priority: 50, @@ -338,7 +338,7 @@ describe('config defaults', () => { style: 'stack', window_seconds: 3600, }, - type: 'frigate-hass-card', + type: 'advanced-camera-card', view: { camera_select: 'current', default: 'live', @@ -416,7 +416,7 @@ it('should transform dimensions.aspect_ratio', () => { describe('should refine user_agent_re conditions', () => { it('should successfully parse valid user_agent_re condition', () => { expect( - frigateCardConditionSchema.parse({ + advancedCameraCardConditionSchema.parse({ condition: 'user_agent', user_agent_re: 'Chrome/', }), @@ -428,7 +428,7 @@ describe('should refine user_agent_re conditions', () => { it('should reject invalid user_agent_re conditions', () => { expect(() => - frigateCardConditionSchema.parse({ + advancedCameraCardConditionSchema.parse({ condition: 'user_agent', user_agent_re: '[', }), @@ -438,15 +438,15 @@ describe('should refine user_agent_re conditions', () => { it('should transform action', () => { expect( - frigateCardCustomActionsBaseSchema.parse({ - action: 'custom:frigate-card-action', + advancedCameraCardCustomActionsBaseSchema.parse({ + action: 'custom:advanced-camera-card-action', }), ).toEqual({ action: 'fire-dom-event', }); }); -describe('should convert webrtc card PTZ to Frigate card PTZ', () => { +describe('should convert webrtc card PTZ to Advanced Camera Card PTZ', () => { describe('relative actions', () => { it.each([ ['left' as const], @@ -632,29 +632,29 @@ describe('should lazy evaluate schemas', () => { it('status bar actions', () => { const input = { action: 'fire-dom-event', - frigate_card_action: 'status_bar', + advanced_camera_card_action: 'status_bar', status_bar_action: 'reset', items: [ { - type: 'custom:frigate-card-status-bar-string', + type: 'custom:advanced-camera-card-status-bar-string', string: 'Item', }, ], }; - expect(frigateCardCustomActionSchema.parse(input)).toEqual(input); + expect(advancedCameraCardCustomActionSchema.parse(input)).toEqual(input); }); }); -describe('should handle custom frigate elements', () => { - it('should add custom error on frigate entry', () => { +describe('should handle custom advanced camera card elements', () => { + it('should add custom error on advanced camera card element', () => { const result = customSchema.safeParse({ - type: 'custom:frigate-card-foo', + type: 'custom:advanced-camera-card-foo', }); expect(result.success).toBeFalsy(); if (!result.success) { expect(result.error.errors[0]).toEqual({ code: 'custom', - message: 'Frigate-card custom elements must match specific schemas', + message: 'advanced-camera-card custom elements must match specific schemas', fatal: true, path: ['type'], }); @@ -669,7 +669,7 @@ describe('should handle custom frigate elements', () => { }); }); -// https://github.com/dermotduffy/frigate-hass-card/issues/1280 +// https://github.com/dermotduffy/advanced-camera-card/issues/1280 it('should not require title controls to specify all options', () => { expect( createConfig({ diff --git a/tests/test-utils.ts b/tests/test-utils.ts index c93a967a..c93e2cad 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -42,16 +42,16 @@ import { TriggersManager } from '../src/card-controller/triggers-manager'; import { ViewManager } from '../src/card-controller/view/view-manager'; import { ActionsConfig, + AdvancedCameraCardCondition, + AdvancedCameraCardConfig, + AdvancedCameraCardCustomAction, CameraConfig, - FrigateCardCondition, - FrigateCardConfig, - FrigateCardCustomAction, PerformanceConfig, - RawFrigateCardConfig, + RawAdvancedCameraCardConfig, + advancedCameraCardConditionSchema, + advancedCameraCardConfigSchema, + advancedCameraCardCustomActionSchema, cameraConfigSchema, - frigateCardConditionSchema, - frigateCardConfigSchema, - frigateCardCustomActionSchema, performanceConfigSchema, } from '../src/config/types'; import { CapabilitiesRaw, ExtendedHomeAssistant, MediaLoadedInfo } from '../src/types'; @@ -65,9 +65,9 @@ import { View, ViewParameters } from '../src/view/view'; export const createAction = ( action: Record, -): FrigateCardCustomAction | null => { - const result = frigateCardCustomActionSchema.safeParse({ - action: 'custom:frigate-card-action', +): AdvancedCameraCardCustomAction | null => { + const result = advancedCameraCardCustomActionSchema.safeParse({ + action: 'custom:advanced-camera-card-action', ...action, }); return result.success ? result.data : null; @@ -78,23 +78,25 @@ export const createCameraConfig = (config?: unknown): CameraConfig => { }; export const createCondition = ( - condition?: Partial, -): FrigateCardCondition => { - return frigateCardConditionSchema.parse(condition ?? {}); + condition?: Partial, +): AdvancedCameraCardCondition => { + return advancedCameraCardConditionSchema.parse(condition ?? {}); }; export const createRawConfig = ( - config?: Partial, -): RawFrigateCardConfig => { + config?: Partial, +): RawAdvancedCameraCardConfig => { return { - type: 'frigate-hass-card', + type: 'advanced-camera-card', cameras: [{}], ...config, }; }; -export const createConfig = (config?: RawFrigateCardConfig): FrigateCardConfig => { - return frigateCardConfigSchema.parse(createRawConfig(config)); +export const createConfig = ( + config?: RawAdvancedCameraCardConfig, +): AdvancedCameraCardConfig => { + return advancedCameraCardConfigSchema.parse(createRawConfig(config)); }; export const createCamera = ( @@ -296,7 +298,7 @@ export const createMediaLoadedInfo = ( export const createMediaLoadedInfoEvent = ( mediaLoadedInfo?: MediaLoadedInfo, ): CustomEvent => { - return new CustomEvent('frigate-card:media:loaded', { + return new CustomEvent('advanced-camera-card:media:loaded', { detail: mediaLoadedInfo ?? createMediaLoadedInfo(), composed: true, bubbles: true, diff --git a/tests/utils/action.test.ts b/tests/utils/action.test.ts index b0b7eba7..9c9731a4 100644 --- a/tests/utils/action.test.ts +++ b/tests/utils/action.test.ts @@ -1,4 +1,4 @@ -import { hasAction } from '@dermotduffy/custom-card-helpers'; +import { hasAction as customCardHasAction } from '@dermotduffy/custom-card-helpers'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; import { actionSchema } from '../../src/config/types'; @@ -9,18 +9,18 @@ import { createGeneralAction, createLogAction, createMediaPlayerAction, + createPTZAction, + createPTZControlsAction, createPTZDigitalAction, createPTZMultiAction, - createPTZControlsAction, - frigateCardHasAction, getActionConfigGivenAction, + hasAction, stopEventFromActivatingCardWideActions, - createPTZAction, } from '../../src/utils/action'; vi.mock('@dermotduffy/custom-card-helpers'); -describe('convertActionToFrigateCardCustomAction', () => { +describe('convertActionToAdvancedCameraCardCustomAction', () => { it('should skip null action', () => { expect(convertActionToCardCustomAction(null)).toBeFalsy(); }); @@ -28,12 +28,12 @@ describe('convertActionToFrigateCardCustomAction', () => { it('should parse valid', () => { expect( convertActionToCardCustomAction({ - action: 'custom:frigate-card-action', - frigate_card_action: 'download', + action: 'custom:advanced-camera-card-action', + advanced_camera_card_action: 'download', }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'download', + advanced_camera_card_action: 'download', }); }); @@ -50,7 +50,7 @@ describe('createGeneralAction', () => { }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'clips', + advanced_camera_card_action: 'clips', card_id: 'card_id', }); }); @@ -62,7 +62,7 @@ describe('createCameraAction', () => { { action: 'fire-dom-event', camera: 'camera', - frigate_card_action: 'camera_select', + advanced_camera_card_action: 'camera_select', card_id: 'card_id', }, ); @@ -77,7 +77,7 @@ describe('createMediaPlayerAction', () => { }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'media_player', + advanced_camera_card_action: 'media_player', media_player: 'device', media_player_action: 'play', card_id: 'card_id', @@ -93,7 +93,7 @@ describe('createDisplayModeAction', () => { }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'display_mode_select', + advanced_camera_card_action: 'display_mode_select', display_mode: 'grid', card_id: 'card_id', }); @@ -108,7 +108,7 @@ describe('createPTZControlsAction', () => { }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'ptz_controls', + advanced_camera_card_action: 'ptz_controls', enabled: true, card_id: 'card_id', }); @@ -123,7 +123,7 @@ describe('createPTZAction', () => { }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', card_id: 'card_id', }); }); @@ -139,7 +139,7 @@ describe('createPTZAction', () => { }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'ptz', + advanced_camera_card_action: 'ptz', card_id: 'card_id', camera: 'camera_id', ptz_action: 'right', @@ -157,7 +157,7 @@ describe('createPTZDigitalAction', () => { }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'ptz_digital', + advanced_camera_card_action: 'ptz_digital', card_id: 'card_id', }); }); @@ -176,7 +176,7 @@ describe('createPTZDigitalAction', () => { }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'ptz_digital', + advanced_camera_card_action: 'ptz_digital', card_id: 'card_id', target_id: 'target_id', absolute: { @@ -199,7 +199,7 @@ describe('createPTZMultiAction', () => { }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', card_id: 'card_id', ptz_action: 'right', ptz_preset: 'preset', @@ -216,7 +216,7 @@ describe('createPTZMultiAction', () => { }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'ptz_multi', + advanced_camera_card_action: 'ptz_multi', card_id: 'card_id', ptz_action: 'right', ptz_phase: 'start', @@ -233,7 +233,7 @@ describe('createLogAction', () => { }), ).toEqual({ action: 'fire-dom-event', - frigate_card_action: 'log', + advanced_camera_card_action: 'log', message: 'Hello, world!', card_id: 'card_id', level: 'info', @@ -244,7 +244,7 @@ describe('createLogAction', () => { describe('getActionConfigGivenAction', () => { const action = actionSchema.parse({ action: 'fire-dom-event', - frigate_card_action: 'clips', + advanced_camera_card_action: 'clips', }); it('should not handle undefined arguments', () => { @@ -284,7 +284,7 @@ describe('getActionConfigGivenAction', () => { }); }); -describe('frigateCardHasAction', () => { +describe('hasAction', () => { const action = actionSchema.parse({ action: 'toggle', }); @@ -294,12 +294,12 @@ describe('frigateCardHasAction', () => { }); it('should handle non-array case', () => { - expect(frigateCardHasAction(action)).toBeFalsy(); - expect(hasAction).toBeCalledTimes(1); + expect(hasAction(action)).toBeFalsy(); + expect(customCardHasAction).toBeCalledTimes(1); }); it('should handle array case', () => { - expect(frigateCardHasAction([action, action, action])).toBeFalsy(); - expect(hasAction).toBeCalledTimes(3); + expect(hasAction([action, action, action])).toBeFalsy(); + expect(customCardHasAction).toBeCalledTimes(3); }); }); diff --git a/tests/utils/basic.test.ts b/tests/utils/basic.test.ts index 257bbe3c..f0578db6 100644 --- a/tests/utils/basic.test.ts +++ b/tests/utils/basic.test.ts @@ -1,5 +1,5 @@ import { afterAll, describe, expect, it, vi } from 'vitest'; -import { FrigateCardError } from '../../src/types'; +import { AdvancedCameraCardError } from '../../src/types'; import { allPromises, arefloatsApproximatelyEqual, @@ -9,7 +9,7 @@ import { contentsChanged, dayToDate, desparsifyArrays, - dispatchFrigateCardEvent, + dispatchAdvancedCameraCardEvent, errorToConsole, formatDate, formatDateAndTime, @@ -32,13 +32,13 @@ import { import { createSlot, createSlotHost } from '../test-utils'; // @vitest-environment jsdom -describe('dispatchFrigateCardEvent', () => { +describe('dispatchAdvancedCameraCardEvent', () => { it('should dispatch event without data', () => { const element = document.createElement('div'); const handler = vi.fn(); - element.addEventListener('frigate-card:foo', handler); + element.addEventListener('advanced-camera-card:foo', handler); - dispatchFrigateCardEvent(element, 'foo'); + dispatchAdvancedCameraCardEvent(element, 'foo'); expect(handler).toBeCalled(); }); @@ -49,8 +49,8 @@ describe('dispatchFrigateCardEvent', () => { expect(ev.detail).toBe(data); }); - element.addEventListener('frigate-card:foo', handler); - dispatchFrigateCardEvent(element, 'foo', data); + element.addEventListener('advanced-camera-card:foo', handler); + dispatchAdvancedCameraCardEvent(element, 'foo', data); expect(handler).toBeCalled(); }); }); @@ -112,9 +112,9 @@ describe('errorToConsole', () => { errorToConsole(error); expect(spy).toHaveBeenCalledWith('ERROR'); }); - it('should log with context given frigate card error', () => { + it('should log with context given advanced camera card error', () => { const data = { foo: 2 }; - const error = new FrigateCardError('ERROR', { foo: 2 }); + const error = new AdvancedCameraCardError('ERROR', { foo: 2 }); errorToConsole(error); expect(spy).toHaveBeenCalledWith(error, data); }); diff --git a/tests/utils/diagnostics.test.ts b/tests/utils/diagnostics.test.ts index c596df0d..93ffbd07 100644 --- a/tests/utils/diagnostics.test.ts +++ b/tests/utils/diagnostics.test.ts @@ -4,8 +4,8 @@ import { mock } from 'vitest-mock-extended'; import { getLanguage } from '../../src/localize/localize'; import { getDiagnostics, getReleaseVersion } from '../../src/utils/diagnostics.js'; import { DeviceRegistryManager } from '../../src/utils/ha/registry/device'; -import { createHASS, createRegistryDevice } from '../test-utils'; import { homeAssistantWSRequest } from '../../src/utils/ha/ws-request'; +import { createHASS, createRegistryDevice } from '../test-utils'; vi.mock('../../package.json', () => ({ default: { @@ -21,7 +21,7 @@ vi.mock('../../src/utils/ha/ws-request.js'); describe('getReleaseVersion', () => { it('should get release version', () => { - expect(getReleaseVersion()).toBe('__FRIGATE_CARD_RELEASE_VERSION__'); + expect(getReleaseVersion()).toBe('__ADVANCED_CAMERA_CARD_RELEASE_VERSION__'); }); }); @@ -38,7 +38,7 @@ describe('getDiagnostics', () => { vi.setSystemTime(now); vi.mocked(getLanguage).mockReturnValue('en'); - vi.stubGlobal('navigator', { userAgent: 'FrigateCardTest/1.0' }); + vi.stubGlobal('navigator', { userAgent: 'AdvancedCameraCardTest/1.0' }); }); afterEach(() => { @@ -76,8 +76,8 @@ describe('getDiagnostics', () => { cameras: [{ camera_entity: 'camera.office' }], }), ).toEqual({ - browser: 'FrigateCardTest/1.0', - card_version: '__FRIGATE_CARD_RELEASE_VERSION__', + browser: 'AdvancedCameraCardTest/1.0', + card_version: '__ADVANCED_CAMERA_CARD_RELEASE_VERSION__', config: { cameras: [{ camera_entity: 'camera.office' }], }, @@ -130,8 +130,8 @@ describe('getDiagnostics', () => { it('should fetch diagnostics without hass or config', async () => { expect(await getDiagnostics()).toEqual({ - browser: 'FrigateCardTest/1.0', - card_version: '__FRIGATE_CARD_RELEASE_VERSION__', + browser: 'AdvancedCameraCardTest/1.0', + card_version: '__ADVANCED_CAMERA_CARD_RELEASE_VERSION__', git: { build_date: 'Tue, 19 Sep 2023 04:59:27 GMT', commit_date: 'Wed, 6 Sep 2023 21:27:28 -0700', @@ -156,8 +156,8 @@ describe('getDiagnostics', () => { deviceRegistryManager.getMatchingDevices.mockResolvedValue([]); expect(await getDiagnostics(hass, deviceRegistryManager)).toEqual({ - browser: 'FrigateCardTest/1.0', - card_version: '__FRIGATE_CARD_RELEASE_VERSION__', + browser: 'AdvancedCameraCardTest/1.0', + card_version: '__ADVANCED_CAMERA_CARD_RELEASE_VERSION__', git: { build_date: 'Tue, 19 Sep 2023 04:59:27 GMT', commit_date: 'Wed, 6 Sep 2023 21:27:28 -0700', diff --git a/tests/utils/embla/carousel-controller.test.ts b/tests/utils/embla/carousel-controller.test.ts index 5ee90ec5..12045da6 100644 --- a/tests/utils/embla/carousel-controller.test.ts +++ b/tests/utils/embla/carousel-controller.test.ts @@ -112,7 +112,10 @@ describe('CarouselController', () => { const parent = createParent({ children: children }); const forceSelectListener = vi.fn(); - parent.addEventListener('frigate-card:carousel:force-select', forceSelectListener); + parent.addEventListener( + 'advanced-camera-card:carousel:force-select', + forceSelectListener, + ); const carousel = new CarouselController(createRoot(), parent); @@ -131,7 +134,10 @@ describe('CarouselController', () => { const parent = createParent({ children: children }); const forceSelectListener = vi.fn(); - parent.addEventListener('frigate-card:carousel:force-select', forceSelectListener); + parent.addEventListener( + 'advanced-camera-card:carousel:force-select', + forceSelectListener, + ); const carousel = new CarouselController(createRoot(), parent); @@ -147,7 +153,7 @@ describe('CarouselController', () => { new CarouselController(createRoot(), parent); const selectHandler = vi.fn(); - parent.addEventListener('frigate-card:carousel:select', selectHandler); + parent.addEventListener('advanced-camera-card:carousel:select', selectHandler); getEmblaApi()?.selectedScrollSnap.mockReturnValue(6); getEmblaApi()?.slideNodes.mockReturnValue(children); @@ -169,7 +175,7 @@ describe('CarouselController', () => { new CarouselController(createRoot(), parent); const selectHandler = vi.fn(); - parent.addEventListener('frigate-card:carousel:select', selectHandler); + parent.addEventListener('advanced-camera-card:carousel:select', selectHandler); getEmblaApi()?.selectedScrollSnap.mockReturnValue(1000); getEmblaApi()?.slideNodes.mockReturnValue(children); diff --git a/tests/utils/embla/plugins/auto-media-loaded-info/auto-media-loaded-info.test.ts b/tests/utils/embla/plugins/auto-media-loaded-info/auto-media-loaded-info.test.ts index fd31b787..abba7e81 100644 --- a/tests/utils/embla/plugins/auto-media-loaded-info/auto-media-loaded-info.test.ts +++ b/tests/utils/embla/plugins/auto-media-loaded-info/auto-media-loaded-info.test.ts @@ -50,7 +50,7 @@ describe('AutoMediaLoadedInfo', () => { plugin.init(emblaApi, createTestEmblaOptionHandler()); const mediaLoadedHandler = vi.fn(); - parent.addEventListener('frigate-card:media:' + type, mediaLoadedHandler); + parent.addEventListener('advanced-camera-card:media:' + type, mediaLoadedHandler); if (type === 'loaded') { dispatchExistingMediaLoadedInfoAsEvent(children[5], createMediaLoadedInfo()); } else if (type === 'unloaded') { @@ -76,19 +76,19 @@ describe('AutoMediaLoadedInfo', () => { plugin.init(emblaApi, createTestEmblaOptionHandler()); const mediaLoadedHandler = vi.fn(); - parent.addEventListener('frigate-card:media:loaded', mediaLoadedHandler); + parent.addEventListener('advanced-camera-card:media:loaded', mediaLoadedHandler); dispatchExistingMediaLoadedInfoAsEvent(children[5], createMediaLoadedInfo()); vi.mocked(emblaApi.selectedScrollSnap).mockReturnValue(4); emblaApi .containerNode() - .dispatchEvent(new Event('frigate-card:carousel:force-select')); + .dispatchEvent(new Event('advanced-camera-card:carousel:force-select')); expect(mediaLoadedHandler).not.toBeCalled(); vi.mocked(emblaApi.selectedScrollSnap).mockReturnValue(5); emblaApi .containerNode() - .dispatchEvent(new Event('frigate-card:carousel:force-select')); + .dispatchEvent(new Event('advanced-camera-card:carousel:force-select')); expect(mediaLoadedHandler).toBeCalled(); }); }); diff --git a/tests/utils/media-info.test.ts b/tests/utils/media-info.test.ts index 59e11787..ca4553e9 100644 --- a/tests/utils/media-info.test.ts +++ b/tests/utils/media-info.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; -import { FrigateCardMediaPlayer, MediaLoadedCapabilities } from '../../src/types'; +import { AdvancedCameraCardMediaPlayer, MediaLoadedCapabilities } from '../../src/types'; import { createMediaLoadedInfo, dispatchExistingMediaLoadedInfoAsEvent, @@ -14,7 +14,7 @@ import { import { createMediaLoadedInfo as createTestMediaLoadedInfo } from '../test-utils.js'; const options = { - player: mock(), + player: mock(), capabilities: mock(), }; @@ -86,14 +86,14 @@ describe('createMediaLoadedInfo', () => { // @vitest-environment jsdom describe('dispatchMediaLoadedEvent', () => { const options = { - player: mock(), + player: mock(), capabilities: mock(), }; it('should dispatch', () => { const handler = vi.fn(); const div = document.createElement('div'); - div.addEventListener('frigate-card:media:loaded', handler); + div.addEventListener('advanced-camera-card:media:loaded', handler); // Need to write readonly properties. const img = document.createElement('img'); @@ -115,7 +115,7 @@ describe('dispatchMediaLoadedEvent', () => { it('should not dispatch', () => { const handler = vi.fn(); const div = document.createElement('div'); - div.addEventListener('frigate-card:media:loaded', handler); + div.addEventListener('advanced-camera-card:media:loaded', handler); dispatchMediaLoadedEvent(div, div, options); expect(handler).not.toBeCalled(); @@ -127,7 +127,7 @@ describe('dispatchExistingMediaLoadedInfoAsEvent', () => { it('should dispatch', () => { const handler = vi.fn(); const div = document.createElement('div'); - div.addEventListener('frigate-card:media:loaded', handler); + div.addEventListener('advanced-camera-card:media:loaded', handler); const info = createTestMediaLoadedInfo(); dispatchExistingMediaLoadedInfoAsEvent(div, info); @@ -144,7 +144,7 @@ describe('dispatchMediaUnloadedEvent', () => { it('should dispatch', () => { const handler = vi.fn(); const div = document.createElement('div'); - div.addEventListener('frigate-card:media:unloaded', handler); + div.addEventListener('advanced-camera-card:media:unloaded', handler); dispatchMediaUnloadedEvent(div); expect(handler).toBeCalled(); @@ -156,7 +156,7 @@ describe('dispatchMediaVolumeChangeEvent', () => { it('should dispatch', () => { const handler = vi.fn(); const div = document.createElement('div'); - div.addEventListener('frigate-card:media:volumechange', handler); + div.addEventListener('advanced-camera-card:media:volumechange', handler); dispatchMediaVolumeChangeEvent(div); expect(handler).toBeCalled(); @@ -168,7 +168,7 @@ describe('dispatchMediaPlayEvent', () => { it('should dispatch', () => { const handler = vi.fn(); const div = document.createElement('div'); - div.addEventListener('frigate-card:media:play', handler); + div.addEventListener('advanced-camera-card:media:play', handler); dispatchMediaPlayEvent(div); expect(handler).toBeCalled(); @@ -180,7 +180,7 @@ describe('dispatchMediaPauseEvent', () => { it('should dispatch', () => { const handler = vi.fn(); const div = document.createElement('div'); - div.addEventListener('frigate-card:media:pause', handler); + div.addEventListener('advanced-camera-card:media:pause', handler); dispatchMediaPauseEvent(div); expect(handler).toBeCalled(); diff --git a/tests/utils/media-layout.test.ts b/tests/utils/media-layout.test.ts index 85054fc9..80a0db32 100644 --- a/tests/utils/media-layout.test.ts +++ b/tests/utils/media-layout.test.ts @@ -11,19 +11,19 @@ describe('updateElementStyleFromMediaLayoutConfig', () => { fit: 'cover', }); - expect(video.style.getPropertyValue('--frigate-card-media-layout-fit')).toBe( - 'cover', - ); + expect( + video.style.getPropertyValue('--advanced-camera-card-media-layout-fit'), + ).toBe('cover'); }); it('unset', () => { const video = document.createElement('video'); - video.style.setProperty('--frigate-card-media-layout-fit', 'cover'); + video.style.setProperty('--advanced-camera-card-media-layout-fit', 'cover'); updateElementStyleFromMediaLayoutConfig(video); expect( - video.style.getPropertyValue('--frigate-card-media-layout-fit'), + video.style.getPropertyValue('--advanced-camera-card-media-layout-fit'), ).toBeFalsy(); }); }); @@ -40,25 +40,25 @@ describe('updateElementStyleFromMediaLayoutConfig', () => { }); expect( - video.style.getPropertyValue('--frigate-card-media-layout-position-x'), + video.style.getPropertyValue('--advanced-camera-card-media-layout-position-x'), ).toBe('10%'); expect( - video.style.getPropertyValue('--frigate-card-media-layout-position-y'), + video.style.getPropertyValue('--advanced-camera-card-media-layout-position-y'), ).toBe('20%'); }); it('unset', () => { const video = document.createElement('video'); - video.style.setProperty('--frigate-card-media-layout-position-x', '10%'); - video.style.setProperty('--frigate-card-media-layout-position-y', '20%'); + video.style.setProperty('--advanced-camera-card-media-layout-position-x', '10%'); + video.style.setProperty('--advanced-camera-card-media-layout-position-y', '20%'); updateElementStyleFromMediaLayoutConfig(video); expect( - video.style.getPropertyValue('--frigate-card-media-layout-position-x'), + video.style.getPropertyValue('--advanced-camera-card-media-layout-position-x'), ).toBeFalsy(); expect( - video.style.getPropertyValue('--frigate-card-media-layout-position-y'), + video.style.getPropertyValue('--advanced-camera-card-media-layout-position-y'), ).toBeFalsy(); }); }); @@ -77,39 +77,57 @@ describe('updateElementStyleFromMediaLayoutConfig', () => { }); expect( - video.style.getPropertyValue('--frigate-card-media-layout-view-box-top'), + video.style.getPropertyValue('--advanced-camera-card-media-layout-view-box-top'), ).toBe('1%'); expect( - video.style.getPropertyValue('--frigate-card-media-layout-view-box-bottom'), + video.style.getPropertyValue( + '--advanced-camera-card-media-layout-view-box-bottom', + ), ).toBe('2%'); expect( - video.style.getPropertyValue('--frigate-card-media-layout-view-box-left'), + video.style.getPropertyValue( + '--advanced-camera-card-media-layout-view-box-left', + ), ).toBe('3%'); expect( - video.style.getPropertyValue('--frigate-card-media-layout-view-box-right'), + video.style.getPropertyValue( + '--advanced-camera-card-media-layout-view-box-right', + ), ).toBe('4%'); }); it('unset', () => { const video = document.createElement('video'); - video.style.setProperty('--frigate-card-media-layout-view-box-top', '1%'); - video.style.setProperty('--frigate-card-media-layout-view-box-bottom', '2%'); - video.style.setProperty('--frigate-card-media-layout-view-box-left', '3%'); - video.style.setProperty('--frigate-card-media-layout-view-box-right', '4%'); + video.style.setProperty('--advanced-camera-card-media-layout-view-box-top', '1%'); + video.style.setProperty( + '--advanced-camera-card-media-layout-view-box-bottom', + '2%', + ); + video.style.setProperty('--advanced-camera-card-media-layout-view-box-left', '3%'); + video.style.setProperty( + '--advanced-camera-card-media-layout-view-box-right', + '4%', + ); updateElementStyleFromMediaLayoutConfig(video); expect( - video.style.getPropertyValue('--frigate-card-media-layout-view-box-top'), + video.style.getPropertyValue('--advanced-camera-card-media-layout-view-box-top'), ).toBeFalsy(); expect( - video.style.getPropertyValue('--frigate-card-media-layout-view-box-bottom'), + video.style.getPropertyValue( + '--advanced-camera-card-media-layout-view-box-bottom', + ), ).toBeFalsy(); expect( - video.style.getPropertyValue('--frigate-card-media-layout-view-box-left'), + video.style.getPropertyValue( + '--advanced-camera-card-media-layout-view-box-left', + ), ).toBeFalsy(); expect( - video.style.getPropertyValue('--frigate-card-media-layout-view-box-right'), + video.style.getPropertyValue( + '--advanced-camera-card-media-layout-view-box-right', + ), ).toBeFalsy(); }); }); diff --git a/tests/utils/media.test.ts b/tests/utils/media.test.ts index adbdd322..99e16287 100644 --- a/tests/utils/media.test.ts +++ b/tests/utils/media.test.ts @@ -1,8 +1,8 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; import { mock } from 'vitest-mock-extended'; -import { FrigateCardMediaPlayer } from '../../src/types.js'; +import { AdvancedCameraCardMediaPlayer } from '../../src/types.js'; import { - FrigateCardHTMLVideoElement, + AdvancedCameraCardHTMLVideoElement, MEDIA_LOAD_CONTROLS_HIDE_SECONDS, hideMediaControlsTemporarily, playMediaMutingIfNecessary, @@ -19,7 +19,7 @@ describe('setControlsOnVideo', () => { }); it('should stop timer', () => { - const video: FrigateCardHTMLVideoElement = document.createElement('video'); + const video: AdvancedCameraCardHTMLVideoElement = document.createElement('video'); hideMediaControlsTemporarily(video); expect(video._controlsHideTimer).toBeTruthy(); @@ -41,7 +41,7 @@ describe('hideMediaControlsTemporarily', () => { }); it('should set controls', () => { - const video: FrigateCardHTMLVideoElement = document.createElement('video'); + const video: AdvancedCameraCardHTMLVideoElement = document.createElement('video'); video.controls = true; hideMediaControlsTemporarily(video); @@ -53,7 +53,7 @@ describe('hideMediaControlsTemporarily', () => { }); it('should add event listener that resets controls', () => { - const video: FrigateCardHTMLVideoElement = document.createElement('video'); + const video: AdvancedCameraCardHTMLVideoElement = document.createElement('video'); video.controls = true; hideMediaControlsTemporarily(video); @@ -77,7 +77,7 @@ class NotAllowedError extends Error { describe('playMediaMutingIfNecessary', () => { it('should play', async () => { - const player = mock(); + const player = mock(); const video = mock(); video.play.mockResolvedValue(); await playMediaMutingIfNecessary(player, video); @@ -85,7 +85,7 @@ describe('playMediaMutingIfNecessary', () => { }); it('should mute if not allowed to play and unmuted', async () => { - const player = mock(); + const player = mock(); player.isMuted.mockReturnValue(false); player.mute.mockResolvedValue(); @@ -100,7 +100,7 @@ describe('playMediaMutingIfNecessary', () => { }); it('should not mute if not allowed to play and already unmuted', async () => { - const player = mock(); + const player = mock(); player.isMuted.mockReturnValue(true); const video = mock(); @@ -114,7 +114,7 @@ describe('playMediaMutingIfNecessary', () => { }); it('should ignore exception if subsequent play call throws', async () => { - const player = mock(); + const player = mock(); player.isMuted.mockReturnValue(false); const video = mock(); @@ -128,7 +128,7 @@ describe('playMediaMutingIfNecessary', () => { }); it('should ignore calls without a video', async () => { - const player = mock(); + const player = mock(); player.isMuted.mockReturnValue(false); await playMediaMutingIfNecessary(player); diff --git a/tests/utils/ptz.test.ts b/tests/utils/ptz.test.ts index de9ac066..929f44bd 100644 --- a/tests/utils/ptz.test.ts +++ b/tests/utils/ptz.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; import { Capabilities } from '../../src/camera-manager/capabilities'; -import { FrigateCardView } from '../../src/config/types'; +import { AdvancedCameraCardView } from '../../src/config/types'; import { IMAGE_VIEW_ZOOM_TARGET_SENTINEL } from '../../src/const'; import { getPTZTarget, @@ -124,7 +124,7 @@ describe('getPTZTarget', () => { describe('in non-media views', () => { it.each([['timeline' as const], ['diagnostics' as const]])( '%s', - (viewName: FrigateCardView) => { + (viewName: AdvancedCameraCardView) => { const view = createView({ view: viewName, }); diff --git a/tests/view/view-to-cameras.test.ts b/tests/view/view-to-cameras.test.ts index 723ce232..b3cee6ae 100644 --- a/tests/view/view-to-cameras.test.ts +++ b/tests/view/view-to-cameras.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest'; -import { FrigateCardView } from '../../src/config/types'; +import { AdvancedCameraCardView } from '../../src/config/types'; import { CapabilityKey } from '../../src/types'; import { getCameraIDsForViewName } from '../../src/view/view-to-cameras'; import { @@ -13,7 +13,7 @@ describe('getCameraIDsForViewName', () => { describe('views that are always supported', () => { it.each([['image' as const], ['diagnostics' as const]])( '%s', - (viewName: FrigateCardView) => { + (viewName: AdvancedCameraCardView) => { const cameraManager = createCameraManager(); vi.mocked(cameraManager.getStore).mockReturnValue( createStore([ @@ -53,7 +53,7 @@ describe('getCameraIDsForViewName', () => { ['timeline' as const, 'clips' as const], ['timeline' as const, 'snapshots' as const], ['timeline' as const, 'recordings' as const], - ])('%s', (viewName: FrigateCardView, capabilityKey: CapabilityKey) => { + ])('%s', (viewName: AdvancedCameraCardView, capabilityKey: CapabilityKey) => { const cameraManager = createCameraManager(); vi.mocked(cameraManager.getStore).mockReturnValue( createStore([ diff --git a/yarn.lock b/yarn.lock index 743fdb79..9ce38cc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2461,6 +2461,88 @@ __metadata: languageName: node linkType: hard +"advanced-camera-card@workspace:.": + version: 0.0.0-use.local + resolution: "advanced-camera-card@workspace:." + dependencies: + "@babel/core": "npm:^7.24.7" + "@babel/plugin-proposal-decorators": "npm:^7.24.7" + "@babel/plugin-transform-class-properties": "npm:^7.24.7" + "@cycjimmy/jsmpeg-player": "npm:^6.0.5" + "@dermotduffy/custom-card-helpers": "npm:^1.9.1" + "@dermotduffy/panzoom": "npm:^4.5.1" + "@egjs/hammerjs": "npm:^2.0.17" + "@graphiteds/core": "npm:^1.9.21" + "@lit-labs/scoped-registry-mixin": "npm:^1.0.3" + "@lit-labs/task": "npm:^1.1.3" + "@rollup/plugin-babel": "npm:^6.0.4" + "@rollup/plugin-commonjs": "npm:^26.0.1" + "@rollup/plugin-image": "npm:^3.0.3" + "@rollup/plugin-json": "npm:^6.1.0" + "@rollup/plugin-node-resolve": "npm:^15.2.3" + "@rollup/plugin-replace": "npm:^5.0.7" + "@rollup/plugin-terser": "npm:^0.4.4" + "@rollup/plugin-typescript": "npm:^11.1.6" + "@semantic-release/github": "npm:^10.3.3" + "@types/bluebird": "npm:^3.5.42" + "@types/js-yaml": "npm:^4" + "@types/lodash-es": "npm:^4.17.12" + "@types/masonry-layout": "npm:^4.2.8" + "@typescript-eslint/eslint-plugin": "npm:^7.13.0" + "@typescript-eslint/parser": "npm:^7.13.0" + "@vitest/coverage-istanbul": "npm:^1.6.0" + component-emitter: "npm:^1.3.1" + compute-scroll-into-view: "npm:^3.1.1" + conventional-changelog-conventionalcommits: "npm:^8.0.0" + crypto: "npm:^1.0.1" + date-fns: "npm:^3.6.0" + date-fns-tz: "npm:^3.1.3" + docsify-cli: "npm:^4.4.4" + embla-carousel: "npm:^8.3.0" + embla-carousel-wheel-gestures: "npm:^8.0.1" + eslint: "npm:^8.57.0" + eslint-config-airbnb-base: "npm:^15.0.0" + eslint-config-prettier: "npm:^9.1.0" + eslint-plugin-import: "npm:^2.29.1" + home-assistant-js-websocket: "npm:^9.4.0" + js-yaml: "npm:^4.1.0" + jsdom: "npm:^21.1.2" + keycharm: "npm:^0.4.0" + lit: "npm:^3.1.4" + lodash-es: "npm:^4.17.21" + markdown-link-check: "npm:^3.12.2" + masonry-layout: "npm:^4.2.2" + moment: "npm:^2.30.1" + p-queue: "npm:^8.0.1" + prettier: "npm:^3.3.2" + propagating-hammerjs: "npm:^2.0.1" + quick-lru: "npm:^6.1.2" + rollup: "npm:^3.29.4" + rollup-plugin-git-info: "npm:^1.0.0" + rollup-plugin-serve: "npm:^1.1.1" + rollup-plugin-styler: "npm:^1.8.0" + rollup-plugin-svgo: "npm:^2.0.0" + rollup-plugin-visualizer: "npm:^5.12.0" + sass: "patch:sass@npm%3A1.77.4#~/.yarn/patches/sass-npm-1.77.4-13b6910aea.patch" + screenfull: "npm:^6.0.2" + semantic-release: "npm:^24.1.1" + semantic-release-export-data: "npm:^1.1.0" + side-drawer: "npm:^3.1.0" + ts-prune: "npm:^0.10.3" + type-fest: "npm:^4.20.0" + typescript: "npm:^5.4.5" + uuid: "npm:^8.3.2" + vis-data: "npm:^7.1.9" + vis-timeline: "npm:^7.7.3" + vis-util: "npm:^5.0.7" + vitest: "npm:^1.6.0" + vitest-mock-extended: "npm:^1.3.1" + web-dialog: "npm:^0.0.11" + xss: "npm:^1.0.15" + zod: "npm:^3.23.8" + languageName: unknown + linkType: soft + "agent-base@npm:6": version: 6.0.2 resolution: "agent-base@npm:6.0.2" @@ -5200,88 +5282,6 @@ __metadata: languageName: node linkType: hard -"frigate-hass-card@workspace:.": - version: 0.0.0-use.local - resolution: "frigate-hass-card@workspace:." - dependencies: - "@babel/core": "npm:^7.24.7" - "@babel/plugin-proposal-decorators": "npm:^7.24.7" - "@babel/plugin-transform-class-properties": "npm:^7.24.7" - "@cycjimmy/jsmpeg-player": "npm:^6.0.5" - "@dermotduffy/custom-card-helpers": "npm:^1.9.1" - "@dermotduffy/panzoom": "npm:^4.5.1" - "@egjs/hammerjs": "npm:^2.0.17" - "@graphiteds/core": "npm:^1.9.21" - "@lit-labs/scoped-registry-mixin": "npm:^1.0.3" - "@lit-labs/task": "npm:^1.1.3" - "@rollup/plugin-babel": "npm:^6.0.4" - "@rollup/plugin-commonjs": "npm:^26.0.1" - "@rollup/plugin-image": "npm:^3.0.3" - "@rollup/plugin-json": "npm:^6.1.0" - "@rollup/plugin-node-resolve": "npm:^15.2.3" - "@rollup/plugin-replace": "npm:^5.0.7" - "@rollup/plugin-terser": "npm:^0.4.4" - "@rollup/plugin-typescript": "npm:^11.1.6" - "@semantic-release/github": "npm:^10.3.3" - "@types/bluebird": "npm:^3.5.42" - "@types/js-yaml": "npm:^4" - "@types/lodash-es": "npm:^4.17.12" - "@types/masonry-layout": "npm:^4.2.8" - "@typescript-eslint/eslint-plugin": "npm:^7.13.0" - "@typescript-eslint/parser": "npm:^7.13.0" - "@vitest/coverage-istanbul": "npm:^1.6.0" - component-emitter: "npm:^1.3.1" - compute-scroll-into-view: "npm:^3.1.1" - conventional-changelog-conventionalcommits: "npm:^8.0.0" - crypto: "npm:^1.0.1" - date-fns: "npm:^3.6.0" - date-fns-tz: "npm:^3.1.3" - docsify-cli: "npm:^4.4.4" - embla-carousel: "npm:^8.3.0" - embla-carousel-wheel-gestures: "npm:^8.0.1" - eslint: "npm:^8.57.0" - eslint-config-airbnb-base: "npm:^15.0.0" - eslint-config-prettier: "npm:^9.1.0" - eslint-plugin-import: "npm:^2.29.1" - home-assistant-js-websocket: "npm:^9.4.0" - js-yaml: "npm:^4.1.0" - jsdom: "npm:^21.1.2" - keycharm: "npm:^0.4.0" - lit: "npm:^3.1.4" - lodash-es: "npm:^4.17.21" - markdown-link-check: "npm:^3.12.2" - masonry-layout: "npm:^4.2.2" - moment: "npm:^2.30.1" - p-queue: "npm:^8.0.1" - prettier: "npm:^3.3.2" - propagating-hammerjs: "npm:^2.0.1" - quick-lru: "npm:^6.1.2" - rollup: "npm:^3.29.4" - rollup-plugin-git-info: "npm:^1.0.0" - rollup-plugin-serve: "npm:^1.1.1" - rollup-plugin-styler: "npm:^1.8.0" - rollup-plugin-svgo: "npm:^2.0.0" - rollup-plugin-visualizer: "npm:^5.12.0" - sass: "patch:sass@npm%3A1.77.4#~/.yarn/patches/sass-npm-1.77.4-13b6910aea.patch" - screenfull: "npm:^6.0.2" - semantic-release: "npm:^24.1.1" - semantic-release-export-data: "npm:^1.1.0" - side-drawer: "npm:^3.1.0" - ts-prune: "npm:^0.10.3" - type-fest: "npm:^4.20.0" - typescript: "npm:^5.4.5" - uuid: "npm:^8.3.2" - vis-data: "npm:^7.1.9" - vis-timeline: "npm:^7.7.3" - vis-util: "npm:^5.0.7" - vitest: "npm:^1.6.0" - vitest-mock-extended: "npm:^1.3.1" - web-dialog: "npm:^0.0.11" - xss: "npm:^1.0.15" - zod: "npm:^3.23.8" - languageName: unknown - linkType: soft - "from2@npm:^2.3.0": version: 2.3.0 resolution: "from2@npm:2.3.0"