Add a Scene settings menu entry and editor (#1662)

Closes #1528.

The scene editor allows defining Item — command pairs that are applied when the scene is activated. While designing a scene, it is possible to sync the target state to the current Item state and test the target state command.

Technologically scenes are just rules without triggers, so they can be activated using the „Run Rule, Script, Scene …“ action.

Signed-off-by: Jan N. Klug <github@klug.nrw>
Signed-off-by: Yannick Schaus <github@schaus.net>
pull/1719/head
Yannick Schaus 2023-02-16 22:25:26 +01:00 committed by GitHub
parent b2bd16b43f
commit 5bf98457f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1087 additions and 155 deletions

View File

@ -150,7 +150,7 @@ prev: /docs/ui/components/
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -191,14 +191,14 @@ prev: /docs/ui/components/
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -130,7 +130,7 @@ Button performing an action
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -171,14 +171,14 @@ Button performing an action
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">
@ -289,7 +289,7 @@ Button performing an action
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -330,14 +330,14 @@ Button performing an action
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="taphold_actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="taphold_actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="taphold_actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="taphold_actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="taphold_actionPage" label="Page" context="page">

View File

@ -67,7 +67,7 @@ prev: /docs/ui/components/
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -108,14 +108,14 @@ prev: /docs/ui/components/
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -112,7 +112,7 @@ prev: /docs/ui/components/
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -153,14 +153,14 @@ prev: /docs/ui/components/
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -80,7 +80,7 @@ A regular or expandable cell
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -121,14 +121,14 @@ A regular or expandable cell
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -135,7 +135,7 @@ Display a digital clock in a card
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -176,14 +176,14 @@ Display a digital clock in a card
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -109,7 +109,7 @@ A cell expanding to a color picker
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -150,14 +150,14 @@ A cell expanding to a color picker
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -53,7 +53,7 @@ prev: /docs/ui/components/
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -94,14 +94,14 @@ prev: /docs/ui/components/
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -171,7 +171,7 @@ Display a read-only gauge in a card to visualize a quantifiable item
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -212,14 +212,14 @@ Display a read-only gauge in a card to visualize a quantifiable item
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -89,7 +89,7 @@ Display an openHAB icon
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -130,14 +130,14 @@ Display an openHAB icon
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -101,7 +101,7 @@ Display an image (URL or Image item ) in a card
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -142,14 +142,14 @@ Display an image (URL or Image item ) in a card
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -69,7 +69,7 @@ Displays an image from a URL or an item
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -110,14 +110,14 @@ Displays an image from a URL or an item
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -194,7 +194,7 @@ A cell expanding to a knob control
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -235,14 +235,14 @@ A cell expanding to a knob control
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -70,7 +70,7 @@ Display the state of an item in a card
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -111,14 +111,14 @@ Display the state of an item in a card
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">
@ -229,7 +229,7 @@ Display the state of an item in a card
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -270,14 +270,14 @@ Display the state of an item in a card
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="taphold_actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="taphold_actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="taphold_actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="taphold_actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="taphold_actionPage" label="Page" context="page">

View File

@ -96,7 +96,7 @@ A cell with a big label to show a short item state value
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -137,14 +137,14 @@ A cell with a big label to show a short item state value
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -86,7 +86,7 @@ Display the state of an item in a list
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -127,14 +127,14 @@ Display the state of an item in a list
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -104,7 +104,7 @@ Link performing an action
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -145,14 +145,14 @@ Link performing an action
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -75,7 +75,7 @@ A list item
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -116,14 +116,14 @@ A list item
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -88,7 +88,7 @@ A circle on a map, to represent a radius
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -129,14 +129,14 @@ A circle on a map, to represent a radius
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -72,7 +72,7 @@ An icon on a map
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -113,14 +113,14 @@ An icon on a map
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -175,7 +175,7 @@ A marker on a floor plan
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -216,14 +216,14 @@ A marker on a floor plan
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -141,7 +141,7 @@ A cell expanding to rollershutter controls
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -182,14 +182,14 @@ A cell expanding to rollershutter controls
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -151,7 +151,7 @@ A cell expanding to a big vertical slider
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -192,14 +192,14 @@ A cell expanding to a big vertical slider
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -104,7 +104,7 @@ prev: /docs/ui/components/
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -145,14 +145,14 @@ prev: /docs/ui/components/
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -93,7 +93,7 @@ Displays a video player from a URL or an item
<PropOption value="command" label="Send command" />
<PropOption value="toggle" label="Toggle Item" />
<PropOption value="options" label="Command options" />
<PropOption value="rule" label="Run rule" />
<PropOption value="rule" label="Run scene, script or rule" />
<PropOption value="popup" label="Open popup" />
<PropOption value="popover" label="Open popover" />
<PropOption value="sheet" label="Open sheet" />
@ -134,14 +134,14 @@ Displays a video player from a URL or an item
Comma-separated list of options; if omitted, retrieve the command options from the Item dynamically. Use <code>value=label</code> format to provide a label different than the option.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRule" label="Rule" context="rule">
<PropBlock type="TEXT" name="actionRule" label="Scene, Script or Rule" context="rule">
<PropDescription>
Rule to run
Scene, Script or Rule to run
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionRuleContext" label="Rule Context" context="script">
<PropBlock type="TEXT" name="actionRuleContext" label="Context" context="script">
<PropDescription>
Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.
</PropDescription>
</PropBlock>
<PropBlock type="TEXT" name="actionPage" label="Page" context="page">

View File

@ -19,7 +19,7 @@ export const actionParams = (groupName, paramPrefix) => {
{ value: 'command', label: 'Send command' },
{ value: 'toggle', label: 'Toggle Item' },
{ value: 'options', label: 'Command options' },
{ value: 'rule', label: 'Run rule' },
{ value: 'rule', label: 'Run scene, script or rule' },
{ value: 'popup', label: 'Open popup' },
{ value: 'popover', label: 'Open popover' },
{ value: 'sheet', label: 'Open sheet' },
@ -53,11 +53,11 @@ export const actionParams = (groupName, paramPrefix) => {
.v((value, configuration, configDescription, parameters) => {
return ['options'].indexOf(configuration[paramPrefix + 'action']) >= 0
}),
pt(paramPrefix + 'actionRule', 'Rule', 'Rule to run').c('rule')
pt(paramPrefix + 'actionRule', 'Scene, Script or Rule', 'Scene, Script or Rule to run').c('rule')
.v((value, configuration, configDescription, parameters) => {
return ['rule'].indexOf(configuration[paramPrefix + 'action']) >= 0
}),
pt(paramPrefix + 'actionRuleContext', 'Rule Context', 'Object representing the optional context to pass to the rule. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.').c('script')
pt(paramPrefix + 'actionRuleContext', 'Context', 'Object representing the optional context to pass. Edit in YAML or provide a JSON object, e.g. <code>{ "param1": "value1", "param2": { "subkey1": "testing", "subkey2": 123 } }</code>.').c('script')
.v((value, configuration, configDescription, parameters) => {
return ['rule'].indexOf(configuration[paramPrefix + 'action']) >= 0
}),

View File

@ -26,6 +26,9 @@
"rules.title": "No rules yet",
"rules.text": "Rules are the basic building blocks to automate your home - they define which actions to perform when certain events occur.<br><br>Create your first rule with the button below; for more advanced scenarios, you can also write script files in your configuration folder.",
"scenes.title": "No scenes yet",
"scenes.text": "Scenes are rules sending commands to restore a set of items to a desired state.<br />You can run them on-demand or call them from other rules.<br><br>Create your first scene with the button below.",
"scripts.title": "No scripts yet",
"scripts.text": "Scripts are a special kind of rules, with no triggers and a single action module to execute code.<br />You can run them on-demand or call them from other rules.<br><br>Create your first script with the button below.",

View File

@ -58,6 +58,10 @@
:class="{ currentsection: currentUrl.indexOf('/settings/rules') >= 0 }">
<f7-icon slot="media" f7="wand_stars" color="gray" />
</f7-list-item>
<f7-list-item v-if="$store.getters.apiEndpoint('rules')" link="/settings/scenes/" title="Scenes" view=".view-main" panel-close :animate="false" no-chevron
:class="{ currentsection: currentUrl.indexOf('/settings/scenes') >= 0 }">
<f7-icon slot="media" f7="film" color="gray" />
</f7-list-item>
<f7-list-item v-if="$store.getters.apiEndpoint('rules')" link="/settings/scripts/" title="Scripts" view=".view-main" panel-close :animate="false" no-chevron
:class="{ currentsection: currentUrl.indexOf('/settings/scripts') >= 0 }">
<f7-icon slot="media" f7="doc_plaintext" color="gray" />

View File

@ -3,9 +3,21 @@
<f7-list-item :title="title || 'Rule'" smart-select :smart-select-params="smartSelectParams" v-if="ready" ref="smartSelect">
<select :name="name" :multiple="multiple" @change="select" :required="required">
<option v-if="!multiple" value="" />
<option v-for="rule in rules" :value="rule.uid" :key="rule.uid" :selected="(multiple) ? value && value.indexOf(rule.uid) >= 0 : value === rule.uid">
{{ rule.name }}
</option>
<optgroup v-if="scenes.length > 0" label="Scenes">
<option v-for="rule in scenes" :value="rule.uid" :key="rule.uid" :selected="(multiple) ? value && value.indexOf(rule.uid) >= 0 : value === rule.uid">
{{ rule.name }}
</option>
</optgroup>
<optgroup v-if="scripts.length > 0" label="Scripts">
<option v-for="rule in scripts" :value="rule.uid" :key="rule.uid" :selected="(multiple) ? value && value.indexOf(rule.uid) >= 0 : value === rule.uid">
{{ rule.name }}
</option>
</optgroup>
<optgroup v-if="rules.length > 0" label="Rules">
<option v-for="rule in rules" :value="rule.uid" :key="rule.uid" :selected="(multiple) ? value && value.indexOf(rule.uid) >= 0 : value === rule.uid">
{{ rule.name }}
</option>
</optgroup>
</select>
</f7-list-item>
<!-- for placeholder purposes before items are loaded -->
@ -19,6 +31,8 @@ export default {
data () {
return {
ready: false,
scenes: [],
scripts: [],
rules: [],
icons: {},
smartSelectParams: {
@ -33,8 +47,18 @@ export default {
created () {
this.smartSelectParams.closeOnSelect = !(this.multiple)
// TODO use a Vuex store
this.$oh.api.get('/rest/rules').then((data) => {
this.rules = data.sort((a, b) => {
this.$oh.api.get('/rest/rules?summary=true').then((data) => {
this.scenes = data.filter((r) => r.tags.indexOf('Scene') >= 0).sort((a, b) => {
const labelA = a.name
const labelB = b.name
return labelA.localeCompare(labelB)
})
this.scripts = data.filter((r) => r.tags.indexOf('Script') >= 0).sort((a, b) => {
const labelA = a.name
const labelB = b.name
return labelA.localeCompare(labelB)
})
this.rules = data.filter((r) => r.tags.indexOf('Scene') < 0 && r.tags.indexOf('Script') < 0).sort((a, b) => {
const labelA = a.name
const labelB = b.name
return labelA.localeCompare(labelB)

View File

@ -34,7 +34,7 @@ export default {
created () {
this.smartSelectParams.closeOnSelect = !(this.multiple)
// TODO use a Vuex store
this.$oh.api.get('/rest/things').then((data) => {
this.$oh.api.get('/rest/things?summary=true').then((data) => {
this.things = data.sort((a, b) => {
const labelA = a.label
const labelB = b.label

View File

@ -11,7 +11,7 @@ function getModuleTypes (cm, section) {
})
}
function hintItems (cm, line, replaceAfterColon, addStatePropertySuffix) {
function hintItems (cm, line, replaceAfterColon, replaceAfterLastSpace, addColonSuffix) {
const cursor = cm.getCursor()
if (!cm.state.$oh) return
const promise = (itemsCache) ? Promise.resolve(itemsCache) : cm.state.$oh.api.get('/rest/items')
@ -20,7 +20,7 @@ function hintItems (cm, line, replaceAfterColon, addStatePropertySuffix) {
let ret = {
list: data.map((item) => {
return {
text: item.name + ((addStatePropertySuffix ? '.state' : '')),
text: item.name + ((addColonSuffix ? ': ' : '')),
displayText: item.name,
description: `${(item.label) ? item.label + ' ' : ''}(${item.type})<br />${item.state}`
}
@ -32,6 +32,11 @@ function hintItems (cm, line, replaceAfterColon, addStatePropertySuffix) {
ret.from = { line: cursor.line, ch: colonPos + 2 }
ret.to = { line: cursor.line, ch: line.length }
}
if (replaceAfterLastSpace) {
const lastSpacePos = line.lastIndexOf(' ')
ret.from = { line: cursor.line, ch: lastSpacePos + 1 }
ret.to = { line: cursor.line, ch: line.length }
}
addTooltipHandlers(cm, ret)
return ret
})
@ -144,9 +149,18 @@ function buildModuleStructure (cm, moduleType) {
return ret
}
function hintSceneItems (cm, line, parentNr) {
console.info('hinting in the items section (scenes)')
return hintItems(cm, line, false, true, true)
}
function hintModuleStructure (cm, line, parentLineNr) {
const cursor = cm.getCursor()
const section = cm.getLine(parentLineNr).replace('s:', '').trim()
if (section === 'item') {
if (line.indexOf(':') < 0) return hintSceneItems(cm, line, parentLineNr)
return // todo: hint commands?
}
return getModuleTypes(cm, section).then((moduleTypes) => {
let completions = moduleTypes.map((m) => {
return {

View File

@ -47,7 +47,7 @@ export function isComponent (line) {
export function isRuleSection (line) {
if (!line) return false
return line.match(/^(triggers|conditions|actions):/)
return line.match(/^(triggers|conditions|actions|items):/)
}
export function isChannelsSection (line) {

View File

@ -43,6 +43,7 @@ const PageEditors = {
const RulesListPage = () => import(/* webpackChunkName: "admin-rules" */ '../pages/settings/rules/rules-list.vue')
const RuleEditPage = () => import(/* webpackChunkName: "admin-rules" */ '../pages/settings/rules/rule-edit.vue')
const SceneEditPage = () => import(/* webpackChunkName: "admin-rules" */ '../pages/settings/rules/scene/scene-edit.vue')
const ScriptEditPage = () => import(/* webpackChunkName: "admin-rules" */ '../pages/settings/rules/script/script-edit.vue')
const SchedulePage = () => import(/* webpackChunkName: "admin-schedule" */ '../pages/settings/schedule/schedule.vue')
@ -212,6 +213,17 @@ export default [
}
]
},
{
path: 'scenes/',
async: loadAsync(RulesListPage, { showScenes: true }),
routes: [
{
path: ':ruleId',
beforeLeave: checkDirtyBeforeLeave,
async: loadAsync(SceneEditPage, (routeTo) => (routeTo.params.ruleId === 'add') ? { createMode: true } : {})
}
]
},
{
path: 'scripts/',
async: loadAsync(RulesListPage, { showScripts: true }),

View File

@ -18,7 +18,7 @@
</f7-nav-right>
</f7-navbar>
<f7-block v-if="ruleModule" class="no-margin no-padding">
<f7-col class="margin-top">
<f7-col v-if="!isSceneModule" class="margin-top">
<f7-list inline-labels no-hairlines-md class="no-margin">
<f7-list-input type="text" :placeholder="moduleTitleSuggestion" :value="ruleModule.label" required
@input="ruleModule.label = $event.target.value" clear-button />
@ -46,7 +46,7 @@
<condition-module-wizard v-else-if="!advancedTypePicker && currentSection === 'conditions'" :current-module="ruleModule" :current-module-type="currentRuleModuleType" @typeSelect="setModuleType" @showAdvanced="advancedTypePicker = true" @startScript="startScripting" />
<action-module-wizard v-else-if="!advancedTypePicker && currentSection === 'actions'" :current-module="ruleModule" :current-module-type="currentRuleModuleType" @typeSelect="setModuleType" @showAdvanced="advancedTypePicker = true" @startScript="startScripting" />
</div>
<f7-list v-if="ruleModule.type && (!ruleModule.new || advancedTypePicker)">
<f7-list v-if="!isSceneModule && ruleModule.type && (!ruleModule.new || advancedTypePicker)">
<f7-list-item :title="sectionLabels[currentSection][0]" ref="ruleModuleTypeSmartSelect" smart-select :smart-select-params="{ view: $f7.views.main, openIn: 'popup', closeOnSelect: true }">
<select name="ruleModuleType"
@change="setModuleType(moduleTypes[currentSection].find((t) => t.uid === $refs.ruleModuleTypeSmartSelect.f7SmartSelect.getValue()), true)">
@ -59,7 +59,7 @@
</select>
</f7-list-item>
</f7-list>
<f7-block-title v-if="ruleModule && currentRuleModuleType && (!ruleModule.new || advancedTypePicker)" style="margin-bottom: calc(var(--f7-block-title-margin-bottom) - var(--f7-list-margin-vertical))">
<f7-block-title v-if="!isSceneModule && ruleModule && currentRuleModuleType && (!ruleModule.new || advancedTypePicker)" style="margin-bottom: calc(var(--f7-block-title-margin-bottom) - var(--f7-list-margin-vertical))">
Configuration
</f7-block-title>
<f7-col v-if="ruleModule && currentRuleModuleType && (!ruleModule.new || advancedTypePicker)">
@ -90,7 +90,7 @@ export default {
ActionModuleWizard,
ConfigSheet
},
props: ['rule', 'ruleModule', 'ruleModuleType', 'moduleTypes', 'currentSection'],
props: ['rule', 'ruleModule', 'ruleModuleType', 'moduleTypes', 'currentSection', 'isSceneModule'],
data () {
return {
currentRuleModuleType: this.ruleModuleType,

View File

@ -74,9 +74,13 @@
<f7-block-title v-if="showScripts" class="searchbar-hide-on-search">
{{ rules.length }} scripts
</f7-block-title>
<f7-block-title v-else-if="showScenes" class="searchbar-hide-on-search">
{{ rules.length }} scenes
</f7-block-title>
<f7-block-title v-else class="searchbar-hide-on-search">
{{ rules.length }} rules
</f7-block-title>
<f7-list
v-show="rules.length > 0"
class="searchbar-found col rules-list"
@ -98,10 +102,10 @@
:title="rule.name"
:text="rule.uid"
:footer="rule.description"
:badge="ruleStatusBadgeText(rule.status)"
:badge="showScenes ? '' : ruleStatusBadgeText(rule.status)"
:badge-color="ruleStatusBadgeColor(rule.status)">
<div slot="footer">
<f7-chip v-for="tag in rule.tags.filter((t) => t !== 'Script')" :key="tag" :text="tag" media-bg-color="blue" style="margin-right: 6px">
<f7-chip v-for="tag in rule.tags.filter((t) => t !== 'Script' && t !== 'Scene')" :key="tag" :text="tag" media-bg-color="blue" style="margin-right: 6px">
<f7-icon slot="media" ios="f7:tag_fill" md="material:label" aurora="f7:tag_fill" />
</f7-chip>
</div>
@ -114,6 +118,7 @@
</f7-block>
<f7-block v-if="ready && !noRuleEngine && !rules.length" class="service-config block-narrow">
<empty-state-placeholder v-if="showScripts" icon="doc_plaintext" title="scripts.title" text="scripts.text" />
<empty-state-placeholder v-else-if="showScenes" icon="film" title="scenes.title" text="scenes.text" />
<empty-state-placeholder v-else icon="wand_stars" title="rules.title" text="rules.text" />
</f7-block>
<f7-fab v-show="ready && !showCheckboxes" position="right-bottom" slot="fixed" color="blue" href="add">
@ -128,7 +133,7 @@ import RuleStatus from '@/components/rule/rule-status-mixin'
export default {
mixins: [RuleStatus],
props: ['showScripts'],
props: ['showScripts', 'showScenes'],
components: {
'empty-state-placeholder': () => import('@/components/empty-state-placeholder.vue')
},
@ -166,7 +171,14 @@ export default {
this.loading = true
this.$set(this, 'selectedItems', [])
this.showCheckboxes = false
this.$oh.api.get('/rest/rules?summary=true' + (this.showScripts ? '&tags=Script' : '')).then(data => {
let filter = ''
if (this.showScripts) {
filter = '&tags=Script'
}
if (this.showScenes) {
filter = '&tags=Scene'
}
this.$oh.api.get('/rest/rules?summary=true' + filter).then(data => {
this.rules = data.sort((a, b) => {
return a.name.localeCompare(b.name)
})
@ -175,6 +187,10 @@ export default {
this.rules = this.rules.filter((r) => !r.tags || r.tags.indexOf('Script') < 0)
}
if (!this.showScenes) {
this.rules = this.rules.filter((r) => !r.tags || r.tags.indexOf('Scene') < 0)
}
this.loading = false
this.ready = true
setTimeout(() => {

View File

@ -0,0 +1,242 @@
<template>
<f7-popup ref="sceneItemPopup" class="sceneitemconfig-popup" close-on-escape @popup:open="itemConfigOpened" @popup:closed="itemConfigClosed">
<f7-page>
<f7-navbar>
<f7-nav-left>
<f7-link icon-ios="f7:arrow_left" icon-md="material:arrow_back" icon-aurora="f7:arrow_left" popup-close />
</f7-nav-left>
<f7-nav-title>
Configure Item:
{{ itemName }}
</f7-nav-title>
<f7-nav-right>
<f7-link @click="updateItemConfig" popup-close>
Done
</f7-link>
</f7-nav-right>
</f7-navbar>
<f7-toolbar bottom>
<f7-link class="left" icon-f7="arrow_uturn_left_circle" @click="updateCommandFromCurrentState">
Set to current state
</f7-link>
<f7-link class="right" icon-f7="arrowtriangle_right_circle" @click="testCommand">
Test command
</f7-link>
</f7-toolbar>
<f7-block class="no-padding">
<f7-col v-if="ready">
<f7-list no-hairlines-md>
<f7-list-input
label="Command"
floating-label
:value="command"
@input="command = $event.target.value"
type="text" />
<ul v-if="commandSuggestions.length">
<f7-list-item radio :checked="command === suggestion.command" v-for="suggestion in commandSuggestions" :key="suggestion.command"
:title="suggestion.label" @click="command = suggestion.command" />
</ul>
</f7-list>
</f7-col>
</f7-block>
<f7-block v-show="control" class="scene-item-control" strong>
<f7-col>
<div v-show="control === 'colorpicker'" class="scene-item-control-colorpicker" ref="colorpicker" />
<div v-if="control === 'toggle'" class="scene-item-control-toggle">
<f7-toggle :checked="command === 'ON'" @toggle:change="(value) => command = (value) ? 'ON' : 'OFF'" />
</div>
<div v-else-if="control === 'slider'" class="scene-item-control-slider">
<f7-range v-bind="sliderConfig" :value="command" @range:change="command = $event.toString()" />
</div>
<div v-else-if="control === 'rollershutter'" class="scene-item-control-rollershutter">
<f7-segmented round outline strong class="rollershutter-controls">
<f7-button @click="command = 'UP'" large icon-f7="arrowtriangle_left" icon-size="24" icon-color="gray" />
<f7-button @click="command = 'STOP'" large icon-f7="stop" icon-size="24" icon-color="red" />
<f7-button @click="command = 'DOWN'" large icon-f7="arrowtriangle_right" icon-size="24" icon-color="gray" />
</f7-segmented>
</div>
</f7-col>
</f7-block>
</f7-page>
</f7-popup>
</template>
<style lang="stylus">
.scene-item-control
.scene-item-control-toggle
text-align center
padding-top calc(var(--f7-toggle-width))
padding-bottom calc(var(--f7-toggle-width))
.toggle
transform scale(2) rotate(-90deg)
transform-origin center
.scene-item-control-slider
width 100%
height: 300px
display flex
flex-direction column
justify-content center
--f7-range-bar-size 150px
--f7-range-knob-size 0px
--f7-range-label-size 60px
--f7-range-label-font-size 40px
.scene-item-control-rollershutter
width 100%
height 200px
display flex
flex-direction column
justify-content center
.rollershutter-controls
width 150px
transform rotate(90deg)
transform-origin center
</style>
<script>
export default {
components: {
},
props: ['rule', 'module'],
data () {
return {
ready: false,
itemName: null,
item: null,
command: null,
colorpicker: null,
control: null
}
},
methods: {
itemConfigOpened () {
this.itemName = this.module.configuration.itemName
this.command = this.module.configuration.command
this.$oh.api.get('/rest/items/' + this.itemName).then((item) => {
this.item = item
this.initializeControl()
this.ready = true
})
},
itemConfigClosed () {
if (this.colorpicker) this.colorpicker.destroy()
this.$f7.emit('sceneItemConfigClosed')
this.$emit('closed')
},
updateItemConfig () {
if (this.colorpicker) this.colorpicker.destroy()
this.$f7.emit('sceneItemConfigUpdate', [this.itemName, this.command])
this.$emit('update', [this.itemName, this.command])
this.itemConfigClosed()
},
updateCommandFromCurrentState () {
this.$oh.api.getPlain('/rest/items/' + this.itemName + '/state?metadata=semantics,widget').then((state) => {
this.$set(this, 'command', state)
this.$f7.toast.create({
text: `Updated desired state of ${this.itemName} to ${state}`,
destroyOnClose: true,
closeTimeout: 2000
}).open()
})
},
testCommand () {
this.$oh.api.postPlain('/rest/items/' + this.itemName, this.command, 'text/plain', 'text/plain').then((state) => {
this.$f7.toast.create({
text: `Sent comment ${this.command} to ${this.itemName}`,
destroyOnClose: true,
closeTimeout: 2000
}).open()
})
},
initializeControl () {
if (this.item.commandDescription && this.item.commandDescription.commandOptions) return // no control if command options
if (this.item.type === 'Color' || this.item.groupType === 'Color') {
this.control = 'colorpicker'
const vm = this
this.$nextTick(() => {
this.colorpicker = this.$f7.colorPicker.create(Object.assign({}, this.config, {
containerEl: this.$refs.colorpicker,
modules: ['wheel'],
value: (this.command.split(',').length === 3) ? { hsb: this.color } : null,
on: {
change (colorpicker, value) {
let command = [...value.hsb]
command[0] = Math.round(command[0]) % 360
command[1] = Math.round(command[1] * 100)
command[2] = Math.round(command[2] * 100)
command = command.join(',')
vm.command = command
}
}
}))
})
} else if (this.item.type === 'Switch' || this.item.groupType === 'Switch') {
this.control = 'toggle'
} else if (this.item.type === 'Dimmer' || this.item.groupType === 'Dimmer') {
this.control = 'slider'
} else if (this.item.type === 'Rollershutter' || this.item.groupType === 'Rollershutter') {
this.control = 'rollershutter'
} else if (this.item.type === 'Number' || this.item.groupType === 'Number') {
if (this.item.tags.find((t) => [
'ColorTemperature',
'Temperature',
'Brightness',
'Level',
'SoundVolume',
'Setpoint'
].includes(t))) {
this.control = 'slider'
}
}
}
},
computed: {
commandSuggestions () {
if (!this.item) return []
let type = (this.item.type === 'Group' && this.item.groupType) ? this.item.groupType : this.item.type
if (this.item.commandDescription && this.item.commandDescription.commandOptions) {
return this.item.commandDescription.commandOptions
}
if (type === 'Switch') {
return ['ON', 'OFF'].map((c) => { return { command: c, label: c } })
}
if (type === 'Rollershutter') {
return ['UP', 'DOWN', 'STOP'].map((c) => { return { command: c, label: c } })
}
if (type === 'Contact') {
return ['UP', 'DOWN', 'STOP'].map((c) => { return { command: c, label: c } })
}
if (type === 'Color') {
return ['ON', 'OFF'].map((c) => { return { command: c, label: c } })
}
return []
},
color () {
if (this.item.type === 'Color' && this.command && this.command.split(',').length === 3) {
let color = this.command.split(',')
color[0] = parseInt(color[0])
color[1] = color[1] / 100
color[2] = color[2] / 100
return color
}
return null
},
sliderConfig () {
if (!this.item) return {}
const sd = this.item.stateDescription || { minimum: 0, maximum: 100, step: 1 }
return {
vertical: true,
label: true,
scale: true,
min: sd.minimum,
max: sd.maximum,
step: sd.step
}
}
}
}
</script>

View File

@ -0,0 +1,608 @@
<template>
<f7-page @page:afterin="onPageAfterIn" @page:afterout="onPageAfterOut">
<f7-navbar :title="(createMode) ? 'Create scene' : rule.name" back-link="Back" no-hairline>
<f7-nav-right v-if="isEditable">
<f7-link @click="save()" v-if="$theme.md" icon-md="material:save" icon-only />
<f7-link @click="save()" v-if="!$theme.md">
Save<span v-if="$device.desktop">&nbsp;(Ctrl-S)</span>
</f7-link>
</f7-nav-right>
</f7-navbar>
<f7-toolbar tabbar position="top">
<f7-link @click="switchTab('design', fromYaml)" :tab-link-active="currentTab === 'design'" class="tab-link">
Design
</f7-link>
<f7-link @click="switchTab('code', toYaml)" :tab-link-active="currentTab === 'code'" class="tab-link">
Code
</f7-link>
</f7-toolbar>
<f7-tabs class="scene-editor-tabs">
<f7-tab id="design" @tab:show="() => this.currentTab = 'design'" :tab-active="currentTab === 'design'">
<f7-block v-if="ready && rule.status && !createMode" class="block-narrow padding-left padding-right" strong>
<f7-col v-if="!createMode">
<div class="float-right align-items-flex-start align-items-center">
<!-- <f7-toggle class="enable-toggle"></f7-toggle> -->
<f7-link :icon-color="(rule.status.statusDetail === 'DISABLED') ? 'orange' : 'gray'" :tooltip="((rule.status.statusDetail === 'DISABLED') ? 'Enable' : 'Disable') + (($device.desktop) ? ' (Ctrl-D)' : '')" icon-ios="f7:pause_circle" icon-md="f7:pause_circle" icon-aurora="f7:pause_circle" icon-size="32" color="orange" @click="toggleDisabled" />
<f7-link :tooltip="'Run Now' + (($device.desktop) ? ' (Ctrl-R)' : '')" icon-ios="f7:play_round" icon-md="f7:play_round" icon-aurora="f7:play_round" icon-size="32" color="blue" @click="runNow" />
</div>
Status:
<f7-chip class="margin-left"
:text="rule.status.status"
:color="ruleStatusBadgeColor(rule.status)" />
<div>
<strong>{{ (rule.status.statusDetail !== 'NONE') ? rule.status.statusDetail : '&nbsp;' }}</strong>
<br>
<div v-if="rule.status.description">
{{ rule.status.description }}
</div>
</div>
</f7-col>
</f7-block>
<!-- skeletons for not ready -->
<f7-block v-else-if="!createMode" class="block-narrow padding-left padding-right skeleton-text skeleton-effect-blink" strong>
<f7-col>
______:
<f7-chip class="margin-left" text="________" />
<div>
<strong>____ _______</strong>
<br>
</div>
</f7-col>
</f7-block>
<!-- skeletons -->
<f7-block v-if="!ready" class="block-narrow">
<f7-col class="skeleton-text skeleton-effect-blink">
<f7-list inline-labels no-hairlines-md>
<f7-list-input label="Unique ID" type="text" placeholder="Required" value="_______" required validate
:disabled="true" :info="(createMode) ? 'Note: cannot be changed after the creation' : ''"
@input="rule.uid = $event.target.value" :clear-button="createMode" />
<f7-list-input label="Name" type="text" placeholder="Required" required validate
:disabled="true" @input="rule.name = $event.target.value" :clear-button="isEditable" />
<f7-list-input label="Description" type="text" value="__ _____ ___ __ ___"
:disabled="true" @input="rule.description = $event.target.value" :clear-button="isEditable" />
</f7-list>
</f7-col>
</f7-block>
<f7-block v-else class="block-narrow">
<f7-col>
<f7-list inline-labels no-hairlines-md>
<f7-list-input label="Unique ID" type="text" placeholder="Required" :value="rule.uid" required validate
:disabled="!createMode"
:info="(createMode) ? 'Note: cannot be changed after the creation' : ''"
pattern="[A-Za-z0-9_\-]+" error-message="Required. A-Z,a-z,0-9,_,- only"
@input="rule.uid = $event.target.value" :clear-button="createMode" />
<f7-list-input label="Name" type="text" placeholder="Required" :value="rule.name" required validate
:disabled="!isEditable" @input="rule.name = $event.target.value" :clear-button="isEditable" />
<f7-list-input label="Description" type="text" :value="rule.description"
:disabled="!isEditable" @input="rule.description = $event.target.value"
:clear-button="isEditable" />
</f7-list>
</f7-col>
<f7-block-footer v-if="!isEditable" class="no-margin padding-left">
<f7-icon f7="lock_fill" size="12" color="gray" />&nbsp;Note: this rule is not editable because it has been
provisioned from a file.
</f7-block-footer>
<!-- <f7-col v-if="isEditable" class="text-align-right justify-content-flex-end">
</f7-col> -->
<f7-col class="rule-modules">
<div class="no-padding float-right" v-if="rule['actions'].length > 0">
<f7-button @click="toggleModuleControls" small outline :fill="showModuleControls" sortable-toggle=".sortable"
style="margin-top: -3px; margin-right: 5px"
color="gray" icon-size="12" icon-ios="material:wrap_text" icon-md="material:wrap_text"
icon-aurora="material:wrap_text">
&nbsp;Reorder
</f7-button>
</div>
<div>
<f7-block-title medium style="margin-bottom: var(--f7-list-margin-vertical)">
Configuration
</f7-block-title>
<f7-list class="scene-items" sortable swipeout media-list @sortable:sort="(ev) => reorderModule(ev, 'actions')">
<f7-list-item :title="mod.configuration.itemName" media
v-for="mod in rule['actions']" :key="mod.id"
:link="!showModuleControls"
@click.native="(ev) => editModule(ev, mod)" swipeout no-chevron>
<f7-link slot="media" icon-color="red" icon-aurora="f7:minus_circle_filled"
icon-ios="f7:minus_circle_filled" icon-md="material:remove_circle_outline"
@click="showSwipeout" />
<span slot="inner" class="inline-command-input">
<f7-input type="text" outline
:value="mod.configuration.command"
@input="updateActionModule([mod.configuration.itemName, $event.target.value])"
:disabled="showModuleControls" />
</span>
<span slot="after">
<f7-link icon-f7="arrow_uturn_left_circle"
class="margin-left-half" color="blue" tooltip="Set to current state"
@click.native="(ev) => updateCommandFromCurrentState(ev, mod)" />
<f7-link icon-f7="arrowtriangle_right_circle"
class="margin-left-half" color="blue" tooltip="Test command"
@click.native="(ev) => testCommand(ev, mod)" />
</span>
<f7-swipeout-actions right>
<f7-swipeout-button @click="(ev) => deleteModule(ev, 'actions', mod)"
style="background-color: var(--f7-swipeout-delete-button-bg-color)">
Delete
</f7-swipeout-button>
</f7-swipeout-actions>
</f7-list-item>
</f7-list>
<f7-list v-if="isEditable">
<!-- <f7-list-item link no-chevron media-item :color="($theme.dark) ? 'black' : 'white'" subtitle="Add Item"
@click="addModule()">
<f7-icon slot="media" color="green" aurora="f7:plus_circle_fill" ios="f7:plus_circle_fill"
md="material:control_point" />
</f7-list-item> -->
<item-picker title="Select Items" name="newItem" :multiple="true" :value="selectedItems" @input="selectItems" :no-after="true" class="scene-items-picker" />
<!-- <f7-list-button :color="(showModuleControls) ? 'gray' : 'blue'" :title="sectionLabels[section][1]"></f7-list-button> -->
</f7-list>
</div>
</f7-col>
<f7-col v-if="(isEditable || rule.tags.length > 0)">
<f7-block-title>Tags</f7-block-title>
<semantics-picker v-if="isEditable" :item="rule" />
<tag-input :item="rule" :disabled="!isEditable" />
</f7-col>
<f7-col v-if="isEditable && !createMode">
<f7-list>
<f7-list-button color="red" @click="deleteRule">
Remove Scene
</f7-list-button>
</f7-list>
</f7-col>
</f7-block>
</f7-tab>
<f7-tab id="code" @tab:show="() => { this.currentTab = 'code'; toYaml() }" :tab-active="currentTab === 'code'">
<editor v-if="currentTab === 'code'" class="rule-code-editor" mode="application/vnd.openhab.rule+yaml" :value="ruleYaml" @input="onEditorInput" />
<!-- <pre class="yaml-message padding-horizontal" :class="[yamlError === 'OK' ? 'text-color-green' : 'text-color-red']">{{yamlError}}</pre> -->
</f7-tab>
</f7-tabs>
</f7-page>
</template>
<style lang="stylus">
.enable-toggle
vertical-align inherit
.moduleconfig-popup
.page-content
overflow-x hidden !important
.config-sheet, .parameter-group
margin-top 0 !important
.rule-modules
.swipeout-opened
.sortable-handler
display none
.item-media .icon
color var(--f7-theme-color)
.media-list
margin-bottom 0
.list
margin-top 0
.scene-items
.inline-command-input
position: relative
display: flex
flex-direction: row-reverse
.input
max-width: 30%
padding-left: 5px
background: var(--f7-list-bg-color)
.ios .scene-items
--f7-input-height: 24px
.scene-items-picker
.item-after
display none
.rule-code-editor.vue-codemirror
display block
top calc(var(--f7-navbar-height) + var(--f7-tabbar-height))
height calc(100% - 2*var(--f7-navbar-height))
width 100%
.yaml-message
display block
position absolute
top 80%
white-space pre-wrap
</style>
<script>
import YAML from 'yaml'
import cloneDeep from 'lodash/cloneDeep'
import SemanticsPicker from '@/components/tags/semantics-picker.vue'
import ItemPicker from '@/components/config/controls/item-picker.vue'
import TagInput from '@/components/tags/tag-input.vue'
import SceneConfigureItemPopup from './scene-configure-item-popup.vue'
import RuleStatus from '@/components/rule/rule-status-mixin'
import DirtyMixin from '../../dirty-mixin'
export default {
mixins: [RuleStatus, DirtyMixin],
components: {
SemanticsPicker,
TagInput,
ItemPicker,
'editor': () => import(/* webpackChunkName: "script-editor" */ '@/components/config/controls/script-editor.vue')
},
props: ['ruleId', 'createMode'],
data () {
return {
ready: false,
loading: false,
rule: {},
ruleYaml: '',
moduleTypes: {
actions: [],
conditions: [],
triggers: []
},
showModuleControls: false,
currentTab: 'design',
currentModuleType: null,
currentModule: null,
currentModuleConfig: {},
keyHandler: null,
selectedItems: [],
codeEditorOpened: false
}
},
methods: {
onPageAfterIn () {
if (window) {
window.addEventListener('keydown', this.keyDown)
}
this.load()
},
onPageAfterOut () {
if (window) {
window.removeEventListener('keydown', this.keyDown)
}
},
onEditorInput (value) {
this.ruleYaml = value
this.dirty = true
},
load () {
if (this.loading) return
this.loading = true
const loadModules1 = this.$oh.api.get('/rest/module-types?type=action')
const loadingFinished = () => {
this.$nextTick(() => {
this.savedRule = cloneDeep(this.rule)
this.ready = true
this.loading = false
})
}
Promise.all([loadModules1]).then((data) => {
this.moduleTypes.actions = data[0]
if (this.createMode) {
this.$set(this, 'rule', {
uid: this.$f7.utils.id(),
name: '',
triggers: [],
actions: [],
conditions: [],
tags: [],
configuration: {},
templateUID: null,
visibility: 'VISIBLE',
status: {
status: 'NEW'
}
})
loadingFinished()
} else {
this.$oh.api.get('/rest/rules/' + this.ruleId).then((data2) => {
this.$set(this, 'rule', data2)
this.rule.tags = this.rule.tags.filter(e => e !== 'Scene')
this.$set(this, 'selectedItems', [])
this.rule.actions.forEach((a) => {
if (a.type === 'core.ItemCommandAction') {
this.selectedItems.push(a.configuration.itemName)
}
})
loadingFinished()
})
}
})
},
save (stay) {
if (!this.isEditable) return Promise.reject()
if (!this.rule.uid) {
this.$f7.dialog.alert('Please give an ID to the scene')
return Promise.reject()
}
if (!this.rule.name) {
this.$f7.dialog.alert('Please give a name to the scene')
return Promise.reject()
}
let saveRule = cloneDeep(this.rule)
saveRule.tags.push('Scene')
const promise = (this.createMode)
? this.$oh.api.postPlain('/rest/rules', JSON.stringify(saveRule), 'text/plain', 'application/json')
: this.$oh.api.put('/rest/rules/' + saveRule.uid, saveRule)
return promise.then((data) => {
this.dirty = false
if (this.createMode) {
this.$f7.toast.create({
text: 'Scene created',
destroyOnClose: true,
closeTimeout: 2000
}).open()
this.$f7router.navigate(this.$f7route.url.replace('/add', '/' + this.rule.uid), { reloadCurrent: true })
this.load()
} else {
this.$f7.toast.create({
text: 'Scene updated',
destroyOnClose: true,
closeTimeout: 2000
}).open()
this.savedRule = cloneDeep(this.rule)
}
}).catch((err) => {
this.$f7.toast.create({
text: 'Error while saving scene: ' + err,
destroyOnClose: true,
closeTimeout: 2000
}).open()
})
},
toggleDisabled () {
if (this.createMode) return
const enable = (this.rule.status.statusDetail === 'DISABLED')
this.$oh.api.postPlain('/rest/rules/' + this.rule.uid + '/enable', enable.toString()).then((data) => {
this.$f7.toast.create({
text: (enable) ? 'Rule enabled' : 'Rule disabled',
destroyOnClose: true,
closeTimeout: 2000
}).open()
}).catch((err) => {
this.$f7.toast.create({
text: 'Error while disabling or enabling: ' + err,
destroyOnClose: true,
closeTimeout: 2000
}).open()
})
},
runNow () {
if (this.createMode) return
if (this.rule.status === 'RUNNING') return
this.$f7.toast.create({
text: 'Running rule',
destroyOnClose: true,
closeTimeout: 2000
}).open()
this.$oh.api.postPlain('/rest/rules/' + this.rule.uid + '/runnow', '').catch((err) => {
this.$f7.toast.create({
text: 'Error while running rule: ' + err,
destroyOnClose: true,
closeTimeout: 2000
}).open()
})
},
deleteRule () {
this.$f7.dialog.confirm(
`Are you sure you want to delete ${this.rule.name}?`,
'Delete Scene',
() => {
this.$oh.api.delete('/rest/rules/' + this.rule.uid).then(() => {
this.$f7router.back('/settings/rules/', { force: true })
})
}
)
},
keyDown (ev) {
if ((ev.ctrlKey || ev.metaKey) && !(ev.altKey || ev.shiftKey)) {
if (this.currentModule) return
switch (ev.keyCode) {
case 83:
this.save(!this.createMode)
ev.stopPropagation()
ev.preventDefault()
break
}
}
},
toggleModuleControls () {
this.showModuleControls = !this.showModuleControls
},
showSwipeout (ev) {
let swipeoutElement = ev.target
ev.cancelBubble = true
while (!swipeoutElement.classList.contains('swipeout')) {
swipeoutElement = swipeoutElement.parentElement
}
if (swipeoutElement) {
this.$f7.swipeout.open(swipeoutElement)
}
},
editModule (ev, mod) {
if (ev.target.tagName.toLowerCase() === 'input') {
ev.cancelBubble = true
return
}
if (this.showModuleControls) return
let swipeoutElement = ev.target
ev.cancelBubble = true
while (!swipeoutElement.classList.contains('swipeout')) {
swipeoutElement = swipeoutElement.parentElement
}
if (swipeoutElement && swipeoutElement.classList.contains('swipeout-opened')) return
const popup = {
component: SceneConfigureItemPopup
}
this.$f7router.navigate({
url: 'item-config',
route: {
path: 'item-config',
popup
}
}, {
props: {
rule: this.rule,
module: mod
}
})
this.$f7.once('sceneItemConfigUpdate', this.updateActionModule)
this.$f7.once('sceneItemConfigClosed', () => {
this.$f7.off('sceneItemConfigUpdate', this.updateActionModule)
})
},
deleteModule (ev, section, mod) {
let swipeoutElement = ev.target
if (!this.isEditable) return
ev.cancelBubble = true
while (!swipeoutElement.classList.contains('swipeout')) {
swipeoutElement = swipeoutElement.parentElement
}
this.$f7.swipeout.delete(swipeoutElement, () => {
const idx = this.rule[section].findIndex((m) => m.id === mod.id)
const itemName = this.rule.actions[idx].configuration.itemName
this.rule[section].splice(idx, 1)
console.debug('Removing: ' + itemName)
this.$set(this, 'selectedItems', this.selectedItems.filter((i) => i !== itemName))
this.buildActionModules()
})
},
selectItems (items) {
console.log(items)
this.$set(this, 'selectedItems', items)
this.buildActionModules()
},
reorderModule (ev, section) {
const newSection = [...this.rule[section]]
newSection.splice(ev.to, 0, newSection.splice(ev.from, 1)[0])
this.$set(this.rule, section, newSection)
},
buildActionModules () {
const currentItemNames = this.rule.actions.map((a) => a.configuration.itemName)
const modulesToRemove = this.rule.actions.filter((a) => this.selectedItems.indexOf(a.configuration.itemName) < 0)
if (modulesToRemove.length > 0) console.debug('Removing: ' + modulesToRemove.map((m) => m.configuration.itemName).join(', '))
this.$set(this.rule, 'actions', this.rule.actions.filter((a) => this.selectedItems.indexOf(a.configuration.itemName) >= 0))
const itemsToAdd = this.selectedItems.filter((i) => !this.rule.actions.some((a) => a.configuration.itemName === i))
if (itemsToAdd.length > 0) console.debug('Adding: ' + itemsToAdd.join(', '))
let moduleId = 1
itemsToAdd.forEach((itemName) => {
for (; ['triggers', 'actions', 'conditions'].some((s) => this.rule[s].some((m) => m.id === moduleId.toString())); moduleId++);
console.debug('new moduleId=' + moduleId)
const newModule = {
id: moduleId.toString(),
configuration: {
itemName,
command: null
},
type: 'core.ItemCommandAction'
}
this.rule.actions.push(newModule)
})
const statePromises = itemsToAdd.map((itemName) => this.$oh.api.getPlain('/rest/items/' + itemName + '/state'))
Promise.all(statePromises).then((states) => {
states.forEach((state, idx) => {
const module = this.rule.actions.find((a) => a.configuration.itemName === itemsToAdd[idx])
this.$set(module.configuration, 'command', state)
})
})
},
updateCommandFromCurrentState (ev, module) {
if (ev) ev.cancelBubble = true
const itemName = module.configuration.itemName
this.$oh.api.getPlain('/rest/items/' + itemName + '/state').then((state) => {
this.$set(module.configuration, 'command', state)
})
},
testCommand (ev, module) {
if (ev) ev.cancelBubble = true
const itemName = module.configuration.itemName
const command = module.configuration.command
this.$oh.api.postPlain('/rest/items/' + itemName, command, 'text/plain', 'text/plain').then((state) => {
this.$f7.toast.create({
text: `Updated desired state of ${itemName} to ${state}`,
destroyOnClose: true,
closeTimeout: 2000
}).open()
})
},
updateActionModule (params) {
const [itemName, command] = params
const module = this.rule.actions.find((a) => a.configuration.itemName === itemName)
if (module) module.configuration.command = command
},
toYaml () {
const itemsConfig = {}
this.rule.actions.forEach((a) => {
itemsConfig[a.configuration.itemName] = a.configuration.command
})
this.ruleYaml = YAML.stringify({
items: itemsConfig,
triggers: this.rule.triggers,
conditions: this.rule.conditions
})
},
fromYaml () {
if (!this.isEditable) return
try {
const updatedRule = YAML.parse(this.ruleYaml)
const actions = []
let idx = 0
for (let item in updatedRule.items) {
actions.push({
id: (idx++).toString(),
configuration: {
itemName: item,
command: updatedRule.items[item]
},
type: 'core.ItemCommandAction'
})
}
this.$set(this.rule, 'triggers', updatedRule.triggers)
this.$set(this.rule, 'conditions', updatedRule.conditions)
this.$set(this.rule, 'actions', actions)
return true
} catch (e) {
this.$f7.dialog.alert(e).open()
return false
}
}
},
computed: {
isEditable () {
return this.rule && this.rule.editable !== false
},
yamlError () {
if (this.currentTab !== 'code') return null
try {
YAML.parse(this.ruleYaml, { prettyErrors: true })
return 'OK'
} catch (e) {
return e
}
}
}
}
</script>

View File

@ -73,6 +73,14 @@
:footer="objectsSubtitles.rules">
<f7-icon slot="media" f7="wand_stars" color="gray" />
</f7-list-item>
<f7-list-item
media-item
link="scenes/"
title="Scenes"
badge-color="blue"
:footer="objectsSubtitles.scenes">
<f7-icon slot="media" f7="film" color="gray" />
</f7-list-item>
<f7-list-item
media-item
link="scripts/"
@ -150,6 +158,7 @@ export default {
items: 'Manage the functional layer',
pages: 'Design displays for user control & monitoring',
rules: 'Automate with triggers and actions',
scenes: 'Store a set of desired states as a scene',
scripts: 'Rules dedicated to running code',
schedule: 'View upcoming time-based rules'
},