|
|
<script lang="ts"> import { runCommand } from "$lib/client/lucifer"; import AssignmentState from "$lib/components/scripting/ScriptAssignmentState.svelte"; import Button from "$lib/components/Button.svelte"; import Checkbox from "$lib/components/Checkbox.svelte"; import type { DeviceIconName } from "$lib/components/DeviceIcon.svelte"; import DeviceIconSelector from "$lib/components/DeviceIconSelector.svelte"; import HSplit from "$lib/components/HSplit.svelte"; import HSplitPart from "$lib/components/HSplitPart.svelte"; import Icon from "$lib/components/Icon.svelte"; import Modal from "$lib/components/Modal.svelte"; import ModalBody from "$lib/components/ModalBody.svelte"; import ModalSection from "$lib/components/ModalSection.svelte"; import TagInput from "$lib/components/TagInput.svelte"; import { getModalContext } from "$lib/contexts/ModalContext.svelte"; import { getSelectedContext } from "$lib/contexts/SelectContext.svelte"; import { getStateContext } from "$lib/contexts/StateContext.svelte"; import { toEffectRaw, type EffectRaw, fromEffectRaw } from "$lib/models/assignment"; import type { DeviceEditOp } from "$lib/models/device"; import { iconName } from "@fortawesome/free-solid-svg-icons/faQuestion"; import ScriptAssignmentState from "$lib/components/scripting/ScriptAssignmentState.svelte";
const { modal } = getModalContext(); const { selectedMasks, selectedMap, selectedList } = getSelectedContext(); const { deviceList, assignmentList } = getStateContext();
let show: boolean = false; let match: string = ""; let disabled: boolean = false;
let enableRename: boolean = false; let newName: string = "";
let enableRoom: boolean = false; let newRoom: string = ""; let customRoom: string = "";
let enableGroup: boolean = false; let newGroup: string = ""; let customGroup: string = "";
let enableAssign: boolean = false; let newEffect: EffectRaw = toEffectRaw(undefined); let enableIcon: boolean = false; let newIcon: DeviceIconName = "generic_ball";
let enableTag: boolean = false; let newTags: string[] = []; let oldTags: string[] = [];
let enableRole: boolean = false; let newRoles: string[] = []; let oldRoles: string[] = []; function setupModal(op: DeviceEditOp) { show = true;
enableRename = (op === "rename"); enableAssign = (op === "assign"); enableIcon = (op === "change_icon"); enableRoom = (op === "move_room"); enableGroup = (op === "move_group"); enableTag = (op === "change_tags"); enableRole = (op === "change_roles");
const firstDevice = $deviceList.find(d => $selectedMap[d.id]);
newName = firstDevice?.name || ""; newIcon = firstDevice?.icon || "generic_ball"; newEffect = toEffectRaw(undefined); newRoom = ""; newGroup = "";
reloadTagsAndRoles();
for (const device of $deviceList) { if (!$selectedMap[device.id]) { continue; }
if (newRoom == "") { const roomAlias = device.aliases.find(a => a.startsWith("lucifer:room:"))?.slice("lucifer:room:".length); if (roomAlias != null) { newRoom = roomAlias; } }
if (newGroup == "") { const groupAlias = device.aliases.find(a => a.startsWith("lucifer:group:"))?.slice("lucifer:group:".length); if (groupAlias != null) { newGroup = groupAlias; } } }
let mostPopularEffect = 0; for (const assignment of $assignmentList) { const selectedCount = assignment.deviceIds?.filter(id => $selectedMap[id]).length || 0; if (selectedCount > mostPopularEffect) { newEffect = toEffectRaw(assignment.effect); mostPopularEffect = selectedCount; } } }
function closeModal() { show = false; match = ""; } function addEffectState() { if (newEffect.states.length > 0) { newEffect.states = [...newEffect.states, {...newEffect.states[newEffect.states.length - 1]}]; } else { newEffect.states = [...newEffect.states, {color: null, intensity: null, power: null, temperature: null}] } }
function removeEffectState(i: number) { newEffect.states = [...newEffect.states.slice(0, i), ...newEffect.states.slice(i+1)]; }
function reloadTagsAndRoles() { const firstDevice = $deviceList.find(d => $selectedMap[d.id]);
newTags = firstDevice?.aliases .filter(a => a.startsWith("lucifer:tag:")) .filter(a => !$deviceList.filter(d => $selectedMap[d.id]).find(d => !d.aliases.includes(a))) .map(a => a.slice("lucifer:tag:".length)) || []; newRoles = firstDevice?.aliases .filter(a => a.startsWith("lucifer:role:")) .filter(a => !$deviceList.filter(d => $selectedMap[d.id]).find(d => !d.aliases.includes(a))) .map(a => a.slice("lucifer:role:".length)) || [];
oldTags = [...newTags]; oldRoles = [...newRoles]; }
async function onSubmit() { disabled = true; let shouldWait = false;
try { if (enableRename && newName !== "") { await runCommand({addAlias: { match, alias: `lucifer:name:${newName}` }}); enableRename = false; shouldWait = match.startsWith("lucifer:name:"); } if (enableAssign) { await runCommand({assign: { match, effect: fromEffectRaw(newEffect) }}); } if (enableRoom) { await runCommand({addAlias: { match, alias: `lucifer:room:${newRoom || customRoom}` }}); enableRoom = false; shouldWait = match.startsWith("lucifer:room:"); newRoom = newRoom || customRoom; } if (enableGroup) { await runCommand({addAlias: { match, alias: `lucifer:group:${newGroup || customGroup}` }}); enableGroup = false; shouldWait = match.startsWith("lucifer:group:"); newGroup = newGroup || customGroup; } if (enableIcon) { await runCommand({addAlias: { match, alias: `lucifer:icon:${newIcon}` }}); enableIcon = false; shouldWait = match.startsWith("lucifer:icon:"); } if (enableTag) { const removeTags = oldTags.filter(ot => !newTags.includes(ot)); for (const removeTag of removeTags) { await runCommand({removeAlias: { match, alias: `lucifer:tag:${removeTag}` }}); }
const addTags = newTags.filter(nt => !oldTags.includes(nt)); for (const addTag of addTags) { await runCommand({addAlias: { match, alias: `lucifer:tag:${addTag}` }}); }
shouldWait = removeTags.length > 0 || addTags.length > 0; } if (enableRole) { const removeRoles = oldRoles.filter(or => !newRoles.includes(or)); for (const removeRole of removeRoles) { await runCommand({removeAlias: { match, alias: `lucifer:role:${removeRole}` }}); }
const addRoles = newRoles.filter(nr => !oldRoles.includes(nr)); for (const addRole of addRoles) { await runCommand({addAlias: { match, alias: `lucifer:role:${addRole}` }}); }
shouldWait = removeRoles.length > 0 || addRoles.length > 0; }
if (shouldWait) { await new Promise(resolve => setTimeout(resolve, 1000)) } } catch (err) {}
reloadTagsAndRoles();
disabled = false; }
let roomOptions: string[] = []; $: roomOptions = $deviceList.flatMap(d => d.aliases) .filter(k => k.startsWith("lucifer:room:")) .sort() .filter((v, i, a) => v !== a[i-1]) .map(r => r.slice("lucifer:room:".length));
let groupOptions: string[] = []; $: groupOptions = $deviceList.flatMap(d => d.aliases) .filter(k => k.startsWith("lucifer:group:")) .sort() .filter((v, i, a) => v !== a[i-1]) .map(r => r.slice("lucifer:group:".length));
$: { if ($modal.kind === "device.edit") { setupModal($modal.op); } else { closeModal(); } }
$: if (!$selectedMasks.includes(match)) { match = $selectedMasks[0]; } </script>
<form novalidate on:submit|preventDefault={onSubmit}> <Modal wide disabled={disabled} closable show={show} titleText="Device Editor" submitText="Save Changes"> <ModalBody> <label for="mask">Selection</label> <select bind:value={match}> {#each $selectedMasks as option (option)} <option value={option}>{option}</option> {/each} </select> <ModalSection bind:expanded={enableAssign} title="Assign"> <HSplit reverse> <HSplitPart> <label for="states">Effect</label> <select bind:value={newEffect.kind}> <option value="gradient">Gradient</option> <option value="pattern">Pattern</option> <option value="random">Random</option> <option value="solid">Solid</option> <option value="vrange">Variable Range</option> </select> {#if newEffect.kind !== "manual" && newEffect.kind !== "vrange"} <label for="animationMs">Interval (ms)</label> <input type="number" name="animationMs" min=0 max=10000 step=100 bind:value={newEffect.animationMs} /> {/if} {#if newEffect.kind === "solid"} <label for="interleave">Interleave</label> <input type="number" name="interleave" min=0 step=1 bind:value={newEffect.interleave} /> {/if} {#if newEffect.kind === "vrange"} <label for="states">Variable</label> <select bind:value={newEffect.variable}> <option value="motion.min">Motion Min (Seconds)</option> <option value="motion.avg">Motion Avg (Seconds)</option> <option value="motion.max">Motion Max (Seconds)</option> <option value="temperature.min">Temperature Min (Celcius)</option> <option value="temperature.avg">Temperature Avg (Celcius)</option> <option value="temperature.max">Temperature Max (Celcius)</option> </select> <HSplit> <HSplitPart left> <label for="min">Min</label> <input type="number" name="min" min=0 step=1 bind:value={newEffect.min} /> </HSplitPart> <HSplitPart right> <label for="max">Max</label> <input type="number" name="max" min=0 step=1 bind:value={newEffect.max} /> </HSplitPart> </HSplit> {/if} {#if ["gradient", "random", "vrange"].includes(newEffect.kind)} <label for="states">Options</label> <Checkbox bind:checked={newEffect.interpolate} label="Interpolate" /> {#if (newEffect.kind === "gradient")} <Checkbox bind:checked={newEffect.reverse} label="Reverse" /> {/if} {/if} </HSplitPart> <HSplitPart weight={1.0}> <label for="states">States</label> {#each newEffect.states as state, i } <ScriptAssignmentState deletable bind:value={state} on:delete={() => removeEffectState(i)} /> {/each} <Button on:click={addEffectState} icon><Icon name="plus" /></Button> </HSplitPart> </HSplit> </ModalSection> <ModalSection bind:expanded={enableRename} title="Rename"> <label for="name">New Name</label> <input type="text" name="name" bind:value={newName} /> </ModalSection> <ModalSection bind:expanded={enableRoom} title="Change Room"> <label for="newRoom">Select Room</label> <select bind:value={newRoom}> {#each roomOptions as roomOption} <option value={roomOption}>{roomOption}</option> {/each} <option value="">Create Room</option> </select> {#if newRoom == ""} <label for="customRoom">New Room</label> <input type="text" name="customRoom" bind:value={customRoom} /> {/if} </ModalSection> <ModalSection bind:expanded={enableGroup} title="Change Group"> <label for="newGroup">Select Group</label> <select bind:value={newGroup}> {#each groupOptions as groupOption} <option value={groupOption}>{groupOption}</option> {/each} <option value="">Create Group</option> </select> {#if newGroup == ""} <label for="customGroup">New Group</label> <input type="text" name="customGroup" bind:value={customGroup} /> {/if} </ModalSection> <ModalSection bind:expanded={enableIcon} title="Change Icon"> <label for="icon">New Icon</label> <DeviceIconSelector bind:value={newIcon} /> </ModalSection> <ModalSection bind:expanded={enableTag} title="Change Tags"> <label for="icon">Tags</label> <TagInput bind:value={newTags} /> </ModalSection> <ModalSection bind:expanded={enableRole} title="Change Roles"> <label for="icon">Roles</label> <TagInput bind:value={newRoles} /> </ModalSection> </ModalBody> </Modal> </form>
|