diff --git a/custom_components/omni_pca/frontend/src/omni-panel-programs.ts b/custom_components/omni_pca/frontend/src/omni-panel-programs.ts index e089efd..6d0a8f3 100644 --- a/custom_components/omni_pca/frontend/src/omni-panel-programs.ts +++ b/custom_components/omni_pca/frontend/src/omni-panel-programs.ts @@ -1849,9 +1849,9 @@ export class OmniPanelPrograms extends LitElement { ): TemplateResult { const s = decodeStructuredAnd(cond); if (!isEditableStructuredAnd(s)) { - // Out of editor scope (non-constant Arg2, unsupported Arg1 type, - // or non-zero compConst). Surface as preserve-only so the user - // can still remove the row but can't damage the encoded data. + // Out of editor scope (unsupported Arg1/Arg2 type or non-zero + // CompConst). Surface as preserve-only so the user can still + // remove the row but can't damage the encoded data. return html`
@@ -1862,8 +1862,8 @@ export class OmniPanelPrograms extends LitElement {
Structured comparison with a shape the editor can't drive - yet (Arg2 references another object, Arg1 is an unsupported - type, or a CompConst value is present). Preserved on save. + yet (Arg1 or Arg2 is an unsupported type, or a CompConst + value is present). Preserved on save.
`; } @@ -1881,24 +1881,24 @@ export class OmniPanelPrograms extends LitElement { /** Render the editor for one structured-AND condition. Lays out as: * - * Arg1 type ▸ object/picker ▸ field ▸ operator ▸ Arg2 constant + * Arg1 type ▸ object/picker ▸ field ▸ operator ▸ + * Arg2 type ▸ (constant | object/picker ▸ field) * - * Arg2 is locked to Constant in this pass. For unary operators - * (ODD / EVEN) the Arg2 input is hidden. + * Both Arg1 and Arg2 support Zone / Unit / Thermostat / Area / + * TimeDate references; Arg2 also supports plain Constant. For + * unary operators (ODD / EVEN) the Arg2 controls are hidden. */ private _renderStructuredAndForm( s: DecodedStructuredAnd, idx: number, ): TemplateResult { const update = (patch: Partial) => { const merged = { ...s, ...patch }; - // Force Arg2 = Constant in editor scope so nothing accidentally - // promotes to an object reference. - merged.arg2Type = 0; - merged.arg2Field = 0; this._patchChainCondition(idx, encodeStructuredAnd(merged)); }; const arg1Fields = FIELDS_BY_TYPE[s.arg1Type] ?? []; const arg1Kind = argTypeKind(s.arg1Type); + const arg2Fields = FIELDS_BY_TYPE[s.arg2Type] ?? []; + const arg2Kind = argTypeKind(s.arg2Type); const showArg2 = !isUnaryOp(s.op); return html`
@@ -1906,19 +1906,10 @@ export class OmniPanelPrograms extends LitElement { Arg1 type - ${arg1Kind ? this._renderStructuredArg1Picker(s, arg1Kind, update) : ""} + ${arg1Kind ? this._renderStructuredObjectPicker( + arg1Kind, s.arg1Ix, (v) => update({ arg1Ix: v }), "Arg1", + ) : ""} ${arg1Fields.length > 0 ? html` + + ${s.arg2Type === 0 ? html` + ` : ""} + + ${arg2Kind ? this._renderStructuredObjectPicker( + arg2Kind, s.arg2Ix, (v) => update({ arg2Ix: v }), "Arg2", + ) : ""} + + ${s.arg2Type !== 0 && arg2Fields.length > 0 ? html` + ` : ""} + ` : ""}
`; } - private _renderStructuredArg1Picker( - s: DecodedStructuredAnd, + /** First discovered object index for a given kind, falling back to 1 + * for reference kinds (TimeDate / null returns 0 — "no object"). */ + private _defaultIxForKind(kind: string | null): number { + switch (kind) { + case "zone": return this._objects?.zones?.[0]?.index ?? 1; + case "unit": return this._objects?.units?.[0]?.index ?? 1; + case "thermostat": return this._objects?.thermostats?.[0]?.index ?? 1; + case "area": return this._objects?.areas?.[0]?.index ?? 1; + default: return 0; + } + } + + private _renderStructuredObjectPicker( kind: string, - update: (p: Partial) => void, + current: number, + onChange: (v: number) => void, + labelPrefix: string, ): TemplateResult { const bucket = this._bucketWithPreserve( - this._pickBucket(kind), kind, s.arg1Ix, + this._pickBucket(kind), kind, current, ); - const label = kind[0].toUpperCase() + kind.slice(1); + const kindLabel = kind[0].toUpperCase() + kind.slice(1); return html` + + ${e.arg2Type===0?o` + `:""} + + ${l?this._renderStructuredObjectPicker(l,e.arg2Ix,c=>i({arg2Ix:c}),"Arg2"):""} + + ${e.arg2Type!==0&&u.length>0?o` + `:""} + `:""} + `}_defaultIxForKind(e){switch(e){case"zone":return this._objects?.zones?.[0]?.index??1;case"unit":return this._objects?.units?.[0]?.index??1;case"thermostat":return this._objects?.thermostats?.[0]?.index??1;case"area":return this._objects?.areas?.[0]?.index??1;default:return 0}}_renderStructuredObjectPicker(e,t,i,r){let s=this._bucketWithPreserve(this._pickBucket(e),e,t),u=e[0].toUpperCase()+e.slice(1);return o` `}_renderChainCondFamily(e,t){let i=s=>{let c=this._objects?.zones?.[0]?.index??1,l=this._objects?.units?.[0]?.index??1,d=this._objects?.areas?.[0]?.index??1,p;switch(s){case"none":p={family:"none"};break;case"misc":p={family:"misc",misc:1};break;case"zone":p={family:"zone",index:c,active:!1};break;case"unit":p={family:"unit",index:l,active:!0};break;case"time":p={family:"time",index:1,active:!0};break;case"sec":p={family:"sec",index:d,mode:0};break}let u=ve(p);this._patchChainCondition(t,u)},r=s=>{this._patchChainCondition(t,ve(s))};return o` + `}_renderChainCondFamily(e,t){let i=s=>{let u=this._objects?.zones?.[0]?.index??1,l=this._objects?.units?.[0]?.index??1,p=this._objects?.areas?.[0]?.index??1,c;switch(s){case"none":c={family:"none"};break;case"misc":c={family:"misc",misc:1};break;case"zone":c={family:"zone",index:u,active:!1};break;case"unit":c={family:"unit",index:l,active:!0};break;case"time":c={family:"time",index:1,active:!0};break;case"sec":c={family:"sec",index:p,mode:0};break}let d=$e(c);this._patchChainCondition(t,d)},r=s=>{this._patchChainCondition(t,$e(s))};return o`