import { h } from 'preact'; import { useState, useEffect } from 'preact/hooks'; import { wsClient } from '../lib/ws'; import { getSignalStore, getMetaStore } from '../lib/stores'; import { logicEngine } from '../lib/logic'; import { useAuth, canWrite } from '../lib/auth'; import type { Widget, SignalMeta } from '../lib/types'; interface Props { widget: Widget; onContextMenu?: (e: MouseEvent) => void; } export default function Button({ widget, onContextMenu }: Props) { const sigRef = widget.signals[0]; const label = widget.options['label'] ?? 'Button'; const sendValue = widget.options['value'] ?? '1'; const resetValue = widget.options['resetValue'] ?? '0'; // mode: oneshot | setReset | persistent const mode = widget.options['mode'] ?? 'oneshot'; const resetTimeSec = parseFloat(widget.options['resetTime'] ?? '1'); const confirmOpt = widget.options['confirm'] === 'true'; // Optional panel-logic sequence fired on click (in addition to any signal write). const action = widget.options['action'] ?? ''; const me = useAuth(); const [signalVal, setSignalVal] = useState(null); const [meta, setMeta] = useState(null); // Subscribe to signal only for persistent mode (to track whether it is active) useEffect(() => { if (!sigRef || mode !== 'persistent') return; return getSignalStore(sigRef).subscribe(sv => setSignalVal(sv.value)); }, [sigRef?.ds, sigRef?.name, mode]); // Track the target signal's writability so the button can be disabled when // the user has no permission to write it (real signals only; local panel // vars are written client-side and have no backend metadata). useEffect(() => { if (!sigRef || sigRef.ds === 'local') return; return getMetaStore(sigRef).subscribe(setMeta); }, [sigRef?.ds, sigRef?.name]); // A button that drives a real signal is disabled when the user cannot write // it (read-only access, or the signal reports it is not writable). Buttons // that only run a logic action — or target a local var — stay enabled. const drivesRealSignal = !!sigRef && sigRef.ds !== 'local'; const writeAllowed = !drivesRealSignal || (canWrite(me.level) && meta?.writable !== false); const parsedSend = isNaN(parseFloat(sendValue)) ? sendValue : parseFloat(sendValue); const parsedReset = isNaN(parseFloat(resetValue)) ? resetValue : parseFloat(resetValue); // Persistent mode: button appears pressed while signal value matches sendValue const isPressed = mode === 'persistent' && signalVal !== null && String(signalVal) === String(parsedSend); function doWrite(val: any) { if (!sigRef) return; wsClient.write(sigRef, val); } function execute() { if (sigRef) { if (mode === 'setReset') { doWrite(parsedSend); setTimeout(() => doWrite(parsedReset), Math.max(0, resetTimeSec) * 1000); } else if (mode === 'persistent') { doWrite(isPressed ? parsedReset : parsedSend); } else { doWrite(parsedSend); } } if (action) logicEngine.runAction(action); } function handleClick() { // A button may drive a signal, a logic sequence, or both. if (!sigRef && !action) return; if (!writeAllowed) return; if (confirmOpt) { if (window.confirm(`Send ${label}?`)) execute(); } else { execute(); } } return (
); }