v0.16.3
This commit is contained in:
1 parent
f376f0a7b6
commit
8059e16d12
36 files changed
+1140
-193
No files matched your search
+84
-17
@@ -636,7 +636,7 @@ h3 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.louver-control > span {
|
||||
.louver-control>span {
|
||||
color: var(--muted);
|
||||
font-size: 11px;
|
||||
line-height: 1.15;
|
||||
@@ -1549,20 +1549,20 @@ legend {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.local-discovery-head > input[type="checkbox"] {
|
||||
.local-discovery-head>input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.local-discovery-head > label {
|
||||
.local-discovery-head>label {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.local-discovery-head > .badge {
|
||||
.local-discovery-head>.badge {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
@@ -6608,6 +6608,59 @@ textarea[aria-invalid="true"] {
|
||||
margin: 2px 0 0;
|
||||
}
|
||||
|
||||
.flow-inspector-description {
|
||||
margin: -7px 0 15px;
|
||||
color: var(--muted);
|
||||
font-size: .82rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.flow-block-help {
|
||||
margin: 16px 0 18px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: var(--surface-2);
|
||||
color: var(--text);
|
||||
font-size: .81rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.flow-block-help summary {
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.flow-block-help[open] summary {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.flow-block-help p {
|
||||
margin: 8px 0 10px;
|
||||
}
|
||||
|
||||
.flow-block-help strong {
|
||||
display: block;
|
||||
margin: 12px 0 5px;
|
||||
}
|
||||
|
||||
.flow-block-help ul {
|
||||
margin: 0 0 10px;
|
||||
padding-left: 19px;
|
||||
}
|
||||
|
||||
.flow-block-help li {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.flow-block-library-description {
|
||||
display: block;
|
||||
color: var(--muted);
|
||||
font-weight: 400;
|
||||
line-height: 1.45;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.flow-inspector label {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
@@ -10632,6 +10685,7 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
min-height: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep Flow interpretation compact on every viewport. Details are opt-in. */
|
||||
.flow-natural-preview {
|
||||
display: block;
|
||||
@@ -10708,7 +10762,7 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.flow-shortcut-grid > div {
|
||||
.flow-shortcut-grid>div {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, auto) 1fr;
|
||||
align-items: center;
|
||||
@@ -10725,7 +10779,7 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.flow-shortcut-grid > div > span:last-child {
|
||||
.flow-shortcut-grid>div>span:last-child {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
@@ -10765,14 +10819,17 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
transition: opacity .14s ease, transform .14s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.flow-editor-notice.is-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.flow-editor-notice.is-error {
|
||||
color: var(--danger);
|
||||
background: var(--danger-soft);
|
||||
}
|
||||
|
||||
.flow-context-menu {
|
||||
position: fixed;
|
||||
z-index: 1200;
|
||||
@@ -10784,7 +10841,11 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
background: var(--surface);
|
||||
box-shadow: 0 16px 38px rgba(0, 0, 0, .28);
|
||||
}
|
||||
.flow-context-menu[hidden] { display: none; }
|
||||
|
||||
.flow-context-menu[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.flow-context-menu button {
|
||||
width: 100%;
|
||||
min-height: 34px;
|
||||
@@ -10797,22 +10858,28 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
font: inherit;
|
||||
font-size: .82rem;
|
||||
}
|
||||
|
||||
.flow-context-menu button:hover:not(:disabled),
|
||||
.flow-context-menu button:focus-visible:not(:disabled) {
|
||||
background: var(--accent-soft);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.flow-context-menu button:disabled {
|
||||
opacity: .45;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.flow-context-separator {
|
||||
height: 1px;
|
||||
margin: 5px 4px;
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.flow-editor-notice { max-width: 42vw; }
|
||||
.flow-editor-notice {
|
||||
max-width: 42vw;
|
||||
}
|
||||
}
|
||||
|
||||
/* Flow quick actions: description editing and simple simulator */
|
||||
@@ -10920,12 +10987,12 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.flow-simple-intro > div {
|
||||
.flow-simple-intro>div {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.flow-simple-intro > div > span {
|
||||
.flow-simple-intro>div>span {
|
||||
color: var(--muted);
|
||||
font-size: .88rem;
|
||||
}
|
||||
@@ -10971,7 +11038,7 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.flow-simple-scenario-field > span {
|
||||
.flow-simple-scenario-field>span {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
}
|
||||
@@ -11012,19 +11079,19 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.flow-simple-result-head > div {
|
||||
.flow-simple-result-head>div {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.flow-simple-result-head > div > small {
|
||||
.flow-simple-result-head>div>small {
|
||||
color: var(--muted);
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
|
||||
.flow-simple-result > p {
|
||||
.flow-simple-result>p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
}
|
||||
@@ -11050,7 +11117,7 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.flow-simple-condition > span:nth-child(2) {
|
||||
.flow-simple-condition>span:nth-child(2) {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
@@ -11079,7 +11146,7 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.flow-card .card-footer > small {
|
||||
.flow-card .card-footer>small {
|
||||
max-width: 100%;
|
||||
white-space: normal;
|
||||
}
|
||||
@@ -11121,4 +11188,4 @@ select.select-native-proxy[aria-invalid="true"]+.custom-select .custom-select-tr
|
||||
grid-column: 2;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
+111
-47
@@ -86,7 +86,8 @@
|
||||
data-action="house-power" data-value="true" data-i18n="house.powerOnAll">Turn all on</button><button
|
||||
id="housePowerOff" type="button" class="secondary" data-action="house-power" data-value="false"
|
||||
data-i18n="house.powerOffAll">Turn all off</button><button id="houseEmergencyStop" type="button"
|
||||
class="secondary house-emergency-stop" data-action="house-emergency-stop" data-value="true" hidden>Emergency stop</button></div>
|
||||
class="secondary house-emergency-stop" data-action="house-emergency-stop" data-value="true"
|
||||
hidden>Emergency stop</button></div>
|
||||
</div>
|
||||
<div class="dashboard-global-divider"></div>
|
||||
<div class="house-climate dashboard-house-climate" id="houseClimate"></div>
|
||||
@@ -960,7 +961,8 @@
|
||||
</div>
|
||||
<div class="flow-palette-group block-category-sensor"><span data-i18n="flow.sensors">Sensory / wartości</span>
|
||||
<button type="button" data-flow-add="heating_curve" data-i18n="flow.node.heatingCurve">Krzywa
|
||||
grzewcza</button><button type="button" data-flow-add="outdoor_temperature" data-i18n="flow.node.outdoorTemperature">Temperatura
|
||||
grzewcza</button><button type="button" data-flow-add="outdoor_temperature"
|
||||
data-i18n="flow.node.outdoorTemperature">Temperatura
|
||||
zewn.</button><button type="button" data-flow-add="device_temperature"
|
||||
data-i18n="flow.node.deviceTemperature">Temperatura urządzenia</button><button type="button"
|
||||
data-flow-add="zone_temperature" data-i18n="flow.node.zoneTemperature">Temperatura strefy</button><button
|
||||
@@ -972,21 +974,24 @@
|
||||
data-flow-add="ha_numeric" data-i18n="flow.node.haNumeric">HA: wartość</button><button type="button"
|
||||
data-flow-add="ha_attribute" data-i18n="flow.node.haAttribute">HA: atrybut</button><button type="button"
|
||||
data-flow-add="ha_available" data-i18n="flow.node.haAvailable">HA: dostępność</button><button type="button"
|
||||
data-flow-add="constant" data-i18n="flow.node.constant">Stała testowa</button><button type="button"
|
||||
data-flow-add="shared_input" data-i18n="flow.node.sharedInput">Wspólne wejście</button><button type="button"
|
||||
data-flow-add="rolling_stat" data-i18n="flow.node.rollingStat">Średnia / mediana</button><button
|
||||
type="button" data-flow-add="oscillates" data-i18n="flow.node.oscillates">Wartość oscyluje</button>
|
||||
data-flow-add="constant" data-i18n="flow.node.constant" data-i18n-title="flow.constantDescription">Stała
|
||||
testowa</button><button type="button" data-flow-add="shared_input" data-i18n="flow.node.sharedInput">Wspólne
|
||||
wejście</button><button type="button" data-flow-add="rolling_stat" data-i18n="flow.node.rollingStat">Średnia
|
||||
/ mediana</button><button type="button" data-flow-add="oscillates" data-i18n="flow.node.oscillates">Wartość
|
||||
oscyluje</button>
|
||||
</div>
|
||||
<div class="flow-palette-group block-category-logic"><span data-i18n="flow.logic">Logika</span><button
|
||||
type="button" data-flow-add="logic_and" data-i18n="flow.node.and">AND</button><button type="button"
|
||||
data-flow-add="logic_or" data-i18n="flow.node.or">OR</button><button type="button" data-flow-add="logic_not"
|
||||
data-i18n="flow.node.not">NOT</button></div>
|
||||
<div class="flow-palette-group block-category-action"><span data-i18n="flow.actions">Akcje</span>
|
||||
<button type="button" data-flow-add="zone_heat_cycle" data-i18n="flow.node.simpleHeat"
|
||||
data-i18n-title="flow.simpleHeatDescription">Proste grzanie dzień/noc</button>
|
||||
<button type="button" data-flow-add="zone_thermostat"
|
||||
data-i18n="flow.node.thermostat">Termostat</button><button type="button" data-flow-add="device_action"
|
||||
data-i18n="flow.node.greeDevice">Urządzenie GREE</button><button type="button" data-flow-add="device_feature_action"
|
||||
data-i18n="flow.node.greeFeature">Pojedyncza funkcja GREE</button><button type="button" data-flow-add="group_action"
|
||||
data-i18n="flow.node.group">Grupa</button>
|
||||
data-i18n="flow.node.greeDevice">Urządzenie GREE</button><button type="button"
|
||||
data-flow-add="device_feature_action" data-i18n="flow.node.greeFeature">Pojedyncza funkcja
|
||||
GREE</button><button type="button" data-flow-add="group_action" data-i18n="flow.node.group">Grupa</button>
|
||||
</div>
|
||||
<div class="flow-palette-group block-category-haaction"><span data-i18n="flow.haActions">Home Assistant</span>
|
||||
<button type="button" data-flow-add="ha_service_action" data-i18n="flow.node.haServiceAction">Home Assistant:
|
||||
@@ -1005,8 +1010,8 @@
|
||||
class="secondary flow-selection-action" data-action="flow-clear-selection"
|
||||
data-i18n="flow.clearSelection">Wyczyść zaznaczenie</button><button type="button"
|
||||
class="secondary flow-selection-action" data-action="flow-shortcuts"
|
||||
data-i18n="flow.shortcuts">Skróty</button><span id="flowSelectionCount"
|
||||
class="badge"></span><span id="flowEditorNotice" class="flow-editor-notice" role="status" aria-live="polite"></span></div>
|
||||
data-i18n="flow.shortcuts">Skróty</button><span id="flowSelectionCount" class="badge"></span><span
|
||||
id="flowEditorNotice" class="flow-editor-notice" role="status" aria-live="polite"></span></div>
|
||||
<div class="flow-zoom-controls" role="group" data-i18n-aria="flow.zoomControls" aria-label="Canvas zoom">
|
||||
<button type="button" class="secondary icon-button" data-action="flow-zoom-out"
|
||||
data-i18n-title="flow.zoomOut" title="Zoom out" data-ui-icon="minus"></button><span id="flowZoomLabel"
|
||||
@@ -1054,25 +1059,43 @@
|
||||
<h2 data-i18n="flow.shortcuts">Skróty klawiaturowe</h2>
|
||||
</div><button type="button" data-close data-ui-icon="close"></button>
|
||||
</div>
|
||||
<p class="field-note" data-i18n="flow.shortcutsHint">Skróty działają, gdy kursor nie znajduje się w polu formularza.</p>
|
||||
<p class="field-note" data-i18n="flow.shortcutsHint">Skróty działają, gdy kursor nie znajduje się w polu formularza.
|
||||
</p>
|
||||
<div class="flow-shortcut-grid">
|
||||
<div><span class="flow-shortcut-keys"><kbd>A</kbd><kbd>Insert</kbd><kbd>Ctrl/Cmd+K</kbd></span><span data-i18n="flow.shortcutAdd">Dodaj / wyszukaj blok</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Enter</kbd></span><span data-i18n="flow.shortcutAddFirst">Dodaj pierwszy wynik wyszukiwania</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+S</kbd></span><span data-i18n="flow.shortcutSave">Zapisz Flow</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+Z</kbd></span><span data-i18n="flow.shortcutUndo">Cofnij ostatnią zmianę</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+A</kbd></span><span data-i18n="flow.shortcutSelectAll">Zaznacz wszystkie bloki</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+C</kbd></span><span data-i18n="flow.shortcutCopy">Kopiuj zaznaczone bloki</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+X</kbd></span><span data-i18n="flow.shortcutCut">Wytnij zaznaczone bloki</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+V</kbd></span><span data-i18n="flow.shortcutPaste">Wklej bloki</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+D</kbd></span><span data-i18n="flow.shortcutDuplicate">Duplikuj zaznaczenie</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Delete</kbd><kbd>Backspace</kbd></span><span data-i18n="flow.shortcutDelete">Usuń zaznaczone bloki</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>← ↑ ↓ →</kbd></span><span data-i18n="flow.shortcutMove">Przesuń zaznaczenie o 10 px</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Shift+← ↑ ↓ →</kbd></span><span data-i18n="flow.shortcutMoveFine">Przesuń precyzyjnie o 1 px</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Esc</kbd></span><span data-i18n="flow.shortcutClear">Wyczyść zaznaczenie / anuluj łączenie</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+0</kbd></span><span data-i18n="flow.shortcutFit">Dopasuj cały Flow do widoku</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd++ / -</kbd></span><span data-i18n="flow.shortcutZoom">Powiększ / pomniejsz</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>?</kbd></span><span data-i18n="flow.shortcutHelp">Pokaż tę listę skrótów</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd data-i18n="flow.rightClickKey">Prawy przycisk</kbd></span><span data-i18n="flow.shortcutContextMenu">Menu kontekstowe Flow</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>A</kbd><kbd>Insert</kbd><kbd>Ctrl/Cmd+K</kbd></span><span
|
||||
data-i18n="flow.shortcutAdd">Dodaj / wyszukaj blok</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Enter</kbd></span><span data-i18n="flow.shortcutAddFirst">Dodaj
|
||||
pierwszy wynik wyszukiwania</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+S</kbd></span><span data-i18n="flow.shortcutSave">Zapisz
|
||||
Flow</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+Z</kbd></span><span data-i18n="flow.shortcutUndo">Cofnij
|
||||
ostatnią zmianę</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+A</kbd></span><span data-i18n="flow.shortcutSelectAll">Zaznacz
|
||||
wszystkie bloki</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+C</kbd></span><span data-i18n="flow.shortcutCopy">Kopiuj
|
||||
zaznaczone bloki</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+X</kbd></span><span data-i18n="flow.shortcutCut">Wytnij
|
||||
zaznaczone bloki</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+V</kbd></span><span data-i18n="flow.shortcutPaste">Wklej
|
||||
bloki</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+D</kbd></span><span
|
||||
data-i18n="flow.shortcutDuplicate">Duplikuj zaznaczenie</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Delete</kbd><kbd>Backspace</kbd></span><span
|
||||
data-i18n="flow.shortcutDelete">Usuń zaznaczone bloki</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>← ↑ ↓ →</kbd></span><span data-i18n="flow.shortcutMove">Przesuń
|
||||
zaznaczenie o 10 px</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Shift+← ↑ ↓ →</kbd></span><span
|
||||
data-i18n="flow.shortcutMoveFine">Przesuń precyzyjnie o 1 px</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Esc</kbd></span><span data-i18n="flow.shortcutClear">Wyczyść
|
||||
zaznaczenie / anuluj łączenie</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd+0</kbd></span><span data-i18n="flow.shortcutFit">Dopasuj cały
|
||||
Flow do widoku</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>Ctrl/Cmd++ / -</kbd></span><span data-i18n="flow.shortcutZoom">Powiększ
|
||||
/ pomniejsz</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd>?</kbd></span><span data-i18n="flow.shortcutHelp">Pokaż tę listę
|
||||
skrótów</span></div>
|
||||
<div><span class="flow-shortcut-keys"><kbd data-i18n="flow.rightClickKey">Prawy przycisk</kbd></span><span
|
||||
data-i18n="flow.shortcutContextMenu">Menu kontekstowe Flow</span></div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
@@ -1173,8 +1196,10 @@
|
||||
</div>
|
||||
<p class="field-note"><strong id="flowDescriptionName"></strong></p>
|
||||
<label><span data-i18n="flow.description">Description</span><textarea name="description" rows="6" maxlength="1200"
|
||||
data-i18n-placeholder="flow.descriptionPlaceholder" placeholder="Describe what this Flow does…"></textarea></label>
|
||||
<p class="field-note" data-i18n="flow.descriptionQuickHint">Only the description is changed. You do not need to open the Flow editor.</p>
|
||||
data-i18n-placeholder="flow.descriptionPlaceholder"
|
||||
placeholder="Describe what this Flow does…"></textarea></label>
|
||||
<p class="field-note" data-i18n="flow.descriptionQuickHint">Only the description is changed. You do not need to
|
||||
open the Flow editor.</p>
|
||||
<div class="form-actions"><button type="button" class="secondary" data-close
|
||||
data-i18n="actions.cancel">Cancel</button><button type="submit" class="primary"
|
||||
data-i18n="actions.save">Save</button></div>
|
||||
@@ -1189,12 +1214,17 @@
|
||||
</div><button type="button" data-close data-ui-icon="close"></button>
|
||||
</div>
|
||||
<div class="flow-simple-intro">
|
||||
<div><strong id="flowSimpleSimulationName"></strong><span data-i18n="flow.simpleSimulatorHint">Set a situation and see whether each Flow action would run.</span></div>
|
||||
<div><strong id="flowSimpleSimulationName"></strong><span data-i18n="flow.simpleSimulatorHint">Set a situation
|
||||
and see whether each Flow action would run.</span></div>
|
||||
<span class="badge" data-i18n="flow.simpleSimulatorSafe">Preview only — nothing is changed</span>
|
||||
</div>
|
||||
<section class="flow-simple-scenario">
|
||||
<div class="flow-simple-section-head"><div><strong data-i18n="flow.simpleScenario">Situation to check</strong><p class="field-note"
|
||||
data-i18n="flow.simpleScenarioHint">Leave values unchanged to use the current application and Home Assistant state.</p></div></div>
|
||||
<div class="flow-simple-section-head">
|
||||
<div><strong data-i18n="flow.simpleScenario">Situation to check</strong>
|
||||
<p class="field-note" data-i18n="flow.simpleScenarioHint">Leave values unchanged to use the current
|
||||
application and Home Assistant state.</p>
|
||||
</div>
|
||||
</div>
|
||||
<label class="flow-simple-time"><span data-i18n="flow.simulationTime">Simulation date and time</span><input
|
||||
id="flowSimpleSimulationAt" type="datetime-local"></label>
|
||||
<div id="flowSimpleSimulationOverrides" class="flow-simple-simulation-overrides"></div>
|
||||
@@ -1345,7 +1375,10 @@
|
||||
<h2 data-i18n="discovery.title">Discover GREE devices</h2><button type="button" data-close
|
||||
data-ui-icon="close"></button>
|
||||
</div>
|
||||
<p class="field-note" data-i18n="discovery.help">Auto accepts both AES-ECB and AES-GCM devices, uses discovery metadata only as a protocol hint and verifies the actual protocol during binding. Selecting V1 or V2 keeps discovery and binding locked to that protocol. Multiple passes improve discovery when several Wi-Fi modules answer the same broadcast.</p>
|
||||
<p class="field-note" data-i18n="discovery.help">Auto accepts both AES-ECB and AES-GCM devices, uses discovery
|
||||
metadata only as a protocol hint and verifies the actual protocol during binding. Selecting V1 or V2 keeps
|
||||
discovery and binding locked to that protocol. Multiple passes improve discovery when several Wi-Fi modules
|
||||
answer the same broadcast.</p>
|
||||
<label><span data-i18n="discovery.protocol">Discovery protocol</span><select name="protocol_version">
|
||||
<option value="0" data-i18n="devices.protocolAuto">Auto (V1 + V2)</option>
|
||||
<option value="1">V1 AES-ECB</option>
|
||||
@@ -1366,7 +1399,8 @@
|
||||
<h2 data-i18n="discovery.nameDevices">Discovered local devices</h2><button type="button" data-close
|
||||
data-ui-icon="close"></button>
|
||||
</div>
|
||||
<p class="field-note" data-i18n="discovery.nameDevicesHelp">Select the units you want to add. MAC address, IP address and the discovery protocol hint are shown for every result; Auto is resolved during binding.</p>
|
||||
<p class="field-note" data-i18n="discovery.nameDevicesHelp">Select the units you want to add. MAC address, IP
|
||||
address and the discovery protocol hint are shown for every result; Auto is resolved during binding.</p>
|
||||
<div id="discoveryNamesList" class="discovery-name-list"></div>
|
||||
<div class="form-actions"><button type="button" class="secondary" data-close
|
||||
data-i18n="actions.cancel">Cancel</button><button class="primary" type="submit"
|
||||
@@ -1598,9 +1632,10 @@
|
||||
type="number" min="15" name="min_adjust_seconds" value="120"></label><label><span
|
||||
data-i18n="zones.standbyOffset">Standby setpoint offset °C</span><input type="text" inputmode="decimal"
|
||||
step="0.5" min="0.5" max="8" name="standby_offset_c" value="2"></label></div>
|
||||
<label><span data-i18n="zones.heatAnticipation">Early heat coasting (°C before target)</span><input
|
||||
type="text" inputmode="decimal" step="0.1" min="0" max="2.5" name="heat_anticipation_c" value="0"></label>
|
||||
<p class="field-note" data-i18n="zones.heatAnticipationHint">A positive value lowers the GREE setpoint early without powering the indoor unit off. 0 disables early coasting.</p>
|
||||
<label><span data-i18n="zones.heatAnticipation">Early heat coasting (°C before target)</span><input type="text"
|
||||
inputmode="decimal" step="0.1" min="0" max="2.5" name="heat_anticipation_c" value="0"></label>
|
||||
<p class="field-note" data-i18n="zones.heatAnticipationHint">A positive value lowers the GREE setpoint early
|
||||
without powering the indoor unit off. 0 disables early coasting.</p>
|
||||
<label class="check"><input type="checkbox" name="smart_fan" checked> <span data-i18n="zones.smartFan">Smart fan
|
||||
assist</span></label>
|
||||
<input type="hidden" name="min_on_seconds" value="180"><input type="hidden" name="min_off_seconds"
|
||||
@@ -1882,15 +1917,44 @@
|
||||
<option value="4" data-i18n="fan.mediumHigh">Medium-high</option>
|
||||
<option value="5" data-i18n="fan.high">High</option>
|
||||
</select></label>
|
||||
<label><span data-i18n="devices.quiet">Quiet</span><select name="action_quiet"><option value="" data-i18n="actions.noChange">No change</option><option value="true" data-i18n="common.on">On</option><option value="false" data-i18n="common.off">Off</option></select></label>
|
||||
<label><span data-i18n="devices.turbo">Turbo</span><select name="action_turbo"><option value="" data-i18n="actions.noChange">No change</option><option value="true" data-i18n="common.on">On</option><option value="false" data-i18n="common.off">Off</option></select></label>
|
||||
<label><span data-i18n="devices.light">Panel light</span><select name="action_light"><option value="" data-i18n="actions.noChange">No change</option><option value="true" data-i18n="common.on">On</option><option value="false" data-i18n="common.off">Off</option></select></label>
|
||||
<label><span data-i18n="devices.air">Air</span><select name="action_air"><option value="" data-i18n="actions.noChange">No change</option><option value="true" data-i18n="common.on">On</option><option value="false" data-i18n="common.off">Off</option></select></label>
|
||||
<label><span data-i18n="devices.xfan">X-Fan</span><select name="action_xfan"><option value="" data-i18n="actions.noChange">No change</option><option value="true" data-i18n="common.on">On</option><option value="false" data-i18n="common.off">Off</option></select></label>
|
||||
<label><span data-i18n="devices.health">Health</span><select name="action_health"><option value="" data-i18n="actions.noChange">No change</option><option value="true" data-i18n="common.on">On</option><option value="false" data-i18n="common.off">Off</option></select></label>
|
||||
<label><span data-i18n="devices.sleep">Sleep</span><select name="action_sleep"><option value="" data-i18n="actions.noChange">No change</option><option value="true" data-i18n="common.on">On</option><option value="false" data-i18n="common.off">Off</option></select></label>
|
||||
<label><span data-i18n="devices.quiet">Quiet</span><select name="action_quiet">
|
||||
<option value="" data-i18n="actions.noChange">No change</option>
|
||||
<option value="true" data-i18n="common.on">On</option>
|
||||
<option value="false" data-i18n="common.off">Off</option>
|
||||
</select></label>
|
||||
<label><span data-i18n="devices.turbo">Turbo</span><select name="action_turbo">
|
||||
<option value="" data-i18n="actions.noChange">No change</option>
|
||||
<option value="true" data-i18n="common.on">On</option>
|
||||
<option value="false" data-i18n="common.off">Off</option>
|
||||
</select></label>
|
||||
<label><span data-i18n="devices.light">Panel light</span><select name="action_light">
|
||||
<option value="" data-i18n="actions.noChange">No change</option>
|
||||
<option value="true" data-i18n="common.on">On</option>
|
||||
<option value="false" data-i18n="common.off">Off</option>
|
||||
</select></label>
|
||||
<label><span data-i18n="devices.air">Air</span><select name="action_air">
|
||||
<option value="" data-i18n="actions.noChange">No change</option>
|
||||
<option value="true" data-i18n="common.on">On</option>
|
||||
<option value="false" data-i18n="common.off">Off</option>
|
||||
</select></label>
|
||||
<label><span data-i18n="devices.xfan">X-Fan</span><select name="action_xfan">
|
||||
<option value="" data-i18n="actions.noChange">No change</option>
|
||||
<option value="true" data-i18n="common.on">On</option>
|
||||
<option value="false" data-i18n="common.off">Off</option>
|
||||
</select></label>
|
||||
<label><span data-i18n="devices.health">Health</span><select name="action_health">
|
||||
<option value="" data-i18n="actions.noChange">No change</option>
|
||||
<option value="true" data-i18n="common.on">On</option>
|
||||
<option value="false" data-i18n="common.off">Off</option>
|
||||
</select></label>
|
||||
<label><span data-i18n="devices.sleep">Sleep</span><select name="action_sleep">
|
||||
<option value="" data-i18n="actions.noChange">No change</option>
|
||||
<option value="true" data-i18n="common.on">On</option>
|
||||
<option value="false" data-i18n="common.off">Off</option>
|
||||
</select></label>
|
||||
</div>
|
||||
<p class="field-note" data-i18n="automations.singleDeviceFunctionsHint">Leave every other field as No change to modify only the selected unit function.</p>
|
||||
<p class="field-note" data-i18n="automations.singleDeviceFunctionsHint">Leave every other field as No change to
|
||||
modify only the selected unit function.</p>
|
||||
</details>
|
||||
<p class="field-note" id="automationGroupHint" hidden data-i18n="automations.groupHint">For a group, Auto mode
|
||||
means follow the whole-house heating/cooling mode. Group presets are applied to all member zones.</p>
|
||||
|
||||
@@ -471,7 +471,10 @@ function zoneRuntimeStatusLabel(zone, effectiveMode, device = null) {
|
||||
if (zone.device_manual_override === true) return tr('zones.manualDeviceControl');
|
||||
if (!zone.enabled) return tr('common.disabled');
|
||||
if (zone.local_thermostat_power === false) return tr('zones.localThermostatOff');
|
||||
if ((effectiveMode || 'off') === 'off') return tr('zones.waiting');
|
||||
if ((effectiveMode || 'off') === 'off') {
|
||||
if (zone.control_source === 'automation.flow' && zone.simple_heat && zone.control_owner === 'automation') return tr(device?.power ? 'zones.flowDayPendingOff' : 'zones.flowDayOff');
|
||||
return tr('zones.waiting');
|
||||
}
|
||||
if (device?.enabled === false) return tr('zones.waitingDeviceDisabled');
|
||||
if (device && (!device.online || Number(device.communication_failures || 0) > 0)) return tr('zones.waitingOffline');
|
||||
if (zone.current_temperature == null) return tr('zones.noMeasurement');
|
||||
@@ -480,7 +483,7 @@ function zoneRuntimeStatusLabel(zone, effectiveMode, device = null) {
|
||||
if (zone.demand && device && !device.power) return tr('zones.waitingStart');
|
||||
if (zone.demand && device?.power && ['heat', 'cool'].includes(effectiveMode) && device.mode !== effectiveMode) return tr('zones.waitingMode');
|
||||
if (zone.control_owner === 'automation' && zone.control_source === 'automation.flow') {
|
||||
if (zone.demand && zone.heat_coasting && device?.power) return tr('zones.flowCoasting');
|
||||
if (zone.heat_coasting && device?.power) return tr('zones.flowCoasting');
|
||||
return zone.demand ? tr('zones.flowHeating') : tr('zones.flowStandby');
|
||||
}
|
||||
return zone.demand ? tr('zones.runningDemand') : tr('zones.satisfied');
|
||||
@@ -501,7 +504,8 @@ function zoneCard(zone, detailed = true) {
|
||||
const displayPreset = idleWithoutTarget ? 'auto' : (zone.manual_preset || zone.active_preset || 'comfort');
|
||||
const globalModeAvailable = ['heat', 'cool'].includes(app.settings?.house_mode);
|
||||
const globalModeDisabled = globalModeAvailable ? '' : ` disabled aria-disabled="true" title="${esc(tr('zones.globalModeUnavailable'))}"`;
|
||||
const hasManualOverride = zone.manual_preset != null || zone.manual_setpoint != null;
|
||||
const flowOwned = zone.control_owner === 'automation' && zone.control_source === 'automation.flow';
|
||||
const hasManualOverride = !flowOwned && (zone.manual_preset != null || zone.manual_setpoint != null);
|
||||
const deviceManualOverride = zone.device_manual_override === true;
|
||||
const controlGroup = groupControlForZone(zone);
|
||||
const deviceUnavailable = !!device && (device.enabled === false || !device.online || Number(device.communication_failures || 0) > 0);
|
||||
@@ -527,9 +531,9 @@ function zoneCard(zone, detailed = true) {
|
||||
? tr('zones.temporaryOverride', { time: temporaryStatus?.countdown || '—' })
|
||||
: localThermostatOverride
|
||||
? tr(localThermostatPower ? 'zones.localThermostatOn' : 'zones.localThermostatOff')
|
||||
: (zone.manual_override_until
|
||||
: (flowOwned ? tr('zones.ownerFlow') : (zone.manual_override_until
|
||||
? `${tr('zones.overrideUntil')} ${new Date(zone.manual_override_until).toLocaleTimeString(locale(), { hour: '2-digit', minute: '2-digit' })}`
|
||||
: (hasManualOverride ? tr('zones.manualNoBoundary') : tr('zones.scheduleControl')));
|
||||
: (hasManualOverride ? tr('zones.manualNoBoundary') : tr('zones.scheduleControl'))));
|
||||
const manualTakeover = deviceManualOverride
|
||||
? `<div class="manual-override-panel"><div><strong>${esc(tr('zones.manualDeviceControl'))}</strong><p>${esc(tr('zones.manualDeviceDescription'))}</p></div><button type="button" data-action="zone-resume-automation" data-resume="device" data-id="${esc(zone.id)}">${esc(tr('zones.resumeAutomation'))}</button></div>`
|
||||
: localThermostatOverride && !temporarySession
|
||||
|
||||
+75
-6
@@ -23,11 +23,12 @@ const FLOW_NODE_META = Object.freeze({
|
||||
zone_state: { titleKey: 'flow.node.zoneState', category: 'sensor' },
|
||||
group_state: { titleKey: 'flow.node.groupState', category: 'sensor' },
|
||||
night_mode: { titleKey: 'flow.node.nightMode', category: 'time' },
|
||||
constant: { titleKey: 'flow.node.constant', category: 'sensor' },
|
||||
constant: { titleKey: 'flow.node.constant', category: 'sensor', descriptionKey: 'flow.constantDescription' },
|
||||
shared_input: { titleKey: 'flow.node.sharedInput', category: 'sensor' },
|
||||
logic_and: { titleKey: 'flow.node.and', category: 'logic' },
|
||||
logic_or: { titleKey: 'flow.node.or', category: 'logic' },
|
||||
logic_not: { titleKey: 'flow.node.not', category: 'logic' },
|
||||
zone_heat_cycle: { titleKey: 'flow.node.simpleHeat', category: 'action', descriptionKey: 'flow.simpleHeatDescription' },
|
||||
zone_thermostat: { titleKey: 'flow.node.thermostat', category: 'action' },
|
||||
device_action: { titleKey: 'flow.node.greeDevice', category: 'action' },
|
||||
device_feature_action: { titleKey: 'flow.node.greeFeature', category: 'action' },
|
||||
@@ -98,6 +99,7 @@ function flowDefaultConfig(kind) {
|
||||
if (kind === 'night_mode') return {};
|
||||
if (kind === 'constant') return { value: true };
|
||||
if (kind === 'shared_input') return sharedFlowReferenceDefaultConfig(app.flowSharedInputs?.[0]);
|
||||
if (kind === 'zone_heat_cycle') return { zone_id: app.zones[0]?.id || '', day_target: 20.0, night_target: 20.6, day_min: 19.7, boost_after_minutes: 10, day_off_after_minutes: 30, cooldown_seconds: 60 };
|
||||
if (kind === 'zone_thermostat') return { zone_id: app.zones[0]?.id || '', preset: 'comfort', setpoint: 21, mode: 'auto', power: null, heat_anticipation_c: null, standby_offset_c: null, swing_vertical: null, swing_horizontal: null, cooldown_seconds: 60 };
|
||||
if (kind === 'device_action') return { device_id: app.devices[0]?.id || '', power: true, mode: '', target_temperature: null, fan_speed: null, swing_vertical: null, swing_horizontal: null, quiet: null, turbo: null, light: null, air: null, xfan: null, health: null, sleep: null, cooldown_seconds: 60 };
|
||||
if (kind === 'device_feature_action') return { device_id: app.devices[0]?.id || '', feature: 'light', value: true, cooldown_seconds: 60 };
|
||||
@@ -424,6 +426,10 @@ function flowNodeSummary(node) {
|
||||
if (node.kind === 'logic_and') return tr('flow.allConditions');
|
||||
if (node.kind === 'logic_or') return tr('flow.anyCondition');
|
||||
if (node.kind === 'logic_not') return tr('flow.invertCondition');
|
||||
if (node.kind === 'zone_heat_cycle') {
|
||||
const zone = app.zones.find(z => z.id === c.zone_id);
|
||||
return `${zone?.name || tr('common.noZone')} · ${tr('flow.simpleHeatDay')} ${fmtTemp(c.day_target)} · ${tr('flow.simpleHeatNight')} ${fmtTemp(c.night_target)}`;
|
||||
}
|
||||
if (node.kind === 'zone_thermostat') {
|
||||
const zone = app.zones.find(z => z.id === c.zone_id);
|
||||
const power = c.power == null ? null : (c.power ? tr('common.on') : tr('common.off'));
|
||||
@@ -627,10 +633,18 @@ function renderFlowBlockLibrary(filter = '') {
|
||||
const groups = $$('.flow-palette .flow-palette-group');
|
||||
host.innerHTML = groups.map(group => {
|
||||
const title = group.querySelector(':scope > span')?.textContent?.trim() || '';
|
||||
const buttons = $$('[data-flow-add]', group).filter(button => !query || `${title} ${button.textContent}`.toLocaleLowerCase(locale()).includes(query));
|
||||
const buttons = $$('[data-flow-add]', group).filter(button => {
|
||||
const meta = FLOW_NODE_META[button.dataset.flowAdd];
|
||||
const description = meta?.descriptionKey ? tr(meta.descriptionKey) : '';
|
||||
return !query || `${title} ${button.textContent} ${description}`.toLocaleLowerCase(locale()).includes(query);
|
||||
});
|
||||
if (!buttons.length) return '';
|
||||
const categoryClass = [...group.classList].find(name => name.startsWith('block-category-')) || '';
|
||||
return `<section class="flow-block-library-group ${esc(categoryClass)}"><h3>${esc(title)}</h3><div>${buttons.map(button => `<button type="button" class="secondary" data-flow-add="${esc(button.dataset.flowAdd)}">${esc(button.textContent.trim())}</button>`).join('')}</div></section>`;
|
||||
return `<section class="flow-block-library-group ${esc(categoryClass)}"><h3>${esc(title)}</h3><div>${buttons.map(button => {
|
||||
const meta = FLOW_NODE_META[button.dataset.flowAdd];
|
||||
const description = meta?.descriptionKey ? tr(meta.descriptionKey) : '';
|
||||
return `<button type="button" class="secondary" data-flow-add="${esc(button.dataset.flowAdd)}" ${description ? `title="${esc(description)}"` : ''}><span>${esc(button.textContent.trim())}</span>${description ? `<small class="flow-block-library-description">${esc(description)}</small>` : ''}</button>`;
|
||||
}).join('')}</div></section>`;
|
||||
}).join('') || `<div class="empty compact"><strong>${esc(tr('flow.noBlocksFound'))}</strong><span>${esc(tr('flow.noBlocksFoundHint'))}</span></div>`;
|
||||
}
|
||||
|
||||
@@ -647,7 +661,7 @@ function renderFlowEditor() {
|
||||
const nodesHost = $('#flowNodes');
|
||||
nodesHost.innerHTML = draft.nodes.map(node => {
|
||||
const meta = FLOW_NODE_META[node.kind] || { title: node.kind, category: 'logic' };
|
||||
return `<article class="diagram-node flow-node-${esc(meta.category)} ${(app.flowSelectedNodeIds || []).includes(node.id) ? 'selected' : ''}" data-flow-node="${esc(node.id)}" style="left:${Number(node.x || 0)}px;top:${Number(node.y || 0)}px">
|
||||
return `<article class="diagram-node flow-node-${esc(meta.category)} ${(app.flowSelectedNodeIds || []).includes(node.id) ? 'selected' : ''}" data-flow-node="${esc(node.id)}" ${meta.descriptionKey ? `title="${esc(tr(meta.descriptionKey))}"` : ''} style="left:${Number(node.x || 0)}px;top:${Number(node.y || 0)}px">
|
||||
<button class="diagram-port flow-port-in" type="button" data-flow-input="${esc(node.id)}" title="${esc(tr('flow.connectHere'))}"></button>
|
||||
<div class="flow-node-head"><span>${esc(flowNodeTitle(meta))}</span><button type="button" data-flow-remove="${esc(node.id)}" aria-label="${esc(tr('actions.delete'))}">${uiIcon('close')}</button></div>
|
||||
<div class="flow-node-body">${esc(flowNodeSummary(node))}${node.kind === 'shared_input' && node.config?.input_id ? `<div class="flow-node-current" data-flow-shared-current="${esc(node.config.input_id)}"><span>${esc(tr('flow.sharedInputTestCurrent'))}</span><strong>—</strong></div>` : ''}</div>
|
||||
@@ -773,11 +787,50 @@ function flowIncomingHeatingCurve(nodeId) {
|
||||
return edge ? flowNodeById(edge.from) : null;
|
||||
}
|
||||
|
||||
// The inspector derives all examples from this block's room targets; GREE
|
||||
// accepts whole-degree setpoints but the room targets may have tenths.
|
||||
function flowSimpleHeatHelp(config, nodeId = '', expanded = false) {
|
||||
const day = Number.isFinite(Number(config.day_target)) ? Number(config.day_target) : 20;
|
||||
const night = Number.isFinite(Number(config.night_target)) ? Number(config.night_target) : 20.6;
|
||||
const minimum = Number.isFinite(Number(config.day_min)) ? Number(config.day_min) : 19.7;
|
||||
const boostMinutes = Number.isFinite(Number(config.boost_after_minutes)) ? Number(config.boost_after_minutes) : 10;
|
||||
const offMinutes = Number.isFinite(Number(config.day_off_after_minutes)) ? Number(config.day_off_after_minutes) : 30;
|
||||
const nightGentle = Math.min(30, Math.max(16, Math.floor(night) + 1));
|
||||
const nightBoost = Math.min(30, Math.max(16, Math.ceil(night + 2)));
|
||||
const nightBrake = Math.min(30, Math.max(16, Math.floor(night - 0.5)));
|
||||
const nightStandby = Math.min(30, Math.max(16, Math.floor(night - 1)));
|
||||
const dayGentle = Math.min(30, Math.max(16, Math.floor(day) + 1));
|
||||
const dayBoost = Math.min(30, Math.max(16, Math.ceil(day + 2)));
|
||||
const line = (key, params) => `<li>${esc(tr(key, params))}</li>`;
|
||||
return `<details class="flow-block-help" data-flow-help-node="${esc(nodeId)}" ${expanded ? 'open' : ''}>
|
||||
<summary>${esc(tr('flow.simpleHeatHowItWorks'))}</summary>
|
||||
<p>${esc(tr('flow.simpleHeatHelpIntro'))}</p>
|
||||
<strong>${esc(tr('flow.simpleHeatHelpNightTitle', { target: fmtTemp(night) }))}</strong>
|
||||
<ul>
|
||||
${line('flow.simpleHeatHelpNightSleep', { room: fmtTemp(night), gree: fmtTemp(nightStandby) })}
|
||||
${line('flow.simpleHeatHelpNightBrake', { room: fmtTemp(night - 0.1), gree: fmtTemp(nightBrake) })}
|
||||
${line('flow.simpleHeatHelpNightGentle', { room: fmtTemp(night - 0.1), gree: fmtTemp(nightGentle) })}
|
||||
${line('flow.simpleHeatHelpNightBoost', { room: fmtTemp(night - 0.8), minutes: boostMinutes, gree: fmtTemp(nightBoost) })}
|
||||
</ul>
|
||||
<strong>${esc(tr('flow.simpleHeatHelpDayTitle', { target: fmtTemp(day), minimum: fmtTemp(minimum) }))}</strong>
|
||||
<ul>
|
||||
${line('flow.simpleHeatHelpDayOff', { minimum: fmtTemp(minimum) })}
|
||||
${line('flow.simpleHeatHelpDayHeat', { gentle: fmtTemp(dayGentle), boost: fmtTemp(dayBoost), room: fmtTemp(day - 0.8), minutes: boostMinutes })}
|
||||
${line('flow.simpleHeatHelpDayStop', { target: fmtTemp(day), minutes: offMinutes })}
|
||||
</ul>
|
||||
<p>${esc(tr('flow.simpleHeatHelpSensors'))}</p>
|
||||
<p>${esc(tr('flow.simpleHeatHelpSchedule'))}</p>
|
||||
<p>${esc(tr('flow.simpleHeatHelpControl'))}</p>
|
||||
</details>`;
|
||||
}
|
||||
|
||||
function renderFlowInspector() {
|
||||
const host = $('#flowInspector'), node = flowNodeById(app.flowSelectedNodeId); if (!host) return;
|
||||
host.classList.toggle('has-selection', Boolean(node));
|
||||
if (!node) { host.classList.remove('is-expanded'); host.innerHTML = `<div class="empty compact"><strong>${esc(tr('flow.selectBlock'))}</strong><span>${esc(tr('flow.selectBlockHint'))}</span></div>`; return; }
|
||||
const c = node.config || {}, meta = FLOW_NODE_META[node.kind] || { title: node.kind };
|
||||
const currentHelp = host.querySelector('.flow-block-help');
|
||||
const helpOpen = Boolean(currentHelp?.open && currentHelp.dataset.flowHelpNode === node.id);
|
||||
let fields = '';
|
||||
if (node.kind === 'weekday') fields = `<div class="flow-day-grid">${[1, 2, 3, 4, 5, 6, 7].map(day => `<label><input type="checkbox" data-flow-config="days" value="${day}" ${(c.days || []).includes(day) ? 'checked' : ''}><span>${esc(tr(`day.${day}`))}</span></label>`).join('')}</div>`;
|
||||
else if (node.kind === 'time_range') fields = `<div class="two"><label><span>${esc(tr('common.from'))}</span><input type="time" data-flow-config="start" value="${esc(c.start || '06:00')}"></label><label><span>${esc(tr('common.to'))}</span><input type="time" data-flow-config="end" value="${esc(c.end || '08:00')}"></label></div>`;
|
||||
@@ -803,7 +856,7 @@ function renderFlowInspector() {
|
||||
else if (node.kind === 'zone_state') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-flow-config="field">${['enabled', 'mode', 'active_preset', 'demand', 'control_owner', 'device_manual_override', 'local_thermostat_power'].map(v => `<option value="${v}" ${c.field === v ? 'selected' : ''}>${esc(v)}</option>`).join('')}</select></label>${flowTextComparisonFields(c)}`;
|
||||
else if (node.kind === 'group_state') fields = `<label><span>${esc(tr('groups.group'))}</span><select data-flow-config="group_id">${flowSelectOptions(app.groups, c.group_id)}</select></label><label><span>${esc(tr('flow.field'))}</span><select data-flow-config="field"><option value="power_enabled" selected>power_enabled</option></select></label>${flowTextComparisonFields(c)}`;
|
||||
else if (node.kind === 'night_mode') fields = `<p class="field-note">${esc(tr('flow.nightModeHint'))}</p>`;
|
||||
else if (node.kind === 'constant') fields = `<label><span>${esc(tr('flow.value'))}</span><select data-flow-config="value"><option value="true" ${c.value !== false ? 'selected' : ''}>${esc(tr('common.yes'))}</option><option value="false" ${c.value === false ? 'selected' : ''}>${esc(tr('common.no'))}</option></select></label>`;
|
||||
else if (node.kind === 'constant') fields = `<label><span>${esc(tr('flow.value'))}</span><select data-flow-config="value"><option value="true" ${c.value !== false ? 'selected' : ''}>${esc(tr('common.yes'))}</option><option value="false" ${c.value === false ? 'selected' : ''}>${esc(tr('common.no'))}</option></select></label><p class="field-note">${esc(tr('flow.constantHelp'))}</p>`;
|
||||
else if (node.kind === 'shared_input') {
|
||||
const item = (app.flowSharedInputs || []).find(value => value.id === c.input_id) || app.flowSharedInputs?.[0];
|
||||
fields = (app.flowSharedInputs || []).length
|
||||
@@ -813,6 +866,19 @@ function renderFlowInspector() {
|
||||
else if (node.kind === 'logic_and') fields = `<p class="field-note">${esc(tr('flow.andHint'))}</p>`;
|
||||
else if (node.kind === 'logic_or') fields = `<p class="field-note">${esc(tr('flow.orHint'))}</p>`;
|
||||
else if (node.kind === 'logic_not') fields = `<p class="field-note">${esc(tr('flow.notHint'))}</p>`;
|
||||
else if (node.kind === 'zone_heat_cycle') fields = `
|
||||
<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label>
|
||||
<div class="two">
|
||||
<label title="${esc(tr('flow.simpleHeatDayTip'))}"><span>${esc(tr('flow.simpleHeatDay'))} °C</span><input type="number" min="17" max="28" step="0.1" data-flow-config="day_target" value="${Number(c.day_target ?? 20)}"></label>
|
||||
<label title="${esc(tr('flow.simpleHeatNightTip'))}"><span>${esc(tr('flow.simpleHeatNight'))} °C</span><input type="number" min="17" max="28" step="0.1" data-flow-config="night_target" value="${Number(c.night_target ?? 20.6)}"></label>
|
||||
</div>
|
||||
<label title="${esc(tr('flow.simpleHeatMinimumTip'))}"><span>${esc(tr('flow.simpleHeatMinimum'))} °C</span><input type="number" min="17" max="28" step="0.1" data-flow-config="day_min" value="${Number(c.day_min ?? 19.7)}"></label>
|
||||
<div class="two">
|
||||
<label title="${esc(tr('flow.simpleHeatBoostTip'))}"><span>${esc(tr('flow.simpleHeatBoostMinutes'))}</span><input type="number" min="3" max="30" step="1" data-flow-config="boost_after_minutes" value="${Number(c.boost_after_minutes ?? 10)}"></label>
|
||||
<label title="${esc(tr('flow.simpleHeatOffTip'))}"><span>${esc(tr('flow.simpleHeatOffMinutes'))}</span><input type="number" min="10" max="120" step="1" data-flow-config="day_off_after_minutes" value="${Number(c.day_off_after_minutes ?? 30)}"></label>
|
||||
</div>
|
||||
<p class="field-note">${esc(tr('flow.simpleHeatHint'))}</p>
|
||||
${flowSimpleHeatHelp(c, node.id, helpOpen)}`;
|
||||
else if (node.kind === 'zone_thermostat') fields = `<label><span>${esc(tr('common.zone'))}</span><select data-flow-config="zone_id">${flowSelectOptions(app.zones, c.zone_id)}</select></label><div class="two"><label><span>${esc(tr('schedules.profile'))}</span><select data-flow-config="preset">${['auto', 'comfort', 'sleep', 'away', 'custom'].map(v => `<option value="${v}" ${c.preset === v ? 'selected' : ''}>${esc(zonePresetLabel(v))}</option>`).join('')}</select></label><label><span>${esc(tr('common.targetC'))}</span><input type="number" min="8" max="30" step="0.1" data-flow-config="setpoint" value="${Number(c.setpoint ?? 21)}"></label></div><div class="two"><label><span>${esc(tr('common.mode'))}</span><select data-flow-config="mode"><option value="auto" ${c.mode === 'auto' ? 'selected' : ''}>Auto</option><option value="heat" ${c.mode === 'heat' ? 'selected' : ''}>${esc(tr('mode.heat'))}</option><option value="cool" ${c.mode === 'cool' ? 'selected' : ''}>${esc(tr('mode.cool'))}</option></select></label><label><span>${esc(tr('common.power'))}</span><select data-flow-config="power"><option value="" ${c.power == null ? 'selected' : ''}>${esc(tr('actions.noChange'))}</option><option value="true" ${c.power === true ? 'selected' : ''}>${esc(tr('common.on'))}</option><option value="false" ${c.power === false ? 'selected' : ''}>${esc(tr('common.off'))}</option></select></label></div><div class="two"><label><span>${esc(tr('zones.heatAnticipation'))}</span><input type="number" min="0" max="2.5" step="0.1" data-flow-config="heat_anticipation_c" value="${c.heat_anticipation_c == null ? '' : Number(c.heat_anticipation_c)}" placeholder="—"></label><label><span>${esc(tr('zones.standbyOffset'))}</span><input type="number" min="0.5" max="8" step="0.1" data-flow-config="standby_offset_c" value="${c.standby_offset_c == null ? '' : Number(c.standby_offset_c)}" placeholder="—"></label></div><details class="flow-device-options"><summary>${esc(tr('flow.thermostatDeviceOptions'))}</summary><div class="two">${flowOptionalLouverField(c, 'swing_vertical', 'vertical')}${flowOptionalLouverField(c, 'swing_horizontal', 'horizontal')}</div><p class="field-note">${esc(tr('flow.thermostatDeviceOptionsHint'))}</p></details><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
||||
else if (node.kind === 'device_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label>${flowActionFields(c, false)}`;
|
||||
else if (node.kind === 'device_feature_action') fields = `<label><span>${esc(tr('common.device'))}</span><select data-flow-config="device_id">${flowSelectOptions(app.devices, c.device_id)}</select></label><label><span>${esc(tr('flow.deviceFeature'))}</span><select data-flow-config="feature">${flowDeviceFeatureOptions(c.feature)}</select></label>${flowDeviceFeatureValueField(c)}<p class="field-note">${esc(tr('flow.deviceFeatureHint'))}</p><label><span>${esc(tr('common.cooldownSeconds'))}</span><input type="number" min="30" data-flow-config="cooldown_seconds" value="${Number(c.cooldown_seconds || 60)}"></label>`;
|
||||
@@ -826,7 +892,8 @@ function renderFlowInspector() {
|
||||
} else if (incomingCurve && node.kind === 'ha_service_action') {
|
||||
fields += `<label><span>${esc(tr('flow.heatingCurveHaValueKey'))}</span><input data-flow-config="value_key" value="${esc(c.value_key || 'temperature')}" placeholder="temperature"></label><p class="field-note flow-value-binding-note">${esc(tr('flow.heatingCurveHaBindingHint'))}</p>`;
|
||||
}
|
||||
host.innerHTML = `<div class="flow-inspector-head"><div><span class="eyebrow">${esc(tr('flow.blockSettings'))}</span><h3>${esc(flowNodeTitle(meta))}</h3></div><div class="flow-inspector-head-actions"><button type="button" class="secondary flow-inspector-expand" data-action="flow-toggle-inspector" aria-label="${esc(tr('flow.expandSettings'))}">${uiIcon('expand-vertical')}</button><button type="button" class="secondary flow-inspector-close" data-action="flow-clear-selection" aria-label="${esc(tr('flow.clearSelection'))}">${uiIcon('close')}</button><button type="button" class="danger flow-inspector-delete" data-flow-remove="${esc(node.id)}">${esc(tr('actions.delete'))}</button></div></div>${fields}<div class="flow-inspector-meta"><small>ID</small><code>${esc(node.id)}</code></div>`;
|
||||
const description = meta.descriptionKey ? `<p class="flow-inspector-description">${esc(tr(meta.descriptionKey))}</p>` : '';
|
||||
host.innerHTML = `<div class="flow-inspector-head"><div><span class="eyebrow">${esc(tr('flow.blockSettings'))}</span><h3>${esc(flowNodeTitle(meta))}</h3></div><div class="flow-inspector-head-actions"><button type="button" class="secondary flow-inspector-expand" data-action="flow-toggle-inspector" aria-label="${esc(tr('flow.expandSettings'))}">${uiIcon('expand-vertical')}</button><button type="button" class="secondary flow-inspector-close" data-action="flow-clear-selection" aria-label="${esc(tr('flow.clearSelection'))}">${uiIcon('close')}</button><button type="button" class="danger flow-inspector-delete" data-flow-remove="${esc(node.id)}">${esc(tr('actions.delete'))}</button></div></div>${description}${fields}<div class="flow-inspector-meta"><small>ID</small><code>${esc(node.id)}</code></div>`;
|
||||
}
|
||||
|
||||
function flowTextComparisonFields(c, numeric = false) { const options = numeric ? flowOperatorOptions(c.operator || 'eq') : [['eq', '='], ['neq', '≠']].map(([v, l]) => `<option value="${v}" ${v === (c.operator || 'eq') ? 'selected' : ''}>${l}</option>`).join(''); return `<div class="two"><label><span>${esc(tr('flow.operator'))}</span><select data-flow-config="operator">${options}</select></label><label><span>${esc(tr('flow.value'))}</span><input data-flow-config="value" value="${esc(c.value ?? '')}"></label></div>`; }
|
||||
@@ -1736,6 +1803,8 @@ function updateFlowConfig(input) {
|
||||
else if (wasLouver) node.config.value = '';
|
||||
}
|
||||
else if (node.kind === 'device_state' && key === 'value' && ['swing_vertical', 'swing_horizontal'].includes(node.config.field)) node.config.value = Number(input.value);
|
||||
else if (node.kind === 'zone_heat_cycle' && ['day_target', 'night_target', 'day_min', 'boost_after_minutes', 'day_off_after_minutes'].includes(key))
|
||||
node.config[key] = Number(input.value);
|
||||
else if (node.kind === 'zone_thermostat' && ['heat_anticipation_c', 'standby_offset_c'].includes(key))
|
||||
node.config[key] = input.value === '' ? null : Number(input.value);
|
||||
else if (['swing_vertical', 'swing_horizontal'].includes(key)) node.config[key] = input.value === '' ? null : Number(input.value);
|
||||
|
||||
Reference in new issue
Block a user