123 lines
5.2 KiB
TypeScript
123 lines
5.2 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
||
import { useLocation } from "react-router-dom";
|
||
import { HelpCircle, Info, BookOpen, GitBranch } from "lucide-react";
|
||
import Button from "../components/Button";
|
||
import { helpContextForPathname, helpQueryForContext, type HelpContext } from "../utils/helpContext";
|
||
|
||
export default function HelpMenu() {
|
||
const [open, setOpen] = useState(false);
|
||
const [aboutOpen, setAboutOpen] = useState(false);
|
||
const [contextOpen, setContextOpen] = useState(false);
|
||
const wrapRef = useRef<HTMLDivElement>(null);
|
||
const location = useLocation();
|
||
const helpContext = helpContextForPathname(location.pathname);
|
||
|
||
useEffect(() => {
|
||
function openContextHelp(event: KeyboardEvent) {
|
||
if (event.key !== "F1") return;
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
event.stopImmediatePropagation();
|
||
setOpen(false);
|
||
setContextOpen(true);
|
||
}
|
||
function onPointerDown(event: MouseEvent) {
|
||
if (wrapRef.current && !wrapRef.current.contains(event.target as Node)) {
|
||
setOpen(false);
|
||
}
|
||
}
|
||
window.addEventListener("keydown", openContextHelp, true);
|
||
window.addEventListener("mousedown", onPointerDown);
|
||
return () => {
|
||
window.removeEventListener("keydown", openContextHelp, true);
|
||
window.removeEventListener("mousedown", onPointerDown);
|
||
};
|
||
}, []);
|
||
|
||
function openHelp() {
|
||
setOpen(false);
|
||
setContextOpen(true);
|
||
}
|
||
|
||
return (
|
||
<div className="context-menu-wrap" ref={wrapRef}>
|
||
<button
|
||
className="titlebar-link"
|
||
onClick={() => setOpen(!open)}
|
||
onKeyDown={(event) => {
|
||
if (event.key === "F1") {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
openHelp();
|
||
}
|
||
}}
|
||
title="Help (F1)"
|
||
>
|
||
<HelpCircle size={17} /> Help
|
||
</button>
|
||
{open && (
|
||
<div className="dropdown-menu">
|
||
<button className="dropdown-item" onClick={openHelp}>
|
||
<HelpCircle size={16} /> Help <small>F1</small>
|
||
</button>
|
||
<hr />
|
||
<a className="dropdown-item" href="#" onClick={(e) => e.preventDefault()}><BookOpen size={16} /> User docs</a>
|
||
<a className="dropdown-item" href="#" onClick={(e) => e.preventDefault()}><BookOpen size={16} /> Admin docs</a>
|
||
<hr />
|
||
<a className="dropdown-item" href="https://git.add-ideas.de/add-ideas" target="_blank" rel="noreferrer"><GitBranch size={16} /> GitLab</a>
|
||
<button className="dropdown-item" onClick={() => { setAboutOpen(true); setOpen(false); }}><Info size={16} /> About</button>
|
||
</div>
|
||
)}
|
||
{contextOpen && <ContextHelpModal context={helpContext} onClose={() => setContextOpen(false)} />}
|
||
{aboutOpen && <AboutModal onClose={() => setAboutOpen(false)} />}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function ContextHelpModal({ context, onClose }: { context: HelpContext; onClose: () => void }) {
|
||
return (
|
||
<div className="overlay-backdrop" role="dialog" aria-modal="true" data-help-context={context.id}>
|
||
<div className="modal-panel">
|
||
<header className="modal-header">
|
||
<h2>Context help</h2>
|
||
<button className="modal-close" onClick={onClose}>×</button>
|
||
</header>
|
||
<div className="modal-body">
|
||
<div className="help-panel-section">
|
||
<h3>{context.title}</h3>
|
||
<p className="mono-small">Help context: {context.id}</p>
|
||
<p className="muted">This area is prepared for context-sensitive help. Future help content can use this context identifier, or the equivalent help query parameter <span className="kbd">{helpQueryForContext(context)}</span>, to open the right page or section.</p>
|
||
</div>
|
||
<div className="help-panel-section">
|
||
<h3>Next actions</h3>
|
||
<p className="muted">The first guided help content can cover campaign creation, review, attachment resolution and sending preparation.</p>
|
||
</div>
|
||
</div>
|
||
<footer className="modal-footer"><Button variant="primary" onClick={onClose}>Close</Button></footer>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function AboutModal({ onClose }: { onClose: () => void }) {
|
||
return (
|
||
<div className="overlay-backdrop" role="dialog" aria-modal="true">
|
||
<div className="modal-panel">
|
||
<header className="modal-header">
|
||
<h2>About MultiMailer</h2>
|
||
<button className="modal-close" onClick={onClose}>×</button>
|
||
</header>
|
||
<div className="modal-body">
|
||
<div className="about-logo" />
|
||
<p><strong>MultiMailer WebUI</strong></p>
|
||
<p className="muted">Version 0.1.0 — early development build.</p>
|
||
<p>MultiMailer is a local-first / server-assisted campaign mailer for structured, personalized bulk messages with attachment resolution, review workflows and auditable sending.</p>
|
||
<p><a href="https://add-ideas.de" target="_blank" rel="noreferrer">add-ideas.de</a></p>
|
||
<p className="muted">License: project license pending / to be finalized. Backend components are currently development prototypes.</p>
|
||
</div>
|
||
<footer className="modal-footer"><Button variant="primary" onClick={onClose}>Close</Button></footer>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|