From 879702a56c1d9a3121f8414c6ab6a33ce9cadd88 Mon Sep 17 00:00:00 2001 From: Mattias Persson Date: Sat, 10 Feb 2024 15:36:00 +0100 Subject: [PATCH 1/3] Add button modal templates --- src/lib/Components/CodeEditor.svelte | 29 +- src/lib/Components/ComputeIcon.svelte | 17 +- src/lib/Components/Select.svelte | 4 + src/lib/Components/SelectItem.svelte | 17 +- src/lib/Components/StateLogic.svelte | 17 +- src/lib/Main/Button.svelte | 194 +++++++--- src/lib/Modal/ButtonConfig.svelte | 300 ++++++++++----- src/lib/Modal/Index.svelte | 4 +- src/lib/Modal/Templater.svelte | 520 ++++++++++++++++++++++++++ src/lib/Sidebar/Template.svelte | 2 +- src/lib/Stores.ts | 15 +- src/lib/Types.ts | 29 +- src/lib/Utils.ts | 55 +++ 13 files changed, 1021 insertions(+), 182 deletions(-) create mode 100644 src/lib/Modal/Templater.svelte diff --git a/src/lib/Components/CodeEditor.svelte b/src/lib/Components/CodeEditor.svelte index 4451d0b4..5fcb5ae4 100644 --- a/src/lib/Components/CodeEditor.svelte +++ b/src/lib/Components/CodeEditor.svelte @@ -1,5 +1,5 @@
diff --git a/src/lib/Components/ComputeIcon.svelte b/src/lib/Components/ComputeIcon.svelte index 66423333..2b6b3ec0 100644 --- a/src/lib/Components/ComputeIcon.svelte +++ b/src/lib/Components/ComputeIcon.svelte @@ -1,5 +1,5 @@ diff --git a/src/lib/Components/Select.svelte b/src/lib/Components/Select.svelte index 112e6774..71421029 100644 --- a/src/lib/Components/Select.svelte +++ b/src/lib/Components/Select.svelte @@ -129,5 +129,9 @@ .sv-dropdown-content .sv-item-content { padding: 0.3rem !important; } + + .inputBox { + margin: 0 !important; + }
diff --git a/src/lib/Components/SelectItem.svelte b/src/lib/Components/SelectItem.svelte index 608da38f..204ac09a 100644 --- a/src/lib/Components/SelectItem.svelte +++ b/src/lib/Components/SelectItem.svelte @@ -31,7 +31,7 @@
{#if item?.icon && !isDisabled} - + {:else if item?.label && !isDisabled} {/if} @@ -40,6 +40,11 @@ {@html isSelected ? `${formatter(item, isSelected, inputValue)}` : highlightSearch(item, isSelected, inputValue, formatter, disableHighlight)} + +
{#if isSelected && isMultiple} @@ -51,14 +56,14 @@ .icon-text-wrapper { display: flex; align-items: center; - gap: 0.8rem; + gap: 0.7rem; + font-size: 0.95rem; } .icon-container { - --icon-size: 1.5rem; display: flex; - min-width: var(--icon-size); - width: var(--icon-size); - height: var(--icon-size); + width: 1.2rem; + height: 1.2rem; + align-items: center; } diff --git a/src/lib/Components/StateLogic.svelte b/src/lib/Components/StateLogic.svelte index bf8d96d5..a9ea73ed 100644 --- a/src/lib/Components/StateLogic.svelte +++ b/src/lib/Components/StateLogic.svelte @@ -17,11 +17,6 @@ $: brightness = attributes?.brightness; $: percentage = attributes?.percentage; $: media_title = attributes?.media_title; - - $: statePrecision = - selected?.precision !== undefined && !isNaN(parseFloat(entity?.state)) - ? parseFloat(entity?.state).toFixed(selected?.precision) - : undefined; @@ -110,11 +105,7 @@ {#if selected?.marquee && contentWidth && contentWidth > 153 && !$editMode} {#await import('$lib/Components/Marquee.svelte') then Marquee} - {#if statePrecision} - {statePrecision} - {:else} - {@html $lang(state)} - {/if} + {@html $lang(state)} {#if attributes?.unit_of_measurement} @@ -124,11 +115,7 @@ {/await} {:else} - {#if statePrecision} - {statePrecision} - {:else} - {@html $lang(state)} - {/if} + {@html $lang(state)} {#if attributes?.unit_of_measurement} diff --git a/src/lib/Main/Button.svelte b/src/lib/Main/Button.svelte index 1fe534d0..683d3110 100644 --- a/src/lib/Main/Button.svelte +++ b/src/lib/Main/Button.svelte @@ -10,21 +10,27 @@ onStates, climateHvacActionToMode, ripple, - states + states, + templates, + config } from '$lib/Stores'; - import { getDomain, getName } from '$lib/Utils'; - import Icon from '@iconify/svelte'; + import { getDomain, getName, getTogglableService } from '$lib/Utils'; + import Icon, { loadIcon } from '@iconify/svelte'; import { callService, type HassEntity } from 'home-assistant-js-websocket'; + import { marked } from 'marked'; + import { onDestroy } from 'svelte'; import { openModal } from 'svelte-modals'; import Ripple from 'svelte-ripple'; + import parser from 'js-yaml'; export let demo: string | undefined = undefined; export let sel: any; export let sectionName: string | undefined = undefined; $: entity_id = demo || sel?.entity_id; - $: icon = sel?.icon; - $: color = sel?.color; + $: template = $templates?.[sel?.id]; + $: icon = (sel?.template?.icon && template?.icon?.output) || sel?.icon; + $: color = (sel?.template?.color && template?.color?.output) || sel?.color; $: marquee = sel?.marquee; $: more_info = sel?.more_info; @@ -89,55 +95,49 @@ * using the correct service call... */ function toggle() { - const domain = getDomain(entity_id); - const state = entity?.state; - if (!domain || !state) return; - - const services: Record = { - automation: 'toggle', - button: 'press', - cover: 'toggle', - fan: 'toggle', - humidifier: 'toggle', - input_boolean: 'toggle', - input_button: 'press', - light: 'toggle', - lock: state === 'locked' ? 'unlock' : 'lock', - media_player: 'toggle', - scene: 'turn_on', - script: 'toggle', - siren: 'toggle', - switch: 'toggle', - timer: state === 'active' ? 'pause' : 'start', - vacuum: 'toggle' - }; - - switch (domain) { - // case 'person': - // console.debug('ping phone?'); - // break; - - case 'remote': - callService($connection, 'homeassistant', 'toggle', { entity_id }); - break; - - default: - if (domain in services) { - callService($connection, domain, services[domain], { entity_id }); - - // loader - delayLoading = setTimeout(() => { - loading = true; - }, $motion); - - // loader 20s fallback - resetLoading = setTimeout(() => { - loading = false; - }, 20_000); - } else { - // not listed above just open modal - handleClickEvent(); + // if service template + if (sel?.template?.service && template?.service?.output) { + try { + // template is string, try to parse it + const _template = parser.load(template?.service?.output) as { + service: string; + data: Record; + }; + + if (_template?.service) { + const [domain, service] = _template.service.split('.'); + callService($connection, domain, service, _template?.data); } + } catch (error) { + console.error('Template service YAML parse error:', error); + } + + return; + } + + // default + const service = getTogglableService(entity); + + if (service) { + // use returned domain to handle specific cases such + // as 'remote', which uses 'homeassistant.toggle' + const [_domain, _service] = service.split('.'); + callService($connection, _domain, _service, { + entity_id + }); + + // loader + delayLoading = setTimeout(() => { + loading = true; + }, $motion); + + // loader 20s fallback + resetLoading = setTimeout(() => { + loading = false; + }, 20_000); + } else { + // not in getTogglableService just open modal + handleClickEvent(); } } @@ -364,6 +364,60 @@ } if (module) module.default; } + + ////// templates ////// + + $: if ($config?.state === 'RUNNING' && sel?.template) { + // for each changed entry in template + Object.entries(sel?.template as Record).forEach(([key, value]) => { + const compareTemplate = value === template?.[key]?.input; + const compareEntityId = sel?.entity_id === template?.[key]?.entity_id; + if (compareTemplate && compareEntityId) return; + renderTemplate(key, value); + }); + } + + let unsubscribe: () => void; + + async function renderTemplate(key: string, value: string) { + if (!$connection || !sel?.id) return; + + try { + unsubscribe = await $connection.subscribeMessage( + (response: { result: string } | { error: string; level: 'ERROR' | 'WARNING' }) => { + let data: any = { + input: value + }; + + if ('result' in response) { + data.output = + key === 'state' || key === 'name' + ? marked.parseInline(String(response.result)) + : String(response.result); + } else if (response?.level === 'ERROR') { + console.error(response.error); + data.error = response.error; + } + + data.entity_id = sel?.entity_id; + + $templates[sel?.id] = { ...$templates[sel?.id], [key]: data }; + }, + { + type: 'render_template', + template: value, + report_errors: true, + variables: { + entity_id: sel?.entity_id + } + } + ); + } catch (error) { + console.error('Template error:', error); + } + } + + onDestroy(() => unsubscribe?.()); @@ -399,12 +453,13 @@ } }} > - -
+ {#await loadIcon(icon)} + + + {:then resolvedIcon} + + + {:catch} + + + {/await} {:else if entity_id} {:else} - + {/if}
@@ -429,7 +493,9 @@
- {getName(sel, entity, sectionName) || $lang('unknown')} + {@html (sel?.template?.name && template?.name?.output) || + getName(sel, entity, sectionName) || + $lang('unknown')}
@@ -438,11 +504,19 @@
{#if marquee}
- + {#if sel?.template?.state && template?.state?.output} + {@html sel?.template?.state && template?.state?.output} + {:else} + + {/if}
{:else}
- + {#if sel?.template?.state && template?.state?.output} + {@html sel?.template?.state && template?.state?.output} + {:else} + + {/if}
{/if}
diff --git a/src/lib/Modal/ButtonConfig.svelte b/src/lib/Modal/ButtonConfig.svelte index e660e231..07a75592 100644 --- a/src/lib/Modal/ButtonConfig.svelte +++ b/src/lib/Modal/ButtonConfig.svelte @@ -1,5 +1,15 @@ {#if isOpen} @@ -88,7 +121,7 @@

{$lang('entity')}

-
- {#each precisionValues as precision} - - {/each} -
- {/if} +

{$lang('service')}

-

{$lang('attributes')}

+