Gisle Aune
1 year ago
15 changed files with 410 additions and 45 deletions
-
2frontend/src/lib/components/HSplit.svelte
-
4frontend/src/lib/components/Modal.svelte
-
1frontend/src/lib/contexts/ModalContext.svelte
-
32frontend/src/lib/contexts/StateContext.svelte
-
181frontend/src/lib/modals/TriggerModal.svelte
-
10frontend/src/lib/models/command.ts
-
81frontend/src/lib/models/script.ts
-
10frontend/src/lib/models/uistate.ts
-
6frontend/src/routes/+page.svelte
-
29services/httpapiv1/service.go
-
21services/script/service.go
-
42services/script/variables.go
-
16services/uistate/data.go
-
6services/uistate/patch.go
-
12services/uistate/service.go
@ -0,0 +1,181 @@ |
|||
<script lang="ts"> |
|||
import { runCommand } from "$lib/client/lucifer"; |
|||
import HSplit from "$lib/components/HSplit.svelte"; |
|||
import HSplitPart from "$lib/components/HSplitPart.svelte"; |
|||
import Modal from "$lib/components/Modal.svelte"; |
|||
import ModalBody from "$lib/components/ModalBody.svelte"; |
|||
import ModalSection from "$lib/components/ModalSection.svelte"; |
|||
import ScriptLineBlock from "$lib/components/scripting/ScriptLineBlock.svelte"; |
|||
import { getModalContext } from "$lib/contexts/ModalContext.svelte"; |
|||
import { getStateContext } from "$lib/contexts/StateContext.svelte"; |
|||
import { fromEditableTrigger, newEditableScriptLine, newEditableTrigger, toEditableScriptLine, toEditableTrigger, type TriggerEditable } from "$lib/models/script"; |
|||
|
|||
const { state, deviceList, maskList } = getStateContext(); |
|||
const { modal } = getModalContext(); |
|||
|
|||
let show = false; |
|||
let disabled = false; |
|||
let selectedTrigger = ""; |
|||
let deleteCount = 3; |
|||
|
|||
let current: TriggerEditable = newEditableTrigger("*", ""); |
|||
|
|||
async function submitForm() { |
|||
disabled = true; |
|||
|
|||
try { |
|||
const before = Object.keys($state.triggers); |
|||
await runCommand({updateTrigger: fromEditableTrigger(current)}); |
|||
|
|||
if (current.id === null) { |
|||
await new Promise(resolve => setTimeout(resolve, 1000)); |
|||
const after = Object.keys($state.triggers); |
|||
selectedTrigger = after.find(id => !before.includes(id)) || triggerList[0].id; |
|||
} |
|||
} catch(_) {} |
|||
|
|||
deleteCount = 3; |
|||
disabled = false; |
|||
} |
|||
|
|||
async function deleteTrigger() { |
|||
deleteCount -= 1; |
|||
if (deleteCount <= 0) { |
|||
try { |
|||
await runCommand({deleteTrigger: {id: selectedTrigger}}); |
|||
await new Promise(resolve => setTimeout(resolve, 1000)); |
|||
selectedTrigger = triggerList[0]?.id || ""; |
|||
} catch(_) {} |
|||
} |
|||
} |
|||
|
|||
function loadTrigger(id: string) { |
|||
deleteCount = 3; |
|||
|
|||
if (id !== "") { |
|||
current = toEditableTrigger($state.triggers[id]); |
|||
} else { |
|||
current = newEditableTrigger($maskList[0], scriptList?.[0] || ""); |
|||
} |
|||
} |
|||
|
|||
function addPreLine() { |
|||
current.scriptPre = [...current.scriptPre, newEditableScriptLine()] |
|||
} |
|||
|
|||
function addPostLine() { |
|||
current.scriptPost = [...current.scriptPost, newEditableScriptLine()] |
|||
} |
|||
|
|||
function ensureButton(buttonList: string[]) { |
|||
if (!buttonList.includes(current.button)) { |
|||
current.button = buttonList[0]; |
|||
} |
|||
} |
|||
|
|||
$: loadTrigger(selectedTrigger); |
|||
$: triggerList = Object.keys($state.triggers).map(k => $state.triggers[k]).sort((a, b) => ( |
|||
a.event.localeCompare(b.event) |
|||
|| a.deviceMatch.localeCompare(b.deviceMatch) |
|||
|| a.parameter.localeCompare(b.parameter) |
|||
)); |
|||
$: buttonList = $deviceList.filter(d => d.aliases.includes(current.deviceMatch)) |
|||
.flatMap(d => d.hwState?.buttons||[]) |
|||
.sort() |
|||
.filter((e,i,a) => a[i-1] !== e); |
|||
$: scriptList = Object.keys($state.scripts).sort(); |
|||
$: maskGroups = $maskList.map(s => s.split(":")[1]).filter((e, i, a) => a[i-1] !== e).sort().reverse(); |
|||
$: show = ($modal.kind === "trigger.edit"); |
|||
$: if (scriptList.length > 0 && current.scriptName === "") { current.scriptName = scriptList[0]; }; |
|||
$: ensureButton(buttonList); |
|||
</script> |
|||
|
|||
<form novalidate on:submit|preventDefault={submitForm}> |
|||
<Modal |
|||
ultrawide closable show={show} |
|||
titleText="Trigger Editor" |
|||
disabled={disabled} |
|||
submitText="Save Trigger" |
|||
> |
|||
<ModalBody> |
|||
<HSplit> |
|||
<HSplitPart weight={1}> |
|||
<label for="mask">Trigger</label> |
|||
<select bind:value={selectedTrigger}> |
|||
{#each triggerList as trigger (trigger.id)} |
|||
<option value={trigger.id}>{trigger.name || `${trigger.deviceMatch} – ${trigger.event} ${trigger.parameter}`}</option> |
|||
{/each} |
|||
<option value="">New Trigger</option> |
|||
</select> |
|||
</HSplitPart> |
|||
<HSplitPart weight={2}> |
|||
</HSplitPart> |
|||
</HSplit> |
|||
<HSplit> |
|||
<HSplitPart weight={1}> |
|||
<label for="mask">Event</label> |
|||
<select bind:value={current.event}> |
|||
<option value="Button">Button Pressed</option> |
|||
<option value="Time">Time Changed</option> |
|||
</select> |
|||
</HSplitPart> |
|||
<HSplitPart weight={1}> |
|||
{#if current.event === "Button"} |
|||
<label for="mask">Button</label> |
|||
<select bind:value={current.button}> |
|||
{#each buttonList as buttonOption} |
|||
<option value={buttonOption}>{buttonOption}</option> |
|||
{/each} |
|||
</select> |
|||
{:else} |
|||
<label for="mask">Time</label> |
|||
<input type="time" bind:value={current.time} /> |
|||
{/if} |
|||
</HSplitPart> |
|||
<HSplitPart weight={1}> |
|||
{#if current.event === "Button"} |
|||
<label for="mask">Device</label> |
|||
<select bind:value={current.deviceMatch}> |
|||
{#each maskGroups as g} |
|||
<optgroup label="{g.slice(0, 1).toUpperCase() + g.slice(1)}"> |
|||
{#each $maskList.filter(m => m.split(":")[1] === g) as mask} |
|||
<option value={mask}>{mask}</option> |
|||
{/each} |
|||
</optgroup> |
|||
{/each} |
|||
</select> |
|||
{/if} |
|||
</HSplitPart> |
|||
</HSplit> |
|||
<HSplit> |
|||
<HSplitPart weight={1}> |
|||
<label for="mask">Run Script</label> |
|||
<select bind:value={current.scriptName}> |
|||
{#each scriptList as script} |
|||
<option value={script}>{script}</option> |
|||
{/each} |
|||
</select> |
|||
</HSplitPart> |
|||
<HSplitPart weight={1}> |
|||
<label for="mask">Script Target</label> |
|||
<select bind:value={current.scriptTarget}> |
|||
{#each maskGroups as g} |
|||
<optgroup label="{g.slice(0, 1).toUpperCase() + g.slice(1)}"> |
|||
{#each $maskList.filter(m => m.split(":")[1] === g) as mask} |
|||
<option value={mask}>{mask}</option> |
|||
{/each} |
|||
</optgroup> |
|||
{/each} |
|||
</select> |
|||
</HSplitPart> |
|||
<HSplitPart weight={1}> |
|||
</HSplitPart> |
|||
</HSplit> |
|||
<ModalSection expanded disableExpandToggle title="Trigger-Specific Scripting"> |
|||
<ScriptLineBlock add on:add={addPreLine} label="Before Script" bind:value={current.scriptPre} /> |
|||
<ScriptLineBlock add on:add={addPostLine} label="After Script" bind:value={current.scriptPost} /> |
|||
</ModalSection> |
|||
</ModalBody> |
|||
<button slot="secondary-button-1" disabled={disabled || selectedTrigger === ""} on:click|preventDefault={deleteTrigger}>Delete ({deleteCount})</button> |
|||
</Modal> |
|||
</form> |
@ -1,20 +1,26 @@ |
|||
import type Assignment from "./assignment"; |
|||
import type Device from "./device"; |
|||
import type { ScriptLine } from "./script"; |
|||
import type { ScriptLine, Trigger } from "./script"; |
|||
import type Script from "./script"; |
|||
|
|||
export default interface UIState { |
|||
devices: {[id: string]: Device} |
|||
assignments: {[id: string]: Assignment} |
|||
scripts: {[id: string]: ScriptLine[]} |
|||
triggers: {[id: string]: Trigger} |
|||
} |
|||
|
|||
export interface UIStatePatch { |
|||
device: Partial<Device> & { id: string, delete?: boolean, addAlias?: string, removeAlias?: string } |
|||
assignment: Partial<Assignment> & { id: string, delete?: boolean, addDeviceId?: string, removeDeviceId?: string } |
|||
script: Partial<Script> & { id: string, delete?: boolean } |
|||
trigger: Partial<Trigger> & { delete?: boolean } |
|||
} |
|||
|
|||
export interface UIStatePatch2 extends UIState { |
|||
export interface UIStatePatch2 { |
|||
full?: UIState |
|||
devices: {[id: string]: Device | null} |
|||
assignments: {[id: string]: Assignment | null} |
|||
scripts: {[id: string]: ScriptLine[] | null} |
|||
triggers: {[id: string]: Trigger | null} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue