diff --git a/DETAILED_CONFIGURATION.md b/DETAILED_CONFIGURATION.md
new file mode 100644
index 0000000..4ba5204
--- /dev/null
+++ b/DETAILED_CONFIGURATION.md
@@ -0,0 +1,348 @@
+# Detailed Configuration
+
+**type**: 'custom:plex-meets-homeassistant'
+
+**token**: Enter your [Plex Token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/)
+
+**ip**: Enter ip address of plex server. You can also enter hostname without protocol or port.
+
+**libraryName**: Name of the library you wish to render.
+
+_Available special libraries:_
+
+| Special Library | Description |
+| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
+| Watch Next | Shows Continue Watching feed just like in your new plex interface. **Does not work with old servers.** |
+| Recently Added | Shows recently added tv show episodes, might use old Plex API. For recently added movies use sort with your movies library. |
+| Continue Watching | Shows movies and tv shows in progress, uses old Plex API. |
+| Deck | Shows tv shows on deck, uses old Plex API. |
+
+**entity**: You need to configure at least one supported media_player entity.
+
+- **androidtv**: Entity id of your media_player configured via [Android TV](https://www.home-assistant.io/integrations/androidtv/). See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#android-tv-or-fire-tv). It is also possible to use short declaration with androidtv.
+- **kodi**: Entity id of your media_player configured via [Kodi](https://www.home-assistant.io/integrations/kodi/). See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#kodi). It is also possible to use short declaration with kodi.
+- **plexPlayer**: Name or machine ID of your plex client. Use this if you do not have devices above. See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#all-other-plex-clients). It is required to use detailed declaration with "plexPlayer:" property.
+- **cast**: Entity id of your media_player configured via [Google Cast](https://www.home-assistant.io/integrations/cast/). See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#google-cast). It is also possible to use short declaration with cast.
+
+**port**: _Optional_ Port of your plex sever.
+
+**protocol**: _Optional_ Protocol to use for Plex. Defaults to "http".
+
+**maxCount**: _Optional_ Maximum number of items to display in card.
+
+**sort**: _Optional_ Define sort by. See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#sorting)
+
+**runBefore**: _Optional_ Specify a script to run before playing. This can be for example a script which turns on your TV and waits 5 seconds. If this is specified and provided entity/script exists, all the other play checks for availability of entity are ignored.
+
+**runAfter**: _Optional_ Specify a script to run after playing.
+
+**showExtras**: _Optional_ Specify whether to show extras if any available on movie / episode. Possible values: true, false. Default: true
+
+**showSearch**: _Optional_ Specify whether to show search field on top. Possible values: true, false. Default: true
+
+**playTrailer**: _Optional_ Specify whether to play trailer if available. Possible values: true, false, muted. Default: true
+
+Example of the simplest possible configuration:
+
+```
+type: 'custom:plex-meets-homeassistant'
+token: QWdsqEXAMPLETOKENqwerty
+ip: 192.168.13.37
+libraryName: Movies
+entity: media_player.bedroom_tv # entity provided by cast integration
+```
+
+Example of the simplest possible configuration using multiple entities:
+
+```
+type: 'custom:plex-meets-homeassistant'
+token: QWdsqEXAMPLETOKENqwerty
+ip: 192.168.13.37
+libraryName: Movies
+entity:
+ - media_player.living_room_nvidia_shield # created by androidtv integration
+ - media_player.living_room_tv # created by cast integration
+ - media_player.bedroom_tv # created by cast integration
+ - media_player.kodi_123456qwe789rty # created by kodi integration
+```
+
+Example of card configuration using detailed definitions:
+
+```
+type: 'custom:plex-meets-homeassistant'
+token: QWdsqEXAMPLETOKENqwerty
+ip: 192.168.13.37
+port: 32400
+libraryName: TV Shows
+protocol: http
+maxCount: 10
+sort: title:desc
+entity:
+ kodi: media_player.kodi_123456qwe789rty
+ androidtv: media_player.living_room_nvidia_shield
+ plexPlayer: 192.168.13.38
+ cast: media_player.bedroom_tv
+```
+
+Complex example using detailed definitions, lists and shared plex server for plexPlayer:
+
+```
+type: 'custom:plex-meets-homeassistant'
+token: QWdsqEXAMPLETOKENqwerty
+ip: remote.plex.server.com # remote shared plex instance
+port: 443
+libraryName: Deck
+protocol: https
+maxCount: 10
+sort: title:desc
+runBefore: script.turn_on_tv_and_wait
+runAfter: script.movie_time
+showExtras: true
+playTrailer: muted
+entity:
+ kodi:
+ - media_player.kodi_bedroom
+ - media_player.kodi_living_room
+ androidtv:
+ - media_player.living_room_nvidia_shield
+ - media_player.bedroom_nvidia_shield
+ - media_player.kithen_nvidia_shield
+ plexPlayer:
+ - identifier: TV 2020 # plex client device located on local plex server network
+ server:
+ ip: local.plex.server.com # Mandatory
+ token: QWdsqEXAMPLETOKENqwerty # Mandatory
+ port: 32400
+ protocol: http
+ - 192.168.13.50 # without definition for server, it will look for device on remote.plex.server.com network
+ cast: media_player.bedroom_tv
+```
+
+In this example, it will try to first play via kodi, in bedroom. If that kodi is unavailable or off, it tries in living room kodi.
+If that fails, it moves on to android tvs, starting with living room, continuing with bedroom and ending with kitchen.
+Next, if a possible player still has not been found (all kodis and shields are off) it tries to play via plexPlayer, trying TV 2020 on local plex server and if not found, IP 192.168.13.50 on remote plex server.
+Finally, it tries to cast into media_player.bedroom_tv.
+
+## Detailed configuration instructions for end devices
+
+_You can combine multiple supported entities_, in that case, entity for supported content will be chosen in order how you entered them.
+
+As an example, if content can be played / shown both by kodi and androidtv, and you entered kodi first, it will be shown by kodi. If it cannot be played by kodi but can be played by androidtv, androidtv will be used.
+
+This will also work with play button being shown, it will only show when you can actually play content on your device.
+
+Play button is only visible if all the conditions inside Availability section of end devices below are met.
+
+### Android TV or Fire TV
+
+**Difficulty to setup**: Easy
+
+**Steps**:
+
+- Install plex application on your Android TV device. Open it and do the default setup so that you can see and navigate your libraries.
+- Setup [Android TV](https://www.home-assistant.io/integrations/androidtv/). You need just a [default configuration](https://www.home-assistant.io/integrations/androidtv/#configuration), no optional parameters needed.
+- Use entity_id of media_player provided by Android TV integration in card, example: `androidtv: media_player.living_room_nvidia_shield`.
+
+**Availability**:
+
+- Provided entity ID needs to exists
+- Provided entity ID needs to have attributes
+- Provided entity ID needs to have attribute adb_response
+
+**Supported**:
+
+✅ Shared Plex servers
+
+✅ Movies
+
+✅ Show
+
+✅ Season
+
+✅ Episodes
+
+### Kodi
+
+**Difficulty to setup**: Moderate
+
+**Steps**:
+
+- Install and configure [PlexKodiConnect](https://github.com/croneter/PlexKodiConnect#download-and-installation) on Kodi itself.
+- Setup [Kodi](https://www.home-assistant.io/integrations/kodi/) integration for your device.
+- Install and configure integration [Kodi Recently Added Media](https://github.com/jtbgroup/kodi-media-sensors#installation) and its sensor **kodi_media_sensor_search**
+
+
+ Images of installation of Kodi Recently Added Media
+
+![Click on add integration in integrations](images/kodi_setup/1.png)
+
+![Find integration Kodi Media Sensors](images/kodi_setup/2.png)
+
+![Configure integration Kodi Media Sensors](images/kodi_setup/3.png)
+
+
+
+- Use entity_id of media_player provided by Kodi integration in card, example: `media_player.kodi_123456qwe789rty`.
+
+**Availability**:
+
+- Provided entity ID needs to exists
+- Entity 'sensor.kodi_media_sensor_search' needs to exist
+- State of both entities cannot be 'unavailable'
+- State of kodi cannot be 'off'
+
+**Supported**:
+
+✅ Shared Plex servers _\*if content available in kodi_
+
+✅ Movies
+
+❌ Show
+
+❌ Season
+
+✅ Episodes
+
+### Google Cast
+
+**Difficulty to setup**: Very easy
+
+**Steps**:
+
+- Set up [Google Cast](https://www.home-assistant.io/integrations/cast/) in Home Assistant.
+- Use entity_id of media_player provided by Google Cast integration in card, example: `cast: media_player.bedroom_tv`.
+- Save card configuration and make sure the entity is not `unavailable`, if you see play buttons on movies or individual episodes configuration was successful.
+
+**Availability**:
+
+- Media player entity cannot be `unavailable`
+
+**Supported**:
+
+✅ Shared Plex servers
+
+✅ Movies
+
+❌ Show
+
+❌ Season
+
+✅ Episodes
+
+### All other plex clients
+
+**Difficulty to setup**: Very Easy to Moderate
+
+**Steps**:
+
+_Easy setup_:
+
+Notice: While easy, it might not work if you have multiple devices with the same name, or you buy a second device with the same name in the future. Some plex clients also incorrectly report theirs IP Address, so addition by that might not be working. Take a look at Machine ID setup below if this is a concern for you.
+
+- Open Plex app on the device you wish to add
+- Open your Plex web GUI
+- Click on cast on the top right corner and note down name of your device
+
+![Plex cast](images/plex_player/1.png)
+
+- Add it to card, example:
+
+```
+entity:
+ plexPlayer: TV 2020
+```
+
+Instead of device name, you can also enter device IP address or product name.
+
+- Save card configuration, if you see play buttons everywhere configuration was successful.
+
+If you do not see play button, or have multiple devices with the same name, follow Machine ID setup below.
+
+_Machine ID setup_:
+
+- Open Plex app on the device you wish to add
+- Open your Plex web GUI
+- Modify URL so that just after the port, just after the first slash, you enter `clients?X-Plex-Token=PLEX_TOKEN`. Replace PLEX_TOKEN with your plex token. Example final URL `http://192.168.13.37:32400/clients?X-Plex-Token=qweRTY123456`.
+- You will get a list of all currently connected Plex clients.
+- Find the client you wish to add, and copy machineIdentifier key without quotes.
+- Add machineIdentifier into card, for example:
+
+```
+entity:
+ plexPlayer: mYaweS0meMacHin3Id3ntiFI3r
+```
+
+- Save card configuration, if you see play buttons everywhere configuration was successful.
+
+**Availability**:
+
+- Plex needs to run on the defined device
+
+**Supported**:
+
+✅ Shared Plex servers _\*requires additional configuration, see below_
+
+✅ Movies
+
+✅ Show
+
+✅ Season
+
+✅ Episodes
+
+**Shared Plex servers configuration**
+
+plexPlayer can be configured in multiple ways, achieving the same thing:
+
+```
+entity:
+ plexPlayer: TV 2020
+```
+
+```
+entity:
+ plexPlayer:
+ - TV 2020
+```
+
+```
+entity:
+ plexPlayer:
+ identifier: TV 2020
+```
+
+```
+entity:
+ plexPlayer:
+ - identifier: TV 2020
+```
+
+As can be seen from the last two examples, it is possible to configure it as an object having key "identifier".
+
+That is useful, if you want to stream media from shared or remote Plex server. Add information about your local Plex server which sees your device on which you wish to play content. This is done by including a new key, "server" having additional keys:
+
+Example 1:
+
+```
+entity:
+ plexPlayer:
+ - identifier: TV 2020
+ server:
+ ip: 192.168.13.37 # Mandatory
+ token: QWdsqEXAMPLETOKENqwerty # Mandatory
+ port: 32400
+ protocol: http
+```
+
+Example 2:
+
+```
+entity:
+ plexPlayer:
+ identifier: TV 2020
+ server:
+ ip: 192.168.13.37 # Mandatory
+ token: QWdsqEXAMPLETOKENqwerty # Mandatory
+ port: 32400
+ protocol: http
+```
diff --git a/README.md b/README.md
index adcdc4b..7e53d51 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Custom Home Assistant card which integrates plex into Home Assistant and makes it possible to launch movies or tv shows on TV with a simple click.
-Supported are **ALL** Plex clients, some even with enhanced functionality. Kodi with PlexKodiConnect and Google Cast is also supported.
+Supported are **ALL** Plex clients, some even with enhanced functionality. Kodi with PlexKodiConnect, Android TV and Google Cast is also supported.
Video of the card:
@@ -19,421 +19,28 @@ More images [at the end of the readme](https://github.com/JurajNyiri/PlexMeetsHo
- Install **Plex Meets Home Assistant** from HACS.
- Reload browser, clear cache as usual
- Create a new Home Assistant tab, turn on panel mode
-- Add a new card, see configuration below.
+- Add a card by clicking Add Card in your lovelace edit mode and finding "Custom: Plex Meets Home Assistant"
## Configuration
-**type**: 'custom:plex-meets-homeassistant'
+You can use UI to configure your card which will automatically load up your libraries and entities. If you wish, you can use card code to configure everything manually. See [Detailed Configuration](DETAILED_CONFIGURATION.md) if you wish to see a lot more information about every property of the card.
-**token**: Enter your [Plex Token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/)
+Supported target devices are all entities from integrations [Android TV](https://www.home-assistant.io/integrations/androidtv/), [Kodi](https://www.home-assistant.io/integrations/kodi/), [Google Cast](https://www.home-assistant.io/integrations/cast/) and any other Plex player available for casting through Plex web interface.
-**ip**: Enter ip address of plex server. You can also enter hostname without protocol or port.
+### Regarding "all other plex players" without entities
-**libraryName**: Name of the library you wish to render.
+In order for devices which do not have entities to be detected, they need to have plex app running when configuring the card or trying to play content.
-_Available special libraries:_
+If you are trying to play content on a local device from a remote Plex server **or** trying to play local Plex content on a remote device, you need to configure identifier and server for this device. This can only be done manually through card code configuration. See [Detailed Configuration - All other plex clients](DETAILED_CONFIGURATION.md#all-other-plex-clients). You can still use UI to edit anything else except entities while manually using plexPlayer key in entities.
-| Special Library | Description |
-| ----------------- | --------------------------------------------------------------------------------------------------------------------------- |
-| Watch Next | Shows Continue Watching feed just like in your new plex interface. **Does not work with old servers.** |
-| Recently Added | Shows recently added tv show episodes, might use old Plex API. For recently added movies use sort with your movies library. |
-| Continue Watching | Shows movies and tv shows in progress, uses old Plex API. |
-| Deck | Shows tv shows on deck, uses old Plex API. |
+![Configuration via UI](images/ui_configuration.png)
-**entity**: You need to configure at least one supported media_player entity.
+### Quick guides:
-- **androidtv**: Entity id of your media_player configured via [Android TV](https://www.home-assistant.io/integrations/androidtv/). See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#android-tv-or-fire-tv). It is also possible to use short declaration with androidtv.
-- **kodi**: Entity id of your media_player configured via [Kodi](https://www.home-assistant.io/integrations/kodi/). See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#kodi). It is also possible to use short declaration with kodi.
-- **plexPlayer**: Name or machine ID of your plex client. Use this if you do not have devices above. See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#all-other-plex-clients). It is required to use detailed declaration with "plexPlayer:" property.
-- **cast**: Entity id of your media_player configured via [Google Cast](https://www.home-assistant.io/integrations/cast/). See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#google-cast). It is also possible to use short declaration with cast.
-
-**port**: _Optional_ Port of your plex sever.
-
-**protocol**: _Optional_ Protocol to use for Plex. Defaults to "http".
-
-**maxCount**: _Optional_ Maximum number of items to display in card.
-
-**sort**: _Optional_ Define sort by. See [detailed instructions](https://github.com/JurajNyiri/PlexMeetsHomeAssistant#sorting)
-
-**runBefore**: _Optional_ Specify a script to run before playing. This can be for example a script which turns on your TV and waits 5 seconds. If this is specified and provided entity/script exists, all the other play checks for availability of entity are ignored.
-
-**runAfter**: _Optional_ Specify a script to run after playing.
-
-**showExtras**: _Optional_ Specify whether to show extras if any available on movie / episode. Possible values: true, false. Default: true
-
-**playTrailer**: _Optional_ Specify whether to play trailer if available. Possible values: true, false, muted. Default: true
-
-Example of the simplest possible configuration:
-
-```
-type: 'custom:plex-meets-homeassistant'
-token: QWdsqEXAMPLETOKENqwerty
-ip: 192.168.13.37
-libraryName: Movies
-entity: media_player.bedroom_tv # entity provided by cast integration
-```
-
-Example of the simplest possible configuration using multiple entities:
-
-```
-type: 'custom:plex-meets-homeassistant'
-token: QWdsqEXAMPLETOKENqwerty
-ip: 192.168.13.37
-libraryName: Movies
-entity:
- - media_player.living_room_nvidia_shield # created by androidtv integration
- - media_player.living_room_tv # created by cast integration
- - media_player.bedroom_tv # created by cast integration
- - media_player.kodi_123456qwe789rty # created by kodi integration
-```
-
-Example of card configuration using detailed definitions:
-
-```
-type: 'custom:plex-meets-homeassistant'
-token: QWdsqEXAMPLETOKENqwerty
-ip: 192.168.13.37
-port: 32400
-libraryName: TV Shows
-protocol: http
-maxCount: 10
-sort: title:desc
-entity:
- kodi: media_player.kodi_123456qwe789rty
- androidtv: media_player.living_room_nvidia_shield
- plexPlayer: 192.168.13.38
- cast: media_player.bedroom_tv
-```
-
-Complex example using detailed definitions, lists and shared plex server for plexPlayer:
-
-```
-type: 'custom:plex-meets-homeassistant'
-token: QWdsqEXAMPLETOKENqwerty
-ip: remote.plex.server.com # remote shared plex instance
-port: 443
-libraryName: Deck
-protocol: https
-maxCount: 10
-sort: title:desc
-runBefore: script.turn_on_tv_and_wait
-runAfter: script.movie_time
-showExtras: true
-playTrailer: muted
-entity:
- kodi:
- - media_player.kodi_bedroom
- - media_player.kodi_living_room
- androidtv:
- - media_player.living_room_nvidia_shield
- - media_player.bedroom_nvidia_shield
- - media_player.kithen_nvidia_shield
- plexPlayer:
- - identifier: TV 2020 # plex client device located on local plex server network
- server:
- ip: local.plex.server.com # Mandatory
- token: QWdsqEXAMPLETOKENqwerty # Mandatory
- port: 32400
- protocol: http
- - 192.168.13.50 # without definition for server, it will look for device on remote.plex.server.com network
- cast: media_player.bedroom_tv
-```
-
-In this example, it will try to first play via kodi, in bedroom. If that kodi is unavailable or off, it tries in living room kodi.
-If that fails, it moves on to android tvs, starting with living room, continuing with bedroom and ending with kitchen.
-Next, if a possible player still has not been found (all kodis and shields are off) it tries to play via plexPlayer, trying TV 2020 on local plex server and if not found, IP 192.168.13.50 on remote plex server.
-Finally, it tries to cast into media_player.bedroom_tv.
-
-## Detailed configuration instructions for end devices
-
-_You can combine multiple supported entities_, in that case, entity for supported content will be chosen in order how you entered them.
-
-As an example, if content can be played / shown both by kodi and androidtv, and you entered kodi first, it will be shown by kodi. If it cannot be played by kodi but can be played by androidtv, androidtv will be used.
-
-This will also work with play button being shown, it will only show when you can actually play content on your device.
-
-Play button is only visible if all the conditions inside Availability section of end devices below are met.
-
-### Android TV or Fire TV
-
-**Difficulty to setup**: Easy
-
-**Steps**:
-
-- Install plex application on your Android TV device. Open it and do the default setup so that you can see and navigate your libraries.
-- Setup [Android TV](https://www.home-assistant.io/integrations/androidtv/). You need just a [default configuration](https://www.home-assistant.io/integrations/androidtv/#configuration), no optional parameters needed.
-- Use entity_id of media_player provided by Android TV integration in card, example: `androidtv: media_player.living_room_nvidia_shield`.
-
-**Availability**:
-
-- Provided entity ID needs to exists
-- Provided entity ID needs to have attributes
-- Provided entity ID needs to have attribute adb_response
-
-**Supported**:
-
-✅ Shared Plex servers
-
-✅ Movies
-
-✅ Show
-
-✅ Season
-
-✅ Episodes
-
-### Kodi
-
-**Difficulty to setup**: Moderate
-
-**Steps**:
-
-- Install and configure [PlexKodiConnect](https://github.com/croneter/PlexKodiConnect#download-and-installation) on Kodi itself.
-- Setup [Kodi](https://www.home-assistant.io/integrations/kodi/) integration for your device.
-- Install and configure integration [Kodi Recently Added Media](https://github.com/jtbgroup/kodi-media-sensors#installation) and its sensor **kodi_media_sensor_search**
-
-
- Images of installation of Kodi Recently Added Media
-
-![Click on add integration in integrations](https://github.com/JurajNyiri/PlexMeetsHomeAssistant/blob/main/images/kodi_setup/1.png)
-
-![Find integration Kodi Media Sensors](https://github.com/JurajNyiri/PlexMeetsHomeAssistant/blob/main/images/kodi_setup/2.png)
-
-![Configure integration Kodi Media Sensors](https://github.com/JurajNyiri/PlexMeetsHomeAssistant/blob/main/images/kodi_setup/3.png)
-
-
-
-- Use entity_id of media_player provided by Kodi integration in card, example: `media_player.kodi_123456qwe789rty`.
-
-**Availability**:
-
-- Provided entity ID needs to exists
-- Entity 'sensor.kodi_media_sensor_search' needs to exist
-- State of both entities cannot be 'unavailable'
-- State of kodi cannot be 'off'
-
-**Supported**:
-
-✅ Shared Plex servers _\*if content available in kodi_
-
-✅ Movies
-
-❌ Show
-
-❌ Season
-
-✅ Episodes
-
-### Google Cast
-
-**Difficulty to setup**: Very easy
-
-**Steps**:
-
-- Set up [Google Cast](https://www.home-assistant.io/integrations/cast/) in Home Assistant.
-- Use entity_id of media_player provided by Google Cast integration in card, example: `cast: media_player.bedroom_tv`.
-- Save card configuration and make sure the entity is not `unavailable`, if you see play buttons on movies or individual episodes configuration was successful.
-
-**Availability**:
-
-- Media player entity cannot be `unavailable`
-
-**Supported**:
-
-✅ Shared Plex servers
-
-✅ Movies
-
-❌ Show
-
-❌ Season
-
-✅ Episodes
-
-### All other plex clients
-
-**Difficulty to setup**: Very Easy to Moderate
-
-**Steps**:
-
-_Easy setup_:
-
-Notice: While easy, it might not work if you have multiple devices with the same name, or you buy a second device with the same name in the future. Some plex clients also incorrectly report theirs IP Address, so addition by that might not be working. Take a look at Machine ID setup below if this is a concern for you.
-
-- Open Plex app on the device you wish to add
-- Open your Plex web GUI
-- Click on cast on the top right corner and note down name of your device
-
-![Plex cast](https://github.com/JurajNyiri/PlexMeetsHomeAssistant/blob/main/images/plex_player/1.png)
-
-- Add it to card, example:
-
-```
-entity:
- plexPlayer: TV 2020
-```
-
-Instead of device name, you can also enter device IP address or product name.
-
-- Save card configuration, if you see play buttons everywhere configuration was successful.
-
-If you do not see play button, or have multiple devices with the same name, follow Machine ID setup below.
-
-_Machine ID setup_:
-
-- Open Plex app on the device you wish to add
-- Open your Plex web GUI
-- Modify URL so that just after the port, just after the first slash, you enter `clients?X-Plex-Token=PLEX_TOKEN`. Replace PLEX_TOKEN with your plex token. Example final URL `http://192.168.13.37:32400/clients?X-Plex-Token=qweRTY123456`.
-- You will get a list of all currently connected Plex clients.
-- Find the client you wish to add, and copy machineIdentifier key without quotes.
-- Add machineIdentifier into card, for example:
-
-```
-entity:
- plexPlayer: mYaweS0meMacHin3Id3ntiFI3r
-```
-
-- Save card configuration, if you see play buttons everywhere configuration was successful.
-
-**Availability**:
-
-- Plex needs to run on the defined device
-
-**Supported**:
-
-✅ Shared Plex servers _\*requires additional configuration, see below_
-
-✅ Movies
-
-✅ Show
-
-✅ Season
-
-✅ Episodes
-
-**Shared Plex servers configuration**
-
-plexPlayer can be configured in multiple ways, achieving the same thing:
-
-```
-entity:
- plexPlayer: TV 2020
-```
-
-```
-entity:
- plexPlayer:
- - TV 2020
-```
-
-```
-entity:
- plexPlayer:
- identifier: TV 2020
-```
-
-```
-entity:
- plexPlayer:
- - identifier: TV 2020
-```
-
-As can be seen from the last two examples, it is possible to configure it as an object having key "identifier".
-
-That is useful, if you want to stream media from shared or remote Plex server. Add information about your local Plex server which sees your device on which you wish to play content. This is done by including a new key, "server" having additional keys:
-
-Example 1:
-
-```
-entity:
- plexPlayer:
- - identifier: TV 2020
- server:
- ip: 192.168.13.37 # Mandatory
- token: QWdsqEXAMPLETOKENqwerty # Mandatory
- port: 32400
- protocol: http
-```
-
-Example 2:
-
-```
-entity:
- plexPlayer:
- identifier: TV 2020
- server:
- ip: 192.168.13.37 # Mandatory
- token: QWdsqEXAMPLETOKENqwerty # Mandatory
- port: 32400
- protocol: http
-```
-
-## Sorting
-
-You can use _:desc_ or _:asc_ after every value to change the order from ascending to descending. For example, titlesort would become titleSort:asc, or titleSort:desc.
-
-### TV Shows
-
-| Sort Value | Description |
-| --------------------- | ------------------------------------------------- |
-| titleSort | Sorts by title, removing words like "the" |
-| title | Sorts by title, without removing words like "the" |
-| year | Sorts by year |
-| originallyAvailableAt | Sorts by release date |
-| rating | Sorts by critic rating |
-| audienceRating | Sorts by audience rating |
-| userRating | Sorts by user rating |
-| contentRating | Sorts by content rating |
-| unviewedLeafCount | Sorts by unplayed count |
-| episode.addedAt | Sorts by last episode date added |
-| addedAt | Sorts by date added |
-| lastViewedAt | Sorts by date viewed |
-
-### Movies
-
-| Sort Value | Description |
-| --------------------- | ------------------------------------------------- |
-| titleSort | Sorts by title, removing words like "the" |
-| title | Sorts by title, without removing words like "the" |
-| originallyAvailableAt | Sorts by release date |
-| rating | Sorts by critic rating |
-| audienceRating | Sorts by audience rating |
-| userRating | Sorts by user rating |
-| duration | Sorts by duration |
-| viewOffset | Sorts by progress |
-| viewCount | Sorts by plays |
-| addedAt | Sorts by date added |
-| lastViewedAt | Sorts by date viewed |
-| mediaHeight | Sorts by resolution |
-| mediaBitrate | Sorts by bitrate |
-
-## FAQ
-
-
- I am using plexPlayer and nothing happens when play button is pressed
-
-- Make sure that your device is turned on, and has Plex running
-- Reload tab
-- Try hitting play button again
-
-If it still doesn't work and everything else works, you are probably encountering CORS issue. You can check this by opening developer tools and looking into console log or network tab.
-
-The reason why this is happening only for this call is right now unknown, but there is a workaround.
-
-Edit configuration.yaml of your Home Assistant and add following rest_command:
-
-```
-rest_command:
- pmha_playmedia:
- url: "{{ url }}"
- headers:
- X-Plex-Target-Client-Identifier: "{{ target_client_identifier }}"
- X-Plex-Client-Identifier: "{{ client_identifier }}"
-```
-
-Restart Home Assistant, clear browser cache and try hitting play again. It should now work.
-
-
+- [Guide How to get Plex Token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/)
+- Play icon will only show up when playing is possible.
+- Kodi entities will only work if you are using [PlexKodiConnect](https://github.com/croneter/PlexKodiConnect#download-and-installation) and [Kodi Recently Added Media](https://github.com/jtbgroup/kodi-media-sensors#installation) is installed. See [detailed instructions](DETAILED_CONFIGURATION.md#kodi)
+- If you have Plex integration set up, Plex player is used for cast entities. Otherwise media is casted as a file.
## Ask for help or help development
@@ -441,11 +48,11 @@ Join [Discord](https://discord.gg/jqqz9jQXWx) or [Home Assistant Community](http
## Images
-![View without hover](https://github.com/JurajNyiri/PlexMeetsHomeAssistant/blob/main/images/design_preview/1.png)
+![View without hover](images/design_preview/1.png)
-![View with hover](https://github.com/JurajNyiri/PlexMeetsHomeAssistant/blob/main/images/design_preview/2.png)
+![View with hover](images/design_preview/2.png)
-![Expanded movie view](https://github.com/JurajNyiri/PlexMeetsHomeAssistant/blob/main/images/design_preview/3.png)
+![Expanded movie view](images/design_preview/3.png)
# Want to buy me a beer?
diff --git a/dist/plex-meets-homeassistant.js b/dist/plex-meets-homeassistant.js
index c9e5bdf..c2c1373 100644
--- a/dist/plex-meets-homeassistant.js
+++ b/dist/plex-meets-homeassistant.js
@@ -18909,28 +18909,34 @@ class PlayController {
break;
case 'cast':
if (this.hass.services.plex) {
- switch (data.type) {
- case 'movie':
- this.playViaCastPlex(entity.value, 'movie', `plex://${JSON.stringify({
- // eslint-disable-next-line @typescript-eslint/camelcase
- library_name: data.librarySectionTitle,
- title: data.title
- })}`);
- break;
- case 'episode':
- this.playViaCastPlex(entity.value, 'EPISODE', `plex://${JSON.stringify({
- // eslint-disable-next-line @typescript-eslint/camelcase
- library_name: data.librarySectionTitle,
- // eslint-disable-next-line @typescript-eslint/camelcase
- show_name: data.grandparentTitle,
- // eslint-disable-next-line @typescript-eslint/camelcase
- season_number: data.parentIndex,
- // eslint-disable-next-line @typescript-eslint/camelcase
- episode_number: data.index
- })}`);
- break;
- default:
- this.playViaCast(entity.value, data.Media[0].Part[0].key);
+ try {
+ switch (data.type) {
+ case 'movie':
+ await this.playViaCastPlex(entity.value, 'movie', `plex://${JSON.stringify({
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ library_name: data.librarySectionTitle,
+ title: data.title
+ })}`);
+ break;
+ case 'episode':
+ await this.playViaCastPlex(entity.value, 'EPISODE', `plex://${JSON.stringify({
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ library_name: data.librarySectionTitle,
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ show_name: data.grandparentTitle,
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ season_number: data.parentIndex,
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ episode_number: data.index
+ })}`);
+ break;
+ default:
+ this.playViaCast(entity.value, data.Media[0].Part[0].key);
+ }
+ }
+ catch (err) {
+ console.log(err);
+ this.playViaCast(entity.value, data.Media[0].Part[0].key);
}
}
else {
@@ -19070,7 +19076,7 @@ class PlayController {
});
};
this.playViaCastPlex = (entityName, contentType, mediaLink) => {
- this.hass.callService('media_player', 'play_media', {
+ return this.hass.callService('media_player', 'play_media', {
// eslint-disable-next-line @typescript-eslint/camelcase
entity_id: entityName,
// eslint-disable-next-line @typescript-eslint/camelcase
@@ -19238,7 +19244,6 @@ class PlayController {
}
}
-/* eslint-disable @typescript-eslint/no-explicit-any */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const escapeHtml = (unsafe) => {
if (unsafe) {
@@ -19252,6 +19257,9 @@ const escapeHtml = (unsafe) => {
}
return '';
};
+const fetchEntityRegistry = (conn) => conn.sendMessagePromise({
+ type: 'config/entity_registry/list'
+});
const getHeight = (el) => {
const height = Math.max(el.scrollHeight, el.offsetHeight, el.clientHeight, el.scrollHeight, el.offsetHeight);
return height;
@@ -19438,6 +19446,554 @@ const isScrolledIntoView = (elem) => {
return isVisible;
};
+/* eslint-disable @typescript-eslint/no-explicit-any */
+class PlexMeetsHomeAssistantEditor extends HTMLElement {
+ constructor() {
+ super(...arguments);
+ this.plexPort = false;
+ this.plexProtocol = 'http';
+ this.config = {};
+ this.ip = document.createElement('paper-input');
+ this.token = document.createElement('paper-input');
+ this.port = document.createElement('paper-input');
+ this.maxCount = document.createElement('paper-input');
+ this.libraryName = document.createElement('paper-dropdown-menu');
+ this.protocol = document.createElement('paper-dropdown-menu');
+ this.tabs = document.createElement('paper-tabs');
+ this.sort = document.createElement('paper-dropdown-menu');
+ this.sortOrder = document.createElement('paper-dropdown-menu');
+ this.playTrailer = document.createElement('paper-dropdown-menu');
+ this.showExtras = document.createElement('paper-dropdown-menu');
+ this.showSearch = document.createElement('paper-dropdown-menu');
+ this.runBefore = document.createElement('paper-dropdown-menu');
+ this.runAfter = document.createElement('paper-dropdown-menu');
+ this.entitiesSection = document.createElement('div');
+ this.devicesTabs = 0;
+ this.entities = [];
+ this.scriptEntities = [];
+ this.sections = [];
+ this.clients = {};
+ this.entitiesRegistry = false;
+ this.plexValidSection = document.createElement('div');
+ this.loaded = false;
+ this.fireEvent = (node, type, detail, options = {}) => {
+ // eslint-disable-next-line no-param-reassign
+ detail = detail === null || detail === undefined ? {} : detail;
+ const event = new Event(type, {
+ bubbles: options.bubbles === undefined ? true : options.bubbles,
+ cancelable: Boolean(options.cancelable),
+ composed: options.composed === undefined ? true : options.composed
+ });
+ event.detail = detail;
+ node.dispatchEvent(event);
+ return event;
+ };
+ this.valueUpdated = () => {
+ if (this.loaded) {
+ const originalConfig = lodash.clone(this.config);
+ this.config.protocol = this.protocol.value;
+ this.config.ip = this.ip.value;
+ this.config.token = this.token.value;
+ this.config.port = this.port.value;
+ if (!this.config.entity) {
+ this.config.entity = [];
+ }
+ if (!lodash.isEmpty(this.libraryName.value)) {
+ this.config.libraryName = this.libraryName.value;
+ let sortOrderValue = '';
+ if (lodash.isEqual(this.sortOrder.value, 'Ascending')) {
+ sortOrderValue = 'asc';
+ }
+ else if (lodash.isEqual(this.sortOrder.value, 'Descending')) {
+ sortOrderValue = 'desc';
+ }
+ if (!lodash.isEmpty(sortOrderValue) && !lodash.isEmpty(this.sort.value)) {
+ this.config.sort = `${this.sort.value}:${sortOrderValue}`;
+ }
+ else {
+ this.config.sort = ``;
+ }
+ if (lodash.isEmpty(this.maxCount.value)) {
+ this.config.maxCount = '';
+ }
+ else {
+ this.config.maxCount = this.maxCount.value;
+ }
+ if (!lodash.isEmpty(this.entities)) {
+ this.config.entity = [];
+ lodash.forEach(this.entities, entity => {
+ if (!lodash.isEmpty(entity.value) && !lodash.includes(this.config.entity, entity.value)) {
+ this.config.entity.push(entity.value);
+ }
+ });
+ }
+ if (lodash.isEqual(this.playTrailer.value, 'Yes')) {
+ this.config.playTrailer = true;
+ }
+ else if (lodash.isEqual(this.playTrailer.value, 'No')) {
+ this.config.playTrailer = false;
+ }
+ else if (lodash.isEqual(this.playTrailer.value, 'Muted')) {
+ this.config.playTrailer = 'muted';
+ }
+ if (lodash.isEqual(this.showExtras.value, 'Yes')) {
+ this.config.showExtras = true;
+ }
+ else if (lodash.isEqual(this.showExtras.value, 'No')) {
+ this.config.showExtras = false;
+ }
+ if (lodash.isEqual(this.showSearch.value, 'Yes')) {
+ this.config.showSearch = true;
+ }
+ else if (lodash.isEqual(this.showSearch.value, 'No')) {
+ this.config.showSearch = false;
+ }
+ this.config.runBefore = this.runBefore.value;
+ this.config.runAfter = this.runAfter.value;
+ }
+ if (!lodash.isEqual(this.config, originalConfig)) {
+ this.fireEvent(this, 'config-changed', { config: this.config });
+ }
+ }
+ };
+ this.render = async () => {
+ const addDropdownItem = (text) => {
+ const libraryItem = document.createElement('paper-item');
+ libraryItem.innerHTML = text;
+ return libraryItem;
+ };
+ const createEntitiesDropdown = (selected, changeHandler) => {
+ if (this.entitiesRegistry) {
+ const entitiesDropDown = document.createElement('paper-dropdown-menu');
+ const entities = document.createElement('paper-listbox');
+ entities.appendChild(addDropdownItem(''));
+ const addedEntityStrings = [];
+ lodash.forEach(this.entitiesRegistry, entityRegistry => {
+ if (lodash.isEqual(entityRegistry.platform, 'cast') ||
+ lodash.isEqual(entityRegistry.platform, 'kodi') ||
+ lodash.isEqual(entityRegistry.platform, 'androidtv')) {
+ const entityName = `${entityRegistry.platform} | ${entityRegistry.entity_id}`;
+ entities.appendChild(addDropdownItem(entityName));
+ addedEntityStrings.push(entityName);
+ }
+ });
+ lodash.forEach(this.clients, value => {
+ const entityName = `plexPlayer | ${value.name} | ${value.address} | ${value.machineIdentifier}`;
+ entities.appendChild(addDropdownItem(entityName));
+ addedEntityStrings.push(entityName);
+ });
+ if (lodash.isArray(this.config.entity)) {
+ lodash.forEach(this.config.entity, value => {
+ if (!lodash.includes(addedEntityStrings, value)) {
+ entities.appendChild(addDropdownItem(value));
+ addedEntityStrings.push(value);
+ }
+ });
+ }
+ entities.slot = 'dropdown-content';
+ entitiesDropDown.label = 'Entity';
+ entitiesDropDown.value = selected;
+ entitiesDropDown.appendChild(entities);
+ entitiesDropDown.style.width = '100%';
+ entitiesDropDown.className = 'entitiesDropDown';
+ entitiesDropDown.addEventListener('value-changed', changeHandler);
+ this.entities.push(entitiesDropDown);
+ return entitiesDropDown;
+ }
+ return false;
+ };
+ if (this.content)
+ this.content.remove();
+ if (this.hassObj && !this.entitiesRegistry) {
+ lodash.forOwn(this.hassObj.states, (value, key) => {
+ if (lodash.startsWith(key, 'script.')) {
+ this.scriptEntities.push(key);
+ }
+ });
+ this.entitiesRegistry = await fetchEntityRegistry(this.hassObj.connection);
+ }
+ this.entities = [];
+ this.content = document.createElement('div');
+ const plexTitle = document.createElement('h2');
+ plexTitle.innerHTML = 'Plex Configuration';
+ plexTitle.style.margin = '0px';
+ plexTitle.style.padding = '0px';
+ this.content.appendChild(plexTitle);
+ this.protocol.innerHTML = '';
+ const protocolItems = document.createElement('paper-listbox');
+ protocolItems.appendChild(addDropdownItem('http'));
+ protocolItems.appendChild(addDropdownItem('https'));
+ protocolItems.slot = 'dropdown-content';
+ this.protocol.label = 'Plex Protocol';
+ this.protocol.appendChild(protocolItems);
+ this.protocol.style.width = '100%';
+ this.protocol.addEventListener('value-changed', this.valueUpdated);
+ if (lodash.isEmpty(this.config.protocol)) {
+ this.protocol.value = 'http';
+ }
+ else {
+ this.protocol.value = this.config.protocol;
+ }
+ this.content.appendChild(this.protocol);
+ this.ip.label = 'Plex IP Address';
+ this.ip.value = this.config.ip;
+ this.ip.addEventListener('change', this.valueUpdated);
+ this.content.appendChild(this.ip);
+ this.port.label = 'Plex Port';
+ this.port.value = this.config.port;
+ this.port.type = 'number';
+ this.port.addEventListener('change', this.valueUpdated);
+ this.content.appendChild(this.port);
+ this.token.label = 'Plex Token';
+ this.token.value = this.config.token;
+ this.token.addEventListener('change', this.valueUpdated);
+ this.content.appendChild(this.token);
+ this.libraryName.innerHTML = '';
+ const libraryItems = document.createElement('paper-listbox');
+ libraryItems.appendChild(addDropdownItem('Continue Watching'));
+ libraryItems.appendChild(addDropdownItem('Deck'));
+ libraryItems.appendChild(addDropdownItem('Recently Added'));
+ libraryItems.appendChild(addDropdownItem('Watch Next'));
+ libraryItems.slot = 'dropdown-content';
+ this.libraryName.label = 'Plex Library';
+ this.libraryName.disabled = true;
+ this.libraryName.appendChild(libraryItems);
+ this.libraryName.style.width = '100%';
+ this.libraryName.addEventListener('value-changed', this.valueUpdated);
+ this.content.appendChild(this.libraryName);
+ this.appendChild(this.content);
+ this.plex = new Plex(this.config.ip, this.plexPort, this.config.token, this.plexProtocol, this.config.sort);
+ this.sections = await this.plex.getSections();
+ this.clients = await this.plex.getClients();
+ this.plexValidSection.style.display = 'none';
+ this.plexValidSection.innerHTML = '';
+ let hasUIConfig = true;
+ let canConvert = true;
+ if (lodash.isArray(this.config.entity)) {
+ // eslint-disable-next-line consistent-return
+ lodash.forEach(this.config.entity, entity => {
+ if (lodash.isObjectLike(entity)) {
+ canConvert = !lodash.includes(lodash.keys(this.config.entity), 'plexPlayer');
+ hasUIConfig = false;
+ return false;
+ }
+ });
+ }
+ else if (lodash.isObjectLike(this.config.entity)) {
+ canConvert = !lodash.includes(lodash.keys(this.config.entity), 'plexPlayer');
+ hasUIConfig = false;
+ if (canConvert) {
+ const convertedEntities = [];
+ hasUIConfig = true;
+ if (lodash.isObjectLike(this.config.entity)) {
+ lodash.forOwn(this.config.entity, value => {
+ if (lodash.isString(value)) {
+ convertedEntities.push(value);
+ }
+ else if (lodash.isArray(value)) {
+ lodash.forEach(value, valueStr => {
+ convertedEntities.push(valueStr);
+ });
+ }
+ });
+ }
+ this.config.entity = convertedEntities;
+ }
+ }
+ const devicesTitle = document.createElement('h2');
+ devicesTitle.innerHTML = `Devices Configuration`;
+ devicesTitle.style.lineHeight = '29px';
+ devicesTitle.style.marginBottom = '0px';
+ devicesTitle.style.marginTop = '20px';
+ if (hasUIConfig) {
+ const addDeviceButton = document.createElement('button');
+ addDeviceButton.style.float = 'right';
+ addDeviceButton.style.fontSize = '20px';
+ addDeviceButton.style.cursor = 'pointer';
+ addDeviceButton.innerHTML = '+';
+ addDeviceButton.addEventListener('click', () => {
+ const entitiesDropdown = createEntitiesDropdown('', this.valueUpdated);
+ if (entitiesDropdown) {
+ this.entitiesSection.appendChild(entitiesDropdown);
+ }
+ });
+ devicesTitle.appendChild(addDeviceButton);
+ }
+ this.plexValidSection.appendChild(devicesTitle);
+ this.entitiesSection.innerHTML = '';
+ this.plexValidSection.appendChild(this.entitiesSection);
+ if (hasUIConfig) {
+ if (lodash.isString(this.config.entity)) {
+ this.config.entity = [this.config.entity];
+ }
+ if (lodash.isArray(this.config.entity)) {
+ lodash.forEach(this.config.entity, entity => {
+ if (lodash.isString(entity)) {
+ const entitiesDropdown = createEntitiesDropdown(entity, this.valueUpdated);
+ if (entitiesDropdown) {
+ this.entitiesSection.appendChild(entitiesDropdown);
+ }
+ }
+ });
+ }
+ }
+ else {
+ const entitiesUINotAvailable = document.createElement('div');
+ entitiesUINotAvailable.innerHTML =
+ 'Devices configuration is not available when using plexPlayer client device.
You can edit any other settings through UI and use Show code editor to edit entities.
If you are not using server settings for plexPlayer with identifier and server key, you can migrate your settings to UI by removing plexPlayer section and readd through UI.';
+ this.plexValidSection.appendChild(entitiesUINotAvailable);
+ }
+ const viewTitle = document.createElement('h2');
+ viewTitle.innerHTML = `View Configuration`;
+ viewTitle.style.lineHeight = '29px';
+ viewTitle.style.marginBottom = '0px';
+ viewTitle.style.marginTop = '20px';
+ this.plexValidSection.appendChild(viewTitle);
+ this.maxCount.label = 'Maximum number of items to display';
+ this.maxCount.value = this.config.maxCount;
+ this.maxCount.type = 'number';
+ this.maxCount.addEventListener('change', this.valueUpdated);
+ this.plexValidSection.appendChild(this.maxCount);
+ this.sort.innerHTML = '';
+ const sortItems = document.createElement('paper-listbox');
+ sortItems.slot = 'dropdown-content';
+ this.sort.label = 'Sort';
+ this.sort.appendChild(sortItems);
+ this.sort.style.width = '100%';
+ this.sort.addEventListener('value-changed', this.valueUpdated);
+ this.plexValidSection.appendChild(this.sort);
+ this.sortOrder.innerHTML = '';
+ const sortOrderItems = document.createElement('paper-listbox');
+ sortOrderItems.appendChild(addDropdownItem('Ascending'));
+ sortOrderItems.appendChild(addDropdownItem('Descending'));
+ sortOrderItems.slot = 'dropdown-content';
+ this.sortOrder.label = 'Sort Order';
+ this.sortOrder.appendChild(sortOrderItems);
+ this.sortOrder.style.width = '100%';
+ this.sortOrder.addEventListener('value-changed', this.valueUpdated);
+ if (lodash.isEmpty(this.config.sort)) {
+ this.sortOrder.value = 'Ascending';
+ }
+ else {
+ const sortOrder = this.config.sort.split(':')[1];
+ if (lodash.isEmpty(sortOrder)) {
+ this.sortOrder.value = 'Ascending';
+ }
+ else if (lodash.isEqual(sortOrder, 'asc')) {
+ this.sortOrder.value = 'Ascending';
+ }
+ else if (lodash.isEqual(sortOrder, 'desc')) {
+ this.sortOrder.value = 'Descending';
+ }
+ }
+ this.plexValidSection.appendChild(this.sortOrder);
+ this.playTrailer.innerHTML = '';
+ const playTrailerItems = document.createElement('paper-listbox');
+ playTrailerItems.appendChild(addDropdownItem('Yes'));
+ playTrailerItems.appendChild(addDropdownItem('Muted'));
+ playTrailerItems.appendChild(addDropdownItem('No'));
+ playTrailerItems.slot = 'dropdown-content';
+ this.playTrailer.label = 'Play Trailer';
+ this.playTrailer.appendChild(playTrailerItems);
+ this.playTrailer.style.width = '100%';
+ this.playTrailer.addEventListener('value-changed', this.valueUpdated);
+ let playTrailerValue = 'Yes';
+ if (lodash.isEqual(this.config.playTrailer, 'muted')) {
+ playTrailerValue = 'Muted';
+ }
+ else if (!this.config.playTrailer) {
+ playTrailerValue = 'No';
+ }
+ this.playTrailer.value = playTrailerValue;
+ this.plexValidSection.appendChild(this.playTrailer);
+ this.showExtras.innerHTML = '';
+ const showExtrasItems = document.createElement('paper-listbox');
+ showExtrasItems.appendChild(addDropdownItem('Yes'));
+ showExtrasItems.appendChild(addDropdownItem('No'));
+ showExtrasItems.slot = 'dropdown-content';
+ this.showExtras.label = 'Show Extras';
+ this.showExtras.appendChild(showExtrasItems);
+ this.showExtras.style.width = '100%';
+ this.showExtras.addEventListener('value-changed', this.valueUpdated);
+ let showExtrasValue = 'Yes';
+ if (!this.config.showExtras) {
+ showExtrasValue = 'No';
+ }
+ this.showExtras.value = showExtrasValue;
+ this.plexValidSection.appendChild(this.showExtras);
+ this.showSearch.innerHTML = '';
+ const showSearchItems = document.createElement('paper-listbox');
+ showSearchItems.appendChild(addDropdownItem('Yes'));
+ showSearchItems.appendChild(addDropdownItem('No'));
+ showSearchItems.slot = 'dropdown-content';
+ this.showSearch.label = 'Show Search';
+ this.showSearch.appendChild(showSearchItems);
+ this.showSearch.style.width = '100%';
+ this.showSearch.addEventListener('value-changed', this.valueUpdated);
+ let showSearchValue = 'Yes';
+ if (!this.config.showSearch) {
+ showSearchValue = 'No';
+ }
+ this.showSearch.value = showSearchValue;
+ this.plexValidSection.appendChild(this.showSearch);
+ this.runBefore.innerHTML = '';
+ const runBeforeItems = document.createElement('paper-listbox');
+ runBeforeItems.appendChild(addDropdownItem(''));
+ lodash.forEach(this.scriptEntities, entity => {
+ runBeforeItems.appendChild(addDropdownItem(entity));
+ });
+ runBeforeItems.slot = 'dropdown-content';
+ this.runBefore.label = 'Script to execute before starting the media';
+ this.runBefore.appendChild(runBeforeItems);
+ this.runBefore.style.width = '100%';
+ this.runBefore.addEventListener('value-changed', this.valueUpdated);
+ this.runBefore.value = this.config.runBefore;
+ this.plexValidSection.appendChild(this.runBefore);
+ this.runAfter.innerHTML = '';
+ const runAfterItems = document.createElement('paper-listbox');
+ runAfterItems.appendChild(addDropdownItem(''));
+ lodash.forEach(this.scriptEntities, entity => {
+ runAfterItems.appendChild(addDropdownItem(entity));
+ });
+ runAfterItems.slot = 'dropdown-content';
+ this.runAfter.label = 'Script to execute after starting the media';
+ this.runAfter.appendChild(runAfterItems);
+ this.runAfter.style.width = '100%';
+ this.runAfter.addEventListener('value-changed', this.valueUpdated);
+ this.runAfter.value = this.config.runAfter;
+ this.plexValidSection.appendChild(this.runAfter);
+ if (!lodash.isEmpty(this.sections)) {
+ lodash.forEach(this.sections, (section) => {
+ libraryItems.appendChild(addDropdownItem(section.title));
+ });
+ this.libraryName.disabled = false;
+ this.libraryName.value = this.config.libraryName;
+ let libraryType = '';
+ // eslint-disable-next-line consistent-return
+ lodash.forEach(this.sections, section => {
+ if (lodash.isEqual(section.title, this.libraryName.value)) {
+ libraryType = section.type;
+ return false;
+ }
+ });
+ if (lodash.isEqual(libraryType, 'show')) {
+ sortItems.appendChild(addDropdownItem('titleSort'));
+ sortItems.appendChild(addDropdownItem('title'));
+ sortItems.appendChild(addDropdownItem('year'));
+ sortItems.appendChild(addDropdownItem('originallyAvailableAt'));
+ sortItems.appendChild(addDropdownItem('rating'));
+ sortItems.appendChild(addDropdownItem('audienceRating'));
+ sortItems.appendChild(addDropdownItem('userRating'));
+ sortItems.appendChild(addDropdownItem('contentRating'));
+ sortItems.appendChild(addDropdownItem('unviewedLeafCount'));
+ sortItems.appendChild(addDropdownItem('episode.addedAt'));
+ sortItems.appendChild(addDropdownItem('addedAt'));
+ sortItems.appendChild(addDropdownItem('lastViewedAt'));
+ this.sort.style.display = 'block';
+ this.sortOrder.style.display = 'block';
+ }
+ else if (lodash.isEqual(libraryType, 'movie')) {
+ sortItems.appendChild(addDropdownItem('titleSort'));
+ sortItems.appendChild(addDropdownItem('title'));
+ sortItems.appendChild(addDropdownItem('originallyAvailableAt'));
+ sortItems.appendChild(addDropdownItem('rating'));
+ sortItems.appendChild(addDropdownItem('audienceRating'));
+ sortItems.appendChild(addDropdownItem('userRating'));
+ sortItems.appendChild(addDropdownItem('duration'));
+ sortItems.appendChild(addDropdownItem('viewOffset'));
+ sortItems.appendChild(addDropdownItem('viewCount'));
+ sortItems.appendChild(addDropdownItem('addedAt'));
+ sortItems.appendChild(addDropdownItem('lastViewedAt'));
+ sortItems.appendChild(addDropdownItem('mediaHeight'));
+ sortItems.appendChild(addDropdownItem('mediaBitrate'));
+ this.sort.style.display = 'block';
+ this.sortOrder.style.display = 'block';
+ }
+ else {
+ this.sort.style.display = 'none';
+ this.sortOrder.style.display = 'none';
+ this.config.sort = '';
+ }
+ if (lodash.isEmpty(this.config.sort)) {
+ this.sort.value = '';
+ this.sortOrder.value = '';
+ }
+ else {
+ // eslint-disable-next-line prefer-destructuring
+ this.sort.value = this.config.sort.split(':')[0];
+ const sortOrder = this.config.sort.split(':')[1];
+ if (lodash.isEmpty(sortOrder)) {
+ this.sortOrder.value = 'Ascending';
+ }
+ else if (lodash.isEqual(sortOrder, 'asc')) {
+ this.sortOrder.value = 'Ascending';
+ }
+ else if (lodash.isEqual(sortOrder, 'desc')) {
+ this.sortOrder.value = 'Descending';
+ }
+ }
+ this.plexValidSection.style.display = 'block';
+ }
+ this.loaded = true;
+ this.content.appendChild(this.plexValidSection);
+ };
+ this.setConfig = (config) => {
+ this.config = JSON.parse(JSON.stringify(config));
+ if (config.port && !lodash.isEqual(config.port, '')) {
+ this.plexPort = config.port;
+ }
+ else {
+ this.plexPort = false;
+ }
+ if (config.protocol) {
+ this.plexProtocol = config.protocol;
+ }
+ else {
+ this.config.protocol = 'http';
+ }
+ if (!config.sort) {
+ this.config.sort = 'titleSort:asc';
+ }
+ if (!lodash.isNil(config.playTrailer)) {
+ this.config.playTrailer = config.playTrailer;
+ }
+ else {
+ this.config.playTrailer = true;
+ }
+ if (!lodash.isNil(config.showExtras)) {
+ this.config.showExtras = config.showExtras;
+ }
+ else {
+ this.config.showExtras = true;
+ }
+ if (!lodash.isNil(config.showSearch)) {
+ this.config.showSearch = config.showSearch;
+ }
+ else {
+ this.config.showSearch = true;
+ }
+ if (!lodash.isNil(config.runBefore)) {
+ this.config.runBefore = config.runBefore;
+ }
+ if (!lodash.isNil(config.runAfter)) {
+ this.config.runAfter = config.runAfter;
+ }
+ this.render();
+ };
+ this.configChanged = (newConfig) => {
+ const event = new Event('config-changed', {
+ bubbles: true,
+ composed: true
+ });
+ event.detail = { config: newConfig };
+ this.dispatchEvent(event);
+ };
+ }
+ set hass(hass) {
+ this.hassObj = hass;
+ }
+}
+
/**
* @license
* Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
@@ -20153,6 +20709,7 @@ style.textContent = css `
class PlexMeetsHomeAssistant extends HTMLElement {
constructor() {
super(...arguments);
+ this.searchInputElem = document.createElement('input');
this.plexProtocol = 'http';
this.plexPort = false;
this.detailsShown = false;
@@ -20160,6 +20717,7 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.runBefore = '';
this.playTrailer = true;
this.showExtras = true;
+ this.showSearch = true;
this.previousPageWidth = 0;
this.runAfter = '';
this.columnsCount = 0;
@@ -20180,6 +20738,7 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.maxCount = false;
this.error = '';
this.contentBGHeight = 0;
+ this.initialDataLoaded = false;
this.renderNewElementsIfNeeded = () => {
const loadAdditionalRowsCount = 2; // todo: make this configurable
const height = getHeight(this.content);
@@ -20191,54 +20750,9 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.calculatePositions();
}
};
- this.fetchEntityRegistry = (conn) => conn.sendMessagePromise({
- type: 'config/entity_registry/list'
- });
this.loadInitialData = async () => {
if (this.hassObj) {
- this.entityRegistry = await this.fetchEntityRegistry(this.hassObj.connection);
- }
- let { entity } = JSON.parse(JSON.stringify(this.config));
- const processEntity = (entityObj, entityString) => {
- lodash.forEach(this.entityRegistry, entityInRegister => {
- if (lodash.isEqual(entityInRegister.entity_id, entityString)) {
- switch (entityInRegister.platform) {
- case 'cast':
- if (lodash.isNil(entityObj.cast)) {
- // eslint-disable-next-line no-param-reassign
- entityObj.cast = [];
- }
- entityObj.cast.push(entityInRegister.entity_id);
- break;
- case 'androidtv':
- if (lodash.isNil(entityObj.androidtv)) {
- // eslint-disable-next-line no-param-reassign
- entityObj.androidtv = [];
- }
- entityObj.androidtv.push(entityInRegister.entity_id);
- break;
- case 'kodi':
- if (lodash.isNil(entityObj.kodi)) {
- // eslint-disable-next-line no-param-reassign
- entityObj.kodi = [];
- }
- entityObj.kodi.push(entityInRegister.entity_id);
- break;
- // pass
- }
- }
- });
- };
- const entityOrig = entity;
- if (lodash.isString(entityOrig)) {
- entity = {};
- processEntity(entity, entityOrig);
- }
- else if (lodash.isArray(entityOrig)) {
- entity = {};
- lodash.forEach(entityOrig, entityStr => {
- processEntity(entity, entityStr);
- });
+ this.entityRegistry = await fetchEntityRegistry(this.hassObj.connection);
}
window.addEventListener('scroll', () => {
// todo: improve performance by calculating this when needed only
@@ -20302,15 +20816,84 @@ class PlexMeetsHomeAssistant extends HTMLElement {
if (this.card) {
this.previousPageWidth = this.card.offsetWidth;
}
+ this.resizeBackground();
+ this.initialDataLoaded = true;
+ };
+ this.renderInitialData = async () => {
+ let { entity } = JSON.parse(JSON.stringify(this.config));
+ const processEntity = (entityObj, entityString) => {
+ let realEntityString = entityString;
+ let isPlexPlayer = false;
+ if (lodash.startsWith(entityString, 'plexPlayer | ')) {
+ // eslint-disable-next-line prefer-destructuring
+ realEntityString = entityString.split(' | ')[3];
+ isPlexPlayer = true;
+ }
+ else if (lodash.startsWith(entityString, 'androidtv | ') ||
+ lodash.startsWith(entityString, 'kodi | ') ||
+ lodash.startsWith(entityString, 'cast | ')) {
+ // eslint-disable-next-line prefer-destructuring
+ realEntityString = entityString.split(' | ')[1];
+ isPlexPlayer = false;
+ }
+ if (isPlexPlayer) {
+ if (lodash.isNil(entityObj.plexPlayer)) {
+ // eslint-disable-next-line no-param-reassign
+ entityObj.plexPlayer = [];
+ }
+ entityObj.plexPlayer.push(realEntityString);
+ }
+ else {
+ lodash.forEach(this.entityRegistry, entityInRegister => {
+ if (lodash.isEqual(entityInRegister.entity_id, realEntityString)) {
+ switch (entityInRegister.platform) {
+ case 'cast':
+ if (lodash.isNil(entityObj.cast)) {
+ // eslint-disable-next-line no-param-reassign
+ entityObj.cast = [];
+ }
+ entityObj.cast.push(entityInRegister.entity_id);
+ break;
+ case 'androidtv':
+ if (lodash.isNil(entityObj.androidtv)) {
+ // eslint-disable-next-line no-param-reassign
+ entityObj.androidtv = [];
+ }
+ entityObj.androidtv.push(entityInRegister.entity_id);
+ break;
+ case 'kodi':
+ if (lodash.isNil(entityObj.kodi)) {
+ // eslint-disable-next-line no-param-reassign
+ entityObj.kodi = [];
+ }
+ entityObj.kodi.push(entityInRegister.entity_id);
+ break;
+ // pass
+ }
+ }
+ });
+ }
+ console.log(realEntityString);
+ };
+ const entityOrig = entity;
+ if (lodash.isString(entityOrig)) {
+ entity = {};
+ processEntity(entity, entityOrig);
+ }
+ else if (lodash.isArray(entityOrig)) {
+ entity = {};
+ lodash.forEach(entityOrig, entityStr => {
+ processEntity(entity, entityStr);
+ });
+ }
+ console.log(entity);
this.loading = true;
this.renderPage();
try {
- if (this.plex) {
- if (this.hassObj) {
- this.playController = new PlayController(this.hassObj, this.plex, entity, this.runBefore, this.runAfter);
- if (this.playController) {
- await this.playController.init();
- }
+ if (this.plex && this.hassObj) {
+ this.playController = new PlayController(this.hassObj, this.plex, entity, this.runBefore, this.runAfter);
+ if (this.playController) {
+ await this.playController.init();
}
await this.plex.init();
try {
@@ -20387,14 +20970,15 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.render();
}
else {
- throw Error('Plex not initialized.');
+ setTimeout(() => {
+ this.renderInitialData();
+ }, 250);
}
}
catch (err) {
this.error = `Plex server did not respond.
Details of the error: ${escapeHtml(err.message)}`;
this.renderPage();
}
- this.resizeBackground();
};
this.render = () => {
this.renderPage();
@@ -20402,17 +20986,17 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.searchInput = () => {
const searchContainer = document.createElement('div');
searchContainer.className = 'searchContainer';
- const searchInput = document.createElement('input');
- searchInput.type = 'text';
- searchInput.value = this.searchValue;
- searchInput.placeholder = `Search ${this.config.libraryName}...`;
- searchInput.addEventListener('keyup', () => {
- this.searchValue = searchInput.value;
+ this.searchInputElem = document.createElement('input');
+ this.searchInputElem.type = 'text';
+ this.searchInputElem.value = this.searchValue;
+ this.searchInputElem.placeholder = `Search ${this.config.libraryName}...`;
+ this.searchInputElem.addEventListener('keyup', () => {
+ this.searchValue = this.searchInputElem.value;
this.renderPage();
this.focus();
this.renderNewElementsIfNeeded();
});
- searchContainer.appendChild(searchInput);
+ searchContainer.appendChild(this.searchInputElem);
return searchContainer;
};
this.renderMovieElems = () => {
@@ -20476,6 +21060,13 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.contentBGHeight = getHeight(contentbg);
};
this.renderPage = () => {
+ this.searchInputElem.placeholder = `Search ${this.config.libraryName}...`;
+ if (this.showSearch) {
+ this.searchInputElem.style.display = 'block';
+ }
+ else {
+ this.searchInputElem.style.display = 'none';
+ }
if (this.card) {
const marginRight = 10; // needs to be equal to .container margin right
const areaSize = this.card.offsetWidth - parseInt(this.card.style.paddingRight, 10) - parseInt(this.card.style.paddingLeft, 10);
@@ -20503,6 +21094,12 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.card.style.padding = '16px';
this.card.style.paddingRight = '6px';
this.card.appendChild(this.searchInput());
+ if (this.showSearch) {
+ this.searchInputElem.style.display = 'block';
+ }
+ else {
+ this.searchInputElem.style.display = 'none';
+ }
this.appendChild(this.card);
}
this.content = document.createElement('div');
@@ -21414,6 +22011,15 @@ class PlexMeetsHomeAssistant extends HTMLElement {
};
this.setConfig = (config) => {
this.plexProtocol = 'http';
+ if (!config.ip) {
+ throw new Error('You need to define a Plex IP Address');
+ }
+ if (!config.token) {
+ throw new Error('You need to define a Plex Token');
+ }
+ if (!config.libraryName) {
+ throw new Error('You need to define a libraryName');
+ }
if (!config.entity || config.entity.length === 0) {
throw new Error('You need to define at least one entity');
}
@@ -21433,29 +22039,23 @@ class PlexMeetsHomeAssistant extends HTMLElement {
else if (!lodash.isString(config.entity) && !lodash.isArray(config.entity)) {
throw new Error('You need to define at least one supported entity');
}
- if (!config.token) {
- throw new Error('You need to define a token');
- }
- if (!config.ip) {
- throw new Error('You need to define a ip');
- }
- if (!config.libraryName) {
- throw new Error('You need to define a libraryName');
- }
this.config = config;
if (config.protocol) {
this.plexProtocol = config.protocol;
}
- if (config.port) {
+ if (config.port && !lodash.isEqual(config.port, '')) {
this.plexPort = config.port;
}
- if (config.maxCount) {
+ else {
+ this.plexPort = false;
+ }
+ if (config.maxCount && config.maxCount !== '') {
this.maxCount = config.maxCount;
}
- if (config.runBefore) {
+ if (config.runBefore && !lodash.isEqual(config.runBefore, '')) {
this.runBefore = config.runBefore;
}
- if (config.runAfter) {
+ if (config.runAfter && !lodash.isEqual(config.runAfter, '')) {
this.runAfter = config.runAfter;
}
if (!lodash.isNil(config.playTrailer)) {
@@ -21464,7 +22064,13 @@ class PlexMeetsHomeAssistant extends HTMLElement {
if (!lodash.isNil(config.showExtras)) {
this.showExtras = config.showExtras;
}
+ if (!lodash.isNil(config.showSearch)) {
+ this.showSearch = config.showSearch;
+ }
this.plex = new Plex(this.config.ip, this.plexPort, this.config.token, this.plexProtocol, this.config.sort);
+ this.data = {};
+ this.error = '';
+ this.renderInitialData();
};
this.getCardSize = () => {
return 3;
@@ -21472,12 +22078,20 @@ class PlexMeetsHomeAssistant extends HTMLElement {
}
set hass(hass) {
this.hassObj = hass;
- if (!this.content) {
- this.error = '';
- if (!this.loading) {
- this.loadInitialData();
- }
+ if (!this.initialDataLoaded) {
+ this.loadInitialData();
}
}
+ static getConfigElement() {
+ return document.createElement('plex-meets-homeassistant-editor');
+ }
}
-customElements.define('plex-meets-homeassistant', PlexMeetsHomeAssistant);
+customElements.define('plex-meets-homeassistant-editor', PlexMeetsHomeAssistantEditor);
+customElements.define('plex-meets-homeassistant', PlexMeetsHomeAssistant);
+window.customCards = window.customCards || [];
+window.customCards.push({
+ type: 'plex-meets-homeassistant',
+ name: 'Plex meets Home Assistant',
+ preview: false,
+ description: 'Integrates Plex into Home Assistant. Browse and launch media with a simple click.' // Optional
+});
diff --git a/images/ui_configuration.png b/images/ui_configuration.png
new file mode 100644
index 0000000..a80b6db
Binary files /dev/null and b/images/ui_configuration.png differ
diff --git a/package-lock.json b/package-lock.json
index 2226623..78ada3d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -47,6 +47,259 @@
"tslib": "^2.0.1"
}
},
+ "@polymer/font-roboto": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@polymer/font-roboto/-/font-roboto-3.0.2.tgz",
+ "integrity": "sha512-tx5TauYSmzsIvmSqepUPDYbs4/Ejz2XbZ1IkD7JEGqkdNUJlh+9KU85G56Tfdk/xjEZ8zorFfN09OSwiMrIQWA=="
+ },
+ "@polymer/iron-a11y-announcer": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-a11y-announcer/-/iron-a11y-announcer-3.2.0.tgz",
+ "integrity": "sha512-We+hyaFHcg7Ke8ovsoxUpYEXFIJLHxMCDaLehTB4dELS+C+K0zMnGSiqQvb/YzGS+nSYpAfkQIyg1msOCdHMtA==",
+ "requires": {
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-a11y-keys-behavior": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-a11y-keys-behavior/-/iron-a11y-keys-behavior-3.0.1.tgz",
+ "integrity": "sha512-lnrjKq3ysbBPT/74l0Fj0U9H9C35Tpw2C/tpJ8a+5g8Y3YJs1WSZYnEl1yOkw6sEyaxOq/1DkzH0+60gGu5/PQ==",
+ "requires": {
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-autogrow-textarea": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-autogrow-textarea/-/iron-autogrow-textarea-3.0.3.tgz",
+ "integrity": "sha512-5r0VkWrIlm0JIp5E5wlnvkw7slK72lFRZXncmrsLZF+6n1dg2rI8jt7xpFzSmUWrqpcyXwyKaGaDvUjl3j4JLA==",
+ "requires": {
+ "@polymer/iron-behaviors": "^3.0.0-pre.26",
+ "@polymer/iron-flex-layout": "^3.0.0-pre.26",
+ "@polymer/iron-validatable-behavior": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-behaviors": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-behaviors/-/iron-behaviors-3.0.1.tgz",
+ "integrity": "sha512-IMEwcv1lhf1HSQxuyWOUIL0lOBwmeaoSTpgCJeP9IBYnuB1SPQngmfRuHKgK6/m9LQ9F9miC7p3HeQQUdKAE0w==",
+ "requires": {
+ "@polymer/iron-a11y-keys-behavior": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-checked-element-behavior": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-checked-element-behavior/-/iron-checked-element-behavior-3.0.1.tgz",
+ "integrity": "sha512-aDr0cbCNVq49q+pOqa6CZutFh+wWpwPMLpEth9swx+GkAj+gCURhuQkaUYhIo5f2egDbEioR1aeHMnPlU9dQZA==",
+ "requires": {
+ "@polymer/iron-form-element-behavior": "^3.0.0-pre.26",
+ "@polymer/iron-validatable-behavior": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-dropdown": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-dropdown/-/iron-dropdown-3.0.1.tgz",
+ "integrity": "sha512-22yLhepfcKjuQMfFmRHi/9MPKTqkzgRrmWWW0P5uqK++xle53k2QBO5VYUAYiCN3ZcxIi9lEhZ9YWGeQj2JBig==",
+ "requires": {
+ "@polymer/iron-behaviors": "^3.0.0-pre.26",
+ "@polymer/iron-overlay-behavior": "^3.0.0-pre.27",
+ "@polymer/neon-animation": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-fit-behavior": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-fit-behavior/-/iron-fit-behavior-3.1.0.tgz",
+ "integrity": "sha512-ABcgIYqrjhmUT8tiuolqeGttF/8pd3sEymUDrO1vXbZu4FWIvoLNndrMDFvs++AGd12Mjf5pYy84NJc6dB8Vig==",
+ "requires": {
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-flex-layout": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-flex-layout/-/iron-flex-layout-3.0.1.tgz",
+ "integrity": "sha512-7gB869czArF+HZcPTVSgvA7tXYFze9EKckvM95NB7SqYF+NnsQyhoXgKnpFwGyo95lUjUW9TFDLUwDXnCYFtkw==",
+ "requires": {
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-form-element-behavior": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-form-element-behavior/-/iron-form-element-behavior-3.0.1.tgz",
+ "integrity": "sha512-G/e2KXyL5AY7mMjmomHkGpgS0uAf4ovNpKhkuUTRnMuMJuf589bKqE85KN4ovE1Tzhv2hJoh/igyD6ekHiYU1A==",
+ "requires": {
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-icon": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-icon/-/iron-icon-3.0.1.tgz",
+ "integrity": "sha512-QLPwirk+UPZNaLnMew9VludXA4CWUCenRewgEcGYwdzVgDPCDbXxy6vRJjmweZobMQv/oVLppT2JZtJFnPxX6g==",
+ "requires": {
+ "@polymer/iron-flex-layout": "^3.0.0-pre.26",
+ "@polymer/iron-meta": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-iconset-svg": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-iconset-svg/-/iron-iconset-svg-3.0.1.tgz",
+ "integrity": "sha512-XNwURbNHRw6u2fJe05O5fMYye6GSgDlDqCO+q6K1zAnKIrpgZwf2vTkBd5uCcZwsN0FyCB3mvNZx4jkh85dRDw==",
+ "requires": {
+ "@polymer/iron-meta": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-input": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-input/-/iron-input-3.0.1.tgz",
+ "integrity": "sha512-WLx13kEcbH9GKbj9+pWR6pbJkA5kxn3796ynx6eQd2rueMyUfVTR3GzOvadBKsciUuIuzrxpBWZ2+3UcueVUQQ==",
+ "requires": {
+ "@polymer/iron-a11y-announcer": "^3.0.0-pre.26",
+ "@polymer/iron-validatable-behavior": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-meta": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-meta/-/iron-meta-3.0.1.tgz",
+ "integrity": "sha512-pWguPugiLYmWFV9UWxLWzZ6gm4wBwQdDy4VULKwdHCqR7OP7u98h+XDdGZsSlDPv6qoryV/e3tGHlTIT0mbzJA==",
+ "requires": {
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-overlay-behavior": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-overlay-behavior/-/iron-overlay-behavior-3.0.3.tgz",
+ "integrity": "sha512-Q/Fp0+uOQQ145ebZ7T8Cxl4m1tUKYjyymkjcL2rXUm+aDQGb1wA1M1LYxUF5YBqd+9lipE0PTIiYwA2ZL/sznA==",
+ "requires": {
+ "@polymer/iron-a11y-keys-behavior": "^3.0.0-pre.26",
+ "@polymer/iron-fit-behavior": "^3.0.0-pre.26",
+ "@polymer/iron-resizable-behavior": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-resizable-behavior": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-resizable-behavior/-/iron-resizable-behavior-3.0.1.tgz",
+ "integrity": "sha512-FyHxRxFspVoRaeZSWpT3y0C9awomb4tXXolIJcZ7RvXhMP632V5lez+ch5G5SwK0LpnAPkg35eB0LPMFv+YMMQ==",
+ "requires": {
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-selector": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-selector/-/iron-selector-3.0.1.tgz",
+ "integrity": "sha512-sBVk2uas6prW0glUe2xEJJYlvxmYzM40Au9OKbfDK2Qekou/fLKcBRyIYI39kuI8zWRaip8f3CI8qXcUHnKb1A==",
+ "requires": {
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/iron-validatable-behavior": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/iron-validatable-behavior/-/iron-validatable-behavior-3.0.1.tgz",
+ "integrity": "sha512-wwpYh6wOa4fNI+jH5EYKC7TVPYQ2OfgQqocWat7GsNWcsblKYhLYbwsvEY5nO0n2xKqNfZzDLrUom5INJN7msQ==",
+ "requires": {
+ "@polymer/iron-meta": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/neon-animation": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/neon-animation/-/neon-animation-3.0.1.tgz",
+ "integrity": "sha512-cDDc0llpVCe0ATbDS3clDthI54Bc8YwZIeTGGmBJleKOvbRTUC5+ssJmRL+VwVh+VM5FlnQlx760ppftY3uprg==",
+ "requires": {
+ "@polymer/iron-resizable-behavior": "^3.0.0-pre.26",
+ "@polymer/iron-selector": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/paper-behaviors": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/paper-behaviors/-/paper-behaviors-3.0.1.tgz",
+ "integrity": "sha512-6knhj69fPJejv8qR0kCSUY+Q0XjaUf0OSnkjRjmTJPAwSrRYtgqE+l6P1FfA+py1X/cUjgne9EF5rMZAKJIg1g==",
+ "requires": {
+ "@polymer/iron-behaviors": "^3.0.0-pre.26",
+ "@polymer/iron-checked-element-behavior": "^3.0.0-pre.26",
+ "@polymer/paper-ripple": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/paper-dropdown-menu": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@polymer/paper-dropdown-menu/-/paper-dropdown-menu-3.2.0.tgz",
+ "integrity": "sha512-2ohwSHF+RLSK6kA0UkkMiMQF6EZcaEYWAA25kfisI6DWie7yozKrpQNsqvwfOEHU6DdDMIotrOtH1TM88YS8Zg==",
+ "requires": {
+ "@polymer/iron-a11y-keys-behavior": "^3.0.0-pre.26",
+ "@polymer/iron-form-element-behavior": "^3.0.0-pre.26",
+ "@polymer/iron-icon": "^3.0.0-pre.26",
+ "@polymer/iron-iconset-svg": "^3.0.0-pre.26",
+ "@polymer/iron-validatable-behavior": "^3.0.0-pre.26",
+ "@polymer/paper-behaviors": "^3.0.0-pre.27",
+ "@polymer/paper-input": "^3.1.0",
+ "@polymer/paper-menu-button": "^3.1.0",
+ "@polymer/paper-ripple": "^3.0.0-pre.26",
+ "@polymer/paper-styles": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.3.1"
+ }
+ },
+ "@polymer/paper-input": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/@polymer/paper-input/-/paper-input-3.2.1.tgz",
+ "integrity": "sha512-6ghgwQKM6mS0hAQxQqj+tkeEY1VUBqAsrasAm8V5RpNcfSWQC/hhRFxU0beGuKTAhndzezDzWYP6Zz4b8fExGg==",
+ "requires": {
+ "@polymer/iron-a11y-keys-behavior": "^3.0.0-pre.26",
+ "@polymer/iron-autogrow-textarea": "^3.0.0-pre.26",
+ "@polymer/iron-behaviors": "^3.0.0-pre.26",
+ "@polymer/iron-form-element-behavior": "^3.0.0-pre.26",
+ "@polymer/iron-input": "^3.0.0-pre.26",
+ "@polymer/paper-styles": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/paper-menu-button": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@polymer/paper-menu-button/-/paper-menu-button-3.1.0.tgz",
+ "integrity": "sha512-q0G0/rvYD/FFmIBMGCQWjfXzRqwFw9+WHSYV4uOQzM1Ln8LMXSAd+2CENsbVwtMh6fmBePj15ZlU8SM2dt1WDQ==",
+ "requires": {
+ "@polymer/iron-a11y-keys-behavior": "^3.0.0-pre.26",
+ "@polymer/iron-behaviors": "^3.0.0-pre.26",
+ "@polymer/iron-dropdown": "^3.0.0-pre.26",
+ "@polymer/iron-fit-behavior": "^3.1.0",
+ "@polymer/neon-animation": "^3.0.0-pre.26",
+ "@polymer/paper-styles": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/paper-ripple": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/@polymer/paper-ripple/-/paper-ripple-3.0.2.tgz",
+ "integrity": "sha512-DnLNvYIMsiayeICroYxx6Q6Hg1cUU8HN2sbutXazlemAlGqdq80qz3TIaVdbpbt/pvjcFGX2HtntMlPstCge8Q==",
+ "requires": {
+ "@polymer/iron-a11y-keys-behavior": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/paper-styles": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@polymer/paper-styles/-/paper-styles-3.0.1.tgz",
+ "integrity": "sha512-y6hmObLqlCx602TQiSBKHqjwkE7xmDiFkoxdYGaNjtv4xcysOTdVJsDR/R9UHwIaxJ7gHlthMSykir1nv78++g==",
+ "requires": {
+ "@polymer/font-roboto": "^3.0.1",
+ "@polymer/iron-flex-layout": "^3.0.0-pre.26",
+ "@polymer/polymer": "^3.0.0"
+ }
+ },
+ "@polymer/polymer": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/@polymer/polymer/-/polymer-3.4.1.tgz",
+ "integrity": "sha512-KPWnhDZibtqKrUz7enIPOiO4ZQoJNOuLwqrhV2MXzIt3VVnUVJVG5ORz4Z2sgO+UZ+/UZnPD0jqY+jmw/+a9mQ==",
+ "requires": {
+ "@webcomponents/shadycss": "^1.9.1"
+ }
+ },
"@rollup/plugin-json": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-4.1.0.tgz",
@@ -191,6 +444,11 @@
"resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.28.5.tgz",
"integrity": "sha512-ZSwD4EDCon2EsnPZ2/Qcigx4N2DiuBLV/rDnF04giEPFuDeBeUDdnSTyYYfX8KNic/prrJuS1vUEmAOHmj+fRg=="
},
+ "@webcomponents/shadycss": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/@webcomponents/shadycss/-/shadycss-1.10.2.tgz",
+ "integrity": "sha512-9Iseu8bRtecb0klvv+WXZOVZatsRkbaH7M97Z+f+Pt909R4lDfgUODAnra23DOZTpeMTAkVpf4m/FZztN7Ox1A=="
+ },
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
diff --git a/package.json b/package.json
index 57b93a4..30d1cf4 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,9 @@
},
"homepage": "https://github.com/JurajNyiri/PlexMeetsHomeAssistant#readme",
"dependencies": {
+ "@polymer/paper-dropdown-menu": "^3.2.0",
+ "@polymer/paper-input": "^3.2.1",
+ "@polymer/polymer": "^3.4.1",
"@vercel/ncc": "^0.28.5",
"axios": "^0.21.1",
"custom-card-helpers": "^1.7.0",
diff --git a/src/editor.ts b/src/editor.ts
new file mode 100644
index 0000000..11a97f6
--- /dev/null
+++ b/src/editor.ts
@@ -0,0 +1,611 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/* eslint-env browser */
+import _ from 'lodash';
+import { HomeAssistant } from 'custom-card-helpers';
+import Plex from './modules/Plex';
+import { fetchEntityRegistry } from './modules/utils';
+
+class PlexMeetsHomeAssistantEditor extends HTMLElement {
+ content: any;
+
+ plexPort: number | false = false;
+
+ plexProtocol: 'http' | 'https' = 'http';
+
+ plex: Plex | undefined;
+
+ config: Record = {};
+
+ ip: any = document.createElement('paper-input');
+
+ token: any = document.createElement('paper-input');
+
+ port: any = document.createElement('paper-input');
+
+ maxCount: any = document.createElement('paper-input');
+
+ libraryName: any = document.createElement('paper-dropdown-menu');
+
+ protocol: any = document.createElement('paper-dropdown-menu');
+
+ tabs: any = document.createElement('paper-tabs');
+
+ sort: any = document.createElement('paper-dropdown-menu');
+
+ sortOrder: any = document.createElement('paper-dropdown-menu');
+
+ playTrailer: any = document.createElement('paper-dropdown-menu');
+
+ showExtras: any = document.createElement('paper-dropdown-menu');
+
+ showSearch: any = document.createElement('paper-dropdown-menu');
+
+ runBefore: any = document.createElement('paper-dropdown-menu');
+
+ runAfter: any = document.createElement('paper-dropdown-menu');
+
+ entitiesSection: any = document.createElement('div');
+
+ devicesTabs = 0;
+
+ hassObj: HomeAssistant | undefined;
+
+ entities: Array = [];
+
+ scriptEntities: Array = [];
+
+ sections: Array> = [];
+
+ clients: Record = {};
+
+ entitiesRegistry: false | Array> = false;
+
+ plexValidSection = document.createElement('div');
+
+ loaded = false;
+
+ fireEvent = (
+ node: HTMLElement,
+ type: string,
+ detail: Record,
+ options: Record = {}
+ ): Event => {
+ // eslint-disable-next-line no-param-reassign
+ detail = detail === null || detail === undefined ? {} : detail;
+ const event: any = new Event(type, {
+ bubbles: options.bubbles === undefined ? true : options.bubbles,
+ cancelable: Boolean(options.cancelable),
+ composed: options.composed === undefined ? true : options.composed
+ });
+ event.detail = detail;
+ node.dispatchEvent(event);
+ return event;
+ };
+
+ valueUpdated = (): void => {
+ if (this.loaded) {
+ const originalConfig = _.clone(this.config);
+ this.config.protocol = this.protocol.value;
+ this.config.ip = this.ip.value;
+ this.config.token = this.token.value;
+ this.config.port = this.port.value;
+ if (!this.config.entity) {
+ this.config.entity = [];
+ }
+ if (!_.isEmpty(this.libraryName.value)) {
+ this.config.libraryName = this.libraryName.value;
+
+ let sortOrderValue = '';
+ if (_.isEqual(this.sortOrder.value, 'Ascending')) {
+ sortOrderValue = 'asc';
+ } else if (_.isEqual(this.sortOrder.value, 'Descending')) {
+ sortOrderValue = 'desc';
+ }
+ if (!_.isEmpty(sortOrderValue) && !_.isEmpty(this.sort.value)) {
+ this.config.sort = `${this.sort.value}:${sortOrderValue}`;
+ } else {
+ this.config.sort = ``;
+ }
+
+ if (_.isEmpty(this.maxCount.value)) {
+ this.config.maxCount = '';
+ } else {
+ this.config.maxCount = this.maxCount.value;
+ }
+
+ if (!_.isEmpty(this.entities)) {
+ this.config.entity = [];
+ _.forEach(this.entities, entity => {
+ if (!_.isEmpty(entity.value) && !_.includes(this.config.entity, entity.value)) {
+ this.config.entity.push(entity.value);
+ }
+ });
+ }
+ if (_.isEqual(this.playTrailer.value, 'Yes')) {
+ this.config.playTrailer = true;
+ } else if (_.isEqual(this.playTrailer.value, 'No')) {
+ this.config.playTrailer = false;
+ } else if (_.isEqual(this.playTrailer.value, 'Muted')) {
+ this.config.playTrailer = 'muted';
+ }
+ if (_.isEqual(this.showExtras.value, 'Yes')) {
+ this.config.showExtras = true;
+ } else if (_.isEqual(this.showExtras.value, 'No')) {
+ this.config.showExtras = false;
+ }
+ if (_.isEqual(this.showSearch.value, 'Yes')) {
+ this.config.showSearch = true;
+ } else if (_.isEqual(this.showSearch.value, 'No')) {
+ this.config.showSearch = false;
+ }
+ this.config.runBefore = this.runBefore.value;
+ this.config.runAfter = this.runAfter.value;
+ }
+ if (!_.isEqual(this.config, originalConfig)) {
+ this.fireEvent(this, 'config-changed', { config: this.config });
+ }
+ }
+ };
+
+ render = async (): Promise => {
+ const addDropdownItem = (text: string): HTMLElement => {
+ const libraryItem: any = document.createElement('paper-item');
+ libraryItem.innerHTML = text;
+ return libraryItem;
+ };
+ const createEntitiesDropdown = (selected: string, changeHandler: Function): HTMLElement | false => {
+ if (this.entitiesRegistry) {
+ const entitiesDropDown: any = document.createElement('paper-dropdown-menu');
+ const entities: any = document.createElement('paper-listbox');
+
+ entities.appendChild(addDropdownItem(''));
+ const addedEntityStrings: Array = [];
+ _.forEach(this.entitiesRegistry, entityRegistry => {
+ if (
+ _.isEqual(entityRegistry.platform, 'cast') ||
+ _.isEqual(entityRegistry.platform, 'kodi') ||
+ _.isEqual(entityRegistry.platform, 'androidtv')
+ ) {
+ const entityName = `${entityRegistry.platform} | ${entityRegistry.entity_id}`;
+ entities.appendChild(addDropdownItem(entityName));
+ addedEntityStrings.push(entityName);
+ }
+ });
+ _.forEach(this.clients, value => {
+ const entityName = `plexPlayer | ${value.name} | ${value.address} | ${value.machineIdentifier}`;
+ entities.appendChild(addDropdownItem(entityName));
+ addedEntityStrings.push(entityName);
+ });
+
+ if (_.isArray(this.config.entity)) {
+ _.forEach(this.config.entity, value => {
+ if (!_.includes(addedEntityStrings, value)) {
+ entities.appendChild(addDropdownItem(value));
+ addedEntityStrings.push(value);
+ }
+ });
+ }
+
+ entities.slot = 'dropdown-content';
+ entitiesDropDown.label = 'Entity';
+ entitiesDropDown.value = selected;
+ entitiesDropDown.appendChild(entities);
+ entitiesDropDown.style.width = '100%';
+ entitiesDropDown.className = 'entitiesDropDown';
+ entitiesDropDown.addEventListener('value-changed', changeHandler);
+ this.entities.push(entitiesDropDown);
+ return entitiesDropDown;
+ }
+ return false;
+ };
+ if (this.content) this.content.remove();
+ if (this.hassObj && !this.entitiesRegistry) {
+ _.forOwn(this.hassObj.states, (value, key) => {
+ if (_.startsWith(key, 'script.')) {
+ this.scriptEntities.push(key);
+ }
+ });
+ this.entitiesRegistry = await fetchEntityRegistry(this.hassObj.connection);
+ }
+
+ this.entities = [];
+ this.content = document.createElement('div');
+
+ const plexTitle = document.createElement('h2');
+ plexTitle.innerHTML = 'Plex Configuration';
+ plexTitle.style.margin = '0px';
+ plexTitle.style.padding = '0px';
+ this.content.appendChild(plexTitle);
+
+ this.protocol.innerHTML = '';
+ const protocolItems: any = document.createElement('paper-listbox');
+ protocolItems.appendChild(addDropdownItem('http'));
+ protocolItems.appendChild(addDropdownItem('https'));
+ protocolItems.slot = 'dropdown-content';
+ this.protocol.label = 'Plex Protocol';
+ this.protocol.appendChild(protocolItems);
+ this.protocol.style.width = '100%';
+ this.protocol.addEventListener('value-changed', this.valueUpdated);
+ if (_.isEmpty(this.config.protocol)) {
+ this.protocol.value = 'http';
+ } else {
+ this.protocol.value = this.config.protocol;
+ }
+ this.content.appendChild(this.protocol);
+
+ this.ip.label = 'Plex IP Address';
+ this.ip.value = this.config.ip;
+ this.ip.addEventListener('change', this.valueUpdated);
+ this.content.appendChild(this.ip);
+
+ this.port.label = 'Plex Port';
+ this.port.value = this.config.port;
+ this.port.type = 'number';
+ this.port.addEventListener('change', this.valueUpdated);
+ this.content.appendChild(this.port);
+
+ this.token.label = 'Plex Token';
+ this.token.value = this.config.token;
+ this.token.addEventListener('change', this.valueUpdated);
+ this.content.appendChild(this.token);
+
+ this.libraryName.innerHTML = '';
+ const libraryItems: any = document.createElement('paper-listbox');
+ libraryItems.appendChild(addDropdownItem('Continue Watching'));
+ libraryItems.appendChild(addDropdownItem('Deck'));
+ libraryItems.appendChild(addDropdownItem('Recently Added'));
+ libraryItems.appendChild(addDropdownItem('Watch Next'));
+ libraryItems.slot = 'dropdown-content';
+ this.libraryName.label = 'Plex Library';
+ this.libraryName.disabled = true;
+ this.libraryName.appendChild(libraryItems);
+ this.libraryName.style.width = '100%';
+ this.libraryName.addEventListener('value-changed', this.valueUpdated);
+ this.content.appendChild(this.libraryName);
+
+ this.appendChild(this.content);
+
+ this.plex = new Plex(this.config.ip, this.plexPort, this.config.token, this.plexProtocol, this.config.sort);
+ this.sections = await this.plex.getSections();
+ this.clients = await this.plex.getClients();
+
+ this.plexValidSection.style.display = 'none';
+ this.plexValidSection.innerHTML = '';
+
+ let hasUIConfig = true;
+ let canConvert = true;
+ if (_.isArray(this.config.entity)) {
+ // eslint-disable-next-line consistent-return
+ _.forEach(this.config.entity, entity => {
+ if (_.isObjectLike(entity)) {
+ canConvert = !_.includes(_.keys(this.config.entity), 'plexPlayer');
+ hasUIConfig = false;
+ return false;
+ }
+ });
+ } else if (_.isObjectLike(this.config.entity)) {
+ canConvert = !_.includes(_.keys(this.config.entity), 'plexPlayer');
+ hasUIConfig = false;
+ if (canConvert) {
+ const convertedEntities: Array = [];
+ hasUIConfig = true;
+ if (_.isObjectLike(this.config.entity)) {
+ _.forOwn(this.config.entity, value => {
+ if (_.isString(value)) {
+ convertedEntities.push(value);
+ } else if (_.isArray(value)) {
+ _.forEach(value, valueStr => {
+ convertedEntities.push(valueStr);
+ });
+ }
+ });
+ }
+ this.config.entity = convertedEntities;
+ }
+ }
+
+ const devicesTitle = document.createElement('h2');
+ devicesTitle.innerHTML = `Devices Configuration`;
+ devicesTitle.style.lineHeight = '29px';
+ devicesTitle.style.marginBottom = '0px';
+ devicesTitle.style.marginTop = '20px';
+ if (hasUIConfig) {
+ const addDeviceButton = document.createElement('button');
+ addDeviceButton.style.float = 'right';
+ addDeviceButton.style.fontSize = '20px';
+ addDeviceButton.style.cursor = 'pointer';
+ addDeviceButton.innerHTML = '+';
+ addDeviceButton.addEventListener('click', () => {
+ const entitiesDropdown = createEntitiesDropdown('', this.valueUpdated);
+ if (entitiesDropdown) {
+ this.entitiesSection.appendChild(entitiesDropdown);
+ }
+ });
+ devicesTitle.appendChild(addDeviceButton);
+ }
+
+ this.plexValidSection.appendChild(devicesTitle);
+ this.entitiesSection.innerHTML = '';
+ this.plexValidSection.appendChild(this.entitiesSection);
+ if (hasUIConfig) {
+ if (_.isString(this.config.entity)) {
+ this.config.entity = [this.config.entity];
+ }
+ if (_.isArray(this.config.entity)) {
+ _.forEach(this.config.entity, entity => {
+ if (_.isString(entity)) {
+ const entitiesDropdown = createEntitiesDropdown(entity, this.valueUpdated);
+ if (entitiesDropdown) {
+ this.entitiesSection.appendChild(entitiesDropdown);
+ }
+ }
+ });
+ }
+ } else {
+ const entitiesUINotAvailable = document.createElement('div');
+ entitiesUINotAvailable.innerHTML =
+ 'Devices configuration is not available when using plexPlayer client device.
You can edit any other settings through UI and use Show code editor to edit entities.
If you are not using server settings for plexPlayer with identifier and server key, you can migrate your settings to UI by removing plexPlayer section and readd through UI.';
+ this.plexValidSection.appendChild(entitiesUINotAvailable);
+ }
+
+ const viewTitle = document.createElement('h2');
+ viewTitle.innerHTML = `View Configuration`;
+ viewTitle.style.lineHeight = '29px';
+ viewTitle.style.marginBottom = '0px';
+ viewTitle.style.marginTop = '20px';
+ this.plexValidSection.appendChild(viewTitle);
+
+ this.maxCount.label = 'Maximum number of items to display';
+ this.maxCount.value = this.config.maxCount;
+ this.maxCount.type = 'number';
+ this.maxCount.addEventListener('change', this.valueUpdated);
+ this.plexValidSection.appendChild(this.maxCount);
+
+ this.sort.innerHTML = '';
+
+ const sortItems: any = document.createElement('paper-listbox');
+ sortItems.slot = 'dropdown-content';
+ this.sort.label = 'Sort';
+ this.sort.appendChild(sortItems);
+ this.sort.style.width = '100%';
+ this.sort.addEventListener('value-changed', this.valueUpdated);
+ this.plexValidSection.appendChild(this.sort);
+
+ this.sortOrder.innerHTML = '';
+ const sortOrderItems: any = document.createElement('paper-listbox');
+ sortOrderItems.appendChild(addDropdownItem('Ascending'));
+ sortOrderItems.appendChild(addDropdownItem('Descending'));
+ sortOrderItems.slot = 'dropdown-content';
+ this.sortOrder.label = 'Sort Order';
+ this.sortOrder.appendChild(sortOrderItems);
+ this.sortOrder.style.width = '100%';
+ this.sortOrder.addEventListener('value-changed', this.valueUpdated);
+ if (_.isEmpty(this.config.sort)) {
+ this.sortOrder.value = 'Ascending';
+ } else {
+ const sortOrder = this.config.sort.split(':')[1];
+ if (_.isEmpty(sortOrder)) {
+ this.sortOrder.value = 'Ascending';
+ } else if (_.isEqual(sortOrder, 'asc')) {
+ this.sortOrder.value = 'Ascending';
+ } else if (_.isEqual(sortOrder, 'desc')) {
+ this.sortOrder.value = 'Descending';
+ }
+ }
+ this.plexValidSection.appendChild(this.sortOrder);
+
+ this.playTrailer.innerHTML = '';
+ const playTrailerItems: any = document.createElement('paper-listbox');
+ playTrailerItems.appendChild(addDropdownItem('Yes'));
+ playTrailerItems.appendChild(addDropdownItem('Muted'));
+ playTrailerItems.appendChild(addDropdownItem('No'));
+ playTrailerItems.slot = 'dropdown-content';
+ this.playTrailer.label = 'Play Trailer';
+ this.playTrailer.appendChild(playTrailerItems);
+ this.playTrailer.style.width = '100%';
+ this.playTrailer.addEventListener('value-changed', this.valueUpdated);
+ let playTrailerValue = 'Yes';
+ if (_.isEqual(this.config.playTrailer, 'muted')) {
+ playTrailerValue = 'Muted';
+ } else if (!this.config.playTrailer) {
+ playTrailerValue = 'No';
+ }
+ this.playTrailer.value = playTrailerValue;
+ this.plexValidSection.appendChild(this.playTrailer);
+
+ this.showExtras.innerHTML = '';
+ const showExtrasItems: any = document.createElement('paper-listbox');
+ showExtrasItems.appendChild(addDropdownItem('Yes'));
+ showExtrasItems.appendChild(addDropdownItem('No'));
+ showExtrasItems.slot = 'dropdown-content';
+ this.showExtras.label = 'Show Extras';
+ this.showExtras.appendChild(showExtrasItems);
+ this.showExtras.style.width = '100%';
+ this.showExtras.addEventListener('value-changed', this.valueUpdated);
+ let showExtrasValue = 'Yes';
+ if (!this.config.showExtras) {
+ showExtrasValue = 'No';
+ }
+ this.showExtras.value = showExtrasValue;
+ this.plexValidSection.appendChild(this.showExtras);
+
+ this.showSearch.innerHTML = '';
+ const showSearchItems: any = document.createElement('paper-listbox');
+ showSearchItems.appendChild(addDropdownItem('Yes'));
+ showSearchItems.appendChild(addDropdownItem('No'));
+ showSearchItems.slot = 'dropdown-content';
+ this.showSearch.label = 'Show Search';
+ this.showSearch.appendChild(showSearchItems);
+ this.showSearch.style.width = '100%';
+ this.showSearch.addEventListener('value-changed', this.valueUpdated);
+ let showSearchValue = 'Yes';
+ if (!this.config.showSearch) {
+ showSearchValue = 'No';
+ }
+ this.showSearch.value = showSearchValue;
+ this.plexValidSection.appendChild(this.showSearch);
+
+ this.runBefore.innerHTML = '';
+ const runBeforeItems: any = document.createElement('paper-listbox');
+ runBeforeItems.appendChild(addDropdownItem(''));
+ _.forEach(this.scriptEntities, entity => {
+ runBeforeItems.appendChild(addDropdownItem(entity));
+ });
+ runBeforeItems.slot = 'dropdown-content';
+ this.runBefore.label = 'Script to execute before starting the media';
+ this.runBefore.appendChild(runBeforeItems);
+ this.runBefore.style.width = '100%';
+ this.runBefore.addEventListener('value-changed', this.valueUpdated);
+ this.runBefore.value = this.config.runBefore;
+ this.plexValidSection.appendChild(this.runBefore);
+
+ this.runAfter.innerHTML = '';
+ const runAfterItems: any = document.createElement('paper-listbox');
+ runAfterItems.appendChild(addDropdownItem(''));
+ _.forEach(this.scriptEntities, entity => {
+ runAfterItems.appendChild(addDropdownItem(entity));
+ });
+ runAfterItems.slot = 'dropdown-content';
+ this.runAfter.label = 'Script to execute after starting the media';
+ this.runAfter.appendChild(runAfterItems);
+ this.runAfter.style.width = '100%';
+ this.runAfter.addEventListener('value-changed', this.valueUpdated);
+ this.runAfter.value = this.config.runAfter;
+ this.plexValidSection.appendChild(this.runAfter);
+
+ if (!_.isEmpty(this.sections)) {
+ _.forEach(this.sections, (section: Record) => {
+ libraryItems.appendChild(addDropdownItem(section.title));
+ });
+ this.libraryName.disabled = false;
+ this.libraryName.value = this.config.libraryName;
+
+ let libraryType = '';
+ // eslint-disable-next-line consistent-return
+ _.forEach(this.sections, section => {
+ if (_.isEqual(section.title, this.libraryName.value)) {
+ libraryType = section.type;
+ return false;
+ }
+ });
+ if (_.isEqual(libraryType, 'show')) {
+ sortItems.appendChild(addDropdownItem('titleSort'));
+ sortItems.appendChild(addDropdownItem('title'));
+ sortItems.appendChild(addDropdownItem('year'));
+ sortItems.appendChild(addDropdownItem('originallyAvailableAt'));
+ sortItems.appendChild(addDropdownItem('rating'));
+ sortItems.appendChild(addDropdownItem('audienceRating'));
+ sortItems.appendChild(addDropdownItem('userRating'));
+ sortItems.appendChild(addDropdownItem('contentRating'));
+ sortItems.appendChild(addDropdownItem('unviewedLeafCount'));
+ sortItems.appendChild(addDropdownItem('episode.addedAt'));
+ sortItems.appendChild(addDropdownItem('addedAt'));
+ sortItems.appendChild(addDropdownItem('lastViewedAt'));
+ this.sort.style.display = 'block';
+ this.sortOrder.style.display = 'block';
+ } else if (_.isEqual(libraryType, 'movie')) {
+ sortItems.appendChild(addDropdownItem('titleSort'));
+ sortItems.appendChild(addDropdownItem('title'));
+ sortItems.appendChild(addDropdownItem('originallyAvailableAt'));
+ sortItems.appendChild(addDropdownItem('rating'));
+ sortItems.appendChild(addDropdownItem('audienceRating'));
+ sortItems.appendChild(addDropdownItem('userRating'));
+ sortItems.appendChild(addDropdownItem('duration'));
+ sortItems.appendChild(addDropdownItem('viewOffset'));
+ sortItems.appendChild(addDropdownItem('viewCount'));
+ sortItems.appendChild(addDropdownItem('addedAt'));
+ sortItems.appendChild(addDropdownItem('lastViewedAt'));
+ sortItems.appendChild(addDropdownItem('mediaHeight'));
+ sortItems.appendChild(addDropdownItem('mediaBitrate'));
+ this.sort.style.display = 'block';
+ this.sortOrder.style.display = 'block';
+ } else {
+ this.sort.style.display = 'none';
+ this.sortOrder.style.display = 'none';
+ this.config.sort = '';
+ }
+
+ if (_.isEmpty(this.config.sort)) {
+ this.sort.value = '';
+ this.sortOrder.value = '';
+ } else {
+ // eslint-disable-next-line prefer-destructuring
+ this.sort.value = this.config.sort.split(':')[0];
+ const sortOrder = this.config.sort.split(':')[1];
+ if (_.isEmpty(sortOrder)) {
+ this.sortOrder.value = 'Ascending';
+ } else if (_.isEqual(sortOrder, 'asc')) {
+ this.sortOrder.value = 'Ascending';
+ } else if (_.isEqual(sortOrder, 'desc')) {
+ this.sortOrder.value = 'Descending';
+ }
+ }
+
+ this.plexValidSection.style.display = 'block';
+ }
+ this.loaded = true;
+ this.content.appendChild(this.plexValidSection);
+ };
+
+ setConfig = (config: Record): void => {
+ this.config = JSON.parse(JSON.stringify(config));
+
+ if (config.port && !_.isEqual(config.port, '')) {
+ this.plexPort = config.port;
+ } else {
+ this.plexPort = false;
+ }
+
+ if (config.protocol) {
+ this.plexProtocol = config.protocol;
+ } else {
+ this.config.protocol = 'http';
+ }
+
+ if (!config.sort) {
+ this.config.sort = 'titleSort:asc';
+ }
+
+ if (!_.isNil(config.playTrailer)) {
+ this.config.playTrailer = config.playTrailer;
+ } else {
+ this.config.playTrailer = true;
+ }
+
+ if (!_.isNil(config.showExtras)) {
+ this.config.showExtras = config.showExtras;
+ } else {
+ this.config.showExtras = true;
+ }
+
+ if (!_.isNil(config.showSearch)) {
+ this.config.showSearch = config.showSearch;
+ } else {
+ this.config.showSearch = true;
+ }
+
+ if (!_.isNil(config.runBefore)) {
+ this.config.runBefore = config.runBefore;
+ }
+
+ if (!_.isNil(config.runAfter)) {
+ this.config.runAfter = config.runAfter;
+ }
+
+ this.render();
+ };
+
+ configChanged = (newConfig: any): void => {
+ const event: any = new Event('config-changed', {
+ bubbles: true,
+ composed: true
+ });
+ event.detail = { config: newConfig };
+ this.dispatchEvent(event);
+ };
+
+ set hass(hass: HomeAssistant) {
+ this.hassObj = hass;
+ }
+}
+export default PlexMeetsHomeAssistantEditor;
diff --git a/src/modules/PlayController.ts b/src/modules/PlayController.ts
index eba5f2b..ae19246 100644
--- a/src/modules/PlayController.ts
+++ b/src/modules/PlayController.ts
@@ -112,36 +112,41 @@ class PlayController {
break;
case 'cast':
if (this.hass.services.plex) {
- switch (data.type) {
- case 'movie':
- this.playViaCastPlex(
- entity.value,
- 'movie',
- `plex://${JSON.stringify({
- // eslint-disable-next-line @typescript-eslint/camelcase
- library_name: data.librarySectionTitle,
- title: data.title
- })}`
- );
- break;
- case 'episode':
- this.playViaCastPlex(
- entity.value,
- 'EPISODE',
- `plex://${JSON.stringify({
- // eslint-disable-next-line @typescript-eslint/camelcase
- library_name: data.librarySectionTitle,
- // eslint-disable-next-line @typescript-eslint/camelcase
- show_name: data.grandparentTitle,
- // eslint-disable-next-line @typescript-eslint/camelcase
- season_number: data.parentIndex,
- // eslint-disable-next-line @typescript-eslint/camelcase
- episode_number: data.index
- })}`
- );
- break;
- default:
- this.playViaCast(entity.value, data.Media[0].Part[0].key);
+ try {
+ switch (data.type) {
+ case 'movie':
+ await this.playViaCastPlex(
+ entity.value,
+ 'movie',
+ `plex://${JSON.stringify({
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ library_name: data.librarySectionTitle,
+ title: data.title
+ })}`
+ );
+ break;
+ case 'episode':
+ await this.playViaCastPlex(
+ entity.value,
+ 'EPISODE',
+ `plex://${JSON.stringify({
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ library_name: data.librarySectionTitle,
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ show_name: data.grandparentTitle,
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ season_number: data.parentIndex,
+ // eslint-disable-next-line @typescript-eslint/camelcase
+ episode_number: data.index
+ })}`
+ );
+ break;
+ default:
+ this.playViaCast(entity.value, data.Media[0].Part[0].key);
+ }
+ } catch (err) {
+ console.log(err);
+ this.playViaCast(entity.value, data.Media[0].Part[0].key);
}
} else {
this.playViaCast(entity.value, data.Media[0].Part[0].key);
@@ -287,8 +292,8 @@ class PlayController {
});
};
- private playViaCastPlex = (entityName: string, contentType: string, mediaLink: string): void => {
- this.hass.callService('media_player', 'play_media', {
+ private playViaCastPlex = (entityName: string, contentType: string, mediaLink: string): Promise => {
+ return this.hass.callService('media_player', 'play_media', {
// eslint-disable-next-line @typescript-eslint/camelcase
entity_id: entityName,
// eslint-disable-next-line @typescript-eslint/camelcase
diff --git a/src/modules/utils.ts b/src/modules/utils.ts
index a767206..4667865 100644
--- a/src/modules/utils.ts
+++ b/src/modules/utils.ts
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-env browser */
+import { Connection } from 'home-assistant-js-websocket';
import _ from 'lodash';
import { CSS_STYLE } from '../const';
import PlayController from './PlayController';
@@ -18,6 +19,11 @@ const escapeHtml = (unsafe: any): string => {
return '';
};
+const fetchEntityRegistry = (conn: Connection): Promise>> =>
+ conn.sendMessagePromise({
+ type: 'config/entity_registry/list'
+ });
+
const getHeight = (el: HTMLElement): number => {
const height = Math.max(el.scrollHeight, el.offsetHeight, el.clientHeight, el.scrollHeight, el.offsetHeight);
return height;
@@ -255,5 +261,6 @@ export {
getOldPlexServerErrorMessage,
getWidth,
getDetailsBottom,
- clickHandler
+ clickHandler,
+ fetchEntityRegistry
};
diff --git a/src/plex-meets-homeassistant.ts b/src/plex-meets-homeassistant.ts
index 534852d..0f2245d 100644
--- a/src/plex-meets-homeassistant.ts
+++ b/src/plex-meets-homeassistant.ts
@@ -2,10 +2,10 @@
/* eslint-env browser */
import { HomeAssistant } from 'custom-card-helpers';
import _ from 'lodash';
-import { Connection } from 'home-assistant-js-websocket';
import { supported, CSS_STYLE } from './const';
import Plex from './modules/Plex';
import PlayController from './modules/PlayController';
+import PlexMeetsHomeAssistantEditor from './editor';
import {
escapeHtml,
getOffset,
@@ -17,11 +17,20 @@ import {
hasEpisodes,
getOldPlexServerErrorMessage,
getDetailsBottom,
- clickHandler
+ clickHandler,
+ fetchEntityRegistry
} from './modules/utils';
import style from './modules/style';
+declare global {
+ interface Window {
+ customCards: any;
+ }
+}
+
class PlexMeetsHomeAssistant extends HTMLElement {
+ searchInputElem = document.createElement('input');
+
plexProtocol: 'http' | 'https' = 'http';
plexPort: number | false = false;
@@ -36,6 +45,8 @@ class PlexMeetsHomeAssistant extends HTMLElement {
showExtras = true;
+ showSearch = true;
+
previousPageWidth = 0;
runAfter = '';
@@ -118,17 +129,20 @@ class PlexMeetsHomeAssistant extends HTMLElement {
card: HTMLElement | undefined;
+ initialDataLoaded = false;
+
set hass(hass: HomeAssistant) {
this.hassObj = hass;
- if (!this.content) {
- this.error = '';
- if (!this.loading) {
- this.loadInitialData();
- }
+ if (!this.initialDataLoaded) {
+ this.loadInitialData();
}
}
+ static getConfigElement(): HTMLElement {
+ return document.createElement('plex-meets-homeassistant-editor');
+ }
+
renderNewElementsIfNeeded = (): void => {
const loadAdditionalRowsCount = 2; // todo: make this configurable
const height = getHeight(this.content);
@@ -144,59 +158,9 @@ class PlexMeetsHomeAssistant extends HTMLElement {
}
};
- fetchEntityRegistry = (conn: Connection): Promise>> =>
- conn.sendMessagePromise({
- type: 'config/entity_registry/list'
- });
-
loadInitialData = async (): Promise => {
if (this.hassObj) {
- this.entityRegistry = await this.fetchEntityRegistry(this.hassObj.connection);
- }
-
- let { entity } = JSON.parse(JSON.stringify(this.config));
-
- const processEntity = (entityObj: Record, entityString: string): void => {
- _.forEach(this.entityRegistry, entityInRegister => {
- if (_.isEqual(entityInRegister.entity_id, entityString)) {
- switch (entityInRegister.platform) {
- case 'cast':
- if (_.isNil(entityObj.cast)) {
- // eslint-disable-next-line no-param-reassign
- entityObj.cast = [];
- }
- entityObj.cast.push(entityInRegister.entity_id);
- break;
- case 'androidtv':
- if (_.isNil(entityObj.androidtv)) {
- // eslint-disable-next-line no-param-reassign
- entityObj.androidtv = [];
- }
- entityObj.androidtv.push(entityInRegister.entity_id);
- break;
- case 'kodi':
- if (_.isNil(entityObj.kodi)) {
- // eslint-disable-next-line no-param-reassign
- entityObj.kodi = [];
- }
- entityObj.kodi.push(entityInRegister.entity_id);
- break;
- default:
- // pass
- }
- }
- });
- };
-
- const entityOrig = entity;
- if (_.isString(entityOrig)) {
- entity = {};
- processEntity(entity, entityOrig);
- } else if (_.isArray(entityOrig)) {
- entity = {};
- _.forEach(entityOrig, entityStr => {
- processEntity(entity, entityStr);
- });
+ this.entityRegistry = await fetchEntityRegistry(this.hassObj.connection);
}
window.addEventListener('scroll', () => {
@@ -263,16 +227,87 @@ class PlexMeetsHomeAssistant extends HTMLElement {
if (this.card) {
this.previousPageWidth = this.card.offsetWidth;
}
+ this.resizeBackground();
+ this.initialDataLoaded = true;
+ };
+
+ renderInitialData = async (): Promise => {
+ let { entity } = JSON.parse(JSON.stringify(this.config));
+ const processEntity = (entityObj: Record, entityString: string): void => {
+ let realEntityString = entityString;
+ let isPlexPlayer = false;
+ if (_.startsWith(entityString, 'plexPlayer | ')) {
+ // eslint-disable-next-line prefer-destructuring
+ realEntityString = entityString.split(' | ')[3];
+ isPlexPlayer = true;
+ } else if (
+ _.startsWith(entityString, 'androidtv | ') ||
+ _.startsWith(entityString, 'kodi | ') ||
+ _.startsWith(entityString, 'cast | ')
+ ) {
+ // eslint-disable-next-line prefer-destructuring
+ realEntityString = entityString.split(' | ')[1];
+ isPlexPlayer = false;
+ }
+ if (isPlexPlayer) {
+ if (_.isNil(entityObj.plexPlayer)) {
+ // eslint-disable-next-line no-param-reassign
+ entityObj.plexPlayer = [];
+ }
+ entityObj.plexPlayer.push(realEntityString);
+ } else {
+ _.forEach(this.entityRegistry, entityInRegister => {
+ if (_.isEqual(entityInRegister.entity_id, realEntityString)) {
+ switch (entityInRegister.platform) {
+ case 'cast':
+ if (_.isNil(entityObj.cast)) {
+ // eslint-disable-next-line no-param-reassign
+ entityObj.cast = [];
+ }
+ entityObj.cast.push(entityInRegister.entity_id);
+ break;
+ case 'androidtv':
+ if (_.isNil(entityObj.androidtv)) {
+ // eslint-disable-next-line no-param-reassign
+ entityObj.androidtv = [];
+ }
+ entityObj.androidtv.push(entityInRegister.entity_id);
+ break;
+ case 'kodi':
+ if (_.isNil(entityObj.kodi)) {
+ // eslint-disable-next-line no-param-reassign
+ entityObj.kodi = [];
+ }
+ entityObj.kodi.push(entityInRegister.entity_id);
+ break;
+ default:
+ // pass
+ }
+ }
+ });
+ }
+ console.log(realEntityString);
+ };
+
+ const entityOrig = entity;
+ if (_.isString(entityOrig)) {
+ entity = {};
+ processEntity(entity, entityOrig);
+ } else if (_.isArray(entityOrig)) {
+ entity = {};
+ _.forEach(entityOrig, entityStr => {
+ processEntity(entity, entityStr);
+ });
+ }
+ console.log(entity);
this.loading = true;
this.renderPage();
try {
- if (this.plex) {
- if (this.hassObj) {
- this.playController = new PlayController(this.hassObj, this.plex, entity, this.runBefore, this.runAfter);
- if (this.playController) {
- await this.playController.init();
- }
+ if (this.plex && this.hassObj) {
+ this.playController = new PlayController(this.hassObj, this.plex, entity, this.runBefore, this.runAfter);
+ if (this.playController) {
+ await this.playController.init();
}
await this.plex.init();
@@ -347,14 +382,14 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.loading = false;
this.render();
} else {
- throw Error('Plex not initialized.');
+ setTimeout(() => {
+ this.renderInitialData();
+ }, 250);
}
} catch (err) {
this.error = `Plex server did not respond.
Details of the error: ${escapeHtml(err.message)}`;
this.renderPage();
}
-
- this.resizeBackground();
};
render = (): void => {
@@ -365,19 +400,19 @@ class PlexMeetsHomeAssistant extends HTMLElement {
const searchContainer = document.createElement('div');
searchContainer.className = 'searchContainer';
- const searchInput = document.createElement('input');
- searchInput.type = 'text';
- searchInput.value = this.searchValue;
- searchInput.placeholder = `Search ${this.config.libraryName}...`;
+ this.searchInputElem = document.createElement('input');
+ this.searchInputElem.type = 'text';
+ this.searchInputElem.value = this.searchValue;
+ this.searchInputElem.placeholder = `Search ${this.config.libraryName}...`;
- searchInput.addEventListener('keyup', () => {
- this.searchValue = searchInput.value;
+ this.searchInputElem.addEventListener('keyup', () => {
+ this.searchValue = this.searchInputElem.value;
this.renderPage();
this.focus();
this.renderNewElementsIfNeeded();
});
- searchContainer.appendChild(searchInput);
+ searchContainer.appendChild(this.searchInputElem);
return searchContainer;
};
@@ -449,6 +484,13 @@ class PlexMeetsHomeAssistant extends HTMLElement {
};
renderPage = (): void => {
+ this.searchInputElem.placeholder = `Search ${this.config.libraryName}...`;
+ if (this.showSearch) {
+ this.searchInputElem.style.display = 'block';
+ } else {
+ this.searchInputElem.style.display = 'none';
+ }
+
if (this.card) {
const marginRight = 10; // needs to be equal to .container margin right
const areaSize =
@@ -480,7 +522,14 @@ class PlexMeetsHomeAssistant extends HTMLElement {
this.card.style.overflow = 'hidden';
this.card.style.padding = '16px';
this.card.style.paddingRight = '6px';
+
this.card.appendChild(this.searchInput());
+ if (this.showSearch) {
+ this.searchInputElem.style.display = 'block';
+ } else {
+ this.searchInputElem.style.display = 'none';
+ }
+
this.appendChild(this.card);
}
@@ -1488,6 +1537,15 @@ class PlexMeetsHomeAssistant extends HTMLElement {
setConfig = (config: any): void => {
this.plexProtocol = 'http';
+ if (!config.ip) {
+ throw new Error('You need to define a Plex IP Address');
+ }
+ if (!config.token) {
+ throw new Error('You need to define a Plex Token');
+ }
+ if (!config.libraryName) {
+ throw new Error('You need to define a libraryName');
+ }
if (!config.entity || config.entity.length === 0) {
throw new Error('You need to define at least one entity');
}
@@ -1506,29 +1564,22 @@ class PlexMeetsHomeAssistant extends HTMLElement {
} else if (!_.isString(config.entity) && !_.isArray(config.entity)) {
throw new Error('You need to define at least one supported entity');
}
- if (!config.token) {
- throw new Error('You need to define a token');
- }
- if (!config.ip) {
- throw new Error('You need to define a ip');
- }
- if (!config.libraryName) {
- throw new Error('You need to define a libraryName');
- }
this.config = config;
if (config.protocol) {
this.plexProtocol = config.protocol;
}
- if (config.port) {
+ if (config.port && !_.isEqual(config.port, '')) {
this.plexPort = config.port;
+ } else {
+ this.plexPort = false;
}
- if (config.maxCount) {
+ if (config.maxCount && config.maxCount !== '') {
this.maxCount = config.maxCount;
}
- if (config.runBefore) {
+ if (config.runBefore && !_.isEqual(config.runBefore, '')) {
this.runBefore = config.runBefore;
}
- if (config.runAfter) {
+ if (config.runAfter && !_.isEqual(config.runAfter, '')) {
this.runAfter = config.runAfter;
}
if (!_.isNil(config.playTrailer)) {
@@ -1537,8 +1588,14 @@ class PlexMeetsHomeAssistant extends HTMLElement {
if (!_.isNil(config.showExtras)) {
this.showExtras = config.showExtras;
}
+ if (!_.isNil(config.showSearch)) {
+ this.showSearch = config.showSearch;
+ }
this.plex = new Plex(this.config.ip, this.plexPort, this.config.token, this.plexProtocol, this.config.sort);
+ this.data = {};
+ this.error = '';
+ this.renderInitialData();
};
getCardSize = (): number => {
@@ -1546,4 +1603,13 @@ class PlexMeetsHomeAssistant extends HTMLElement {
};
}
+customElements.define('plex-meets-homeassistant-editor', PlexMeetsHomeAssistantEditor);
customElements.define('plex-meets-homeassistant', PlexMeetsHomeAssistant);
+
+window.customCards = window.customCards || [];
+window.customCards.push({
+ type: 'plex-meets-homeassistant',
+ name: 'Plex meets Home Assistant',
+ preview: false,
+ description: 'Integrates Plex into Home Assistant. Browse and launch media with a simple click.' // Optional
+});