92 lines
3.1 KiB
TypeScript
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>
|
|
);
|
|
}
|