Files
uopi/web/src/ContextualHelp.tsx
T
2026-04-26 14:28:48 +02:00

92 lines
3.1 KiB
TypeScript

import { h } from 'preact';
import { useState, useRef, useEffect } from 'preact/hooks';
interface Props {
mode: 'view' | 'edit';
onOpenManual: (section?: string) => void;
}
const TIPS: Record<string, Array<{ text: string; section?: string }>> = {
view: [
{ text: 'Click any interface in the left panel to load it on the canvas.', section: 'view' },
{ text: 'Right-click a widget to view signal info or export data as CSV.', section: 'view' },
{ text: 'The ● Live button shows live streaming is active. Use the date pickers to load historical data.', section: 'history' },
{ text: 'Click Edit (top-right) to open the drag-and-drop panel builder.', section: 'edit' },
{ text: 'The dot in the status chip turns green when the WebSocket is connected.', section: 'view' },
],
edit: [
{ text: 'Drag a signal from the left tree onto the canvas to create a widget.', section: 'edit' },
{ text: 'Click a widget to select it, then adjust its properties on the right.', section: 'edit' },
{ text: 'Ctrl+click to select multiple widgets; then use the align toolbar.', section: 'edit' },
{ text: 'Ctrl+Z to undo, Ctrl+Y to redo. Up to 50 steps are remembered.', section: 'shortcuts' },
{ text: 'Use "+ Synthetic" in the signal tree to create computed/filtered signals.', section: 'signals' },
{ text: 'Arrow keys nudge the selected widget by 1 px (Shift for grid size).', section: 'shortcuts' },
],
};
export default function ContextualHelp({ mode, onOpenManual }: Props) {
const [open, setOpen] = useState(false);
const [tipIndex, setTipIndex] = useState(0);
const ref = useRef<HTMLDivElement>(null);
const tips = TIPS[mode];
// Close on outside click
useEffect(() => {
if (!open) return;
function handler(e: MouseEvent) {
if (ref.current && !ref.current.contains(e.target as Node)) {
setOpen(false);
}
}
document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler);
}, [open]);
// Cycle to a new random tip each time the popover opens
function handleOpen() {
setTipIndex(t => (t + 1) % tips.length);
setOpen(o => !o);
}
const tip = tips[tipIndex];
return (
<div class="ctx-help" ref={ref}>
<button
class="ctx-help-btn"
onClick={handleOpen}
title="Contextual help"
aria-label="Open contextual help"
aria-expanded={open}
>
?
</button>
{open && (
<div class="ctx-help-popover" role="tooltip">
<div class="ctx-help-tip">
<span class="ctx-help-icon">💡</span>
<span>{tip.text}</span>
</div>
<div class="ctx-help-footer">
<button
class="ctx-help-nav"
onClick={() => setTipIndex(t => (t + 1) % tips.length)}
title="Next tip"
>
Next tip
</button>
<button
class="ctx-help-link"
onClick={() => { setOpen(false); onOpenManual(tip.section); }}
>
Open manual
</button>
</div>
</div>
)}
</div>
);
}