import { toEffectRaw, type Effect, type EffectRaw, fromEffectRaw } from "./assignment" export default interface Script { name: string lines: ScriptLine[] } export interface Trigger { id: string name: string event: "Button" | "Time" deviceMatch: string parameter: string scriptTarget: string scriptName: string scriptPre: ScriptLine[] scriptPost: ScriptLine[] } export interface TriggerInput extends Pick> { id: string | undefined } export interface TriggerEditable { id: string | null name: string event: "Button" | "Time" deviceMatch: string time: string button: string scriptTarget: string scriptName: string scriptPre: ScriptLineEditable[] scriptPost: ScriptLineEditable[] } type ScriptLineEditableData = ScriptLineIf & ScriptLineAssign & ScriptLineSet & ScriptLineSelect export interface ScriptLineEditable { kind: "if" | "assign" | "set" | "select" condition: Required effect: EffectRaw matchKind: "all" | "name" | "role" | "raw" matchValue: string scope: string key: string value: string then: ScriptLineEditable[] else: ScriptLineEditable[] } export type ScriptLine = | { if: ScriptLineIf } | { assign: ScriptLineAssign } | { set: ScriptLineSet } | { select: ScriptLineSelect } export interface ScriptLineIf { condition: ScriptCondition then: ScriptLine[] else: ScriptLine[] } export interface ScriptCondition { scope: string key: string op: string value?: string not?: boolean } export interface ScriptLineAssign { match: string effect: Effect } export interface ScriptLineSet { scope: string key: string value: string } export interface ScriptLineAssign { match: string effect: Effect } export interface ScriptLineSelect { match: string then: ScriptLine[] } export function fromEditableScriptLine(line: ScriptLineEditable): ScriptLine { switch (line.kind) { case "if": return { if: { condition: line.condition, then: line.then.map(fromEditableScriptLine), else: line.else.map(fromEditableScriptLine), } }; case "assign": return { assign: { match: fromMatchKindValue(line), effect: fromEffectRaw(line.effect), } } case "select": return { select: { match: fromMatchKindValue(line), then: line.then.map(fromEditableScriptLine), } } case "set": return { set: { key: line.key, scope: line.scope, value: line.value, } } } } export function newEditableScriptLine(): ScriptLineEditable { return toEditableScriptLine({if: { condition: { scope: "global", key: "", op: "eq" }, then: [], else: [], }}) } export function toEditableScriptLine(line: ScriptLine): ScriptLineEditable { const base = emptyEditable(); if ("assign" in line) { base.kind = "assign"; base.effect = toEffectRaw(line.assign.effect); Object.assign(base, toMatchKindValue(line.assign.match)); } else if ("if" in line) { base.kind = "if"; base.condition = { ...line.if.condition, not: line.if.condition.not || false, value: line.if.condition.value || "", }; base.then = (line.if.then||[]).map(toEditableScriptLine); base.else = (line.if.else||[]).map(toEditableScriptLine); } else if ("select" in line) { base.kind = "select"; Object.assign(base, toMatchKindValue(line.select.match)); base.then = (line.select.then||[]).map(toEditableScriptLine); } else if ("set" in line) { base.kind = "set"; base.key = line.set.key base.scope = line.set.scope base.value = line.set.value } return base; } function toMatchKindValue(match: string): { matchKind: "all" | "name" | "role" | "raw", matchValue: string } { if (match.startsWith("lucifer:name:")) { return { matchKind: "name", matchValue: match.slice("lucifer:name:".length) }; } else if (match.startsWith("lucifer:role:")) { return { matchKind: "role", matchValue: match.slice("lucifer:role:".length) }; } else if (match === "*") { return { matchKind: "all", matchValue: "*" }; } else { return { matchKind: "raw", matchValue: match } } } function fromMatchKindValue({matchKind, matchValue }: { matchKind: "all" | "name" | "role" | "raw", matchValue: string }): string { switch (matchKind) { case "name": return `lucifer:name:${matchValue}`; case "role": return `lucifer:role:${matchValue}`; case "all": return "*"; case "raw": return matchValue; } } function emptyEditable(): ScriptLineEditable { return { condition: { key: "", op: "add", scope: "global", not: false, value: "", }, effect: toEffectRaw({ solid: { states: [ {color: null, intensity: null, power: null, temperature: null} ], interleave: 0, animationMs: 0, } }), key: "", kind: "assign", matchKind: "name", matchValue: "*", scope: "match", value: "", then: [], else: [], } } export function newEditableTrigger(deviceMatch: string, scriptName: string): TriggerEditable { return { id: null, name: "", event: "Button", deviceMatch: deviceMatch, button: "On", time: "00:00", scriptName: scriptName, scriptTarget: deviceMatch, scriptPre: [], scriptPost: [], } } export function toEditableTrigger(trigger: Trigger): TriggerEditable { return { id: trigger.id, name: trigger.name, event: trigger.event, deviceMatch: trigger.deviceMatch, button: trigger.event === "Button" ? trigger.parameter : "Up", time: trigger.event === "Time" ? trigger.parameter : "00:00", scriptName: trigger.scriptName, scriptTarget: trigger.scriptTarget, scriptPre: trigger.scriptPre.map(toEditableScriptLine), scriptPost: trigger.scriptPost.map(toEditableScriptLine), } } export function fromEditableTrigger(editable: TriggerEditable): TriggerInput { return { id: editable.id || undefined, name: editable.name, event: editable.event, deviceMatch: editable.deviceMatch, parameter: (editable.event === "Button") ? editable.button : editable.time, scriptTarget: editable.scriptTarget, scriptName: editable.scriptName, scriptPre: editable.scriptPre.map(fromEditableScriptLine), scriptPost: editable.scriptPost.map(fromEditableScriptLine), } }