master 1af7c43f186c cached
52 files
218.1 KB
61.6k tokens
52 symbols
1 requests
Download .txt
Showing preview only (237K chars total). Download the full file or copy to clipboard to get everything.
Repository: elgatosf/streamdeck-philipshue
Branch: master
Commit: 1af7c43f186c
Files: 52
Total size: 218.1 KB

Directory structure:
gitextract_alvhxsyj/

├── .editorconfig
├── .gitignore
├── .hintrc
├── LICENSE
├── README.md
├── Release/
│   └── com.elgato.philips-hue.streamDeckPlugin
└── Sources/
    └── com.elgato.philips-hue.sdPlugin/
        ├── de.json
        ├── en.json
        ├── es.json
        ├── fr.json
        ├── ja.json
        ├── ko.json
        ├── manifest.json
        ├── pi/
        │   ├── css/
        │   │   ├── colorPI.css
        │   │   ├── cyclePI.css
        │   │   ├── pi.css
        │   │   └── sdpi.css
        │   ├── index.html
        │   └── js/
        │       ├── brightnessPI.js
        │       ├── brightnessRelPI.js
        │       ├── colorPI.js
        │       ├── cyclePI.js
        │       ├── main.js
        │       ├── pi.js
        │       ├── powerPI.js
        │       ├── scenePI.js
        │       ├── temperaturePI.js
        │       └── tooltips.js
        ├── plugin/
        │   ├── index.html
        │   └── js/
        │       ├── action.js
        │       ├── brightnessAction.js
        │       ├── brightnessRelAction.js
        │       ├── colorAction.js
        │       ├── cycleAction.js
        │       ├── main.js
        │       ├── philips/
        │       │   ├── cache.js
        │       │   └── meethue.js
        │       ├── powerAction.js
        │       ├── propertyAction.js
        │       ├── sceneAction.js
        │       ├── temperatureAction.js
        │       ├── timers.js
        │       └── utils.js
        ├── setup/
        │   ├── css/
        │   │   └── main.css
        │   ├── index.html
        │   └── js/
        │       ├── discoveryView.js
        │       ├── introView.js
        │       ├── main.js
        │       ├── manualView.js
        │       ├── pairingView.js
        │       └── saveView.js
        └── zh_CN.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2


================================================
FILE: .gitignore
================================================
__xx/*


================================================
FILE: .hintrc
================================================
{
  "extends": [
    "development"
  ],
  "hints": {
    "axe/forms": "off",
    "meta-viewport": "off",
    "axe/language": "off",
    "no-inline-styles": "off"
  }
}

================================================
FILE: LICENSE
================================================
The MIT License

Copyright 2018 Corsair Memory, Inc

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================

# Philips Hue Plugin for Elgato Stream Deck 
# UPDATE INFORMATION

## There's a new version of the Philips Hue plugin available on the [Elgato Marketplace](https://marketplace.elgato.com/product/philips-hue-27f49792-2de3-455f-8892-fd382716f548).

You'll find that version [here](https://marketplace.elgato.com/product/philips-hue-27f49792-2de3-455f-8892-fd382716f548)

New discussions/support will move to [Discord](https://discord.gg/elgato)

The new version of the plugin should work with all recent Philips Hue Bridges (sold like 6-7 years ago).

**If you have the original plugin installed, please, first install the update to the old plugin** [Philips-Hue original plugin 1.6.8](https://github.com/user-attachments/files/15746596/com.elgato.philips-hue.streamDeckPlugin.zip), or remove it - otherwise your sidebar gets mixed up...

You can install both versions (old and new) in parallel without problems, but it has no benefits. The new version is far more capable than the legacy version.

<img width="952" alt="CleanShot_2024-06-08_08 35 59" src="https://github.com/elgatosf/streamdeck-philipshue/assets/80752/621322d9-5e19-42fb-aec4-7fce9a11aac6">


----

(The notes below are kept for reference)

## Philips Hue Plugin for Elgato Stream Deck (legacy version)
This sample plugin allows controlling `Philips Hue` lights in your network. It's a demonstration of the [Stream Deck SDK](https://developer.elgato.com/documentation/stream-deck/).
Since version 1.6.0, it also supports the Stream Deck +. The brightness-action contains an example of how to change the display of the dial-control's touch-panel.

## Version 1.6.7 is also available in the Stream Deck Store!

## Features
- Code written in JavaScript
- Cross-platform (macOS, Windows)
- Localized
- Basic support for Stream Deck +

![](screenshot.png)


# Installation
In the [Release](./Release) folder, you can find the file `com.elgato.philips-hue.streamDeckPlugin`. If you double-click this file on your machine, Stream Deck will install the plugin.


# Source code
The [Sources](./Sources) folder contains the source code of the plugin.

# Changes
## 1.6.4
- fixed/improved support for temperature actions
- PI now lets you only select lights for a temperature action if they support color temperature

## 1.6.3
- updated CSS to the latest versions of our SDK-libs
- added an option to the PI to allow larger steps if you rotate dials (1,2,3,4,5,10).

## 1.6.0
- fixed broken localizations
- changed versioning to semver
- added basic support for Stream Deck +

# How it works (since 1.6.0)
![](touchpanel.png)

 You can now drag a brightness-/ or temperature-action to a SD+ dial-control. It supports these actions:
 - Turn the dial to change the brightness/temperature
 - Press the dial to:
 - - set the brightness/temperature to the configured value - if the light is on
 - - turn the light on - if the light is off
 - Long-Press the dial to toggle the light on/off
 - Tap the touch-panel to toggle the light on/off
  
 DialStacks are not properly supported yet.



================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/de.json
================================================
{
  "Name": "Philips Hue",
  "Category": "Philips Hue",
  "Description": "Steuere deine Philips Hue-Lampen.",
  "com.elgato.philips-hue.power": {
    "Name": "Ein/Aus",
    "Tooltip": "Schalte das Licht ein oder aus.",
    "States": [
      {
        "Name": "Ein"
      },
      {
        "Name": "Aus"
      }
    ]
  },
  "com.elgato.philips-hue.color": {
    "Name": "Farbe",
    "Tooltip": "Stelle die Farbe des Lichts ein."
  },
  "com.elgato.philips-hue.cycle": {
    "Name": "Farben durchschalten",
    "Tooltip": "Wechsle zwischen mehreren Lichtfarben."
  },
  "com.elgato.philips-hue.brightness": {
    "Name": "Helligkeit",
    "Tooltip": "Stelle die Helligkeit des Lichts ein.",
    "Encoder": {
      "TriggerDescription": {
        "Rotate": "Helligkeit anpassen",
        "Touch": "Licht ein/aus"
      }
    }
  },
  "com.elgato.philips-hue.brightness-rel": {
    "Name": "Relative Helligkeit",
    "Tooltip": "Stelle die Helligkeit des Lichts relativ zur aktuellen Helligkeit ein."
  },
  "com.elgato.philips-hue.scene": {
    "Name": "Szene",
    "Tooltip": "Stelle eine Szene ein."
  },
  "Localization": {
    "PI": {
      "Bridge": "Bridge",
      "NoBridges": "Keine Bridges",
      "AddBridge": "Neue hinzufügen",
      "Lights": "Lampen",
      "Group": "Gruppe",
      "LightsTitle": "Lampen",
      "GroupsTitle": "Gruppen",
      "NoLights": "Keine Lampen",
      "NoGroups": "Keine Gruppen",
      "Color": "Farbe",
      "Colors": "Farben",
      "Temperature": "Temperatur",
      "Brightness": "Helligkeit",
      "Steps": "Schritte",
      "Scene": "Szene",
      "NoScenes": "Keine Szenen"
    },
    "Setup": {
      "Intro": {
        "Title": "Bridge hinzufügen",
        "Description": "Mit einer Philips Hue-Bridge koppeln, um deine Lichter zu steuern.",
        "Start": "Bridges suchen",
        "Manual": "Bridge manuell hinzufügen",
        "Close": "Nicht jetzt"
      },
      "Discovery": {
        "Title": "Bridges werden gesucht …",
        "TitleNone": "Keine Bridges gefunden",
        "TitleOne": "Eine Bridge gefunden",
        "TitleMultiple": "{{ number }} Bridges gefunden",
        "DescriptionFound": "Jetzt koppeln?",
        "DescriptionNone": "Vergewissere dich, dass die Bridge eingeschaltet und mit dem Netzwerk verbunden ist.",
        "Pair": "Koppeln",
        "Close": "Schließen",
        "Retry": "Erneut versuchen"
      },
      "Manual": {
        "Title": "Bridge manuell hinzufügen",
        "Description": "Füge deine Philips Hue-Bridge deinem Netzwerk manuell über die IP-Adresse hinzu, falls Auto-Discovery nicht unterstützt wird.",
        "IPAddress": "IP-Adresse der Bridge",
        "Check": "Eingabe überprüfen",
        "Close": "Schließen",
        "Error": {
          "Empty": "Bitte trage eine IP-Adresse in das Eingabefeld ein.",
          "Invalid": "Die eingetragene IP-Adresse hat ein ungültiges Format. Bitte überprüfe deine Eingabe.",
          "Unreachable": "Die Bridge ist unter der IP-Adresse nicht erreichbar. Bitte überprüfe die IP-Adresse."
        }
      },
      "Pairing": {
        "Title": "Wird gekoppelt …",
        "Description": "Drücke auf der Bridge jetzt die Taste zum Verbinden.",
        "Close": "Schließen",
        "Retry": "Erneut versuchen"
      },
      "Save": {
        "Title": "Bridge hinzugefügt",
        "Description": "Die Bridge wurde erfolgreich hinzugefügt.",
        "Save": "Fertig"
      }
    }
  }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/en.json
================================================
{
  "Name": "Philips Hue",
  "Category": "Philips Hue",
  "Description": "Control your Philips Hue lights.",
  "com.elgato.philips-hue.power": {
    "Name": "On / Off",
    "Tooltip": "Turn lights on or off.",
    "States": [{
      "Name": "On"
    },
    {
      "Name": "Off"
    }]
  },
  "com.elgato.philips-hue.color": {
    "Name": "Color",
    "Tooltip": "Set the color of your light."
  },
  "com.elgato.philips-hue.cycle": {
    "Name": "Color Cycle",
    "Tooltip": "Cycle between colors of your light."
  },
  "com.elgato.philips-hue.brightness": {
    "Name": "Brightness",
    "Tooltip": "Set the brightness of your light.",
    "Encoder": {
      "TriggerDescription": {
          "Rotate": "Adjust Brightness",
          "Touch": "Toggle Lights on/off"
      }
    }
  },
  "com.elgato.philips-hue.brightness-rel": {
    "Name": "Brightness Relative",
    "Tooltip": "Adjust the brightness of the light relative to the current brightness."
  },
  "com.elgato.philips-hue.scene": {
    "Name": "Scene",
    "Tooltip": "Set a scene."
  },
  "Localization": {
    "PI": {
      "Bridge": "Bridge",
      "NoBridges": "No Bridges",
      "AddBridge": "Add New",
      "Lights": "Lights",
      "Group": "Group",
      "LightsTitle": "Lights",
      "GroupsTitle": "Groups",
      "NoLights": "No Lights",
      "NoGroups": "No Groups",
      "Color": "Color",
      "Colors": "Colors",
      "Temperature": "Temperature",
      "Brightness": "Brightness",
      "Steps": "Steps",
      "Scene": "Scene",
      "NoScenes": "No Scenes"
    },
    "Setup": {
      "Intro": {
        "Title" : "Add Bridge",
        "Description": "Pair with a Philips Hue bridge to control your lights.",
        "Start": "Discover Bridges",
        "Manual": "Add Bridge manually",
        "Close": "Not Now"
      },
      "Discovery": {
        "Title" : "Discovering Bridges …",
        "TitleNone" : "No Bridges Found",
        "TitleOne" : "One Bridge Found",
        "TitleMultiple" : "{{ number }} Bridges Found",
        "DescriptionFound": "Start pairing now?",
        "DescriptionNone": "Make sure your bridge is switched on and connected to the network.",
        "Pair": "Pair",
        "Close": "Close",
        "Retry": "Try Again"
      },
      "Manual": {
        "Title": "Add bridge manually",
        "Description": "Add your Philips Hue-Bridge manually (via IP-Address) to your network, in cases where Auto-Discovery is not supported.",
        "IPAddress": "IP address of the bridge",
        "Check": "Validate input",
        "Close": "Close",
        "Error": {
          "Empty": "Please add the ip address of your bridge.",
          "Invalid": "The entered IP address has an invalid format. Please check your input.",
          "Unreachable": "Bridge is not reachable. Please check the ip address."
        }
      },
      "Pairing": {
        "Title" : "Pairing...",
        "Description": "Please press the link button on the bridge now.",
        "Close": "Close",
        "Retry": "Try Again"
      },
      "Save": {
        "Title" : "Bridge Added",
        "Description": "The bridge has been paired sucessfully.",
        "Save": "Done"
      }
    }
  }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/es.json
================================================
{
  "Name": "Philips Hue",
  "Category": "Philips Hue",
  "Description": "Controla tus luces Philips Hue.",
  "com.elgato.philips-hue.power": {
    "Name": "On / Off",
    "Tooltip": "Encender o apagar las luces.",
    "States": [
      {
        "Name": "On"
      },
      {
        "Name": "Off"
      }
    ]
  },
  "com.elgato.philips-hue.color": {
    "Name": "Color",
    "Tooltip": "Ajusta el color de tu luz."
  },
  "com.elgato.philips-hue.cycle": {
    "Name": "Transición de color",
    "Tooltip": "Hacer transición entre los colores de tu luz."
  },
  "com.elgato.philips-hue.brightness": {
    "Name": "Brillo",
    "Tooltip": "Ajusta el brillo de tu luz.",
    "Encoder": {
      "TriggerDescription": {
        "Rotate": "Ajustar brillo",
        "Touch": "Encender/apagar luces"
      }
    }
  },
  "com.elgato.philips-hue.brightness-rel": {
    "Name": "Brillo relativo",
    "Tooltip": "Ajusta el brillo de la luz con respecto al brillo actual."
  },
  "com.elgato.philips-hue.scene": {
    "Name": "Escena",
    "Tooltip": "Activa una escena."
  },
  "Localization": {
    "PI": {
      "Bridge": "Bridge",
      "NoBridges": "No hay Bridges",
      "AddBridge": "Añadir",
      "Lights": "Luces",
      "Group": "Grupo",
      "LightsTitle": "Luces",
      "GroupsTitle": "Grupos",
      "NoLights": "No hay luces",
      "NoGroups": "No hay grupos",
      "Color": "Color",
      "Colors": "Colores",
      "Temperature": "Temperatura",
      "Brightness": "Brillo",
      "Steps": "Pasos",
      "Scene": "Escena",
      "NoScenes": "No hay escenas"
    },
    "Setup": {
      "Intro": {
        "Title": "Añadir Bridge",
        "Description": "Enlaza con un Bridge Philips Hue para controlar tus luces.",
        "Start": "Detectar Bridge",
        "Manual": "Añadir Bridge a mano",
        "Close": "Ahora no"
      },
      "Discovery": {
        "Title": "Detectando Bridges …",
        "TitleNone": "No se han detectado Bridges",
        "TitleOne": "Se ha detectado un Bridge",
        "TitleMultiple": "{{ number }} Bridges detectados",
        "DescriptionFound": "¿Empezar a enlazar ahora?",
        "DescriptionNone": "Comprueba que el Bridge está encendido y conectado a la red.",
        "Pair": "Enlazar",
        "Close": "Cerrar",
        "Retry": "Volver a intentar"
      },
      "Manual": {
        "Title": "Añadir Bridge a mano",
        "Description": "Añade tu Philips Hue Bridge manualmente (mediante la dirección IP) a tu red, en los casos en los que la detección automática no se puede usar.",
        "IPAddress": "Dirección IP del Bridge",
        "Check": "Validar entrada",
        "Close": "Cerrar",
        "Error": {
          "Empty": "Añade la dirección IP de tu Bridge.",
          "Invalid": "El formato de la dirección IP no es válido. Comprueba lo que has introducido.",
          "Unreachable": "Nose puede conectar con el Bridge. Comprueba la dirección IP."
        }
      },
      "Pairing": {
        "Title": "Enlazando...",
        "Description": "Pulsa ahora el botón de enlace en el Bridge.",
        "Close": "Cerrar",
        "Retry": "Volver a intentar"
      },
      "Save": {
        "Title": "Bridge añadido",
        "Description": "El Bridge se ha enlazado correctamente.",
        "Save": "OK"
      }
    }
  }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/fr.json
================================================
{
  "Name": "Philips Hue",
  "Category": "Philips Hue",
  "Description": "Contrôlez vos systèmes d’éclairage Philips Hue.",
  "com.elgato.philips-hue.power": {
    "Name": "Activer/Désactiver",
    "Tooltip": "Allumez ou éteignez les lumières.",
    "States": [
      {
        "Name": "Activer"
      },
      {
        "Name": "Désactiver"
      }
    ]
  },
  "com.elgato.philips-hue.color": {
    "Name": "Couleur",
    "Tooltip": "Réglez la couleur de la lumière."
  },
  "com.elgato.philips-hue.cycle": {
    "Name": "Cycle de couleurs",
    "Tooltip": "Activez les différentes couleurs de votre lumière de façon cyclique."
  },
  "com.elgato.philips-hue.brightness": {
    "Name": "Luminosité",
    "Tooltip": "Réglez la luminosité de la lumière.",
    "Encoder": {
      "TriggerDescription": {
        "Rotate": "Ajuster la luminosité",
        "Touch": "Allumer ou éteindre les lumières"
      }
    }
  },
  "com.elgato.philips-hue.brightness-rel": {
    "Name": "Luminosité relative",
    "Tooltip": "Permet de régler la luminosité de l’éclairage par rapport à la luminosité actuelle."
  },
  "com.elgato.philips-hue.scene": {
    "Name": "Scène",
    "Tooltip": "Définissez une scène."
  },
  "Localization": {
    "PI": {
      "Bridge": "Pont",
      "NoBridges": "Aucun pont",
      "AddBridge": "Ajouter",
      "Lights": "Lumières",
      "Group": "Groupe",
      "LightsTitle": "Lumières",
      "GroupsTitle": "Groupes",
      "NoLights": "Aucune lumière",
      "NoGroups": "Aucun groupe",
      "Color": "Couleur",
      "Colors": "Couleurs",
      "Temperature": "Température",
      "Brightness": "Luminosité",
      "Steps": "Pas",
      "Scene": "Scène",
      "NoScenes": "Aucune scène"
    },
    "Setup": {
      "Intro": {
        "Title": "Ajouter un pont",
        "Description": "Jumelez l’application à un pont Philips Hue pour contrôler vos lumières.",
        "Start": "Découvrir des ponts",
        "Manual": "Ajouter un pont manuellement",
        "Close": "Pas maintenant"
      },
      "Discovery": {
        "Title": "Découverte des ponts…",
        "TitleNone": "Aucun pont trouvé",
        "TitleOne": "1 pont trouvé",
        "TitleMultiple": "{{ number }} ponts trouvés",
        "DescriptionFound": "Lancer le jumelage ?",
        "DescriptionNone": "Vérifiez que votre pont est allumé et connecté au réseau.",
        "Pair": "Jumeler",
        "Close": "Fermer",
        "Retry": "Réessayer"
      },
      "Manual": {
        "Title": "Ajouter un pont manuellement",
        "Description": "Ajoutez votre pont Philips Hue manuellement (à partir de son adresse IP) à votre réseau, au cas où la découverte automatique serait impossible.",
        "IPAddress": "Adresse IP du pont",
        "Check": "Valider la saisie",
        "Close": "Fermer",
        "Error": {
          "Empty": "Veuillez ajouter l’adresse IP de votre pont.",
          "Invalid": "Le format de l’adresse IP saisie n’est pas valide. Veuillez vérifier votre saisie.",
          "Unreachable": "Le pont est injoignable. Veuillez vérifier l’adresse IP."
        }
      },
      "Pairing": {
        "Title": "Jumelage…",
        "Description": "Veuillez appuyer sur le bouton de jumelage du pont.",
        "Close": "Fermer",
        "Retry": "Réessayer"
      },
      "Save": {
        "Title": "Pont ajouté",
        "Description": "Le pont a bien été jumelé à l’application.",
        "Save": "Terminé"
      }
    }
  }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/ja.json
================================================
{
  "Name": "Philips Hue",
  "Category": "Philips Hue",
  "Description": "Philips Hueの照明をコントロールします。",
  "com.elgato.philips-hue.power": {
    "Name": "オン/オフ",
    "Tooltip": "照明をオン、またはオフにします。",
    "States": [
      {
        "Name": "オン"
      },
      {
        "Name": "オフ"
      }
    ]
  },
  "com.elgato.philips-hue.color": {
    "Name": "カラー",
    "Tooltip": "照明のカラーを設定します。"
  },
  "com.elgato.philips-hue.cycle": {
    "Name": "カラーサイクル",
    "Tooltip": "照明のカラーを循環して表示します。"
  },
  "com.elgato.philips-hue.brightness": {
    "Name": "明るさ",
    "Tooltip": "照明の明るさを調整します。",
    "Encoder": {
      "TriggerDescription": {
        "Rotate": "明るさを調整",
        "Touch": "照明のオン/オフを切り替え"
      }
    }
  },
  "com.elgato.philips-hue.brightness-rel": {
    "Name": "明るさ (相対的)",
    "Tooltip": "現在の明るさを基準にして照明の明るさを調整します。"
  },
  "com.elgato.philips-hue.scene": {
    "Name": "シーン",
    "Tooltip": "シーンを設定します。"
  },
  "Localization": {
    "PI": {
      "Bridge": "ブリッジ",
      "NoBridges": "ブリッジがありません",
      "AddBridge": "新規を追加",
      "Lights": "照明",
      "Group": "グループ",
      "LightsTitle": "照明",
      "GroupsTitle": "グループ",
      "NoLights": "照明がありません",
      "NoGroups": "グループがありません",
      "Color": "カラー",
      "Colors": "カラー",
      "Temperature": "温度",
      "Brightness": "明るさ",
      "Steps": "ステップ",
      "Scene": "シーン",
      "NoScenes": "シーンがありません"
    },
    "Setup": {
      "Intro": {
        "Title": "ブリッジを追加",
        "Description": "照明をコントロールするには、Philips Hueのブリッジを使ってペアリングしてください。",
        "Start": "ブリッジを検出",
        "Manual": "ブリッジを手動で追加",
        "Close": "今はしない"
      },
      "Discovery": {
        "Title": "ブリッジを検出中…",
        "TitleNone": "ブリッジが見つかりません",
        "TitleOne": "1件のブリッジを検出しました",
        "TitleMultiple": "{{ number }}件のブリッジを検出しました",
        "DescriptionFound": "今すぐペアリングしますか?",
        "DescriptionNone": "ブリッジがオンになっていて、ネットワークに接続されていることを確認してください。",
        "Pair": "ペアリング",
        "Close": "閉じる",
        "Retry": "やり直す"
      },
      "Manual": {
        "Title": "ブリッジを手動で追加",
        "Description": "自動検出がサポートされていない場合、Philips Hueブリッジをお使いのネットワークに (IPアドレスを使って) 手動で追加してください。",
        "IPAddress": "ブリッジのIPアドレス",
        "Check": "入力を検証",
        "Close": "閉じる",
        "Error": {
          "Empty": "ブリッジのIPアドレスを追加してください。",
          "Invalid": "入力されたIPアドレスは無効なフォーマットです。入力を確認してください。",
          "Unreachable": "ブリッジに接続できません。IPアドレスを確認してください。"
        }
      },
      "Pairing": {
        "Title": "ペアリング中…",
        "Description": "今すぐブリッジの接続ボタンを押してください。",
        "Close": "閉じる",
        "Retry": "やり直す"
      },
      "Save": {
        "Title": "ブリッジが追加されました",
        "Description": "ブリッジのペアリングを完了しました。",
        "Save": "完了"
      }
    }
  }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/ko.json
================================================
{
  "Name": "Philips Hue",
  "Category": "Philips Hue",
  "Description": "Philips Hue 조명을 조종하세요.",
  "com.elgato.philips-hue.power": {
    "Name": "켬 / 끔",
    "Tooltip": "조명을 끄거나 켭니다.",
    "States": [
      {
        "Name": "켬"
      },
      {
        "Name": "끔"
      }
    ]
  },
  "com.elgato.philips-hue.color": {
    "Name": "색상",
    "Tooltip": "조명 색상을 설정하세요."
  },
  "com.elgato.philips-hue.cycle": {
    "Name": "색상 사이클",
    "Tooltip": "조명 색상 사이를 순환합니다."
  },
  "com.elgato.philips-hue.brightness": {
    "Name": "밝기",
    "Tooltip": "조명의 밝기를 조절합니다.",
    "Encoder": {
      "TriggerDescription": {
        "Rotate": "밝기 조절",
        "Touch": "조명 켬/끔"
      }
    }
  },
  "com.elgato.philips-hue.brightness-rel": {
    "Name": "상대적 밝기",
    "Tooltip": "현재 밝기에 비례하여 밝기를 조절합니다."
  },
  "com.elgato.philips-hue.scene": {
    "Name": "장면",
    "Tooltip": "장면을 설정합니다."
  },
  "Localization": {
    "PI": {
      "Bridge": "브릿지",
      "NoBridges": "브릿지 없음",
      "AddBridge": "신규 추가",
      "Lights": "조명",
      "Group": "그룹",
      "LightsTitle": "조명",
      "GroupsTitle": "그룹",
      "NoLights": "조명 없음",
      "NoGroups": "그룹 없음",
      "Color": "색상",
      "Colors": "색상",
      "Temperature": "온도",
      "Brightness": "밝기",
      "Steps": "단계",
      "Scene": "장면",
      "NoScenes": "장면 없음"
    },
    "Setup": {
      "Intro": {
        "Title": "브릿지 추가",
        "Description": "Philips Hue 브릿지와 연결하여 조명을 조절하세요.",
        "Start": "브릿지 탐색",
        "Manual": "수동으로 브릿지 추가",
        "Close": "나중에"
      },
      "Discovery": {
        "Title": "브릿지 탐색 중...",
        "TitleNone": "브릿지를 찾을 수 없음",
        "TitleOne": "하나의 브릿지 발견",
        "TitleMultiple": "{{ number }}개의 브릿지 발견",
        "DescriptionFound": "지금 연결하시겠습니까?",
        "DescriptionNone": "브릿지가 켜져 있고 네트워크에 연결되어 있는지 확인하세요.",
        "Pair": "연결",
        "Close": "닫기",
        "Retry": "다시 시도하기"
      },
      "Manual": {
        "Title": "수동으로 브릿지 추가",
        "Description": "자동 탐색이 지원되지 않는 경우, (IP 주소를 통해) Philips Hue 브릿지를 수동으로 네트워크에 추가하십시오.",
        "IPAddress": "브릿지의 IP 주소",
        "Check": "입력 유효성 검사",
        "Close": "닫기",
        "Error": {
          "Empty": "브릿지의 IP 주소를 추가하십시오.",
          "Invalid": "입력한 IP 주소가 유효한 형식이 아닙니다. 입력을 확인하십시오.",
          "Unreachable": "브릿지에 도달할 수 없습니다. IP 주소를 확인하십시오."
        }
      },
      "Pairing": {
        "Title": "연결 중...",
        "Description": "지금 브릿지의 링크 버튼을 누르세요.",
        "Close": "닫기",
        "Retry": "다시 시도하기"
      },
      "Save": {
        "Title": "브릿지 추가됨",
        "Description": "브릿지가 성공적으로 연결되었습니다.",
        "Save": "완료"
      }
    }
  }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/manifest.json
================================================
{
  "Actions": [
    {
      "Icon": "plugin/images/actions/power",
      "Name": "On / Off",
      "States": [
        {
          "Image": "plugin/images/icons/on",
          "Name": "On"
        },
        {
          "Image": "plugin/images/icons/off",
          "Name": "Off"
        }
      ],
      "Tooltip": "Turn lights on or off.",
      "UUID": "com.elgato.philips-hue.power"
    },
    {
      "Icon": "plugin/images/actions/color",
      "Name": "Color",
      "States": [
        {
          "Image": "plugin/images/icons/color"
        }
      ],
      "Tooltip": "Set the color of your light.",
      "UUID": "com.elgato.philips-hue.color"
    },
    {
      "Icon": "plugin/images/actions/cycle",
      "Name": "Color Cycle",
      "States": [
        {
          "Image": "plugin/images/icons/cycle"
        }
      ],
      "Tooltip": "Cycle between colors of your light.",
      "UUID": "com.elgato.philips-hue.cycle"
    },
    {
      "Icon": "plugin/images/actions/brightness",
      "Name": "Brightness",
      "States": [
        {
          "Image": "plugin/images/icons/brightness"
        }
      ],
      "Controllers": [
        "Keypad",
        "Encoder"
      ],
      "Encoder": {
        "layout": "$B1",
        "TriggerDescription": {
          "Rotate": "Adjust Brightness",
          "Touch": "Toggle Light on/off"
        }
      },
      "Tooltip": "Set the brightness of your light.",
      "UUID": "com.elgato.philips-hue.brightness"
    },
    {
      "Icon": "plugin/images/actions/temperature",
      "Name": "Temperature",
      "States": [
        {
          "Image": "plugin/images/icons/temperature"
        }
      ],
      "Controllers": [
        "Keypad",
        "Encoder"
      ],
      "Encoder": {
        "layout": "$B1",
        "TriggerDescription": {
          "Rotate": "Adjust Temperature",
          "Touch": "Toggle Light on/off"
        }
      },
      "Tooltip": "Set the temperature of your light.",
      "UUID": "com.elgato.philips-hue.temperature"
    },
    {
      "Icon": "plugin/images/actions/brightness",
      "Name": "Brightness Relative",
      "States": [
        {
          "Image": "plugin/images/icons/brightness"
        }
      ],
      "Tooltip": "Set the brightness of your light.",
      "UUID": "com.elgato.philips-hue.brightness-rel"
    },
    {
      "Icon": "plugin/images/actions/scene",
      "Name": "Scene",
      "States": [
        {
          "Image": "plugin/images/icons/scene"
        }
      ],
      "Tooltip": "Set a scene.",
      "UUID": "com.elgato.philips-hue.scene"
    }
  ],
  "SDKVersion": 2,
  "Author": "Elgato",
  "CodePath": "plugin/index.html",
  "Description": "Control your Philips Hue lights.",
  "Name": "Philips Hue",
  "Icon": "plugin/images/icons/plugin",
  "Category": "Philips Hue",
  "CategoryIcon": "plugin/images/actions/category",
  "PropertyInspectorPath": "pi/index.html",
  "URL": "https://www.elgato.com/gaming/stream-deck",
  "Version": "1.6.5",
  "DefaultWindowSize": [
    500,
    650
  ],
  "OS": [
    {
      "Platform": "mac",
      "MinimumVersion": "10.11"
    },
    {
      "Platform": "windows",
      "MinimumVersion": "10"
    }
  ],
  "Software": {
    "MinimumVersion": "5.0"
  }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/css/colorPI.css
================================================
/**
@file      colorPI.css
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

.temperature::-webkit-slider-runnable-track {
  background-image: linear-gradient(to right, #faa04e, #86c6e8) !important;
}

.temperature::-webkit-slider-thumb {
  background-color: #86c6e8 !important;
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/css/cyclePI.css
================================================
/**
@file      cyclePI.css
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

#cycle-buttons .sdpi-item-value {
  font-size: 0;
}

#cycle-buttons button {
  width: 36px;
  font-weight: 900;
  display: inline-block;
  margin: 0 6px 0 2px;
  padding: 0;
}

#color-input-container div.sdpi-item-value {
  font-size: 0;
}

#color-input-container input[type='color'] {
  margin: 0 4px 0 0;
  padding: 0;
  width: 40px;
}

#color-input-container span:nth-child(5n) input {
  margin-right: 0;
}

#color-input-container span:nth-child(5n):after {
  content: ' ';
  display: block;
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/css/pi.css
================================================
/**
@file      pi.css
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

#pi {
  display: none;
}

#placeholder {
  margin-top: 3px;
}

select option:disabled {
  font-size: 1pt;
  background-color: #505050;
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/css/sdpi.css
================================================
:root {
  --sdpi-bgcolor: #2D2D2D;
  --sdpi-background: #3D3D3D;
  --sdpi-color: #d8d8d8;
  --sdpi-bordercolor: #3a3a3a;
  --sdpi-buttonbordercolor: #969696;
  --sdpi-borderradius: 0px;
  --sdpi-width: 224px;
  --sdpi-fontweight: 600;
  --sdpi-letterspacing: -0.25pt;
}

html {
  --sdpi-bgcolor: #2D2D2D;
  --sdpi-background: #3D3D3D;
  --sdpi-color: #d8d8d8;
  --sdpi-bordercolor: #3a3a3a;
  --sdpi-buttonbordercolor: #969696;
  --sdpi-borderradius: 0px;
  --sdpi-width: 224px;
  --sdpi-fontweight: 600;
  --sdpi-letterspacing: -0.25pt;
  height: 100%;
  width: 100%;
  overflow: hidden;
  touch-action: none;
}

html,
body {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  font-size: 9pt;
  background-color: var(--sdpi-bgcolor);
  color: #9a9a9a;
}

body {
  height: 100%;
  padding: 0;
  overflow-x: hidden;
  overflow-y: auto;
  margin: 0;
  -webkit-overflow-scrolling: touch;
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
}

mark {
  background-color: var(--sdpi-bgcolor);
  color: var(--sdpi-color);
}

hr,
hr2 {
  -webkit-margin-before: 1em;
  -webkit-margin-after: 1em;
  border-style: none;
  background: var(--sdpi-background);
  height: 1px;
}

hr2,
.sdpi-heading {
  display: flex;
  flex-basis: 100%;
  align-items: center;
  color: inherit;
  font-size: 9pt;
  margin: 8px 0px;
}

.sdpi-heading::before,
.sdpi-heading::after {
  content: "";
  flex-grow: 1;
  background: var(--sdpi-background);
  height: 1px;
  font-size: 0px;
  line-height: 0px;
  margin: 0px 16px;
}

hr2 {
  height: 2px;
}

hr,
hr2 {
  margin-left: 16px;
  margin-right: 16px;
}

.sdpi-item-value,
option,
input,
select,
button {
  font-size: 10pt;
  font-weight: var(--sdpi-fontweight);
  letter-spacing: var(--sdpi-letterspacing);
}

.sdpi-item-value > :last-of-type,
.sdpi-item-value:last-child {
  margin-bottom: 4px;
}

.win .sdpi-item-value,
.win option,
.win input,
.win select,
.win button {
  font-size: 11px;
  font-style: normal;
  letter-spacing: inherit;
  font-weight: 100;
}

.win button {
  font-size: 12px;
}

::-webkit-progress-value,
meter::-webkit-meter-optimum-value {
  border-radius: 2px;
  /* background: linear-gradient(#ccf, #99f 20%, #77f 45%, #77f 55%, #cdf); */
}

::-webkit-progress-bar,
meter::-webkit-meter-bar {
  border-radius: 3px;
  background: var(--sdpi-background);
}

::-webkit-progress-bar:active,
meter::-webkit-meter-bar:active {
  border-radius: 3px;
  background: #222222;
}

::-webkit-progress-value:active,
meter::-webkit-meter-optimum-value:active {
  background: #99f;
}

progress,
progress.sdpi-item-value {
  min-height: 5px !important;
  height: 5px;
  background-color: #303030;
}

progress {
  margin-top: 8px !important;
  margin-bottom: 8px !important;
}

.full progress,
progress.full {
  margin-top: 3px !important;
}

::-webkit-progress-inner-element {
  background-color: transparent;
}


.sdpi-item[type="progress"] {
  margin-top: 4px !important;
  margin-bottom: 12px;
  min-height: 15px;
}

.sdpi-item-child.full:last-child {
  margin-bottom: 4px;
}

.tabs {
  /**
 * Setting display to flex makes this container lay
 * out its children using flexbox, the exact same
 * as in the above "Stepper input" example.
 */
  display: flex;

  border-bottom: 1px solid #D7DBDD;
}

.tab {
  cursor: pointer;
  padding: 5px 30px;
  color: #16a2d7;
  font-size: 9pt;
  border-bottom: 2px solid transparent;
}

.tab.is-tab-selected {
  border-bottom-color: #4ebbe4;
}

select {
  -webkit-appearance: none;
  -moz-appearance: none;
  -o-appearance: none;
  appearance: none;
  background: url(../assets/caret.svg) no-repeat 97% center;
}

label.sdpi-file-label,
input[type="button"],
input[type="submit"],
input[type="reset"],
input[type="file"],
input[type=file]::-webkit-file-upload-button,
button,
select {
  color: var(--sdpi-color);
  border: 1pt solid #303030;
  font-size: 8pt;
  background-color: var(--sdpi-background);
  border-radius: var(--sdpi-borderradius);
}

label.sdpi-file-label,
input[type="button"],
input[type="submit"],
input[type="reset"],
input[type="file"],
input[type=file]::-webkit-file-upload-button,
button {
  border: 1pt solid var(--sdpi-buttonbordercolor);
  border-radius: var(--sdpi-borderradius);
  border-color: var(--sdpi-buttonbordercolor);
  min-height: 23px !important;
  height: 23px !important;
  margin-right: 8px;
}

input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type="file"] {
  border-radius: var(--sdpi-borderradius);
  max-width: 220px;
}

option {
  height: 1.5em;
  padding: 4px;
}

/* SDPI */

.sdpi-wrapper {
  overflow-x: hidden;
  height: 100%;
}

.sdpi-item {
  display: flex;
  flex-direction: row;
  min-height: 32px;
  align-items: center;
  margin-top: 2px;
  max-width: 344px;
  -webkit-user-drag: none;
}

.sdpi-item:first-child {
  margin-top: -1px;
}

.sdpi-item:first-of-type {
  margin-top: 2px;
}

.sdpi-item[type="radio"]:first-of-type,
.sdpi-item[type="checkbox"]:first-of-type {
  margin-top: -4px;
}

.sdpi-item:last-child {
  margin-bottom: 0px;
}

.sdpi-item > *:not(.sdpi-item-label):not(meter):not(details):not(canvas) {
  min-height: 26px;
}

.sdpi-item > *:not(.sdpi-item-label.empty):not(meter) {
  min-height: 26px;
}

.sdpi-item > input {
  padding: 0px 4px;
}

.sdpi-item-group {
  padding: 0 !important;
}

meter.sdpi-item-value {
  margin-left: 6px;
}

.sdpi-item[type="group"] {
  display: block;
  margin-top: 12px;
  margin-bottom: 12px;
  /* border: 1px solid white; */
  flex-direction: unset;
  text-align: left;
}

.sdpi-item[type="group"] > .sdpi-item-label,
.sdpi-item[type="group"].sdpi-item-label {
  width: 96%;
  text-align: left;
  font-weight: 700;
  margin-bottom: 4px;
  padding-left: 4px;
}

dl,
ul,
ol {
  -webkit-margin-before: 0px;
  -webkit-margin-after: 4px;
  -webkit-padding-start: 1em;
  max-height: 90px;
  overflow-y: scroll;
  cursor: pointer;
  user-select: none;
}

table.sdpi-item-value,
dl.sdpi-item-value,
ul.sdpi-item-value,
ol.sdpi-item-value {
  -webkit-margin-before: 4px;
  -webkit-margin-after: 8px;
  -webkit-padding-start: 1em;
  width: var(--sdpi-width);
  text-align: center;
}

table > caption {
  margin: 2px;
}

.list,
.sdpi-item[type="list"] {
  align-items: baseline;
}

.sdpi-item-label {
  text-align: right;
  flex: none;
  width: 94px;
  padding-right: 5px;
  font-weight: 600;
  -webkit-user-select: none;
  line-height: 24px;
  margin-left: -1px;
}

.win .sdpi-item-label,
.sdpi-item-label > small {
  font-weight: normal;
}

.sdpi-item-label:after {
  content: ": ";
}

.sdpi-item-label.empty:after {
  content: "";
}

.sdpi-test,
.sdpi-item-value {
  flex: 1 0 0;
  /* flex-grow: 1;
flex-shrink: 0; */
  margin-right: 14px;
  margin-left: 4px;
  justify-content: space-evenly;
}

canvas.sdpi-item-value {
  max-width: 144px;
  max-height: 144px;
  width: 144px;
  height: 144px;
  margin: 0 auto;
  cursor: pointer;
}

input.sdpi-item-value {
  margin-left: 5px;
}

.sdpi-item-value button,
button.sdpi-item-value {
  margin-left: 6px;
  margin-right: 14px;
}

.sdpi-item-value.range {
  margin-left: 0px;
}

table,
dl.sdpi-item-value,
ul.sdpi-item-value,
ol.sdpi-item-value,
.sdpi-item-value > dl,
.sdpi-item-value > ul,
.sdpi-item-value > ol {
  list-style-type: none;
  list-style-position: outside;
  margin-left: -4px;
  margin-right: -4px;
  padding: 4px;
  border: 1px solid var(--sdpi-bordercolor);
}

dl.sdpi-item-value,
ul.sdpi-item-value,
ol.sdpi-item-value,
.sdpi-item-value > ol {
  list-style-type: none;
  list-style-position: inside;
  margin-left: 5px;
  margin-right: 12px;
  padding: 4px !important;
  display: flex;
  flex-direction: column;
}

.two-items li {
  display: flex;
}

.two-items li > *:first-child {
  flex: 0 0 50%;
  text-align: left;
}

.two-items.thirtyseventy li > *:first-child {
  flex: 0 0 30%;
}

ol.sdpi-item-value,
.sdpi-item-value > ol[listtype="none"] {
  list-style-type: none;
}

ol.sdpi-item-value[type="decimal"],
.sdpi-item-value > ol[type="decimal"] {
  list-style-type: decimal;
}

ol.sdpi-item-value[type="decimal-leading-zero"],
.sdpi-item-value > ol[type="decimal-leading-zero"] {
  list-style-type: decimal-leading-zero;
}

ol.sdpi-item-value[type="lower-alpha"],
.sdpi-item-value > ol[type="lower-alpha"] {
  list-style-type: lower-alpha;
}

ol.sdpi-item-value[type="upper-alpha"],
.sdpi-item-value > ol[type="upper-alpha"] {
  list-style-type: upper-alpha;
}

ol.sdpi-item-value[type="upper-roman"],
.sdpi-item-value > ol[type="upper-roman"] {
  list-style-type: upper-roman;
}

ol.sdpi-item-value[type="lower-roman"],
.sdpi-item-value > ol[type="lower-roman"] {
  list-style-type: upper-roman;
}

tr:nth-child(even),
.sdpi-item-value > ul > li:nth-child(even),
.sdpi-item-value > ol > li:nth-child(even),
li:nth-child(even) {
  background-color: rgba(0, 0, 0, .2)
}

td:hover,
.sdpi-item-value > ul > li:hover:nth-child(even),
.sdpi-item-value > ol > li:hover:nth-child(even),
li:hover:nth-child(even),
li:hover {
  background-color: rgba(255, 255, 255, .1);
}

td.selected,
td.selected:hover,
li.selected:hover,
li.selected {
  color: white;
  background-color: #77f;
}

tr {
  border: 1px solid var(--sdpi-bordercolor);
}

td {
  border-right: 1px solid var(--sdpi-bordercolor);
  -webkit-user-select: none;
}

tr:last-child,
td:last-child {
  border: none;
}

.sdpi-item-value.select,
.sdpi-item-value > select {
  margin-right: 13px;
  margin-left: 4px;
  padding: 0px 4px;
}

.sdpi-item-child,
.sdpi-item-group > .sdpi-item > input[type="color"] {
  margin-top: 0.4em;
  margin-right: 4px;
  margin-left: 4px;
}

.full,
.full *,
.sdpi-item-value.full,
.sdpi-item-child > full > *,
.sdpi-item-child.full,
.sdpi-item-child.full > *,
.full > .sdpi-item-child,
.full > .sdpi-item-child > * {
  display: flex;
  flex: 1 1 0;
  margin-bottom: 4px;
  margin-left: 0px;
  width: 100%;

  justify-content: space-evenly;
}

.sdpi-item-group > .sdpi-item > input[type="color"] {
  margin-top: 0px;
}

::-webkit-calendar-picker-indicator:focus,
input[type=file]::-webkit-file-upload-button:focus,
button:focus,
textarea:focus,
input:focus,
select:focus,
option:focus,
details:focus,
summary:focus,
.custom-select select {
  outline: none;
}

summary {
  cursor: default;
  -webkit-user-select: none;
}

.pointer,
summary .pointer {
  cursor: pointer;
}

details * {
  font-size: 12px;
  font-weight: normal;
}

details.message {
  padding: 4px 18px 4px 12px;
}

details.message summary {
  font-size: 10pt;
  font-weight: 600;
  min-height: 18px;
}

details.message:first-child {
  margin-top: 4px;
  margin-left: 0;
  padding-left: 102px !important;
}

details.message > summary:first-of-type {
  line-height: 20px;
}

details.message h1 {
  text-align: left;
}

details:not(.pointer) > summary {
  list-style: none;
}

details > summary::-webkit-details-marker
.message > summary::-webkit-details-marker {
  display: none;
}

.info20,
.question,
.caution,
.info {
  background-repeat: no-repeat;
  background-position: 72px center;
}

.info20 {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,20 C4.4771525,20 0,15.5228475 0,10 C0,4.4771525 4.4771525,0 10,0 C15.5228475,0 20,4.4771525 20,10 C20,15.5228475 15.5228475,20 10,20 Z M10,8 C8.8954305,8 8,8.84275812 8,9.88235294 L8,16.1176471 C8,17.1572419 8.8954305,18 10,18 C11.1045695,18 12,17.1572419 12,16.1176471 L12,9.88235294 C12,8.84275812 11.1045695,8 10,8 Z M10,3 C8.8954305,3 8,3.88165465 8,4.96923077 L8,5.03076923 C8,6.11834535 8.8954305,7 10,7 C11.1045695,7 12,6.11834535 12,5.03076923 L12,4.96923077 C12,3.88165465 11.1045695,3 10,3 Z'/%3E%3C/svg%3E%0A");
}

.info {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M10,8 C9.44771525,8 9,8.42137906 9,8.94117647 L9,14.0588235 C9,14.5786209 9.44771525,15 10,15 C10.5522847,15 11,14.5786209 11,14.0588235 L11,8.94117647 C11,8.42137906 10.5522847,8 10,8 Z M10,5 C9.44771525,5 9,5.44082732 9,5.98461538 L9,6.01538462 C9,6.55917268 9.44771525,7 10,7 C10.5522847,7 11,6.55917268 11,6.01538462 L11,5.98461538 C11,5.44082732 10.5522847,5 10,5 Z'/%3E%3C/svg%3E%0A");
}

.info2 {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 15 15'%3E%3Cpath fill='%23999' d='M7.5,15 C3.35786438,15 0,11.6421356 0,7.5 C0,3.35786438 3.35786438,0 7.5,0 C11.6421356,0 15,3.35786438 15,7.5 C15,11.6421356 11.6421356,15 7.5,15 Z M7.5,2 C6.67157287,2 6,2.66124098 6,3.47692307 L6,3.52307693 C6,4.33875902 6.67157287,5 7.5,5 C8.32842705,5 9,4.33875902 9,3.52307693 L9,3.47692307 C9,2.66124098 8.32842705,2 7.5,2 Z M5,6 L5,7.02155172 L6,7 L6,12 L5,12.0076778 L5,13 L10,13 L10,12 L9,12.0076778 L9,6 L5,6 Z'/%3E%3C/svg%3E%0A");
}

.sdpi-more-info {
  background-image: linear-gradient(to right, #00000000 0%, #00000040 80%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpolygon fill='%23999' points='4 7 8 7 8 5 12 8 8 11 8 9 4 9'/%3E%3C/svg%3E%0A");
}

.caution {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' fill-rule='evenodd' d='M9.03952676,0.746646542 C9.57068894,-0.245797319 10.4285735,-0.25196227 10.9630352,0.746646542 L19.7705903,17.2030214 C20.3017525,18.1954653 19.8777595,19 18.8371387,19 L1.16542323,19 C0.118729947,19 -0.302490098,18.2016302 0.231971607,17.2030214 L9.03952676,0.746646542 Z M10,2.25584053 L1.9601405,17.3478261 L18.04099,17.3478261 L10,2.25584053 Z M10,5.9375 C10.531043,5.9375 10.9615385,6.37373537 10.9615385,6.91185897 L10.9615385,11.6923077 C10.9615385,12.2304313 10.531043,12.6666667 10,12.6666667 C9.46895697,12.6666667 9.03846154,12.2304313 9.03846154,11.6923077 L9.03846154,6.91185897 C9.03846154,6.37373537 9.46895697,5.9375 10,5.9375 Z M10,13.4583333 C10.6372516,13.4583333 11.1538462,13.9818158 11.1538462,14.6275641 L11.1538462,14.6641026 C11.1538462,15.3098509 10.6372516,15.8333333 10,15.8333333 C9.36274837,15.8333333 8.84615385,15.3098509 8.84615385,14.6641026 L8.84615385,14.6275641 C8.84615385,13.9818158 9.36274837,13.4583333 10,13.4583333 Z'/%3E%3C/svg%3E%0A");
}

.question {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M6.77783203,7.65332031 C6.77783203,7.84798274 6.85929281,8.02888914 7.0222168,8.19604492 C7.18514079,8.36320071 7.38508996,8.44677734 7.62207031,8.44677734 C8.02409055,8.44677734 8.29703704,8.20768468 8.44091797,7.72949219 C8.59326248,7.27245865 8.77945854,6.92651485 8.99951172,6.69165039 C9.2195649,6.45678594 9.56233491,6.33935547 10.027832,6.33935547 C10.4256205,6.33935547 10.7006836,6.37695313 11.0021973,6.68847656 C11.652832,7.53271484 10.942627,8.472229 10.3750916,9.1321106 C9.80755615,9.79199219 8.29492188,11.9897461 10.027832,12.1347656 C10.4498423,12.1700818 10.7027991,11.9147157 10.7832031,11.4746094 C11.0021973,9.59857178 13.1254883,8.82415771 13.1254883,7.53271484 C13.1254883,7.07568131 12.9974785,6.65250846 12.7414551,6.26318359 C12.4854317,5.87385873 12.1225609,5.56600048 11.652832,5.33959961 C11.1831031,5.11319874 10.6414419,5 10.027832,5 C9.36767248,5 8.79004154,5.13541531 8.29492187,5.40625 C7.79980221,5.67708469 7.42317837,6.01879677 7.16503906,6.43139648 C6.90689975,6.8439962 6.77783203,7.25130007 6.77783203,7.65332031 Z M10.0099668,15 C10.2713191,15 10.5016601,14.9108147 10.7009967,14.7324415 C10.9003332,14.5540682 11,14.3088087 11,13.9966555 C11,13.7157177 10.9047629,13.4793767 10.7142857,13.2876254 C10.5238086,13.0958742 10.2890379,13 10.0099668,13 C9.72646591,13 9.48726565,13.0958742 9.2923588,13.2876254 C9.09745196,13.4793767 9,13.7157177 9,13.9966555 C9,14.313268 9.10077419,14.5596424 9.30232558,14.735786 C9.50387698,14.9119295 9.73975502,15 10.0099668,15 Z'/%3E%3C/svg%3E%0A");
}


.sdpi-more-info {
  position: fixed;
  left: 0px;
  right: 0px;
  bottom: 0px;
  min-height: 16px;
  padding-right: 16px;
  text-align: right;
  -webkit-touch-callout: none;
  cursor: pointer;
  user-select: none;
  background-position: right center;
  background-repeat: no-repeat;
  border-radius: var(--sdpi-borderradius);
  text-decoration: none;
  color: var(--sdpi-color);
}

.sdpi-more-info-button {
  display: flex;
  align-self: right;
  margin-left: auto;
  position: fixed;
  right: 17px;
  bottom: 0px;
  user-select: none;
}


.sdpi-bottom-bar {
  display: flex;
  align-self: right;
  margin-left: auto;
  position: fixed;
  right: 17px;
  bottom: 0px;
  user-select: none;
}

.sdpi-bottom-bar.right {
  right: 0px;
}

.sdpi-bottom-bar button {
  min-height: 20px !important;
  height: 20px !important;
}

details a {
  background-position: right !important;
  min-height: 24px;
  display: inline-block;
  line-height: 24px;
  padding-right: 28px;
}


input:not([type="range"]),
textarea {
  -webkit-appearance: none;
  background: var(--sdpi-background);
  color: var(--sdpi-color);
  font-weight: normal;
  font-size: 9pt;
  border: none;
  margin-top: 2px;
  margin-bottom: 2px;
  min-width: 219px;
}

textarea + label {
  display: flex;
  justify-content: flex-end
}

input[type="radio"],
input[type="checkbox"] {
  display: none;
}

input[type="radio"] + label,
input[type="checkbox"] + label {
  font-size: 9pt;
  color: var(--sdpi-color);
  font-weight: normal;
  margin-right: 8px;
  -webkit-user-select: none;
}

input[type="radio"] + label:after,
input[type="checkbox"] + label:after {
  content: " " !important;
}

.sdpi-item[type="radio"] > .sdpi-item-value,
.sdpi-item[type="checkbox"] > .sdpi-item-value {
  padding-top: 2px;
}

.sdpi-item[type="checkbox"] > .sdpi-item-value > * {
  margin-top: 4px;
}

.sdpi-item[type="checkbox"] .sdpi-item-child,
.sdpi-item[type="radio"] .sdpi-item-child {
  display: inline-block;
}

.sdpi-item[type="range"] .sdpi-item-value,
.sdpi-item[type="meter"] .sdpi-item-child,
.sdpi-item[type="progress"] .sdpi-item-child {
  display: flex;
}

.sdpi-item[type="range"] .sdpi-item-value {
  min-height: 26px;
}

.sdpi-item[type="range"] .sdpi-item-value span,
.sdpi-item[type="meter"] .sdpi-item-child span,
.sdpi-item[type="progress"] .sdpi-item-child span {
  margin-top: -2px;
  min-width: 8px;
  text-align: right;
  user-select: none;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}

.sdpi-item[type="range"] .sdpi-item-value span {
  margin-top: 7px;
  text-align: right;
}

span + input[type="range"] {
  display: flex;
  max-width: 168px;

}

.sdpi-item[type="range"] .sdpi-item-value span:first-child,
.sdpi-item[type="meter"] .sdpi-item-child span:first-child,
.sdpi-item[type="progress"] .sdpi-item-child span:first-child {
  margin-right: 4px;
}

.sdpi-item[type="range"] .sdpi-item-value span:last-child,
.sdpi-item[type="meter"] .sdpi-item-child span:last-child,
.sdpi-item[type="progress"] .sdpi-item-child span:last-child {
  margin-left: 4px;
}

.reverse {
  transform: rotate(180deg);
}

.sdpi-item[type="meter"] .sdpi-item-child meter + span:last-child {
  margin-left: -10px;
}

.sdpi-item[type="progress"] .sdpi-item-child meter + span:last-child {
  margin-left: -14px;
}

.sdpi-item[type="radio"] > .sdpi-item-value > * {
  margin-top: 2px;
}

details {
  padding: 8px 18px 8px 12px;
  min-width: 86px;
}

details > h4 {
  border-bottom: 1px solid var(--sdpi-bordercolor);
}

legend {
  display: none;
}

.sdpi-item-value > textarea {
  padding: 0px;
  width: 219px;
  margin-left: 1px;
  margin-top: 3px;
  padding: 4px;
}

input[type="radio"] + label span,
input[type="checkbox"] + label span {
  display: inline-block;
  width: 16px;
  height: 16px;
  margin: 2px 4px 2px 0;
  border-radius: 3px;
  vertical-align: middle;
  background: var(--sdpi-background);
  cursor: pointer;
  border: 1px solid rgb(0, 0, 0, .2);
}

input[type="radio"] + label span {
  border-radius: 100%;
}

input[type="radio"]:checked + label span,
input[type="checkbox"]:checked + label span {
  background-color: #77f;
  background-image: url(../assets/check.svg);
  background-repeat: no-repeat;
  background-position: center center;
  border: 1px solid rgb(0, 0, 0, .4);
}

input[type="radio"]:active:checked + label span,
input[type="radio"]:active + label span,
input[type="checkbox"]:active:checked + label span,
input[type="checkbox"]:active + label span {
  background-color: #303030;
}

input[type="radio"]:checked + label span {
  background-image: url(../assets/rcheck.svg);
}

input[type="range"] {
  width: var(--sdpi-width);
  height: 30px;
  overflow: hidden;
  cursor: pointer;
  background: transparent !important;
}

.sdpi-item > input[type="range"] {
  margin-left: 2px;
  max-width: var(--sdpi-width);
  width: var(--sdpi-width);
  padding: 0px;
  margin-top: 2px;
}

/*
input[type="range"],
input[type="range"]::-webkit-slider-runnable-track,
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
}
*/

input[type="range"]::-webkit-slider-runnable-track {
  height: 5px;
  background: #979797;
  border-radius: 3px;
  padding: 0px !important;
  border: 1px solid var(--sdpi-background);
}

input[type="range"]::-webkit-slider-thumb {
  position: relative;
  -webkit-appearance: none;
  background-color: var(--sdpi-color);
  width: 12px;
  height: 12px;
  border-radius: 20px;
  margin-top: -5px;
  border: none;
}

input[type="range" i] {
  margin: 0;
}

input[type="range"]::-webkit-slider-thumb::before {
  position: absolute;
  content: "";
  height: 5px; /* equal to height of runnable track or 1 less */
  width: 500px; /* make this bigger than the widest range input element */
  left: -502px; /* this should be -2px - width */
  top: 8px; /* don't change this */
  background: #77f;
}

input[type="color"] {
  min-width: 32px;
  min-height: 32px;
  width: 32px;
  height: 32px;
  padding: 0;
  background-color: var(--sdpi-bgcolor);
  flex: none;
}

::-webkit-color-swatch {
  min-width: 24px;
}

textarea {
  height: 3em;
  word-break: break-word;
  line-height: 1.5em;
}

.textarea {
  padding: 0px !important;
}

textarea {
  width: 219px; /*98%;*/
  height: 96%;
  min-height: 6em;
  resize: none;
  border-radius: var(--sdpi-borderradius);
}

/* CAROUSEL */

.sdpi-item[type="carousel"] {}

.sdpi-item.card-carousel-wrapper,
.sdpi-item > .card-carousel-wrapper {
  padding: 0;
}


.card-carousel-wrapper {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 12px auto;
  color: #666a73;
}

.card-carousel {
  display: flex;
  justify-content: center;
  width: 278px;
}

.card-carousel--overflow-container {
  overflow: hidden;
}

.card-carousel--nav__left,
.card-carousel--nav__right {
  /* display: inline-block; */
  width: 12px;
  height: 12px;
  border-top: 2px solid #42b883;
  border-right: 2px solid #42b883;
  cursor: pointer;
  margin: 0 4px;
  transition: transform 150ms linear;
}

.card-carousel--nav__left[disabled],
.card-carousel--nav__right[disabled] {
  opacity: 0.2;
  border-color: black;
}

.card-carousel--nav__left {
  transform: rotate(-135deg);
}

.card-carousel--nav__left:active {
  transform: rotate(-135deg) scale(0.85);
}

.card-carousel--nav__right {
  transform: rotate(45deg);
}

.card-carousel--nav__right:active {
  transform: rotate(45deg) scale(0.85);
}

.card-carousel-cards {
  display: flex;
  transition: transform 150ms ease-out;
  transform: translatex(0px);
}

.card-carousel-cards .card-carousel--card {
  margin: 0 5px;
  cursor: pointer;
  /* box-shadow: 0 4px 15px 0 rgba(40, 44, 53, 0.06), 0 2px 2px 0 rgba(40, 44, 53, 0.08); */
  background-color: #fff;
  border-radius: 4px;
  z-index: 3;
}

.xxcard-carousel-cards .card-carousel--card:first-child {
  margin-left: 0;
}

.xxcard-carousel-cards .card-carousel--card:last-child {
  margin-right: 0;
}

.card-carousel-cards .card-carousel--card img {
  vertical-align: bottom;
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
  transition: opacity 150ms linear;
  width: 60px;
}

.card-carousel-cards .card-carousel--card img:hover {
  opacity: 0.5;
}

.card-carousel-cards .card-carousel--card--footer {
  border-top: 0;
  max-width: 80px;
  overflow: hidden;
  display: flex;
  height: 100%;
  flex-direction: column;
}

.card-carousel-cards .card-carousel--card--footer p {
  padding: 3px 0;
  margin: 0;
  margin-bottom: 2px;
  font-size: 15px;
  font-weight: 500;
  color: #2c3e50;
}

.card-carousel-cards .card-carousel--card--footer p:nth-of-type(2) {
  font-size: 12px;
  font-weight: 300;
  padding: 6px;
  color: #666a73;
}


h1 {
  font-size: 1.3em;
  font-weight: 500;
  text-align: center;
  margin-bottom: 12px;
}

::-webkit-datetime-edit {
  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  background: url(../assets/elg_calendar_inv.svg) no-repeat left center;
  padding-right: 1em;
  padding-left: 25px;
  background-position: 4px 0px;
}

::-webkit-datetime-edit-fields-wrapper {}

::-webkit-datetime-edit-text {
  padding: 0 0.3em;
}

::-webkit-datetime-edit-month-field {}

::-webkit-datetime-edit-day-field {}

::-webkit-datetime-edit-year-field {}

::-webkit-inner-spin-button {

  /* display: none; */
}

::-webkit-calendar-picker-indicator {
  background: transparent;
  font-size: 17px;
}

::-webkit-calendar-picker-indicator:focus {
  background-color: rgba(0, 0, 0, 0.2);
}

input[type="date"] {
  -webkit-align-items: center;
  display: -webkit-inline-flex;
  font-family: monospace;
  overflow: hidden;
  padding: 0;
  -webkit-padding-start: 1px;
}

input::-webkit-datetime-edit {
  -webkit-flex: 1;
  -webkit-user-modify: read-only !important;
  display: inline-block;
  min-width: 0;
  overflow: hidden;
}

/*
input::-webkit-datetime-edit-fields-wrapper {
-webkit-user-modify: read-only !important;
display: inline-block;
padding: 1px 0;
white-space: pre;

}
*/

/*
input[type="date"] {
background-color: red;
outline: none;
}

input[type="date"]::-webkit-clear-button {
font-size: 18px;
height: 30px;
position: relative;
}

input[type="date"]::-webkit-inner-spin-button {
height: 28px;
}

input[type="date"]::-webkit-calendar-picker-indicator {
font-size: 15px;
} */

input[type="file"] {
  opacity: 0;
  display: none;
}

.sdpi-item > input[type="file"] {
  opacity: 1;
  display: flex;
}

input[type="file"] + span {
  display: flex;
  flex: 0 1 auto;
  background-color: #0000ff50;
}

label.sdpi-file-label {
  cursor: pointer;
  user-select: none;
  display: inline-block;
  min-height: 21px !important;
  height: 21px !important;
  line-height: 20px;
  padding: 0px 4px;
  margin: auto;
  margin-right: 0px;
  float: right;
}

.sdpi-file-label > label:active,
.sdpi-file-label.file:active,
label.sdpi-file-label:active,
label.sdpi-file-info:active,
input[type="file"]::-webkit-file-upload-button:active,
button:active {
  background-color: var(--sdpi-color);
  color: #303030;
}

input:required:invalid,
input:focus:invalid {
  background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPgogICAgPHBhdGggZmlsbD0iI0Q4RDhEOCIgZD0iTTQuNSwwIEM2Ljk4NTI4MTM3LC00LjU2NTM4NzgyZS0xNiA5LDIuMDE0NzE4NjMgOSw0LjUgQzksNi45ODUyODEzNyA2Ljk4NTI4MTM3LDkgNC41LDkgQzIuMDE0NzE4NjMsOSAzLjA0MzU5MTg4ZS0xNiw2Ljk4NTI4MTM3IDAsNC41IEMtMy4wNDM1OTE4OGUtMTYsMi4wMTQ3MTg2MyAyLjAxNDcxODYzLDQuNTY1Mzg3ODJlLTE2IDQuNSwwIFogTTQsMSBMNCw2IEw1LDYgTDUsMSBMNCwxIFogTTQuNSw4IEM0Ljc3NjE0MjM3LDggNSw3Ljc3NjE0MjM3IDUsNy41IEM1LDcuMjIzODU3NjMgNC43NzYxNDIzNyw3IDQuNSw3IEM0LjIyMzg1NzYzLDcgNCw3LjIyMzg1NzYzIDQsNy41IEM0LDcuNzc2MTQyMzcgNC4yMjM4NTc2Myw4IDQuNSw4IFoiLz4KICA8L3N2Zz4) no-repeat 98% center;
}

input:required:valid {
  background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPjxwb2x5Z29uIGZpbGw9IiNEOEQ4RDgiIHBvaW50cz0iNS4yIDEgNi4yIDEgNi4yIDcgMy4yIDcgMy4yIDYgNS4yIDYiIHRyYW5zZm9ybT0icm90YXRlKDQwIDQuNjc3IDQpIi8+PC9zdmc+) no-repeat 98% center;
}

.tooltip,
:tooltip,
:title {
  color: yellow;
}

.sdpi-item-group.file {
  width: 232px;
  display: flex;
  align-items: center;
}

.sdpi-file-info {
  overflow-wrap: break-word;
  word-wrap: break-word;
  hyphens: auto;

  min-width: 132px;
  max-width: 144px;
  max-height: 32px;
  margin-top: 0px;
  margin-left: 5px;
  display: inline-block;
  overflow: hidden;
  padding: 6px 4px;
  background-color: var(--sdpi-background);
}


::-webkit-scrollbar {
  width: 8px;
}

::-webkit-scrollbar-track {
  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
  box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
  margin: 4px;
  border-radius: 8px;
}

::-webkit-scrollbar-thumb {
  background-color: #999999;
  outline: 1px solid slategrey;
  border-radius: 8px;
}

a {
  color: #7397d2;
}

.testcontainer {
  display: flex;
  background-color: #0000ff20;
  max-width: 400px;
  height: 200px;
  align-content: space-evenly;
}

input[type=range] {
  -webkit-appearance: none;
  /* background-color: green; */
  height: 6px;
  margin-top: 12px;
  z-index: 0;
  overflow: visible;
}

/*
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
background-color: var(--sdpi-color);
width: 12px;
height: 12px;
border-radius: 20px;
margin-top: -6px;
border: none;
} */

:-webkit-slider-thumb {
  -webkit-appearance: none;
  background-color: var(--sdpi-color);
  width: 16px;
  height: 16px;
  border-radius: 20px;
  margin-top: -6px;
  border: 1px solid #999999;
}

.sdpi-item[type="range"] .sdpi-item-group {
  display: flex;
  flex-direction: column;
}

.xxsdpi-item[type="range"] .sdpi-item-group input {
  max-width: 204px;
}

.sdpi-item[type="range"] .sdpi-item-group span {
  margin-left: 0px !important;
}

.sdpi-item[type="range"] .sdpi-item-group > .sdpi-item-child {
  display: flex;
  flex-direction: row;
}

.rangeLabel {
  position: absolute;
  font-weight: normal;
  margin-top: 24px;
}

:disabled {
  color: #993333;
}

select,
select option {
  color: var(--sdpi-color);
}

select.disabled,
select option:disabled {
  color: #fd9494;
  font-style: italic;
}

.runningAppsContainer {
  display: none;
}

.one-line {
  min-height: 1.5em;
}

.two-lines {
  min-height: 3em;
}

.three-lines {
  min-height: 4.5em;
}

.four-lines {
  min-height: 6em;
}

.min80 > .sdpi-item-child {
  min-width: 80px;
}

.min100 > .sdpi-item-child {
  min-width: 100px;
}

.min120 > .sdpi-item-child {
  min-width: 120px;
}

.min140 > .sdpi-item-child {
  min-width: 140px;
}

.min160 > .sdpi-item-child {
  min-width: 160px;
}

.min200 > .sdpi-item-child {
  min-width: 200px;
}

.max40 {
  flex-basis: 40%;
  flex-grow: 0;
}

.max30 {
  flex-basis: 30%;
  flex-grow: 0;
}

.max20 {
  flex-basis: 20%;
  flex-grow: 0;
}

.up20 {
  margin-top: -20px;
}

.alignCenter {
  align-items: center;
}

.alignTop {
  align-items: flex-start;
}

.alignBaseline {
  align-items: baseline;
}

.noMargins,
.noMargins *,
.noInnerMargins * {
  margin: 0;
  padding: 0;
}

.hidden {
  display: none !important;
}

.icon-help,
.icon-help-line,
.icon-help-fill,
.icon-help-inv,
.icon-brighter,
.icon-darker,
.icon-warmer,
.icon-cooler {
  min-width: 20px;
  width: 20px;
  background-repeat: no-repeat;
  opacity: 1;
}

.icon-help:active,
.icon-help-line:active,
.icon-help-fill:active,
.icon-help-inv:active,
.icon-brighter:active,
.icon-darker:active,
.icon-warmer:active,
.icon-cooler:active {
  opacity: 0.5;
}

.icon-brighter,
.icon-darker,
.icon-warmer,
.icon-cooler {
  margin-top: 5px !important;
}

.icon-help,
.icon-help-line,
.icon-help-fill,
.icon-help-inv {
  cursor: pointer;
  margin: 0px;
  margin-left: 4px;
}

.icon-brighter {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Ccircle cx='10' cy='10' r='4'/%3E%3Cpath d='M14.8532861,7.77530426 C14.7173255,7.4682615 14.5540843,7.17599221 14.3666368,6.90157083 L16.6782032,5.5669873 L17.1782032,6.4330127 L14.8532861,7.77530426 Z M10.5,4.5414007 C10.2777625,4.51407201 10.051423,4.5 9.82179677,4.5 C9.71377555,4.5 9.60648167,4.50311409 9.5,4.50925739 L9.5,2 L10.5,2 L10.5,4.5414007 Z M5.38028092,6.75545367 C5.18389364,7.02383457 5.01124349,7.31068015 4.86542112,7.61289977 L2.82179677,6.4330127 L3.32179677,5.5669873 L5.38028092,6.75545367 Z M4.86542112,12.3871002 C5.01124349,12.6893198 5.18389364,12.9761654 5.38028092,13.2445463 L3.32179677,14.4330127 L2.82179677,13.5669873 L4.86542112,12.3871002 Z M9.5,15.4907426 C9.60648167,15.4968859 9.71377555,15.5 9.82179677,15.5 C10.051423,15.5 10.2777625,15.485928 10.5,15.4585993 L10.5,18 L9.5,18 L9.5,15.4907426 Z M14.3666368,13.0984292 C14.5540843,12.8240078 14.7173255,12.5317385 14.8532861,12.2246957 L17.1782032,13.5669873 L16.6782032,14.4330127 L14.3666368,13.0984292 Z'/%3E%3C/g%3E%3C/svg%3E");
}

.icon-darker {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10 14C7.790861 14 6 12.209139 6 10 6 7.790861 7.790861 6 10 6 12.209139 6 14 7.790861 14 10 14 12.209139 12.209139 14 10 14zM10 13C11.6568542 13 13 11.6568542 13 10 13 8.34314575 11.6568542 7 10 7 8.34314575 7 7 8.34314575 7 10 7 11.6568542 8.34314575 13 10 13zM14.8532861 7.77530426C14.7173255 7.4682615 14.5540843 7.17599221 14.3666368 6.90157083L16.6782032 5.5669873 17.1782032 6.4330127 14.8532861 7.77530426zM10.5 4.5414007C10.2777625 4.51407201 10.051423 4.5 9.82179677 4.5 9.71377555 4.5 9.60648167 4.50311409 9.5 4.50925739L9.5 2 10.5 2 10.5 4.5414007zM5.38028092 6.75545367C5.18389364 7.02383457 5.01124349 7.31068015 4.86542112 7.61289977L2.82179677 6.4330127 3.32179677 5.5669873 5.38028092 6.75545367zM4.86542112 12.3871002C5.01124349 12.6893198 5.18389364 12.9761654 5.38028092 13.2445463L3.32179677 14.4330127 2.82179677 13.5669873 4.86542112 12.3871002zM9.5 15.4907426C9.60648167 15.4968859 9.71377555 15.5 9.82179677 15.5 10.051423 15.5 10.2777625 15.485928 10.5 15.4585993L10.5 18 9.5 18 9.5 15.4907426zM14.3666368 13.0984292C14.5540843 12.8240078 14.7173255 12.5317385 14.8532861 12.2246957L17.1782032 13.5669873 16.6782032 14.4330127 14.3666368 13.0984292z'/%3E%3C/g%3E%3C/svg%3E");
}

.icon-warmer {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M12.3247275 11.4890349C12.0406216 11.0007637 11.6761954 10.5649925 11.2495475 10.1998198 11.0890394 9.83238991 11 9.42659309 11 9 11 7.34314575 12.3431458 6 14 6 15.6568542 6 17 7.34314575 17 9 17 10.6568542 15.6568542 12 14 12 13.3795687 12 12.8031265 11.8116603 12.3247275 11.4890349zM17.6232392 11.6692284C17.8205899 11.4017892 17.9890383 11.1117186 18.123974 10.8036272L20.3121778 12.0669873 19.8121778 12.9330127 17.6232392 11.6692284zM18.123974 7.19637279C17.9890383 6.88828142 17.8205899 6.5982108 17.6232392 6.33077158L19.8121778 5.0669873 20.3121778 5.9330127 18.123974 7.19637279zM14.5 4.52746439C14.3358331 4.50931666 14.1690045 4.5 14 4.5 13.8309955 4.5 13.6641669 4.50931666 13.5 4.52746439L13.5 2 14.5 2 14.5 4.52746439zM13.5 13.4725356C13.6641669 13.4906833 13.8309955 13.5 14 13.5 14.1690045 13.5 14.3358331 13.4906833 14.5 13.4725356L14.5 16 13.5 16 13.5 13.4725356zM14 11C15.1045695 11 16 10.1045695 16 9 16 7.8954305 15.1045695 7 14 7 12.8954305 7 12 7.8954305 12 9 12 10.1045695 12.8954305 11 14 11zM9.5 11C10.6651924 11.4118364 11.5 12.5 11.5 14 11.5 16 10 17.5 8 17.5 6 17.5 4.5 16 4.5 14 4.5 12.6937812 5 11.5 6.5 11L6.5 7 9.5 7 9.5 11z'/%3E%3Cpath d='M12,14 C12,16.209139 10.209139,18 8,18 C5.790861,18 4,16.209139 4,14 C4,12.5194353 4.80439726,11.2267476 6,10.5351288 L6,4 C6,2.8954305 6.8954305,2 8,2 C9.1045695,2 10,2.8954305 10,4 L10,10.5351288 C11.1956027,11.2267476 12,12.5194353 12,14 Z M11,14 C11,12.6937812 10.1651924,11.5825421 9,11.1707057 L9,4 C9,3.44771525 8.55228475,3 8,3 C7.44771525,3 7,3.44771525 7,4 L7,11.1707057 C5.83480763,11.5825421 5,12.6937812 5,14 C5,15.6568542 6.34314575,17 8,17 C9.65685425,17 11,15.6568542 11,14 Z'/%3E%3C/g%3E%3C/svg%3E");
}

.icon-cooler {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10.4004569 11.6239517C10.0554735 10.9863849 9.57597206 10.4322632 9 9.99963381L9 9.7450467 9.53471338 9.7450467 10.8155381 8.46422201C10.7766941 8.39376637 10.7419749 8.32071759 10.7117062 8.2454012L9 8.2454012 9 6.96057868 10.6417702 6.96057868C10.6677696 6.86753378 10.7003289 6.77722682 10.7389179 6.69018783L9.44918707 5.40045694 9 5.40045694 9 4.34532219 9.32816127 4.34532219 9.34532219 2.91912025 10.4004569 2.91912025 10.4004569 4.53471338 11.6098599 5.74411634C11.7208059 5.68343597 11.8381332 5.63296451 11.9605787 5.59396526L11.9605787 3.8884898 10.8181818 2.74609294 11.5642748 2 12.5727518 3.00847706 13.5812289 2 14.3273218 2.74609294 13.2454012 3.82801356 13.2454012 5.61756719C13.3449693 5.65339299 13.4408747 5.69689391 13.5324038 5.74735625L14.7450467 4.53471338 14.7450467 2.91912025 15.8001815 2.91912025 15.8001815 4.34532219 17.2263834 4.34532219 17.2263834 5.40045694 15.6963166 5.40045694 14.4002441 6.69652946C14.437611 6.78161093 14.4692249 6.86979146 14.4945934 6.96057868L16.2570138 6.96057868 17.3994107 5.81818182 18.1455036 6.56427476 17.1370266 7.57275182 18.1455036 8.58122888 17.3994107 9.32732182 16.3174901 8.2454012 14.4246574 8.2454012C14.3952328 8.31861737 14.3616024 8.38969062 14.3240655 8.45832192L15.6107903 9.7450467 17.2263834 9.7450467 17.2263834 10.8001815 15.8001815 10.8001815 15.8001815 12.2263834 14.7450467 12.2263834 14.7450467 10.6963166 13.377994 9.32926387C13.3345872 9.34850842 13.2903677 9.36625331 13.2454012 9.38243281L13.2454012 11.3174901 14.3273218 12.3994107 13.5812289 13.1455036 12.5848864 12.1491612 11.5642748 13.1455036 10.8181818 12.3994107 11.9605787 11.2570138 11.9605787 9.40603474C11.8936938 9.38473169 11.828336 9.36000556 11.7647113 9.33206224L10.4004569 10.6963166 10.4004569 11.6239517zM12.75 8.5C13.3022847 8.5 13.75 8.05228475 13.75 7.5 13.75 6.94771525 13.3022847 6.5 12.75 6.5 12.1977153 6.5 11.75 6.94771525 11.75 7.5 11.75 8.05228475 12.1977153 8.5 12.75 8.5zM9.5 14C8.5 16.3333333 7.33333333 17.5 6 17.5 4.66666667 17.5 3.5 16.3333333 2.5 14L9.5 14z'/%3E%3Cpath d='M10,14 C10,16.209139 8.209139,18 6,18 C3.790861,18 2,16.209139 2,14 C2,12.5194353 2.80439726,11.2267476 4,10.5351288 L4,4 C4,2.8954305 4.8954305,2 6,2 C7.1045695,2 8,2.8954305 8,4 L8,10.5351288 C9.19560274,11.2267476 10,12.5194353 10,14 Z M9,14 C9,12.6937812 8.16519237,11.5825421 7,11.1707057 L7,4 C7,3.44771525 6.55228475,3 6,3 C5.44771525,3 5,3.44771525 5,4 L5,11.1707057 C3.83480763,11.5825421 3,12.6937812 3,14 C3,15.6568542 4.34314575,17 6,17 C7.65685425,17 9,15.6568542 9,14 Z'/%3E%3C/g%3E%3C/svg%3E");
}

.icon-help {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill='%23999' d='M11.292 12.516l.022 1.782H9.07v-1.804c0-1.98 1.276-2.574 2.662-3.278h-.022c.814-.44 1.65-.88 1.694-2.2.044-1.386-1.122-2.728-3.234-2.728-1.518 0-2.662.902-3.366 2.354L5 5.608C5.946 3.584 7.662 2 10.17 2c3.564 0 5.632 2.442 5.588 5.06-.066 2.618-1.716 3.41-3.102 4.158-.704.374-1.364.682-1.364 1.298zm-1.122 2.442c.858 0 1.452.594 1.452 1.452 0 .682-.594 1.408-1.452 1.408-.77 0-1.386-.726-1.386-1.408 0-.858.616-1.452 1.386-1.452z'/%3E%3C/svg%3E");
}

.icon-help-line {
  background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-1a9 9 0 1 0 0-18 9 9 0 0 0 0 18z'/%3E%3Cpath d='M10.848 12.307l.02 1.578H8.784v-1.597c0-1.753 1.186-2.278 2.474-2.901h-.02c.756-.39 1.533-.78 1.574-1.948.041-1.226-1.043-2.414-3.006-2.414-1.41 0-2.474.798-3.128 2.083L5 6.193C5.88 4.402 7.474 3 9.805 3 13.118 3 15.04 5.161 15 7.478c-.061 2.318-1.595 3.019-2.883 3.68-.654.332-1.268.604-1.268 1.15zM9.805 14.47c.798 0 1.35.525 1.35 1.285 0 .603-.552 1.246-1.35 1.246-.715 0-1.288-.643-1.288-1.246 0-.76.573-1.285 1.288-1.285z' fill-rule='nonzero'/%3E%3C/g%3E%3C/svg%3E");
}

.icon-help-fill {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Ccircle cx='10' cy='10' r='10' fill='%23999'/%3E%3Cpath fill='%23FFF' fill-rule='nonzero' d='M8.368 7.189H5C5 3.5 7.668 2 10.292 2 13.966 2 16 4.076 16 7.012c0 3.754-3.849 3.136-3.849 5.211v1.656H8.455v-1.832c0-2.164 1.4-2.893 2.778-3.6.437-.242 1.006-.574 1.006-1.236 0-2.208-3.871-2.142-3.871-.022zM10.25 18a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5z'/%3E%3C/g%3E%3C/svg%3E");
}

.icon-help-inv {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill='%23999' fill-rule='evenodd' d='M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zM8.368 7.189c0-2.12 3.87-2.186 3.87.022 0 .662-.568.994-1.005 1.236-1.378.707-2.778 1.436-2.778 3.6v1.832h3.696v-1.656c0-2.075 3.849-1.457 3.849-5.21C16 4.075 13.966 2 10.292 2 7.668 2 5 3.501 5 7.189h3.368zM10.25 18a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5z'/%3E%3C/svg%3E");
}

.kelvin::after {
  content: "K";
}

.mired::after {
  content: " Mired";
}

.percent::after {
  content: "%";
}

.sdpi-item-value + .icon-cooler,
.sdpi-item-value + .icon-warmer {
  margin-left: 0px !important;
  margin-top: 15px !important;
}

/**
CONTROL-CENTER STYLES
*/
input[type="range"].colorbrightness::-webkit-slider-runnable-track,
input[type="range"].colortemperature::-webkit-slider-runnable-track {
  height: 8px;
  background: #979797;
  border-radius: 4px;
  background-image: linear-gradient(to right, #94d0ec, #ffb165);
}

input[type="range"].colorbrightness::-webkit-slider-runnable-track {
  background-color: #efefef;
  background-image: linear-gradient(to right, black, rgba(0, 0, 0, 0));
}


input[type="range"].colorbrightness::-webkit-slider-thumb,
input[type="range"].colortemperature::-webkit-slider-thumb {
  width: 16px;
  height: 16px;
  border-radius: 20px;
  margin-top: -5px;
  background-color: #86c6e8;
  box-shadow: 0px 0px 1px #000000;
  border: 1px solid #d8d8d8;
}

.sdpi-info-label {
  display: inline-block;
  user-select: none;
  position: absolute;
  height: 15px;
  width: auto;
  text-align: center;
  border-radius: 4px;
  min-width: 44px;
  max-width: 80px;
  background: white;
  font-size: 11px;
  color: black;
  z-index: 1000;
  box-shadow: 0px 0px 12px rgba(0, 0, 0, .8);
  padding: 2px;

}

.sdpi-info-label.hidden {
  opacity: 0;
  transition: opacity 0.25s linear;
}

.sdpi-info-label.shown {
  position: absolute;
  opacity: 1;
  transition: opacity 0.25s ease-out;
}

/* adding some styles here that override sdpi things so we can use this as notes for sdpi updates*/
select {
  min-width: 0px;

  /* this is a clunky fix for using background image as select arrow with long text options */
  -webkit-appearance: media-slider;
  text-overflow: ellipsis;
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/index.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <title>com.elgato.philips-hue.pi</title>
    <meta charset="UTF-8" />
    <!-- Import style sheets -->
    <link rel="stylesheet" type="text/css" href="css/sdpi.css" />
    <link rel="stylesheet" type="text/css" href="css/colorPI.css" />
    <link rel="stylesheet" type="text/css" href="css/cyclePI.css" />
    <link rel="stylesheet" type="text/css" href="css/pi.css" />
    <!-- Import scripts -->
    <script src="../plugin/js/utils.js"></script>
    <script src="../plugin/js/philips/meethue.js"></script>
    <script src="js/tooltips.js"></script>
    <script src="js/pi.js"></script>
    <script src="js/powerPI.js"></script>
    <script src="js/colorPI.js"></script>
    <script src="js/cyclePI.js"></script>
    <script src="js/temperaturePI.js"></script>
    <script src="js/brightnessPI.js"></script>
    <script src="js/brightnessRelPI.js"></script>
    <script src="js/scenePI.js"></script>
    <script src="js/main.js"></script>
  </head>
  <body>
    <!-- Property Inspector -->
    <div class="sdpi-wrapper" id="pi">
      <!-- Bridge select -->
      <div class="sdpi-item">
        <div class="sdpi-item-label" id="bridge-label"></div>
        <select class="sdpi-item-value select" id="bridge-select">
          <option id="no-bridges" value="no-bridges"></option>
          <option disabled></option>
          <option id="add-bridge" value="add"></option>
        </select>
      </div>
      <!-- Light/Group select -->
      <div class="sdpi-item">
        <div class="sdpi-item-label" id="lights-label"></div>
        <select class="sdpi-item-value select" id="light-select">
          <optgroup id="groups">
            <option id="no-groups" value="no-groups"></option>
          </optgroup>
          <optgroup id="lights">
            <option id="no-lights" value="no-lights"></option>
          </optgroup>
        </select>
      </div>
      <!-- Placeholder for more UI elements -->
      <div id="placeholder"></div>
    </div>
    <!-- Floating tooltips element -->
    <div class="sdpi-info-label hidden" style="top: -1000px" data-value=""></div>
  </body>
</html>


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/brightnessPI.js
================================================
/**
@file      brightnessPI.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

function BrightnessPI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion, isEncoder) {
    // Init BrightnessPI
    let instance = this;

    // Inherit from PI
    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);

    // Before overwriting parent method, save a copy of it
    let piLocalize = this.localize;

    // Localize the UI
    this.localize = () => {
        // Call PIs localize method
        piLocalize.call(instance);

        // Localize the brightness label
        document.getElementById('brightness-label').innerHTML = instance.localization['Brightness'];
        if(isEncoder) {
          document.getElementById('scaleticks-label').innerHTML = instance.localization['Scale Ticks'] || 'Scale Ticks';
        }

    };

    const values = [1,2,3,4,5,10,20];
    const selectedIndex = values.indexOf(Number(settings.scaleTicks));

    // Add brightness slider
    document.getElementById('placeholder').innerHTML = `
      <div type="range" class="sdpi-item">
        <div class="sdpi-item-label" id="brightness-label"></div>
        <div class="sdpi-item-value">
            <input class="floating-tooltip" data-suffix="%" type="range" id="brightness-input" min="1" max="100" value="${settings.brightness}">
        </div>
      </div>
      ${this.getEncoderOptions(settings.scaleTicks, isEncoder)}
      `;

      console.log("value:", selectedIndex, settings.scaleTicks, typeof settings.scaleTicks, document.getElementById('placeholder').innerHTML);


    // Initialize the tooltips
    initToolTips();

    // Add event listener
    document.getElementById('brightness-input').addEventListener('change', brightnessChanged);
    if(isEncoder) {
      document.getElementById('scaleticks-input').addEventListener('change', scaleticksChanged);
    }

    // Brightness changed
    function brightnessChanged(inEvent) {
        // Save the new brightness settings
        settings.brightness = inEvent.target.value;
        instance.saveSettings();

        // Inform the plugin that a new brightness is set
        instance.sendToPlugin({
            piEvent: 'valueChanged',
        });
    }

    function scaleticksChanged(inEvent) {
      settings.scaleTicks = inEvent.target.value;
      instance.saveSettings();
    }

}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/brightnessRelPI.js
================================================
/**
@file      brightnessRelPI.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

function BrightnessRelPI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {
    // Init BrightnessPI
    let instance = this;

    // Inherit from PI
    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);

    // Before overwriting parent method, save a copy of it
    let piLocalize = this.localize;

    // Localize the UI
    this.localize = function() {
        // Call PIs localize method
        piLocalize.call(instance);

        // Localize the brightness label
        document.getElementById('brightness-rel-label').innerHTML = instance.localization['Steps'];
    };

    // Add steps slider
    document.getElementById('placeholder').innerHTML = `
      <div type="range" class="sdpi-item">
        <div class="sdpi-item-label" id="brightness-rel-label"></div>
        <div class="sdpi-item-value">
          <input class="floating-tooltip" data-suffix="%" type="range" id="brightness-rel-input" min="-50" max="50" value="${settings.brightnessRel}">
        </div>
      </div>
    `;

    // Initialize the tooltips
    initToolTips();

    // Add event listener
    document.getElementById('brightness-rel-input').addEventListener('change', brightnessRelChanged);

    // Brightness changed
    function brightnessRelChanged(inEvent) {
        // Save the new brightness settings
        settings.brightnessRel = inEvent.target.value;
        instance.saveSettings();

        // Inform the plugin that a new brightness is set
        instance.sendToPlugin({ 'piEvent': 'valueChanged' });
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/colorPI.js
================================================
/**
@file      colorPI.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

function ColorPI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {
    // Init ColorPI
    let instance = this;

    // Inherit from PI
    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);

    // Add event listener
    document.getElementById('light-select').addEventListener('change', lightChanged);

    // Color changed
    function colorChanged(inEvent) {
        // Get the selected color
        let color = inEvent.target.value;

        // If the color is hex
        if (color.charAt(0) === '#') {
            // Convert the color to HSV
            let hsv = Bridge.hex2hsv(color);

            // Check if the color is valid
            if (hsv.v !== 1) {
                // Remove brightness component
                hsv.v = 1;

                // Set the color to the corrected color
                color = Bridge.hsv2hex(hsv);
            }
        }

        // Save the new color
        settings.color = color;
        instance.saveSettings();

        // Inform the plugin that a new color is set
        instance.sendToPlugin({
            piEvent: 'valueChanged',
        });
    }

    // Light changed
    function lightChanged() {
        // Get the light value manually
        // Because it is not set if this function was triggered via a CustomEvent
        let lightID = document.getElementById('light-select').value;

        // Don't show any color picker if no light or group is set
        if (lightID === 'no-lights' || lightID === 'no-groups') {
            return;
        }

        // Check if any bridge is configured
        if (!('bridge' in settings)) {
            return;
        }

        // Check if the configured bridge is in the cache
        if (!(settings.bridge in cache)) {
            return;
        }

        // Find the configured bridge
        let bridgeCache = cache[settings.bridge];

        // Check if the selected light or group is in the cache
        if (!(lightID in bridgeCache.lights || lightID in bridgeCache.groups)) {
            return;
        }

        // Get light or group cache
        let lightCache;

        if (lightID.indexOf('l') !== -1) {
            lightCache = bridgeCache.lights[lightID];
        }
        else {
            lightCache = bridgeCache.groups[lightID];
        }

        // Add full color picker or only temperature slider
        let colorPicker;

        if (lightCache.xy !== null) {
            colorPicker = `
              <div type="color" class="sdpi-item">
                <div class="sdpi-item-label" id="color-label">${instance.localization['Color']}</div>
                <input type="color" class="sdpi-item-value" id="color-input" value="${settings.color}">
              </div>
            `;
        }
        else {
            colorPicker = `
              <div type="range" class="sdpi-item">
                <div class="sdpi-item-label" id="temperature-label">${instance.localization['Temperature']}</div>
                <div class="sdpi-item-value">
                  <input class="temperature floating-tooltip" data-suffix="K" type="range" id="color-input" min="2000" max="6500" value="${settings.color}">
                </div>
              </div>
            `;
        }

        // Add color picker
        document.getElementById('placeholder').innerHTML = colorPicker;

        // Initialize the tooltips
        initToolTips();

        // Add event listener
        document.getElementById('color-input').addEventListener('change', colorChanged);
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/cyclePI.js
================================================
/**
@file      cyclePI.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

function CyclePI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {
    // Init CyclePI
    let instance = this;

    // Maximum amount of Colors
    let maxColors = 10;

    // Current amount of Colors
    let curColors = settings?.colors?.length || 0;

    // Default color for new pickers
    let defaultColor = "#ffffff";

    // Default temperature for new pickers
    let defaultTemperature = 2000;

    // Inherit from PI
    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);

    // Add event listener
    document.getElementById('light-select').addEventListener('change', lightChanged);

    // Color changed
    function colorChanged(inEvent) {
        // Get the selected index and color
        let index = inEvent.target.dataset.id;
        let color = inEvent.target.value;

        // If the color is hex
        if (color.charAt(0) === '#') {
            // Convert the color to HSV
            let hsv = Bridge.hex2hsv(color);

            // Check if the color is valid
            if (hsv.v !== 1) {
                // Remove brightness component
                hsv.v = 1;

                // Set the color to the corrected color
                color = Bridge.hsv2hex(hsv);
            }
        }

        // Save the new color
        settings.colors[index] = color;
        instance.saveSettings();

        // Inform the plugin that a new color is set
        instance.sendToPlugin({
            piEvent: 'valueChanged',
        });
    }

    // Light changed
    function lightChanged() {
        // Get the light value manually
        // Because it is not set if this function was triggered via a CustomEvent
        let lightID = document.getElementById('light-select').value;

        // Don't show any color picker if no light or group is set
        if (lightID === 'no-lights' || lightID === 'no-groups') {
            return;
        }

        // Check if any bridge is configured
        if (!('bridge' in settings)) {
            return;
        }

        // Check if the configured bridge is in the cache
        if (!(settings.bridge in cache)) {
            return;
        }

        // Find the configured bridge
        let bridgeCache = cache[settings.bridge];

        // Check if the selected light or group is in the cache
        if (!(lightID in bridgeCache.lights || lightID in bridgeCache.groups)) {
            return;
        }

        // Get light or group cache
        let lightCache;

        if (lightID.indexOf('l') !== -1) {
            lightCache = bridgeCache.lights[lightID];
        }
        else {
            lightCache = bridgeCache.groups[lightID];
        }

        // Get html of color picker or temperature slider
        let getColorPicker = i => {
            let colorIndex = i - 1;

            if (lightCache.xy != null) {
                if (i === 0) {
                    return `
                      <div type="color" class="sdpi-item" id="color-input-container">
                        <div class="sdpi-item-label" class="color-label">${instance.localization['Colors']}</div>
                        <div class="sdpi-item-value"></div>
                      </div>
                    `;
                }
                else {
                    return `
                      <span id="color-input-container-${colorIndex}"><input type="color" class="sdpi-item-value" id="color-input-${colorIndex}" name="color-input" data-id="${colorIndex}" value="${(settings.colors[colorIndex] || defaultColor)}"></span>
                    `;
                }
            }
            else if (i > 0) {
                return `
                  <div type="range" class="sdpi-item" id="color-input-container-${colorIndex}">
                    <div class="sdpi-item-label" id="temperature-label">${instance.localization['Temperature']} ${i}</div>
                    <div class="sdpi-item-value">
                      <input class="temperature floating-tooltip" data-suffix="K" type="range" id="color-input-${colorIndex}" name="color-input" data-id="${colorIndex}" min="2000" max="6500" value="${(settings.colors[colorIndex] || defaultTemperature)}">
                    </div>
                  </div>
                `;
            }

            return '';
        };

        let placeholder = document.getElementById('placeholder');

        // Add a new color picker to document
        let addColorPicker = i => {
            let picker = document.createElement('div');
            const cphtml = getColorPicker(i).trim();
            picker.innerHTML = cphtml;


            if (lightCache.xy != null) {
                document.querySelector('#color-input-container .sdpi-item-value').append(picker.firstChild);
            }
            else {
                placeholder.insertBefore(picker.firstChild, document.getElementById('cycle-buttons'));
            }

            document.getElementById('color-input-' + (i - 1)).addEventListener('change', colorChanged);
        };

        // Add first color pickers container and buttons
        placeholder.innerHTML = getColorPicker(0) + `
          <div id="cycle-buttons" class="sdpi-item">
            <div class="sdpi-item-label empty"></div>
            <div class="sdpi-item-value">
              <button id="add-color">+</button>
              <button id="remove-color">-</button>
            </div>
          </div>
        `;

        // Initial create color pickers from settings
        for (let n = 1; n <= settings.colors.length; n++) {
            addColorPicker(n);
        }

        // Get buttons for later usage
        let addButton = document.getElementById('add-color');
        let removeButton = document.getElementById('remove-color');
        let checkButtonStates = () => {
            // Hide add button when reached max color pickers
            addButton.style.display = curColors >= maxColors ? 'none' : 'inline-block';

            // Hide remove button when only two color pickers left
            removeButton.style.display = curColors <= 2 ? 'none' : 'inline-block';
        };

        // Event listener for add color
        addButton.addEventListener('click', () => {
            addColorPicker((++curColors));

            // Add new picker value to settings
            let colorIndex = curColors - 1;

            if (!settings.colors[colorIndex]) {
                if (lightCache.xy != null) {
                    settings.colors[colorIndex] = defaultColor;
                }
                else {
                    settings.colors[colorIndex] = defaultTemperature;
                }

                instance.saveSettings();
            }

            checkButtonStates();
        });

        // Event listener for remove last color
        removeButton.addEventListener('click', () => {
            document.getElementById('color-input-container-' + (--curColors)).remove();

            // Remove color from settings
            settings.colors = settings.colors.splice(0, settings.colors.length - 1);
            instance.saveSettings();

            checkButtonStates();
        });

        // Initial button states
        checkButtonStates();

        // Initialize the tooltips
        initToolTips();
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/main.js
================================================
/**
@file      main.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

// Global web socket
var websocket = null;

// Global plugin settings
var globalSettings = {};

// Global settings
var settings = {};

// Global cache
var cache = {};

// Setup the websocket and handle communication
function connectElgatoStreamDeckSocket(inPort, inUUID, inRegisterEvent, inInfo, inActionInfo) {
    // Parse parameter from string to object
    let actionInfo = JSON.parse(inActionInfo);
    let info = JSON.parse(inInfo);
    let isEncoder = actionInfo?.payload?.controller == 'Encoder';
    let streamDeckVersion = info['application']['version'];
    let pluginVersion = info['plugin']['version'];

    // Save global settings
    settings = actionInfo['payload']['settings'];

    // Retrieve language
    let language = info['application']['language'];

    // Retrieve action identifier
    let action = actionInfo['action'];

    // Open the web socket to Stream Deck
    // Use 127.0.0.1 because Windows needs 300ms to resolve localhost
    websocket = new WebSocket(`ws://127.0.0.1:${inPort}`);

    // WebSocket is connected, send message
    websocket.onopen = () => {
        // Register property inspector to Stream Deck
        registerPluginOrPI(inRegisterEvent, inUUID);

        // Request the global settings of the plugin
        requestGlobalSettings(inUUID);
    };

    // Create actions
    let pi;

    if (action === 'com.elgato.philips-hue.power') {
        pi = new PowerPI(inUUID, language, streamDeckVersion, pluginVersion);
    }
    else if (action === 'com.elgato.philips-hue.color') {
        pi = new ColorPI(inUUID, language, streamDeckVersion, pluginVersion);
    }
    else if (action === 'com.elgato.philips-hue.cycle') {
        pi = new CyclePI(inUUID, language, streamDeckVersion, pluginVersion);
    }
    else if (action === 'com.elgato.philips-hue.brightness') {
        pi = new BrightnessPI(inUUID, language, streamDeckVersion, pluginVersion, isEncoder);
    }
    else if (action === 'com.elgato.philips-hue.temperature') {
      pi = new TemperaturePI(inUUID, language, streamDeckVersion, pluginVersion, isEncoder);
  }
    else if (action === 'com.elgato.philips-hue.scene') {
        pi = new ScenePI(inUUID, language, streamDeckVersion, pluginVersion);
    }
    else if (action === 'com.elgato.philips-hue.brightness-rel') {
        pi = new BrightnessRelPI(inUUID, language, streamDeckVersion, pluginVersion);
    }

    websocket.onmessage = msg => {
        // Received message from Stream Deck
        let jsonObj = JSON.parse(msg.data);
        let event = jsonObj['event'];
        let jsonPayload = jsonObj['payload'];

        if (event === 'didReceiveGlobalSettings') {
            // Set global plugin settings
            globalSettings = jsonPayload['settings'];
        }
        else if (event === 'didReceiveSettings') {
            // Save global settings after default was set
            settings = jsonPayload['settings'];
        }
        else if (event === 'sendToPropertyInspector') {
            // Save global cache
            cache = jsonPayload;

            // Load bridges and lights
            pi.loadBridges();
        }
    };
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/pi.js
================================================
/**
@file      pi.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

function PI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {
    // Init PI
    let instance = this;

    const values = [1,2,3,4,5,10,20];
    this.getEncoderOptions = (settingsValue, forEncoder) => {
      const selectedIndex = values.indexOf(Number(settingsValue));
      return forEncoder === true ? `<div type="select" class="sdpi-item">
    <div class="sdpi-item-label" id="scaleticks-label"></div>
    <select id="scaleticks-input" class="sdpi-item-value select" selectedIndex="${selectedIndex}">
      ${values.map((value, index) => `<option value="${value}" ${index === selectedIndex ? 'selected' : ''}>${value}x</option>`).join('')}
  </select>
    </select>
  </div>`: ''
    };

    // Public localizations for the UI
    this.localization = {};

    // Add event listener
    document.getElementById('bridge-select').addEventListener('change', bridgeChanged);
    document.getElementById('light-select').addEventListener('change', lightsChanged);
    document.addEventListener('saveBridge', setupCallback);

    // Load the localizations
    getLocalization(inLanguage, (inStatus, inLocalization) => {
        if (inStatus) {
            // Save public localization
            instance.localization = inLocalization['PI'];

            // Localize the PI
            instance.localize();
        }
        else {
            log(inLocalization);
        }
    });

    // Localize the UI
    this.localize = () => {
        // Check if localizations were loaded
        if (instance.localization == null) {
            return;
        }

        // Localize the bridge select
        document.getElementById('bridge-label').innerHTML = instance.localization['Bridge'];
        document.getElementById('no-bridges').innerHTML = instance.localization['NoBridges'];
        document.getElementById('add-bridge').innerHTML = instance.localization['AddBridge'];

        // Localize the light and group select
        document.getElementById('lights-label').innerHTML = instance.localization['Lights'];
        document.getElementById('lights').label = instance.localization['LightsTitle'];
        document.getElementById('no-lights').innerHTML = instance.localization['NoLights'];
        document.getElementById('no-groups').innerHTML = instance.localization['NoGroups'];

        // Groups label is removed for scenes PI
        if (document.getElementById('groups') != null) {
            document.getElementById('groups').label = instance.localization['GroupsTitle'];
        }
    };

    // Show all paired bridges
    this.loadBridges = () => {
        // Remove previously shown bridges
        let bridges = document.getElementsByClassName('bridges');
        while (bridges.length > 0) {
            bridges[0].parentNode.removeChild(bridges[0]);
        }

        // Check if any bridge is paired
        if (Object.keys(cache).length > 0) {
            // Hide the 'No Bridges' option
            document.getElementById('no-bridges').style.display = 'none';

            // Sort the bridges alphabetically
            let bridgeIDsSorted = Object.keys(cache).sort((a, b) => {
                return cache[a].name.localeCompare(cache[b].name);
            });

            // Add the bridges
            bridgeIDsSorted.forEach(inBridgeID => {
                // Add the group
                let option = `
                  <option value='${inBridgeID}' class='bridges'>${cache[inBridgeID].name}</option>
                `;
                document.getElementById('no-bridges').insertAdjacentHTML('beforebegin', option);
            });

            // Check if the bridge is already configured
            if (settings.bridge !== undefined) {
                // Select the currently configured bridge
                document.getElementById('bridge-select').value = settings.bridge;
            }

            // Load the lights
            loadLights();
        }
        else {
            // Show the 'No Bridges' option
            document.getElementById('no-bridges').style.display = 'block';
        }

        // Show PI
        document.getElementById('pi').style.display = 'block';
    }

    // Show all lights
    function loadLights() {
        // Check if any bridge is configured
        if (!('bridge' in settings)) {
            return;
        }

        // Check if the configured bridge is in the cache
        if (!(settings.bridge in cache)) {
            return;
        }

        // Find the configured bridge
        let bridgeCache = cache[settings.bridge];

        // Remove previously shown lights
        let lights = document.getElementsByClassName('lights');
        while (lights.length > 0) {
            lights[0].parentNode.removeChild(lights[0]);
        }

        let requireTemperature = instance instanceof ColorPI || instance instanceof TemperaturePI;

        // Check if the bridge has at least one light
        if (Object.keys(bridgeCache.lights).length > 0) {
            // Hide the 'No Light' option
            document.getElementById('no-lights').style.display = 'none';

            // Sort the lights alphabetically
            let lightIDsSorted = Object.keys(bridgeCache.lights).sort((a, b) => {
                return bridgeCache.lights[a].name.localeCompare(bridgeCache.lights[b].name);
            });

            // Add the lights
            lightIDsSorted.forEach(inLightID => {
                let light = bridgeCache.lights[inLightID];

                // Check if this is a color action and the lights supports colors
                if (!(requireTemperature && light.temperature == null && light.xy == null)) {
                    // Add the light
                    let option = `
                      <option value='l-${light.id}' class='lights'>${light.name}</option>
                    `;
                    document.getElementById('no-lights').insertAdjacentHTML('beforebegin', option);
                }
            });
        }
        else {
            // Show the 'No Light' option
            document.getElementById('no-lights').style.display = 'block';
        }

        // Remove previously shown groups
        let groups = document.getElementsByClassName('groups');
        while (groups.length > 0) {
            groups[0].parentNode.removeChild(groups[0]);
        }

        // Check if the bridge has at least one group
        if (Object.keys(bridgeCache.groups).length > 0) {
            // Hide the 'No Group' option
            document.getElementById('no-groups').style.display = 'none';

            // Sort the groups alphabetically
            let groupIDsSorted = Object.keys(bridgeCache.groups).sort((a, b) => {
                return bridgeCache.groups[a].name.localeCompare(bridgeCache.groups[b].name);
            });

            // Add the groups
            groupIDsSorted.forEach(inGroupID => {
                let group = bridgeCache.groups[inGroupID];

                // Check if this is a color action and the lights supports colors
                if (!(instance instanceof ColorPI && group.temperature == null && group.xy == null)) {
                    // Add the group
                    let option = `
                      <option value='g-${group.id}' class='groups'>${group.name}</option>
                    `;
                    document.getElementById('no-groups').insertAdjacentHTML('beforebegin', option);
                }
            });
        }
        else {
            // Show the 'No Group' option
            document.getElementById('no-groups').style.display = 'block';
        }

        // Check if a light is already setup
        if (settings.light !== undefined) {
            // Check if the configured light or group is part of the bridge cache
            if (!(settings.light in bridgeCache.lights || settings.light in bridgeCache.groups)) {
                return;
            }

            // Select the currently configured light or group
            document.getElementById('light-select').value = settings.light;

            // Dispatch light change event manually
            // So that the colorPI can set the correct color picker at initialization
            document.getElementById('light-select').dispatchEvent(new CustomEvent('change', {'detail': {'manual': true}} ));
        }

        // If this is a scene PI
        if (instance instanceof ScenePI) {
            //Load the scenes
            instance.loadScenes();
        }
    }

    // Function called on successful bridge pairing
    function setupCallback(inEvent) {
        // Set bridge to the newly added bridge
        settings.bridge = inEvent.detail.id;
        instance.saveSettings();

        // Check if global settings need to be initialized
        if (globalSettings.bridges === undefined) {
            globalSettings.bridges = {};
        }

        // Add new bridge to the global settings
        globalSettings.bridges[inEvent.detail.id] = {
            ip: inEvent.detail.ip,
            id: inEvent.detail.id,
            username: inEvent.detail.username,
        };
        saveGlobalSettings(inContext);
    }

    // Bridge select changed
    function bridgeChanged(inEvent) {
        if (inEvent.target.value === 'add') {
            // Open setup window
            window.open(`../setup/index.html?language=${inLanguage}&streamDeckVersion=${inStreamDeckVersion}&pluginVersion=${inPluginVersion}`);

            // Select the first in case user cancels the setup
            document.getElementById('bridge-select').selectedIndex = 0;
        }
        else if (inEvent.target.value === 'no-bridges') {
            // If no bridge was selected, do nothing
        }
        else {
            settings.bridge = inEvent.target.value;
            instance.saveSettings();
            instance.loadBridges();
        }
    }

    // Light select changed
    function lightsChanged(inEvent) {
        if (inEvent.target.value === 'no-lights' || inEvent.target.value === 'no-groups') {
            // If no light or group was selected, do nothing
        }
        else if (inEvent.detail !== undefined) {
            // If the light was changed via code
            if (inEvent.detail.manual === true) {
                // do nothing
            }
        }
        else {
            settings.light = inEvent.target.value;
            instance.saveSettings();

            // If this is a scene PI
            if (instance instanceof ScenePI) {
                //Load the scenes
                instance.loadScenes();
            }

            instance.sendToPlugin({
              piEvent: 'lightsChanged',
          });
        }
    }

    // Private function to return the action identifier
    function getAction() {
        let action

        // Find out type of action
        if (instance instanceof PowerPI) {
            action = 'com.elgato.philips-hue.power';
        }
        else if (instance instanceof ColorPI) {
            action = 'com.elgato.philips-hue.color';
        }
        else if (instance instanceof CyclePI) {
            action = 'com.elgato.philips-hue.cycle';
        }
        else if (instance instanceof BrightnessPI) {
            action = 'com.elgato.philips-hue.brightness';
        }
        else if (instance instanceof TemperaturePI) {
          action = 'com.elgato.philips-hue.temperature';
      }
        else if (instance instanceof BrightnessRelPI) {
            action = 'com.elgato.philips-hue.brightness-rel';
        }
        else if (instance instanceof ScenePI) {
            action = 'com.elgato.philips-hue.scene';
        }

        return action;
    }

    // Public function to save the settings
    this.saveSettings = () => {
        saveSettings(getAction(), inContext, settings);
    }

    // Public function to send data to the plugin
    this.sendToPlugin = inData => {
        sendToPlugin(getAction(), inContext, inData);
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/powerPI.js
================================================
/**
@file      powerPI.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

function PowerPI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {
    // Inherit from PI
    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/scenePI.js
================================================
/**
@file      scenePI.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

function ScenePI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {
    // Init ScenePI
    let instance = this;

    // Inherit from PI
    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);

    // Hide lights from light select
    document.getElementById('lights').style.display = 'none';

    // Remove groups label from lights select
    let groups = document.getElementById('groups');
    let groupsChildren = document.getElementById('groups').children;
    let lightSelect = document.getElementById('light-select');

    lightSelect.removeChild(groups);
    lightSelect.appendChild(groupsChildren[0]);

    // Before overwriting parent method, save a copy of it
    let piLocalize = this.localize;

    // Localize the UI
    this.localize = () => {
        // Call PIs localize method
        piLocalize.call(instance);

        // Localize the scene select
        document.getElementById('lights-label').innerHTML = instance.localization['Group'];
        document.getElementById('scene-label').innerHTML = instance.localization['Scene'];
        document.getElementById('no-scenes').innerHTML = instance.localization['NoScenes'];
    };

    // Add scene select
    document.getElementById('placeholder').innerHTML = `
        <div class='sdpi-item'>
          <div class='sdpi-item-label' id='scene-label'></div>
          <select class='sdpi-item-value select' id='scene-select'>
            <option id='no-scenes' value='no-scene'></option>
          </select>
        </div>
    `;

    // Add event listener
    document.getElementById('scene-select').addEventListener('change', sceneChanged);

    // Scenes changed
    function sceneChanged(inEvent) {
        if (inEvent.target.value === 'no-scenes') {
            // do nothing
        }
        else {
            // Save the new scene settings
            settings.scene = inEvent.target.value;
            instance.saveSettings();

            // Inform the plugin that a new scene is set
            instance.sendToPlugin({
                piEvent: 'valueChanged',
            });
        }
    }

    // Show all scenes
    this.loadScenes = () => {
        // Check if any bridge is configured
        if (!('bridge' in settings)) {
            return;
        }

        // Check if the configured bridge is in the cache
        if (!(settings.bridge in cache)) {
            return;
        }

        // Find the configured bridge
        let bridgeCache = cache[settings.bridge];

        // Check if any light is configured
        if (!('light' in settings)) {
            return;
        }

        // Check if the light was set to a group
        if (!(settings.light.indexOf('g-') !== -1)) {
            return;
        }

        // Check if the configured group is in the cache
        if (!(settings.light in bridgeCache.groups)) {
            return;
        }

        // Find the configured group
        let groupCache = bridgeCache.groups[settings.light];

        // Remove previously shown scenes
        let scenes = document.getElementsByClassName('scenes');
        while (scenes.length > 0) {
            scenes[0].parentNode.removeChild(scenes[0]);
        }

        // Check if the group has at least one scene
        if (Object.keys(groupCache.scenes).length > 0) {
            // Hide the 'No Scenes' option
            document.getElementById('no-scenes').style.display = 'none';

            // Sort the scenes alphabetically
            let sceneIDsSorted = Object.keys(groupCache.scenes).sort((a, b) => {
                return groupCache.scenes[a].name.localeCompare(groupCache.scenes[b].name);
            });

            // Add the scenes
            sceneIDsSorted.forEach((inSceneID) => {
                // Add the scene
                let scene = groupCache.scenes[inSceneID];
                let option = `<option value="${scene.id}" class="scenes">${scene.name}</option>`;
                document.getElementById('no-scenes').insertAdjacentHTML('beforebegin', option);
            });
        }
        else {
            // Show the 'No Scenes' option
            document.getElementById('no-scenes').style.display = 'block';
        }

        // Check if scene is already setup
        if (settings.scene !== undefined) {
            // Check if the configured scene is in this group
            if (!(settings.scene in groupCache.scenes)) {
                return;
            }

            // Select the currently configured scene
            document.getElementById('scene-select').value = settings.scene;
        }
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/temperaturePI.js
================================================
/**
@file      temperaturePI.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

function TemperaturePI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion, isEncoder) {
    // Init TemperaturePI
    let instance = this;

    // Inherit from PI
    PI.call(this, inContext, inLanguage, inStreamDeckVersion, inPluginVersion);

    // Before overwriting parent method, save a copy of it
    let piLocalize = this.localize;
    
    // Localize the UI
    this.localize = () => {
        // Call PIs localize method
        piLocalize.call(instance);

        // Localize the brightness label
        document.getElementById('temperature-label').innerHTML = instance.localization['Temperature'];
        if(isEncoder) {
          document.getElementById('scaleticks-label').innerHTML = instance.localization['Scale Ticks'] || 'Scale Ticks';
        }
    };

    // Add brightness slider
    document.getElementById('placeholder').innerHTML = `
      <div type="range" class="sdpi-item">
        <div class="sdpi-item-label" id="temperature-label"></div>
        <div class="sdpi-item-value">
            <span class="clickable" value=0>0%</span>
            <input class="floating-tooltip" data-suffix="%" type="range" id="temperature-input" min="1" max="100" value="${settings.temperature}">
            <span class="clickable" value="100">100%</span>
        </div>
      </div>
      ${this.getEncoderOptions(settings.scaleTicks, isEncoder)}
    `;

    // Initialize the tooltips
    initToolTips();

    // Add event listener
    document.getElementById('temperature-input').addEventListener('input', temperatureChanged);
    if(isEncoder) {
      document.getElementById('scaleticks-input').addEventListener('change', scaleticksChanged);
    }

    // Brightness changed
    function temperatureChanged(inEvent) {
        // Save the new brightness settings
        settings.temperature = inEvent.target.value;
        instance.saveSettings();

        // Inform the plugin that a new brightness is set
        instance.sendToPlugin({
            piEvent: 'valueChanged',
        });
    }

    function scaleticksChanged(inEvent) {
      settings.scaleTicks = inEvent.target.value;
      instance.saveSettings();
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/tooltips.js
================================================
//==============================================================================
/**
@file       tooltips.js
@brief      Philips Hue Plugin
@copyright  (c) 2019, Corsair Memory, Inc.
            This source code is licensed under the MIT-style license found in the LICENSE file.
**/
//==============================================================================

function rangeToPercent(value, min, max) {
    return ((value - min) / (max - min));
}

function initToolTips() {
    const tooltip = document.querySelector('.sdpi-info-label');
    const arrElements = document.querySelectorAll('.floating-tooltip');

    arrElements.forEach((e,i) => {
        initToolTip(e, tooltip)
    })
}

function initToolTip(element, tooltip) {
    const tw = tooltip.getBoundingClientRect().width;
    const suffix = element.getAttribute('data-suffix') || '';

    const updateTooltip = () => {
        const elementRect = element.getBoundingClientRect();
        const w = elementRect.width - tw / 2;
        const percent = rangeToPercent(
            element.value,
            element.min,
            element.max,
        );

        tooltip.textContent = suffix !== '' ? `${element.value} ${suffix}` : String(element.value);
        tooltip.style.left = `${elementRect.left + Math.round(w * percent) - tw / 4}px`;
        tooltip.style.top = `${elementRect.top - 32}px`;
    };

    if (element) {
        element.addEventListener('mouseenter', () => {
            tooltip.classList.remove('hidden');
            tooltip.classList.add('shown');
            updateTooltip();
        }, false);

        element.addEventListener('mouseout', () => {
            tooltip.classList.remove('shown');
            tooltip.classList.add('hidden');
            updateTooltip();
        }, false);

		element.addEventListener('input', updateTooltip, false);
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/index.html
================================================
<!DOCTYPE HTML>
<html>
  <head>
    <title>com.elgato.philips-hue</title>
    <meta charset="UTF-8">
    <!-- Import scripts -->
    <script src="js/timers.js"></script>
    <script src="js/utils.js"></script>
    <script src="js/philips/cache.js"></script>
    <script src="js/main.js"></script>
    <script src="js/action.js"></script>
    <script src="js/propertyAction.js"></script>
    <script src="js/colorAction.js"></script>
    <script src="js/cycleAction.js"></script>
    <script src="js/powerAction.js"></script>
    <script src="js/temperatureAction.js"></script>
    <script src="js/brightnessAction.js"></script>
    <script src="js/brightnessRelAction.js"></script>
    <script src="js/sceneAction.js"></script>
    <script src="js/philips/meethue.js"></script>
  </head>
</html>


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/action.js
================================================
/**
@file      action.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

// Prototype which represents an action
function Action(inContext, inSettings, jsn) {
    // Init Action
    let instance = this;
    let debounceDelay = 50;

    // Private variable containing the context of the action
    let context = inContext;
    this.isEncoder = jsn?.payload?.controller == 'Encoder';
    this.isInMultiAction = jsn?.payload?.isInMultiAction;

    this.savedValue = -1;
    this.savedPower = null;
    // Private variable containing the settings of the action
    let settings = inSettings;
    
    let updateActionsEvent = new CustomEvent('updateActions', {detail: {sender: this}} );

    // Set the default values
    setDefaults();

    // Public function returning the context
    this.getContext = () => {
        return context;
    };

    // Public function returning the settings
    this.getSettings = () => {
        return settings;
    };

    // Public function for settings the settings
    this.setSettings = inSettings => {
        settings = inSettings;
    };

    // Public function called when new cache is available
    this.newCacheAvailable = inCallback => {
        // Set default settings
        setDefaults(inCallback);
    };

    this.updateAllActions = () => {
      document.dispatchEvent(updateActionsEvent);
    };

    this.updateActionIfCacheAvailable = (ctx) => {
        // update the action and its display
      const cacheSize = Object.keys(cache.data).length;
      if(cacheSize === 0) {
        // after a willAppear event, the cache is not yet available
        wait(1000).then(() => {
          this.updateAction();
        });
      } else {
        this.updateAction();
      }
    }

    this.setFeedback = (context, value, opacity) => {
      console.assert(websocket, 'no connection to websocket');
      if(websocket && this.isEncoder) {
        // send the values to the encoder  (SD+)
        setFeedback(context, {
          value: {
            value,
            opacity
          },
          indicator: {
            value,
            opacity
          }
        });
      }
    };

    this.updateDisplay = (lightOrGroup, property) => {
      if(!lightOrGroup) {
        if(!this.getCurrentLightOrGroup) return;
        const curLightOrGroup = this.getCurrentLightOrGroup();
        if(curLightOrGroup) {
          lightOrGroup = curLightOrGroup.objCache;
          this.savedValue = -1; // force update
        }
        console.assert(lightOrGroup, 'no light or group', curLightOrGroup);
        if(!lightOrGroup) return;
      };
      if(this.isInMultiAction || !this.isEncoder) return;
      const powerHue = property == 'power' ? !lightOrGroup?.power : lightOrGroup?.power;
      let actionValue = lightOrGroup?.[this.property];
  
      // check if the values have changed
      if(actionValue === this.savedValue && powerHue === this.savedPower) {
        return;
      }
      // cache the values
      this.savedValue = actionValue;
      this.savedPower = powerHue;
  
      // values in hue are 0-254, convert to 0-100 // !this is not true for temperature
      let value;
      if(this.property == 'temperature') {
        const ct = lightOrGroup.originalValue?.capabilities?.control?.ct;
        console.assert(ct, 'no ct in capabilities', lightOrGroup);
        if(!ct) return;
        value = parseInt(Utils.percent(lightOrGroup.temperature, ct.min, ct.max));
      } else {
        value = parseInt(actionValue / 2.54);
      }
      // if the light is off, set the opacity to 0.5
      const opacity = powerHue ? 1 :0.5;
      this.setFeedback(inContext, value, opacity);
    };

    this.togglePower = (inContext) => {
      const target = this.getCurrentLightOrGroup();
      if(!target) return;
      const targetState = !target.objCache.power;
      target.obj.setPower(targetState, (success, error) => {
        if (success) {
            target.objCache.power = targetState;
            // cache.refresh();
            this.updateAllActions();
        }
        else {
            log(error);
            showAlert(inContext);
        }
      });
      return target;
    };

    this.getVerifiedSettings = function(inContext, requiredPropertySetting = null) {

      // Check if any bridge is configured
      if(!('bridge' in settings)) {
        log('No bridge configured');
        showAlert(inContext);
        return false;
      }
  
      // Check if the configured bridge is in the cache
      if(!(settings.bridge in cache.data)) {
        log(`Bridge ${settings.bridge} not found in cache`);
        showAlert(inContext);
        return false;
      }
  
      // Check if any light is configured
      if(!('light' in settings)) {
        log('No light or group configured');
        showAlert(inContext);
        return false;
      }

      if(requiredPropertySetting) {
        if(!(requiredPropertySetting in settings)) {
          log(`No ${requiredPropertySetting} configured`);
          showAlert(inContext);
          return;
        }
      }
   
      // Find the configured bridge
      let bridgeCache = cache.data[settings.bridge];
      if(bridgeCache === false) {
        console.warn('getVerifiedSettings: no bridge in cache');
        return false;
      };
  
      // Check if the configured light or group is in the cache
      if(!(settings.light in bridgeCache.lights || settings.light in bridgeCache.groups)) {
        log(`Light or group ${settings.light} not found in cache`, settings, bridgeCache);
        showAlert(inContext);
        return false;
      }
  
      return settings;
    };

    // Private function to set the defaults
    function setDefaults(inCallback) {
        // If at least one bridge is paired
        if (!(Object.keys(cache.data).length > 0)) {
            // If a callback function was given
            if (inCallback !== undefined) {
                // Execute the callback function
                inCallback();
            }
            return;
        }

        // Find out type of action
        let action;
        if (instance instanceof PowerAction) {
            action = 'com.elgato.philips-hue.power';
        }
        else if (instance instanceof ColorAction) {
            action = 'com.elgato.philips-hue.color';
        }
        else if (instance instanceof CycleAction) {
            action = 'com.elgato.philips-hue.cycle';
        }
        else if (instance instanceof BrightnessAction) {
            action = 'com.elgato.philips-hue.brightness';
        }
		else if (instance instanceof BrightnessRelAction) {
            action = 'com.elgato.philips-hue.brightness-rel';
        }
        else if (instance instanceof SceneAction) {
            action = 'com.elgato.philips-hue.scene';
        }

        // If no bridge is set for this action
        if (!('bridge' in settings)) {
            // Sort the bridges alphabetically
            let bridgeIDsSorted = Object.keys(cache.data).sort((a, b) => {
                return cache.data[a].name.localeCompare(cache.data[b].name);
            });

            // Set the bridge automatically to the first one
            settings.bridge = bridgeIDsSorted[0];

            // Save the settings
            saveSettings(action, inContext, settings);
        }

        // Find the configured bridge
        let bridgeCache = cache.data[settings.bridge];

        // If no light is set for this action
        if (!('light' in settings)) {
            // First try to set a group, because scenes only support groups
            // If the bridge has at least one group
            if (Object.keys(bridgeCache.groups).length > 0) {
                // Sort the groups automatically
                let groupIDsSorted = Object.keys(bridgeCache.groups).sort((a, b) => {
                    return bridgeCache.groups[a].name.localeCompare(bridgeCache.groups[b].name);
                });

                // Set the light automatically to the first group
                settings.light = groupIDsSorted[0];

                // Save the settings
                saveSettings(action, inContext, settings);
            }
            else if (Object.keys(bridgeCache.lights).length > 0) {
                // Sort the lights automatically
                let lightIDsSorted = Object.keys(bridgeCache.lights).sort((a, b) => {
                    return bridgeCache.lights[a].name.localeCompare(bridgeCache.lights[b].name);
                });

                // Set the light automatically to the first light
                settings.light = lightIDsSorted[0];

                // Save the settings
                saveSettings(action, inContext, settings);
            }
        }

        // If a callback function was given
        if (inCallback !== undefined) {
            // Execute the callback function
            inCallback();
        }
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/brightnessAction.js
================================================
/**
@file      brightnessAction.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

function BrightnessAction(inContext, inSettings, jsn) {
  this.property = 'brightness';
  // Inherit from PropertyAction
  PropertyAction.call(this, inContext, inSettings, jsn);
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/brightnessRelAction.js
================================================
/**
@file      brightnessRelAction.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

// Prototype which represents a brightness action
function BrightnessRelAction(inContext, inSettings) {
    // Init BrightnessRelAction
    let instance = this;

    // Inherit from Action
    Action.call(this, inContext, inSettings);

    // Set the default values
    setDefaults();

    // Public function called on key up event
    this.onKeyUp = (inContext, inSettings, inCoordinates, inUserDesiredState, inState) => {
        // If onKeyUp was triggered manually, load settings
        if (inSettings === undefined) {
            inSettings = instance.getSettings();
        }

        // Set icon according to relative value
        setState(inContext, inSettings.brightnessRel >= 0 ? 0 : 1);

        // Check if any bridge is configured
        if (!('bridge' in inSettings)) {
            log('No bridge configured');
            showAlert(inContext);
            return;
        }

        // Check if the configured bridge is in the cache
        if (!(inSettings.bridge in cache.data)) {
            log(`Bridge ${inSettings.bridge} not found in cache`);
            showAlert(inContext);
            return;
        }

        // Find the configured bridge
        let bridgeCache = cache.data[inSettings.bridge];

        // Check if any light is configured
        if (!('light' in inSettings)) {
            log('No light or group configured');
            showAlert(inContext);
            return;
        }

        // Check if the configured light or group is in the cache
        if (!(inSettings.light in bridgeCache.lights || inSettings.light in bridgeCache.groups)) {
            log(`Light or group ${inSettings.light} not found in cache`);
            showAlert(inContext);
            return;
        }

        // Check if any brightness is configured
        if (!('brightnessRel' in inSettings)) {
            log('No relative brightness configured');
            showAlert(inContext);
            return;
        }

        // Create a bridge instance
        let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);

        // Create a light or group object
        let objCache, obj;
        if (inSettings.light.indexOf('l') !== -1) {
            objCache = bridgeCache.lights[inSettings.light];
            obj = new Light(bridge, objCache.id);
        }
        else {
            objCache = bridgeCache.groups[inSettings.light];
            obj = new Group(bridge, objCache.id);
        }

        // Convert brightness
        let brightness;
        if (objCache.power) {
            let brightnessRel = (objCache.brightness / 2.54) + parseInt(inSettings.brightnessRel);
            brightness = Math.round(brightnessRel * 2.54);
        }
        else {
            brightness = parseInt(inSettings.brightnessRel);
        }

        if (brightness > 254) {
            brightness = 254;
        }
        else if (brightness < 0) {
            brightness = 0;
        }

        // Turn lights off if brightness is 0
        if (brightness <= 0) {
            obj.setPower(false, (inSuccess, inError) => {
                if (inSuccess) {
                    objCache.power = false;
                }
                else {
                    log(inError);
                    showAlert(inContext);
                }
            });
        }
        else {
            // Set light or group state
            obj.setBrightness(brightness, (inSuccess, inError) => {
                if (inSuccess) {
                    objCache.brightness = brightness;
                }
                else {
                    log(inError);
                    showAlert(inContext);
                }
            });
        }
    };

    // Before overwriting parent method, save a copy of it
    let actionNewCacheAvailable = this.newCacheAvailable;

    // Public function called when new cache is available
    this.newCacheAvailable = inCallback => {
        // Call actions newCacheAvailable method
        actionNewCacheAvailable.call(instance, () => {
            // Set defaults
            setDefaults();

            // Call the callback function
            inCallback();
        });
    };

    // Private function to set the defaults
    function setDefaults() {
        // Get the settings and the context
        let settings = instance.getSettings();
        let context = instance.getContext();

        // If brightness is already set for this action
        if ('brightnessRel' in settings) {
            return;
        }

        // Set the relative brightness to 0
        settings.brightnessRel = 0;

        // Save the settings
        saveSettings('com.elgato.philips-hue.brightness-rel', context, settings);
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/colorAction.js
================================================
/**
@file      colorAction.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

// Prototype which represents a color action
function ColorAction(inContext, inSettings) {
    // Init ColorAction
    let instance = this;

    // Inherit from Action
    Action.call(this, inContext, inSettings);

    // Set the default values
    setDefaults();

    // Public function called on key up event
    this.onKeyUp = (inContext) => {

      const settings = this.getVerifiedSettings(inContext, 'color');
      if(false === settings) return;
      let bridgeCache = cache.data[settings.bridge];

        // Create a bridge instance
        let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);

        // Create a light or group object
        let objCache, obj;
        if (settings.light.indexOf('l') !== -1) {
            objCache = bridgeCache.lights[settings.light];
            obj = new Light(bridge, objCache.id);
        }
        else {
            objCache = bridgeCache.groups[settings.light];
            obj = new Group(bridge, objCache.id);
        }

        // Check if this is a color or temperature light
        if (settings.color.indexOf('#') !== -1) {
            // Convert light color to hardware independent XY color
            let xy = Bridge.hex2xy(settings.color);

            // Set light or group state
            obj.setXY(xy, (inSuccess, inError) => {
                if (inSuccess) {
                    objCache.xy = xy;
                }
                else {
                    log(inError);
                    showAlert(inContext);
                }
            });
        }
        else {
            // Note: Some lights do not support the full range
            let min = 153.0;
            let max = 500.0;

            let minK = 2000.0;
            let maxK = 6500.0;

            // Convert light color
            let percentage = (settings.color - minK) / (maxK - minK);
            let invertedPercentage = -1 * (percentage - 1.0);
            let temperature = Math.round(invertedPercentage * (max - min) + min);

            // Set light or group state
            obj.setTemperature(temperature, (inSuccess, inError) => {
                if (inSuccess) {
                    objCache.ct = temperature;
                }
                else {
                    log(inError);
                    showAlert(inContext);
                }
            });
        }
    };

    // Before overwriting parent method, save a copy of it
    let actionNewCacheAvailable = this.newCacheAvailable;

    // Public function called when new cache is available
    this.newCacheAvailable = inCallback => {
        // Call actions newCacheAvailable method
        actionNewCacheAvailable.call(instance, () => {
            // Set defaults
            setDefaults();

            // Call the callback function
            inCallback();
        });
    };

    // Private function to set the defaults
    function setDefaults() {
        // Get the settings and the context
        let settings = instance.getSettings();
        let context = instance.getContext();

        // Check if any bridge is configured
        if (!('bridge' in settings)) {
            return;
        }

        // Check if the configured bridge is in the cache
        if (!(settings.bridge in cache.data)) {
            return;
        }

        // Find the configured bridge
        let bridgeCache = cache.data[settings.bridge];

        // Check if a light was set for this action
        if (!('light' in settings)) {
            return;
        }

        // Check if the configured light or group is in the cache
        if (!(settings.light in bridgeCache.lights || settings.light in bridgeCache.groups)) {
            return;
        }

        // Get a light or group cache
        let lightCache;
        if (settings.light.indexOf('l-') !== -1) {
            lightCache = bridgeCache.lights[settings.light];
        }
        else {
            lightCache = bridgeCache.groups[settings.light];
        }

        // Check if any color is configured
        if ('color' in settings) {
            // Check if the set color is supported by the light
            if (settings.color.charAt(0) === '#' && lightCache.xy != null) {
                return;
            }
            else if (settings.color.charAt(0) !== '#' && lightCache.xy == null) {
                return;
            }
        }

        // Check if the light supports all colors
        if (lightCache.xy != null) {
            // Set white as the default color
            settings.color = '#ffffff';
        }
        else {
            // Set white as the default temperature
            settings.color = '4250';
        }

        // Save the settings
        saveSettings('com.elgato.philips-hue.color', context, settings);
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/cycleAction.js
================================================
/**
@file      cycleAction.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

// Prototype which represents a color action
function CycleAction(inContext, inSettings) {
  // Init CycleAction
  let instance = this;

  // Index of current active Color
  let currentColor = -1;

  // Inherit from Action
  Action.call(this, inContext, inSettings);

  // Set the default values
  setDefaults();

  // Public function called on key up event
  this.onKeyUp = (inContext) => {
    const settings = this.getVerifiedSettings(inContext, 'colors');
    if(false === settings) return;

    let bridgeCache = cache.data[settings.bridge];

    // Create a bridge instance
    let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);

    // Create a light or group object
    let objCache, obj;
    if(settings.light.indexOf('l') !== -1) {
      objCache = bridgeCache.lights[settings.light];
      obj = new Light(bridge, objCache.id);
    }
    else {
      objCache = bridgeCache.groups[settings.light];
      obj = new Group(bridge, objCache.id);
    }

    // Reset current Color index
    if(currentColor + 1 >= settings.colors.length) {
      currentColor = -1;
    }

    let colorIndex = currentColor + 1;

    // Check if this is a color or temperature light
    if(settings.colors[colorIndex].indexOf('#') !== -1) {
      // Convert light color to hardware independent XY color
      let xy = Bridge.hex2xy(settings.colors[colorIndex]);

      // Set light or group state
      obj.setXY(xy, (inSuccess, inError) => {
        if(inSuccess) {
          objCache.xy = xy;
          ++currentColor;
        }
        else {
          log(inError);
          showAlert(inContext);
        }
      });
    }
    else {
      // Note: Some lights do not support the full range
      let min = 153.0;
      let max = 500.0;

      let minK = 2000.0;
      let maxK = 6500.0;

      // Convert light color
      let percentage = (settings.colors[colorIndex] - minK) / (maxK - minK);
      let invertedPercentage = -1 * (percentage - 1.0);
      let temperature = Math.round(invertedPercentage * (max - min) + min);

      // Set light or group state
      obj.setTemperature(temperature, (inSuccess, inError) => {
        if(inSuccess) {
          objCache.ct = temperature;
          ++currentColor;
        }
        else {
          log(inError);
          showAlert(inContext);
        }
      });
    }
  };

  // Before overwriting parent method, save a copy of it
  let actionNewCacheAvailable = this.newCacheAvailable;

  // Public function called when new cache is available
  this.newCacheAvailable = inCallback => {
    // Call actions newCacheAvailable method
    actionNewCacheAvailable.call(instance, () => {
      // Set defaults
      setDefaults();

      // Call the callback function
      inCallback();
    });
  };

  // Private function to set the defaults
  function setDefaults() {
    // Get the settings and the context
    let settings = instance.getSettings();
    let context = instance.getContext();

    // Check if any bridge is configured
    if(!('bridge' in settings)) {
      return;
    }

    // Check if the configured bridge is in the cache
    if(!(settings.bridge in cache.data)) {
      return;
    }

    // Find the configured bridge
    let bridgeCache = cache.data[settings.bridge];

    // Check if a light was set for this action
    if(!('light' in settings)) {
      return;
    }

    // Check if the configured light or group is in the cache
    if(!(settings.light in bridgeCache.lights || settings.light in bridgeCache.groups)) {
      return;
    }

    // Get a light or group cache
    let lightCache;
    if(settings.light.indexOf('l-') !== -1) {
      lightCache = bridgeCache.lights[settings.light];
    }
    else {
      lightCache = bridgeCache.groups[settings.light];
    }

    // Check if any color is configured
    if('colors' in settings) {
      // Check if the set color is supported by the light
      if(settings.colors[0].charAt(0) === '#' && lightCache.xy != null) {
        return;
      }
      else if(settings.colors[0].charAt(0) !== '#' && lightCache.xy == null) {
        return;
      }
    }

    // Check if the light supports all colors
    if(lightCache.xy != null) {
      // Set white as the default color
      settings.colors = ['#ff0000', '#00ff00', '#0000ff'];
    }
    else {
      // Set white as the default temperature
      settings.colors = ['2230', '4250', '6410'];
    }

    // Save the settings
    saveSettings('com.elgato.philips-hue.cycle', context, settings);
  }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/main.js
================================================
/**
@file      main.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

// Global web socket
var websocket = null;

// Global cache
var cache = {};

// Global settings
var globalSettings = {};

const throttleDialRotate = Utils.throttle((fn) => {
  if (fn) fn();
}, 60);

const debounceDialRotate = Utils.debounce((jsonObj) => {
  console.log('debounceDialRotate', jsonObj);
}, 300);

// Setup the websocket and handle communication
function connectElgatoStreamDeckSocket(inPort, inPluginUUID, inRegisterEvent, inInfo) {
    // Create array of currently used actions
    let actions = {};
    window.MACTIONS = actions;
    // Create a cache
    cache = new Cache();

    // Open the web socket to Stream Deck
    // Use 127.0.0.1 because Windows needs 300ms to resolve localhost
    websocket = new WebSocket(`ws://127.0.0.1:${inPort}`);
    const _info = JSON.parse(inInfo);
    const [ version, major, minor, build ] = _info.application.version.split(".").map(e => parseInt(e, 10));
    const hasDialPress = version == 6 && major < 4;

    // Web socket is connected
    websocket.onopen = () => {
        // Register plugin to Stream Deck
        registerPluginOrPI(inRegisterEvent, inPluginUUID);

        // Request the global settings of the plugin
        requestGlobalSettings(inPluginUUID);
    };

    document.addEventListener('updateActions', (e) => {
      // updateAction carries the sender of the event so we can skip it
      const sender = e.detail?.sender;
      Object.keys(actions).forEach(inContext => {
        if(actions[inContext].updateAction) {
          // don't update the sender
          if(actions[inContext] === sender) return;
          actions[inContext].updateAction();
        }
      });
    }, false);

    // Add event listener
    document.addEventListener('newCacheAvailable', () => {
        // When a new cache is available
        Object.keys(actions).forEach(inContext => {
            // Inform all used actions that a new cache is available
            actions[inContext].newCacheAvailable(() => {
                let action;

                // Find out type of action
                if (actions[inContext] instanceof PowerAction) {
                    action = 'com.elgato.philips-hue.power';
                }
                else if (actions[inContext] instanceof ColorAction) {
                    action = 'com.elgato.philips-hue.color';
                }
                else if (actions[inContext] instanceof CycleAction) {
                    action = 'com.elgato.philips-hue.cycle';
                }
                else if (actions[inContext] instanceof BrightnessAction) {
                    action = 'com.elgato.philips-hue.brightness';
                    if(actions[inContext].updateAction) {
                      actions[inContext].updateAction();
                    }
                }
                else if (actions[inContext] instanceof TemperatureAction) {
                  action = 'com.elgato.philips-hue.temperature';
                  if(actions[inContext].updateAction) {
                    actions[inContext].updateAction();
                  }
              }
                else if (actions[inContext] instanceof BrightnessRelAction) {
                    action = 'com.elgato.philips-hue.brightness-rel';
                }
                else if (actions[inContext] instanceof SceneAction) {
                    action = 'com.elgato.philips-hue.scene';
                }

                // Inform PI of new cache
                sendToPropertyInspector(action, inContext, cache.data);
            });
        });
    }, false);

    // Web socked received a message
    websocket.onmessage = inEvent => {
        // Parse parameter from string to object
        let jsonObj = JSON.parse(inEvent.data);

        // Extract payload information
        let event = jsonObj['event'];
        let action = jsonObj['action'];
        let context = jsonObj['context'];
        let jsonPayload = jsonObj['payload'];
        let settings;

        if(event === 'dialRotate') {
          if(actions[context]?.onDialRotate) {
            throttleDialRotate(() => {
              actions[context].onDialRotate(jsonObj);
            });
            // debounceDialRotate(jsonObj);
            // actions[context].onDialRotate(jsonObj);
          }
        } else if(!hasDialPress && event === 'dialUp') {
            if(actions[ context ]?.onDialUp) {
                actions[ context ].onDialUp(jsonObj);
            }
        } else if(!hasDialPress && event === 'dialDown') {
            if(actions[ context ]?.onDialDown) {
                actions[ context ].onDialDown(jsonObj);
            }
        } else if(hasDialPress && event === 'dialPress') {
          if(actions[context]?.onDialPress) {
            actions[context].onDialPress(jsonObj);
          }
        } else if(event === 'touchTap') {
          if(actions[context]?.onTouchTap) {
            actions[context].onTouchTap(jsonObj);
          }
        } else if (event === 'keyUp') {
            settings = jsonPayload['settings'];
            let coordinates = jsonPayload['coordinates'];
            let userDesiredState = jsonPayload['userDesiredState'];
            let state = jsonPayload['state'];

            // Send onKeyUp event to actions
            if (context in actions) {
                actions[context].onKeyUp(context, settings, coordinates, userDesiredState, state);
            }

            // Refresh the cache
            cache.refresh();
        }
        else if (event === 'willAppear') {
            settings = jsonPayload['settings'];

            // If this is the first visible action
            if (Object.keys(actions).length === 0) {
                // Start polling
                cache.startPolling();
            }

            // Add current instance is not in actions array
            if (!(context in actions)) {
                // Add current instance to array
                if (action === 'com.elgato.philips-hue.power') {
                    actions[context] = new PowerAction(context, settings);
                }
                else if (action === 'com.elgato.philips-hue.color') {
                    actions[context] = new ColorAction(context, settings);
                }
                else if (action === 'com.elgato.philips-hue.cycle') {
                    actions[context] = new CycleAction(context, settings);
                }
                else if (action === 'com.elgato.philips-hue.brightness') {
                    actions[context] = new BrightnessAction(context, settings, jsonObj);
                }
                else if (action === 'com.elgato.philips-hue.temperature') {
                  actions[context] = new TemperatureAction(context, settings, jsonObj);
              }
                else if (action === 'com.elgato.philips-hue.brightness-rel') {
                    actions[context] = new BrightnessRelAction(context, settings);
                }
                else if (action === 'com.elgato.philips-hue.scene') {
                    actions[context] = new SceneAction(context, settings);
                }
            }
        }
        else if (event === 'willDisappear') {
            // Remove current instance from array
            if (context in actions) {
                delete actions[context];
            }

            // If this is the last visible action
            if (Object.keys(actions).length === 0) {
                // Stop polling
                cache.stopPolling();
            }
        }
        else if (event === 'didReceiveGlobalSettings') {
            // Set global settings
            globalSettings = jsonPayload['settings'];

            // If at least one action is active
            if (Object.keys(actions).length > 0) {
                // Refresh the cache
                cache.refresh();
            }
        }
        else if (event === 'didReceiveSettings') {
            settings = jsonPayload['settings'];

            // Set settings
            if (context in actions) {
                actions[context].setSettings(settings);
            }

            // Refresh the cache
            cache.refresh();
        }
        else if (event === 'propertyInspectorDidAppear') {
            // Send cache to PI
            sendToPropertyInspector(action, context, cache.data);
        }
        else if (event === 'sendToPlugin') {
            let piEvent = jsonPayload['piEvent'];

            if (piEvent === 'valueChanged') {
                // Only color, brightness and scene support live preview
                if (action !== 'com.elgato.philips-hue.power' && action !== 'com.elgato.philips-hue.cycle') {
                    // Send manual onKeyUp event to action
                    if (context in actions) {
                        actions[context].onKeyUp(context);
                    }
                }
            } else if (piEvent === 'lightsChanged') {
                // console.log("lightsChanged", action, context, jsonPayload);
                if (context in actions) {
                  if(actions[context].updateDisplay) {
                    actions[context].updateDisplay();
                  };
                }
            }
        }
    };
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/philips/cache.js
================================================
/**
@file      cache.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

// Prototype for a data cache
function Cache() {
    // Init Cache
    let instance = this;

    // Refresh time of the cache  in seconds
    let autoRefreshTime = 60;

    // Private timer instance
    let timer = null;

    // Private bridge discovery
    let discovery = null;

    // Public variable containing the cached data
    this.data = {};

    // Private function to discover all bridges on the network
    function buildDiscovery(inCallback) {
        // Check if discovery ran already
        if (discovery != null) {
            inCallback(true);
            return;
        }

        // Init discovery variable to indicate that it ran already
        discovery = {};

        // Run discovery
        Bridge.discover((inSuccess, inBridges) => {
            // If the discovery was not successful
            if (!inSuccess) {
                log(inBridges);
                inCallback(false);
                return;
            }

            // For all discovered bridges
            inBridges.forEach(inBridge => {
                // Add new bridge to discovery object
                discovery[inBridge.getID()] = {
                    ip: inBridge.getIP()
                };
            });

            inCallback(true);
        });
    }

    // Gather all required information by a Bridge via ID
    function refreshBridge(pairedBridgeID, pairedBridge) {
        // Older Bridges in Settings may have the ID stored inside the object
        if (!pairedBridge.id) {
            pairedBridge.id = pairedBridgeID;
        }

        // Older Bridges in Settings may have no IP stored
        if (!pairedBridge.ip) {
            // Trying to receive the IP trough auto-discovery
            if (discovery[pairedBridge.id]) {
                pairedBridge.ip = discovery[pairedBridge.id].ip;
            }

            // If no IP can be found for this Bridge we need to stop here
            else {
                log(`No IP found for paired Bridge ID: ${pairedBridge.id}`);
                return;
            }
        }

        // Create a bridge instance
        let bridge = new Bridge(pairedBridge.ip, pairedBridge.id, pairedBridge.username);

        // Create bridge cache
        let bridgeCache = { 'lights': {}, 'groups': {} };
        bridgeCache.id = bridge.getID();
        bridgeCache.ip = bridge.getIP();
        bridgeCache.username = bridge.getUsername();

        // Load the bridge name
        bridge.getName((inSuccess, inName) => {
            // If getName was not successful
            if (!inSuccess) {
                log(inName);
                return;
            }

            // Save the name
            bridgeCache.name = inName;

            // Add bridge to the cache
            // instance.data[bridge.getID()] = bridgeCache;

            // Request all lights of the bridge
            bridge.getLights((inSuccess, inLights) => {
                // If getLights was not successful
                if (!inSuccess) {
                    log(inLights);
                    return;
                }

                // Create cache for each light
                inLights.forEach(inLight => {
                    // Add light to cache
                    bridgeCache.lights['l-' + inLight.getID()] = {
                        id: inLight.getID(),
                        name: inLight.getName(),
                        type: inLight.getType(),
                        power: inLight.getPower(),
                        brightness: inLight.getBrightness(),
                        xy: inLight.getXY(),
                        temperature: inLight.getTemperature(),
                        originalValue: inLight.originalValue,
                    };
                });

                // Request all groups of the bridge
                bridge.getGroups((inSuccess, inGroups) => {
                    // If getGroups was not successful
                    if (!inSuccess) {
                        log(inGroups);
                        return;
                    }

                    // Create cache for each group
                    inGroups.forEach(inGroup => {
                        // Add group to cache
                        bridgeCache.groups['g-' + inGroup.getID()] = {
                            id: inGroup.getID(),
                            name: inGroup.getName(),
                            type: inGroup.getType(),
                            power: inGroup.getPower(),
                            brightness: inGroup.getBrightness(),
                            xy: inGroup.getXY(),
                            temperature: inGroup.getTemperature(),
                            scenes: {},
                        };

                        // If this is the last group
                        if (Object.keys(bridgeCache.groups).length === inGroups.length) {
                            // Request all scenes of the bridge
                            bridge.getScenes((inSuccess, inScenes) => {
                                // If getScenes was not successful
                                if (!inSuccess) {
                                    log(inScenes);
                                    return;
                                }

                                // Create cache for each scene
                                inScenes.forEach(inScene => {
                                    // Check if this is a group scene
                                    if (inScene.getType() !== 'GroupScene') {
                                        return;
                                    }

                                    // If scenes group is in cache
                                    if ('g-' + inScene.getGroup() in bridgeCache.groups) {
                                        // Add scene to cache
                                        bridgeCache.groups['g-' + inScene.getGroup()].scenes[inScene.getID()] = {
                                            id: inScene.getID(),
                                            name: inScene.getName(),
                                            type: inScene.getType(),
                                            group: inScene.getGroup(),
                                        };
                                    }
                                });
                                // console.log(bridgeCache);
                                instance.data[bridge.getID()] = bridgeCache;
                                // Inform keys that updated cache is available
                                let event = new CustomEvent('newCacheAvailable');
                                document.dispatchEvent(event);
                            });
                        }
                    });
                });
            });
        });
    }

    // Public function to start polling
    this.startPolling = () => {
        // Log to the global log file
        log('Start polling to create cache');

        // Start a timer
        instance.refresh();
        timer = setInterval(instance.refresh, autoRefreshTime * 1000);
    }

    // Public function to stop polling
    this.stopPolling = () => {
        // Log to the global log file
        log('Stop polling to create cache');

        // Invalidate the timer
        clearInterval(timer);
        timer = null;
    }

    this.refresh = Utils.debounce(function () {
        // Build discovery if necessary
        buildDiscovery(() => {
            if (globalSettings.bridges) {
                Object.keys(globalSettings.bridges).forEach(bridgeID => refreshBridge(bridgeID, globalSettings.bridges[bridgeID]));
            }
        })
    }, 200); // avoid multiple calls in a short time

    // Private function to build a cache
    this.refresh2 = () => {
        // Build discovery if necessary
        buildDiscovery(() => {
            if (globalSettings.bridges) {
                Object.keys(globalSettings.bridges).forEach(bridgeID => refreshBridge(bridgeID, globalSettings.bridges[bridgeID]));
            }
        })
    };
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/philips/meethue.js
================================================
/**
@file      meethue.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

const MDEBOUNCEDELAYMS = 80;

// Prototype which represents a Philips Hue bridge
function Bridge(ip = null, id = null, username = null) {
    // Init Bridge
    let instance = this;

    // Public function to pair with a bridge
    this.pair = (callback) => {
        if (ip) {
            let url = `http://${ip}/api`;
            let xhr = new XMLHttpRequest();
            xhr.responseType = 'json';
            xhr.open('POST', url, true);
            xhr.timeout = 2500;
            
            xhr.onload = () => {
                if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                    if (xhr.response !== undefined && xhr.response != null) {
                        let result = xhr.response[0];

                        if ('success' in result) {
                            username = result['success']['username'];
                            callback(true, result);
                        }
                        else {
                            let message = result['error']['description'];
                            callback(false, message);
                        }
                    }
                    else {
                        callback(false, 'Bridge response is undefined or null.');
                    }
                }
                else {
                    callback(false, 'Could not connect to the bridge.');
                }
            };

            xhr.onerror = () => {
                callback(false, 'Unable to connect to the bridge.');
            };

            xhr.ontimeout = () => {
                callback(false, 'Connection to the bridge timed out.');
            };

            let obj = {};
            obj.devicetype = 'stream_deck';
            let data = JSON.stringify(obj);
            xhr.send(data);
        }
        else {
            callback(false, 'No IP address given.');
        }
    };

    // Public function to retrieve the username
    this.getUsername = () => {
        return username;
    };

    // Public function to retrieve the IP address
    this.getIP = () => {
        return ip;
    };

    // Public function to retrieve the ID
    this.getID = () => {
        return id;
    };

    // Public function to retrieve the name
    this.getName = callback => {
        let url = `http://${ip}/api/${username}/config`;
        let xhr = new XMLHttpRequest();
        xhr.responseType = 'json';
        xhr.open('GET', url, true);
        xhr.timeout = 5000;

        xhr.onload = () => {
            if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                let result = xhr.response;

                if (result !== undefined && result != null) {
                    if ('name' in result) {
                        let name = result['name'];
                        callback(true, name);
                    }
                    else {
                        let message = result[0]['error']['description'];
                        callback(false, message);
                    }
                }
                else {
                    callback(false, 'Bridge response is undefined or null.');
                }
            }
            else {
                callback(false, 'Could not connect to the bridge.');
            }
        };

        xhr.onerror = () => {
            callback(false, 'Unable to connect to the bridge.');
        };

        xhr.ontimeout = () => {
            callback(false, 'Connection to the bridge timed out.');
        };

        xhr.send();
    };

    // Private function to retrieve objects
    function getMeetHues(type, callback) {
        let url;

        if (type === 'light') {
            url = `http://${ip}/api/${username}/lights`;
        }
        else if (type === 'group') {
            url = `http://${ip}/api/${username}/groups`;
        }
        else if (type === 'scene') {
            url = `http://${ip}/api/${username}/scenes`;
        }
        else {
            callback(false, 'Type does not exist.');
            return;
        }

        let xhr = new XMLHttpRequest();
        xhr.responseType = 'json';
        xhr.open('GET', url, true);
        xhr.timeout = 5000;
    
        xhr.onload = () => {
            if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                let result = xhr.response;

                if (result !== undefined && result != null) {
                    if (!Array.isArray(result)) {
                        let objects = [];

                        Object.keys(result).forEach(key => {
                            let value = result[key];

                            if (type === 'light') {
                              // console.log("Light", value.name, value.capabilities?.control);
                              let light = new Light(instance, key, value.name, value.type, value.state.on, value.state.bri, value.state.xy, value.state.ct);
                              light.originalValue = value;
                                objects.push(light);
                            }
                            else if (type === 'group') {
                                objects.push(new Group(instance, key, value.name, value.type, value.state.all_on, value.action.bri, value.action.xy, value.action.ct));
                            }
                            else if (type === 'scene') {
                                objects.push(new Scene(instance, key, value.name, value.type, value.group));
                            }
                        });

                        callback(true, objects);
                    }
                    else {
                        let message = result[0]['error']['description'];
                        callback(false, message);
                    }
                }
                else {
                    callback(false, 'Bridge response is undefined or null.');
                }
            }
            else {
                callback(false, 'Unable to get objects of type ' + type + '.');
            }
        };

        xhr.onerror = () => {
            callback(false, 'Unable to connect to the bridge.');
        };

        xhr.ontimeout = () => {
            callback(false, 'Connection to the bridge timed out.');
        };

        xhr.send();
    }

    // Public function to retrieve the lights
    this.getLights = callback => {
        getMeetHues('light', callback);
    };

    // Public function to retrieve the groups
    this.getGroups = callback => {
        getMeetHues('group', callback);
    };

    // Public function to retrieve the scenes
    this.getScenes = callback => {
        getMeetHues('scene', callback);
    };
}

// Static function to discover bridges
Bridge.discover = callback => {
    let url = 'https://discovery.meethue.com';
    let xhr = new XMLHttpRequest();
    xhr.responseType = 'json';
    xhr.open('GET', url, true);
    xhr.timeout = 10000;

    xhr.onload = () => {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
            if (xhr.response !== undefined && xhr.response != null) {
                let bridges = [];

                xhr.response.forEach(bridge => {
                    bridges.push(new Bridge(bridge.internalipaddress, bridge.id));
                });

                callback(true, bridges);
            }
            else {
                callback(false, 'Meethue server response is undefined or null.');
            }
        }
        else {
            callback(false, 'Unable to discover bridges.');
        }
    };

    xhr.onerror = () => {
        callback(false, 'Unable to connect to the internet.');
    };

    xhr.ontimeout = () => {
        callback(false, 'Connection to the internet timed out.');
    };

    xhr.send();
};

// Check if a Bridge is available under a certain IP address
// If a username is set it will check that too
Bridge.check = (ip, username, callback) => {
    let url = username ? `http://${ip}/api/${username}config` : `http://${ip}/api/config`;
    let xhr = new XMLHttpRequest();
    xhr.responseType = 'json';
    xhr.open('GET', url, true);
    xhr.timeout = 10000;

    xhr.onload = () => {
        if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200 &&
            xhr.response !== undefined && xhr.response != null &&
            xhr.response.hasOwnProperty('bridgeid') &&
            (!username || xhr.response.hasOwnProperty('ipaddress'))
        ) {
            // at this point the bridge has been found and added to list
            callback(true, {
                ip: ip,
                id: xhr.response.bridgeid.toLowerCase(),
            });
        }

        callback(false);
    };

    xhr.onerror = xhr.ontimeout = () => {
        callback(false);
    };

    xhr.send();
};

// Static function to convert hex to rgb
Bridge.hex2rgb = inHex => {
    // Remove hash if it exists
    if (inHex.charAt(0) === '#') {
        inHex = inHex.substr(1);
    }

    // Split hex into RGB components
    let rgbArray = inHex.match(/.{1,2}/g);

    // Convert RGB component into decimals
    let red = parseInt(rgbArray[0], 16);
    let green = parseInt(rgbArray[1], 16);
    let blue = parseInt(rgbArray[2], 16);

    return {
        r: red,
        g: green,
        b: blue,
    };
}

// Static function to convert rgb to hex
Bridge.rgb2hex = inRGB => {
    return '#' + ((1 << 24) + (inRGB.r << 16) + (inRGB.g << 8) + inRGB.b).toString(16).slice(1);
}

// Static function to convert rgb to hsv
Bridge.rgb2hsv = inRGB => {
    // Calculate the brightness and saturation value
    let max = Math.max(inRGB.r, inRGB.g, inRGB.b);
    let min = Math.min(inRGB.r, inRGB.g, inRGB.b);
    let d = max - min;
    let s = (max === 0 ? 0 : d / max);
    let v = max / 255;

    // Calculate the hue value
    let h;

    switch (max) {
        case min:
            h = 0;
            break;
        case inRGB.r:
            h = (inRGB.g - inRGB.b) + d * (inRGB.g < inRGB.b ? 6: 0);
            h /= 6 * d;
            break;
        case inRGB.g:
            h = (inRGB.b - inRGB.r) + d * 2;
            h /= 6 * d;
            break;
        case inRGB.b:
            h = (inRGB.r - inRGB.g) + d * 4;
            h /= 6 * d;
            break;
    }

    return {h, s, v};
}

// Static function to convert hsv to rgb
Bridge.hsv2rgb = inHSV => {
    let r = null;
    let g = null;
    let b = null;

    let i = Math.floor(inHSV.h * 6);
    let f = inHSV.h * 6 - i;
    let p = inHSV.v * (1 - inHSV.s);
    let q = inHSV.v * (1 - f * inHSV.s);
    let t = inHSV.v * (1 - (1 - f) * inHSV.s);

    // Calculate red, green and blue
    switch (i % 6) {
        case 0:
            r = inHSV.v;
            g = t;
            b = p;
            break;
        case 1:
            r = q;
            g = inHSV.v;
            b = p;
            break;
        case 2:
            r = p;
            g = inHSV.v;
            b = t;
            break;
        case 3:
            r = p;
            g = q;
            b = inHSV.v;
            break;
        case 4:
            r = t;
            g = p;
            b = inHSV.v;
            break;
        case 5:
            r = inHSV.v;
            g = p;
            b = q;
            break;
    }

    // Convert rgb values to int
    let red = Math.round(r * 255);
    let green = Math.round(g * 255);
    let blue = Math.round(b * 255);

    return {
        r: red,
        g: green,
        b: blue,
    };
}

// Static function to convert hex to hsv
Bridge.hex2hsv = inHex => {
    // Convert hex to rgb
    let rgb = Bridge.hex2rgb(inHex);

    // Convert rgb to hsv
    return Bridge.rgb2hsv(rgb);
}

// Static function to convert hsv to hex
Bridge.hsv2hex = inHSV => {
    // Convert hsv to rgb
    let rgb = Bridge.hsv2rgb(inHSV);

    // Convert rgb to hex
    return Bridge.rgb2hex(rgb);
}

// Static function to convert hex to xy
Bridge.hex2xy = inHex => {
    // Convert hex to rgb
    let rgb = Bridge.hex2rgb(inHex);

    // Concert RGB components to floats
    let red = rgb.r / 255;
    let green = rgb.g / 255;
    let blue = rgb.b / 255;

    // Convert RGB to XY
    let r = red > 0.04045 ? Math.pow(((red + 0.055) / 1.055), 2.4000000953674316) : red / 12.92;
    let g = green > 0.04045 ? Math.pow(((green + 0.055) / 1.055), 2.4000000953674316) : green / 12.92;
    let b = blue > 0.04045 ? Math.pow(((blue + 0.055) / 1.055), 2.4000000953674316) : blue / 12.92;
    let x = r * 0.664511 + g * 0.154324 + b * 0.162028;
    let y = r * 0.283881 + g * 0.668433 + b * 0.047685;
    let z = r * 8.8E-5 + g * 0.07231 + b * 0.986039;

    // Convert XYZ zo XY
    let xy = [x / (x + y + z), y / (x + y + z)];

    if (isNaN(xy[0])) {
      xy[0] = 0.0;
    }

    if (isNaN(xy[1])) {
      xy[1] = 0.0;
    }

    return xy;
};

// Prototype which represents a Philips Hue object
function MeetHue(bridge = null, id = null, name = null, type = null) {
    // Init MeetHue
    let instance = this;

    // Override in child prototype
    let url = null;
    this.originalValue = null;

    // Public function to retrieve the type
    this.getType = () => {
        return type;
    };

    // Public function to retrieve the name
    this.getName = () => {
        return name;
    };

    // Public function to retrieve the ID
    this.getID = () => {
        return id;
    };

    // Public function to retrieve the URL
    this.getURL = () => {
        return url;
    };

    // Public function to set the URL
    this.setURL = inURL => {
        url = inURL;
    }

    // Public function to set light state
    this.setState = (state, callback) => {
        // Check if the URL was set
        if (instance.getURL() == null) {
            callback(false, 'URL is not set.');
            return;
        }

        let xhr = new XMLHttpRequest();
        xhr.responseType = 'json';
        xhr.open('PUT', instance.getURL(), true);
        xhr.timeout = 2500;

        xhr.onload = () => {
            if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                if (xhr.response !== undefined && xhr.response != null) {
                    let result = xhr.response[0];

                    if ('success' in result) {
                        callback(true, result);
                    }
                    else {
                        let message = result['error']['description'];
                        callback(false, message);
                    }
                }
                else {
                    callback(false, 'Bridge response is undefined or null.');
                }
            }
            else {
                callback(false, 'Could not set state.');
            }
        };

        xhr.onerror = () => {
            callback(false, 'Unable to connect to the bridge.');
        };

        xhr.ontimeout = () => {
            callback(false, 'Connection to the bridge timed out.');
        };

        let data = JSON.stringify(state);
        xhr.send(data);
    };
}

// Prototype which represents a scene
function Scene(bridge = null, id = null, name = null, type = null, group = null) {
    // Init Scene
    let instance = this;

    // Inherit from MeetHue
    MeetHue.call(this, bridge, id, name, type);

    // Set the URL
    this.setURL(`http://${bridge.getIP()}/api/${bridge.getUsername()}/groups/0/action`);

    // Public function to retrieve the group
    this.getGroup = () => {
        return group;
    };

    // Public function to set the scene
    this.on = callback => {
        // Define state object
        let state = {};
        state.scene = id;

        // Send new state
        instance.setState(state, callback);
    };
}

// Prototype which represents an illumination
function Illumination(bridge = null, id = null, name = null, type = null, power = null, brightness = null, xy = null, temperature = null) {
    // Init Illumination
    let instance = this;

    // Inherit from MeetHue
    MeetHue.call(this, bridge, id, name, type);

    // Public function to retrieve the power state
    this.getPower = () => {
        return power;
    };

    // Public function to retrieve the brightness
    this.getBrightness = () => {
        return brightness;
    };

    // Public function to retrieve xy
    this.getXY = () => {
        return xy;
    };

    // Public function to retrieve the temperature
    this.getTemperature = () => {
        return temperature;
    };

    // Public function to set the power status of the light
    this.setPower = (power, callback) => {
        // Define state object
        let state = {};
        state.on = power;

        // Send new state
        instance.setState(state, callback);
    };

    // Public function to set the brightness
    this.setBrightness = Utils.debounce((brightness, callback) => {
      // Define state object
      let state = {};
      state.bri = brightness;

      // To modify the brightness, the light needs to be on
      state.on = true;
      // Send new state
      instance.setState(state, callback);
  }, MDEBOUNCEDELAYMS);

    // Public function set the xy value
    this.setXY = (xy, callback) => {
        // Define state object
        let state = {};
        state.xy = xy;

        // To modify the color, the light needs to be on
        state.on = true;

        // Send new state
        instance.setState(state, callback);
    };

    // Public function set the temperature value
    this.setTemperature = Utils.debounce((temperature, callback) => {
        // Define state object
        let state = {};
        state.ct = temperature;

        // To modify the temperature, the light needs to be on
        state.on = true;

        // Send new state
        instance.setState(state, callback);
    }, MDEBOUNCEDELAYMS);
}

// Prototype which represents a light
function Light(bridge = null, id = null, name = null, type = null, power = null, brightness = null, xy = null, temperature = null) {
    // Inherit from Illumination
    Illumination.call(this, bridge, id, name, type, power, brightness, xy, temperature);

    // Set the URL
    this.setURL(`http://${bridge.getIP()}/api/${bridge.getUsername()}/lights/${id}/state`);
}

// Prototype which represents a group
function Group(bridge = null, id = null, name = null, type = null, power = null, brightness = null, xy = null, temperature = null) {
    // Inherit from Illumination
    Illumination.call(this, bridge, id, name, type, power, brightness, xy, temperature);

    // Set the URL
    this.setURL(`http://${bridge.getIP()}/api/${bridge.getUsername()}/groups/${id}/action`);
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/powerAction.js
================================================
/**
@file      powerAction.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

// Prototype which represents a power action
function PowerAction(inContext, inSettings) {
    // Init PowerAction
    let instance = this;

    // Inherit from Action
    Action.call(this, inContext, inSettings);

    // Update the state
    updateState();

    this.updateAction = function() {
      updateState();
    };

    // Public function called on key up event
    this.onKeyUp = (inContext, inSettings, inCoordinates, inUserDesiredState, inState) => {
        const settings = this.getVerifiedSettings(inContext);
        if(false === settings) return;
  
        let bridgeCache = cache.data[settings.bridge];
        // Create a bridge instance
        let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);

        // Create a light or group object
        let objCache, obj;
        if (settings.light.indexOf('l-') !== -1) {
            objCache = bridgeCache.lights[settings.light];
            obj = new Light(bridge, objCache.id);
        }
        else {
            objCache = bridgeCache.groups[settings.light];
            obj = new Group(bridge, objCache.id);
        }

        // Check for multi action
        let targetState;
        if (inUserDesiredState !== undefined) {
            targetState = !inUserDesiredState;
        }
        else {
            targetState = !objCache.power;
        }

        // Set light or group state
        obj.setPower(targetState, (success, error) => {
            if (success) {
                setActionState(inContext, targetState ? 0 : 1);
                objCache.power = targetState;
                cache.refresh();
            }
            else {
                log(error);
                setActionState(inContext, inState);
                showAlert(inContext);
            }
        });
    };

    // Before overwriting parent method, save a copy of it
    let actionNewCacheAvailable = this.newCacheAvailable;

    // Public function called when new cache is available
    this.newCacheAvailable = inCallback => {
        // Call actions newCacheAvailable method
        actionNewCacheAvailable.call(instance, () => {
            // Update the state
            updateState();

            // Call the callback function
            inCallback();
        });
    };

    function updateState() {
        // Get the settings and the context
        let settings = instance.getSettings();
        let context = instance.getContext();

        // Check if any bridge is configured
        if (!('bridge' in settings)) {
            return;
        }

        // Check if the configured bridge is in the cache
        if (!(settings.bridge in cache.data)) {
            return;
        }

        // Find the configured bridge
        let bridgeCache = cache.data[settings.bridge];

        // Check if a light was set for this action
        if (!('light' in settings)) {
            return;
        }

        // Check if the configured light or group is in the cache
        if (!(settings.light in bridgeCache.lights || settings.light in bridgeCache.groups)) {
            return;
        }

        // Find out if it is a light or a group
        let objCache;
        if (settings.light.indexOf('l-') !== -1) {
            objCache = bridgeCache.lights[settings.light];
        }
        else {
            objCache = bridgeCache.groups[settings.light];
        }

        // Set the target state
        let targetState = objCache.power;

        // Set the new action state
        setActionState(context, targetState ? 0 : 1);
    }

    // Private function to set the state
    function setActionState(inContext, inState) {
        setState(inContext, inState);
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/propertyAction.js
================================================
/**
@file      propertyAction.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

// Prototype which represents a brightness action
function PropertyAction(inContext, inSettings, jsn) {

  let instance = this;
  this.keyIsDown = false;
  this.actionTriggered = false;
  const setStateFunction = `set${Utils.capitalize(this.property)}`;

  // Inherit from Action
  Action.call(this, inContext, inSettings, jsn);

  // Set the default values
  setDefaults();

  this.updateAction = function() {
    const target = this.getCurrentLightOrGroup();
    if(target === false) return;
    this.updateDisplay(target.objCache, this.property);
  };

  if(this.isEncoder) {
    let timer = setInterval(() => {
      this.updateAction();
    }, 5000);
  }

  this.getCurrentLightOrGroup = function() {
    let settings = this.getVerifiedSettings(inContext);
    if(settings === false) return false; // break if settings are not valid
    let bridgeCache = cache.data[settings.bridge]; // we have a valid bridge (was checked in getVerifiedSettings)
    let objCache = {};
    let obj = {};
    let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);
    if(settings.light.indexOf('l') !== -1) {
      objCache = bridgeCache.lights[settings.light];
      if(objCache) {
        obj = new Light(bridge, objCache.id);
      }
    }
    else {
      objCache = bridgeCache.groups[settings.light];
      if(objCache) {
        obj = new Group(bridge, objCache.id);
      }
    }
    return {obj, objCache};
  };

  this.setValue = function(inValue, jsn) {
    const target = this.getCurrentLightOrGroup();
    if(target) {
      if(target.objCache.power === false) return;
      let value = inValue ? inValue : target.objCache[this.property];

      if(jsn?.payload?.ticks) {
        let settings = this.getSettings();
        const scaleTicks = settings?.scaleTicks || 1;
        const multiplier = scaleTicks * jsn.payload.ticks;
        value = Utils.minmax(parseInt(value + multiplier * 2.55), 0,255);
        // value = parseInt(value + jsn.payload.ticks * 2.55);
      }

      // just update the panel optimistically
      // note: this didn't work well for me, so I'm not using it
      // this.setFeedback(inContext, parseInt(value / 2.54), 1);

      target.obj[setStateFunction](value, (inSuccess, inError) => {
        if(inSuccess) {
          target.objCache[this.property] = value;
          this.updateDisplay(target.objCache, this.property);
          this.updateAllActions();
        } else {
          log(inError);
          showAlert(inContext);
        }
      });
    }

  };

  this.onDialUp = function(jsn) {
    // console.log('onDialUp', jsn);
    if(this.getVerifiedSettings(inContext) === false) return;
    this.keyIsDown = false;
    if(!this.actionTriggered) {
      if(this.isEncoder) {
        return this.togglePower(inContext);
      }
      const target = this.getCurrentLightOrGroup();
      // check if light is off, and if it is, turn it on
      if(target.objCache.power === false) {
        this.togglePower(inContext);
        this.updateDisplay(target.objCache, 'power');
      } else {
        // otherwise, just change the property to the configured value
        this.onKeyUp(inContext);
      }
    }
  };

  this.onDialDown = function(jsn) {
    // console.log('onDialDown', jsn);
    if(this.getVerifiedSettings(inContext) === false) return;
    // temporarily set a flag to mark that the key is down
    this.keyIsDown = true;
    this.actionTriggered = false;
    setTimeout(function() {
      if(instance.keyIsDown) {
        // console.log("***** long keypress detected:", instance.keyIsDown,inContext);
        const target = instance.togglePower(inContext);
        instance.updateDisplay(target.objCache, 'power');
        instance.actionTriggered = true;
      }
      instance.keyIsDown = false;
    }, 500);
  };

  this.onDialPress = function(jsn) {
    if(this.getVerifiedSettings(inContext) === false) return;
    if(jsn?.payload?.pressed === true) {  // dial pressed == down
      this.onDialDown(jsn);
    } else { // dial released == up
      this.onDialUp(jsn);
    }
  };

  this.onDialRotate = function(jsn) {
    this.setValue(null, jsn);
  };

  this.onTouchTap = function(jsn) {
    this.togglePower(inContext);
  };

  // Public function called on key up event
  this.onKeyUp = (inContext) => {
    const settings = this.getVerifiedSettings(inContext);
    if(settings === false) return;
    // Convert value
    // Hack to circumvent original code that converts values from 0-255
    let value = this.property == 'temperature' ? Number(settings[this.property]) : Math.round(settings[this.property] * 2.54);
    this.setValue(value);
  };

  // Before overwriting parent method, save a copy of it
  let actionNewCacheAvailable = this.newCacheAvailable;

  // Public function called when new cache is available
  this.newCacheAvailable = inCallback => {
    // Call actions newCacheAvailable method
    actionNewCacheAvailable.call(instance, () => {
      // Set defaults
      setDefaults();
      // Call the callback function
      inCallback();
    });
  };

  // Private function to set the defaults
  function setDefaults() {
    // Get the settings and the context
    let settings = instance.getSettings();
    let context = instance.getContext();

    // If property is already set for this action
    if(this.property in settings) {
      return;
    }

    // Set the property to 100
    settings[this.property] = 100;

    // Save the settings
    saveSettings(`com.elgato.philips-hue.${this.property}`, context, settings);
  }

  // update the action and its display
  this.updateActionIfCacheAvailable(inContext);

}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/sceneAction.js
================================================
/**
@file      sceneAction.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

// Prototype which represents a scene action
function SceneAction(inContext, inSettings) {
    // Init SceneAction
    let instance = this;

    // Inherit from Action
    Action.call(this, inContext, inSettings);

    // Set the default values
    setDefaults();

    // Public function called on key up event
    this.onKeyUp = (inContext, inSettings, inCoordinates, inUserDesiredState, inState) => {
       
        const settings = this.getVerifiedSettings(inContext, 'scene');
        if(false === settings) return;
        let bridgeCache = cache.data[settings.bridge];

        // Find the configured group
        let groupCache = bridgeCache.groups[inSettings.light];

        // Check if any scene is configured
        if (!('scene' in inSettings)) {
            log('No scene configured');
            showAlert(inContext);
            return;
        }

        // Check if the configured scene is in the group cache
        if (!(settings.scene in groupCache.scenes)) {
            log(`Scene ${settings.scene} not found in cache`);
            showAlert(inContext);
            return;
        }

        // Find the configured scene
        let sceneCache = groupCache.scenes[inSettings.scene];

        // Create a bridge instance
        let bridge = new Bridge(bridgeCache.ip, bridgeCache.id, bridgeCache.username);

        // Create a scene instance
        let scene = new Scene(bridge, sceneCache.id);

        // Set scene
        scene.on((inSuccess, inError) => {
            // Check if setting the scene was successful
            if (!(inSuccess)) {
                log(inError);
                showAlert(inContext);
            }
        });
    };

    // Before overwriting parent method, save a copy of it
    let actionNewCacheAvailable = this.newCacheAvailable;

    // Public function called when new cache is available
    this.newCacheAvailable = (inCallback) => {
        // Call actions newCacheAvailable method
        actionNewCacheAvailable.call(instance, () => {
            // Set defaults
            setDefaults();

            // Call the callback function
            inCallback();
        });
    };

    // Private function to set the defaults
    function setDefaults() {
        // Get the settings and the context
        let settings = instance.getSettings();
        let context = instance.getContext();

        // Check if any bridge is configured
        if (!('bridge' in settings)) {
            return;
        }

        // Check if the configured bridge is in the cache
        if (!(settings.bridge in cache.data)) {
            return;
        }

        // Find the configured bridge
        let bridgeCache = cache.data[settings.bridge];

        // Check if a light was set for this action
        if (!('light' in settings)) {
            return;
        }

        // Check if the light was set to a group
        if (!(settings.light.indexOf('g-') !== -1)) {
            return;
        }

        // Check if the configured group is in the cache
        if (!(settings.light in bridgeCache.groups)) {
            return;
        }

        // Find the configured group
        let groupCache = bridgeCache.groups[settings.light];

        // Check if a scene was configured for this action
        if ('scene' in settings) {
            // Check if the scene is part of the set group
            if (settings.scene in groupCache.scenes) {
                return;
            }
        }

        // Check if the group has at least one scene
        if (!(Object.keys(groupCache.scenes).length > 0)) {
            return;
        }

        // Sort the scenes alphabetically
        let sceneIDsSorted = Object.keys(groupCache.scenes).sort((a, b) => {
            return groupCache.scenes[a].name.localeCompare(groupCache.scenes[b].name);
        });

        // Set the action automatically to the first one
        settings.scene = sceneIDsSorted[0];

        // Save the settings
        saveSettings('com.elgato.philips-hue.scene', context, settings);
    }
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/temperatureAction.js
================================================
/**
@file      temperatureAction.js
@brief     Philips Hue Plugin
@copyright (c) 2019, Corsair Memory, Inc.
@license   This source code is licensed under the MIT-style license found in the LICENSE file.
*/

/**
 * Color temperature range of Philips lights is: 2200K to 6500K ==  455 to 154 Mired.   //154 is the coolest, 500 is the warmest
 */
const percentOfRange = (value, min = 0, max = 100) => {
  return parseInt((max - min) * (value / 100) + min + 1);
};

function TemperatureAction(inContext, inSettings, jsn) {
  this.property = 'temperature';
  const setStateFunction = `set${Utils.capitalize(this.property)}`;
  // Inherit from PropertyAction
  PropertyAction.call(this, inContext, inSettings, jsn);

  // setValue is sent from the 'keyUp' event and
  // contains the value of the slider (0-100)
  this.setValue = (inValue, jsn) => {
    const target = this.getCurrentLightOrGroup();
    if(target === false) return;
    if(target.objCache.power === false) return;
    const ct = target.objCache?.originalValue?.capabilities?.control?.ct;
    if(!ct) return;
    let value = inValue ? percentOfRange(inValue, ct.min, ct.max) : target.objCache[this.property];
    if(jsn?.payload?.ticks) {
      const settings = this.getSettings();
      const scaleTicks = settings?.scaleTicks || 1;
      const multiplier = scaleTicks * jsn.payload.ticks;
      let addThis = (ct.max - ct.min) * (multiplier / 100);
      addThis = addThis > 0 ? Math.floor(addThis) : Math.ceil(addThis);
      value = Utils.minmax(parseInt(value + addThis), ct.min, ct.max);
    }

    target.obj[setStateFunction](value, (inSuccess, inError) => {
      if(inSuccess) {
        target.objCache[this.property] = value;
        this.updateDisplay(target.objCache, this.property, jsn);
        this.updateAllActions();
      } else {
        log(inError);
        showAlert(inContext);
      }
    });
  };
}


================================================
FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/timers.js
================================================
/* global ESDTimerWorker */
/*eslint no-unused-vars: "off"*/
/*eslint-env es6*/

let ESDTimerWorker = new Worker(URL.createObjectURL(
	new Blob([timerFn.toString().replace(/^[^{]*{\s*/, '').replace(/\s*}[^}]*$/, '')], {type: 'text/javascript'})
));
ESDTimerWorker.timerId = 1;
ESDTimerWorker.timers = {};
const ESDDefaultTimeouts = {
	timeout: 0,
	interval: 10
};

Object.freeze(ESDDefaultTimeouts);

function _setTimer(callback, delay, type, params) {
	const id = ESDTimerWorker.timerId++;
	ESDTimerWorker.timers[id] = {callback, params};
	ESDTimerWorker.onmessage = (e) => {
		if (ESDTimerWorker.timers[e.data.id]) {
			if (e.data.type === 'clearTimer') {
				delete ESDTimerWorker.timers[e.data.id];
			} else {
				const cb = ESDTimerWorker.timers[e.data.id].callback;
				if (cb && typeof cb === 'function') cb(...ESDTimerWorker.timers[e.data.id].params);
			}
		}
	};
	ESDTimerWorker.postMessage({type, id, delay});
	return id;
}

function _setTimeoutESD(...args) {
	let [callback, delay = 0, ...params] = [...args];
	return _setTimer(callback, delay, 'setTimeout', params);
}

function _setIntervalESD(...args) {
	let [callback, delay = 0, ...params] = [...args];
	return _setTimer(callback, delay, 'setInterval', params);
}

function _clearTimeoutESD(id) {
	ESDTimerWorker.postMessage({type: 'clearTimeout', id}); //     ESDTimerWorker.postMessage({type: 'clearInterval', id}); = same thing
	delete ESDTimerWorker.timers[id];
}

window.setTimeout = _setTimeoutESD;
window.setInterval = _setIntervalESD;
window.clearTimeout = _clearTimeoutESD; //timeout and interval share the same timer-pool
window.clearInterval = _clearTimeoutESD;

/** This is our worker-code
 *  It is executed in it's own (global) scope
 *  which is wrapped above @ `let ESDTimerWorker`
 */

function timerFn() {
	/*eslint indent: ["error", 4, { "SwitchCase": 1 }]*/

	let timers = {};
	let debug = false;
	let supportedCommands = ['setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'];

	function log(e) {
		console.log('Worker-Info::Timers', timers);
	}

	function clearTimerAndRemove(id) {
		if (timers[id]) {
			if (debug) console.log('clearTimerAndRemove', id, timers[id], timers);
			clearTimeout(timers[id]);
			delete timers[id];
			postMessage({type: 'clearTimer', id: id});
			if (debug) log();
		}
	}

	onmessage = function (e) {
		// first see, if we have a timer with this id and remove it
		// this automatically fulfils clearTimeout and clearInterval
		supportedCommands.includes(e.data.type) && timers[e.data.id] && 
Download .txt
gitextract_alvhxsyj/

├── .editorconfig
├── .gitignore
├── .hintrc
├── LICENSE
├── README.md
├── Release/
│   └── com.elgato.philips-hue.streamDeckPlugin
└── Sources/
    └── com.elgato.philips-hue.sdPlugin/
        ├── de.json
        ├── en.json
        ├── es.json
        ├── fr.json
        ├── ja.json
        ├── ko.json
        ├── manifest.json
        ├── pi/
        │   ├── css/
        │   │   ├── colorPI.css
        │   │   ├── cyclePI.css
        │   │   ├── pi.css
        │   │   └── sdpi.css
        │   ├── index.html
        │   └── js/
        │       ├── brightnessPI.js
        │       ├── brightnessRelPI.js
        │       ├── colorPI.js
        │       ├── cyclePI.js
        │       ├── main.js
        │       ├── pi.js
        │       ├── powerPI.js
        │       ├── scenePI.js
        │       ├── temperaturePI.js
        │       └── tooltips.js
        ├── plugin/
        │   ├── index.html
        │   └── js/
        │       ├── action.js
        │       ├── brightnessAction.js
        │       ├── brightnessRelAction.js
        │       ├── colorAction.js
        │       ├── cycleAction.js
        │       ├── main.js
        │       ├── philips/
        │       │   ├── cache.js
        │       │   └── meethue.js
        │       ├── powerAction.js
        │       ├── propertyAction.js
        │       ├── sceneAction.js
        │       ├── temperatureAction.js
        │       ├── timers.js
        │       └── utils.js
        ├── setup/
        │   ├── css/
        │   │   └── main.css
        │   ├── index.html
        │   └── js/
        │       ├── discoveryView.js
        │       ├── introView.js
        │       ├── main.js
        │       ├── manualView.js
        │       ├── pairingView.js
        │       └── saveView.js
        └── zh_CN.json
Download .txt
SYMBOL INDEX (52 symbols across 30 files)

FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/brightnessPI.js
  function BrightnessPI (line 8) | function BrightnessPI(inContext, inLanguage, inStreamDeckVersion, inPlug...

FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/brightnessRelPI.js
  function BrightnessRelPI (line 8) | function BrightnessRelPI(inContext, inLanguage, inStreamDeckVersion, inP...

FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/colorPI.js
  function ColorPI (line 8) | function ColorPI(inContext, inLanguage, inStreamDeckVersion, inPluginVer...

FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/cyclePI.js
  function CyclePI (line 8) | function CyclePI(inContext, inLanguage, inStreamDeckVersion, inPluginVer...

FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/main.js
  function connectElgatoStreamDeckSocket (line 21) | function connectElgatoStreamDeckSocket(inPort, inUUID, inRegisterEvent, ...

FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/pi.js
  function PI (line 8) | function PI(inContext, inLanguage, inStreamDeckVersion, inPluginVersion) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/powerPI.js
  function PowerPI (line 8) | function PowerPI(inContext, inLanguage, inStreamDeckVersion, inPluginVer...

FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/scenePI.js
  function ScenePI (line 8) | function ScenePI(inContext, inLanguage, inStreamDeckVersion, inPluginVer...

FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/temperaturePI.js
  function TemperaturePI (line 8) | function TemperaturePI(inContext, inLanguage, inStreamDeckVersion, inPlu...

FILE: Sources/com.elgato.philips-hue.sdPlugin/pi/js/tooltips.js
  function rangeToPercent (line 10) | function rangeToPercent(value, min, max) {
  function initToolTips (line 14) | function initToolTips() {
  function initToolTip (line 23) | function initToolTip(element, tooltip) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/action.js
  function Action (line 9) | function Action(inContext, inSettings, jsn) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/brightnessAction.js
  function BrightnessAction (line 8) | function BrightnessAction(inContext, inSettings, jsn) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/brightnessRelAction.js
  function BrightnessRelAction (line 9) | function BrightnessRelAction(inContext, inSettings) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/colorAction.js
  function ColorAction (line 9) | function ColorAction(inContext, inSettings) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/cycleAction.js
  function CycleAction (line 9) | function CycleAction(inContext, inSettings) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/main.js
  function connectElgatoStreamDeckSocket (line 26) | function connectElgatoStreamDeckSocket(inPort, inPluginUUID, inRegisterE...

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/philips/cache.js
  function Cache (line 9) | function Cache() {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/philips/meethue.js
  constant MDEBOUNCEDELAYMS (line 8) | const MDEBOUNCEDELAYMS = 80;
  function Bridge (line 11) | function Bridge(ip = null, id = null, username = null) {
  function MeetHue (line 453) | function MeetHue(bridge = null, id = null, name = null, type = null) {
  function Scene (line 535) | function Scene(bridge = null, id = null, name = null, type = null, group...
  function Illumination (line 562) | function Illumination(bridge = null, id = null, name = null, type = null...
  function Light (line 639) | function Light(bridge = null, id = null, name = null, type = null, power...
  function Group (line 648) | function Group(bridge = null, id = null, name = null, type = null, power...

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/powerAction.js
  function PowerAction (line 9) | function PowerAction(inContext, inSettings) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/propertyAction.js
  function PropertyAction (line 9) | function PropertyAction(inContext, inSettings, jsn) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/sceneAction.js
  function SceneAction (line 9) | function SceneAction(inContext, inSettings) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/temperatureAction.js
  function TemperatureAction (line 15) | function TemperatureAction(inContext, inSettings, jsn) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/timers.js
  function _setTimer (line 17) | function _setTimer(callback, delay, type, params) {
  function _setTimeoutESD (line 34) | function _setTimeoutESD(...args) {
  function _setIntervalESD (line 39) | function _setIntervalESD(...args) {
  function _clearTimeoutESD (line 44) | function _clearTimeoutESD(id) {
  function timerFn (line 59) | function timerFn() {

FILE: Sources/com.elgato.philips-hue.sdPlugin/plugin/js/utils.js
  function registerPluginOrPI (line 9) | function registerPluginOrPI(inEvent, inUUID) {
  function saveSettings (line 19) | function saveSettings(inAction, inUUID, inSettings) {
  function saveGlobalSettings (line 31) | function saveGlobalSettings(inUUID) {
  function requestGlobalSettings (line 42) | function requestGlobalSettings(inUUID) {
  function logToFile (line 52) | function logToFile(inMessage) {
  function showAlert (line 91) | function showAlert(inUUID) {
  function setState (line 101) | function setState(inContext, inState) {
  function sendToPropertyInspector (line 114) | function sendToPropertyInspector(inAction, inContext, inData) {
  function sendToPlugin (line 126) | function sendToPlugin(inAction, inContext, inData) {
  function setFeedback (line 138) | function setFeedback(inContext, inPayload) {
  function getLocalization (line 149) | function getLocalization(inLanguage, inCallback) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/setup/js/discoveryView.js
  function loadDiscoveryView (line 9) | function loadDiscoveryView() {

FILE: Sources/com.elgato.philips-hue.sdPlugin/setup/js/introView.js
  function loadIntroView (line 9) | function loadIntroView() {

FILE: Sources/com.elgato.philips-hue.sdPlugin/setup/js/main.js
  function setStatusBar (line 18) | function setStatusBar(view) {

FILE: Sources/com.elgato.philips-hue.sdPlugin/setup/js/manualView.js
  function loadManualView (line 9) | function loadManualView() {

FILE: Sources/com.elgato.philips-hue.sdPlugin/setup/js/pairingView.js
  function loadPairingView (line 9) | function loadPairingView() {

FILE: Sources/com.elgato.philips-hue.sdPlugin/setup/js/saveView.js
  function loadSaveView (line 9) | function loadSaveView() {
Condensed preview — 52 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (241K chars).
[
  {
    "path": ".editorconfig",
    "chars": 252,
    "preview": "# EditorConfig is awesome: https://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines wit"
  },
  {
    "path": ".gitignore",
    "chars": 8,
    "preview": "__xx/*\r\n"
  },
  {
    "path": ".hintrc",
    "chars": 167,
    "preview": "{\n  \"extends\": [\n    \"development\"\n  ],\n  \"hints\": {\n    \"axe/forms\": \"off\",\n    \"meta-viewport\": \"off\",\n    \"axe/langua"
  },
  {
    "path": "LICENSE",
    "chars": 1076,
    "preview": "The MIT License\n\nCopyright 2018 Corsair Memory, Inc\n\nPermission is hereby granted, free of charge, to any person obtaini"
  },
  {
    "path": "README.md",
    "chars": 3038,
    "preview": "\n# Philips Hue Plugin for Elgato Stream Deck \n# UPDATE INFORMATION\n\n## There's a new version of the Philips Hue plugin a"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/de.json",
    "chars": 3438,
    "preview": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Steuere deine Philips Hue-Lampen.\",\n  \"com.elg"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/en.json",
    "chars": 3189,
    "preview": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Control your Philips Hue lights.\",\n  \"com.elga"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/es.json",
    "chars": 3298,
    "preview": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Controla tus luces Philips Hue.\",\n  \"com.elgat"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/fr.json",
    "chars": 3446,
    "preview": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Contrôlez vos systèmes d’éclairage Philips Hue"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/ja.json",
    "chars": 2698,
    "preview": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Philips Hueの照明をコントロールします。\",\n  \"com.elgato.phil"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/ko.json",
    "chars": 2605,
    "preview": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"Philips Hue 조명을 조종하세요.\",\n  \"com.elgato.philips"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/manifest.json",
    "chars": 3241,
    "preview": "{\n  \"Actions\": [\n    {\n      \"Icon\": \"plugin/images/actions/power\",\n      \"Name\": \"On / Off\",\n      \"States\": [\n        "
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/css/colorPI.css",
    "chars": 402,
    "preview": "/**\n@file      colorPI.css\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This sourc"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/css/cyclePI.css",
    "chars": 695,
    "preview": "/**\n@file      cyclePI.css\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This sourc"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/css/pi.css",
    "chars": 330,
    "preview": "/**\n@file      pi.css\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source cod"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/css/sdpi.css",
    "chars": 43716,
    "preview": ":root {\n  --sdpi-bgcolor: #2D2D2D;\n  --sdpi-background: #3D3D3D;\n  --sdpi-color: #d8d8d8;\n  --sdpi-bordercolor: #3a3a3a;"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/index.html",
    "chars": 2134,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>com.elgato.philips-hue.pi</title>\n    <meta charset=\"UTF-8\" />\n    <!-- Impor"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/brightnessPI.js",
    "chars": 2486,
    "preview": "/**\n@file      brightnessPI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This s"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/brightnessRelPI.js",
    "chars": 1753,
    "preview": "/**\n@file      brightnessRelPI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   Thi"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/colorPI.js",
    "chars": 3734,
    "preview": "/**\n@file      colorPI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/cyclePI.js",
    "chars": 7440,
    "preview": "/**\n@file      cyclePI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/main.js",
    "chars": 3324,
    "preview": "/**\n@file      main.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source co"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/pi.js",
    "chars": 12083,
    "preview": "/**\n@file      pi.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source code"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/powerPI.js",
    "chars": 382,
    "preview": "/**\n@file      powerPI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/scenePI.js",
    "chars": 4785,
    "preview": "/**\n@file      scenePI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/temperaturePI.js",
    "chars": 2356,
    "preview": "/**\n@file      temperaturePI.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This "
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/pi/js/tooltips.js",
    "chars": 1851,
    "preview": "//==============================================================================\n/**\n@file       tooltips.js\n@brief     "
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/index.html",
    "chars": 796,
    "preview": "<!DOCTYPE HTML>\n<html>\n  <head>\n    <title>com.elgato.philips-hue</title>\n    <meta charset=\"UTF-8\">\n    <!-- Import scr"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/action.js",
    "chars": 8991,
    "preview": "/**\n@file      action.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source "
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/brightnessAction.js",
    "chars": 386,
    "preview": "/**\n@file      brightnessAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   Th"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/brightnessRelAction.js",
    "chars": 4905,
    "preview": "/**\n@file      brightnessRelAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license  "
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/colorAction.js",
    "chars": 4973,
    "preview": "/**\n@file      colorAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This so"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/cycleAction.js",
    "chars": 4708,
    "preview": "/**\n@file      cycleAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This so"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/main.js",
    "chars": 9374,
    "preview": "/**\n@file      main.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source co"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/philips/cache.js",
    "chars": 8265,
    "preview": "/**\n@file      cache.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source c"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/philips/meethue.js",
    "chars": 18807,
    "preview": "/**\n@file      meethue.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/powerAction.js",
    "chars": 3883,
    "preview": "/**\n@file      powerAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This so"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/propertyAction.js",
    "chars": 5828,
    "preview": "/**\n@file      propertyAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/sceneAction.js",
    "chars": 4238,
    "preview": "/**\n@file      sceneAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This so"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/temperatureAction.js",
    "chars": 1885,
    "preview": "/**\n@file      temperatureAction.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   T"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/timers.js",
    "chars": 2946,
    "preview": "/* global ESDTimerWorker */\n/*eslint no-unused-vars: \"off\"*/\n/*eslint-env es6*/\n\nlet ESDTimerWorker = new Worker(URL.cre"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/plugin/js/utils.js",
    "chars": 5310,
    "preview": "/**\n@file      utils.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source c"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/css/main.css",
    "chars": 3679,
    "preview": "/**\n@file      main.css\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source c"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/index.html",
    "chars": 1687,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>com.elgato.philips-hue.setup</title>\n    <meta name='viewport' cont"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/discoveryView.js",
    "chars": 5430,
    "preview": "/**\n@file      discoveryView.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This "
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/introView.js",
    "chars": 1818,
    "preview": "/**\n@file      introView.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This sour"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/main.js",
    "chars": 1845,
    "preview": "/**\n@file      main.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This source co"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/manualView.js",
    "chars": 2745,
    "preview": "/**\n@file      manualView.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This sou"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/pairingView.js",
    "chars": 3343,
    "preview": "/**\n@file      pairingView.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This so"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/setup/js/saveView.js",
    "chars": 1195,
    "preview": "/**\n@file      saveView.js\n@brief     Philips Hue Plugin\n@copyright (c) 2019, Corsair Memory, Inc.\n@license   This sourc"
  },
  {
    "path": "Sources/com.elgato.philips-hue.sdPlugin/zh_CN.json",
    "chars": 2393,
    "preview": "{\n  \"Name\": \"Philips Hue\",\n  \"Category\": \"Philips Hue\",\n  \"Description\": \"控制您的 Philips Hue 灯。\",\n  \"com.elgato.philips-hu"
  }
]

// ... and 1 more files (download for full content)

About this extraction

This page contains the full source code of the elgatosf/streamdeck-philipshue GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 52 files (218.1 KB), approximately 61.6k tokens, and a symbol index with 52 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!