complete first iteration
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user