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 +

# 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)

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] &&
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
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.