186 lines
5.8 KiB
TypeScript
186 lines
5.8 KiB
TypeScript
import React, { useEffect } from 'react';
|
||
|
||
interface HelpDialogProps {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
}
|
||
|
||
const shortcuts = [
|
||
{ keys: 'F1 / ?', description: 'Open this help and tutorial dialog' },
|
||
{
|
||
keys: 'Ctrl/⌘ + A',
|
||
description: 'Select all pages in the current workspace',
|
||
},
|
||
{
|
||
keys: 'Delete / Backspace',
|
||
description: 'Delete the selected pages after confirmation',
|
||
},
|
||
{
|
||
keys: 'Esc',
|
||
description: 'Clear the page selection or close an open dialog',
|
||
},
|
||
{ keys: 'Ctrl/⌘ + Z', description: 'Undo the latest workspace command' },
|
||
{
|
||
keys: 'Ctrl/⌘ + Shift + Z',
|
||
description: 'Redo the next workspace command',
|
||
},
|
||
{ keys: 'Ctrl/⌘ + Y', description: 'Redo the next workspace command' },
|
||
{
|
||
keys: '← / → in preview',
|
||
description: 'Move to the previous or next page in the preview overlay',
|
||
},
|
||
];
|
||
|
||
const tutorialSteps = [
|
||
{
|
||
title: '1. Open a PDF or load a workspace',
|
||
body: 'Start by selecting a local PDF file. If you saved workspaces before, you can restore one from browser storage instead.',
|
||
},
|
||
{
|
||
title: '2. Arrange pages visually',
|
||
body: 'Drag page cards to reorder them. Rotate single pages, open the large preview with a click, or remove pages you do not want in the export.',
|
||
},
|
||
{
|
||
title: '3. Select, copy, and delete pages',
|
||
body: 'Use the checkbox on a page card to select it. Shift-click extends a range. Dragging a selected page moves the whole selection; the copy controls duplicate selected pages into a chosen slot.',
|
||
},
|
||
{
|
||
title: '4. Extract selected pages or branch into a new workspace',
|
||
body: 'Extract selected pages when you only need a download. Open the selection as a new workspace when you want to continue working on that subset.',
|
||
},
|
||
{
|
||
title: '5. Save your workspace or export a PDF',
|
||
body: 'Saving a workspace keeps the current working state in this browser. Exporting creates a new PDF file for download.',
|
||
},
|
||
{
|
||
title: '6. Use history deliberately',
|
||
body: 'Each workspace operation is stored as a command with label and timestamp. Undo and redo walk through that command history.',
|
||
},
|
||
];
|
||
|
||
const HelpDialog: React.FC<HelpDialogProps> = ({ open, onClose }) => {
|
||
useEffect(() => {
|
||
if (!open) return;
|
||
|
||
const handleKeyDown = (e: KeyboardEvent) => {
|
||
if (e.key !== 'Escape') return;
|
||
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
e.stopImmediatePropagation();
|
||
onClose();
|
||
};
|
||
|
||
window.addEventListener('keydown', handleKeyDown, { capture: true });
|
||
|
||
return () => {
|
||
window.removeEventListener('keydown', handleKeyDown, { capture: true });
|
||
};
|
||
}, [open, onClose]);
|
||
|
||
if (!open) return null;
|
||
|
||
return (
|
||
<div
|
||
className="help-dialog-backdrop"
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-labelledby="help-dialog-title"
|
||
onPointerDown={(e) => {
|
||
if (e.target === e.currentTarget) {
|
||
onClose();
|
||
}
|
||
}}
|
||
>
|
||
<div className="help-dialog-panel">
|
||
<div className="help-dialog-header">
|
||
<div>
|
||
<h2 id="help-dialog-title">Help & tutorial</h2>
|
||
<p>
|
||
PDF Workbench is a browser-only page workspace. Use it to quickly
|
||
rearrange, split, merge, rotate, duplicate, and export PDFs
|
||
without uploading documents to a server.
|
||
</p>
|
||
</div>
|
||
|
||
<button
|
||
type="button"
|
||
className="help-close-button"
|
||
onClick={onClose}
|
||
aria-label="Close help"
|
||
title="Close help (Esc)"
|
||
>
|
||
×
|
||
</button>
|
||
</div>
|
||
|
||
<div className="help-dialog-content">
|
||
<section className="help-section">
|
||
<h3>Quick tutorial</h3>
|
||
<div className="help-step-list">
|
||
{tutorialSteps.map((step) => (
|
||
<article key={step.title} className="help-step">
|
||
<h4>{step.title}</h4>
|
||
<p>{step.body}</p>
|
||
</article>
|
||
))}
|
||
</div>
|
||
</section>
|
||
|
||
<section className="help-section">
|
||
<h3>Keyboard shortcuts</h3>
|
||
<div className="shortcut-grid">
|
||
{shortcuts.map((shortcut) => (
|
||
<React.Fragment key={shortcut.keys}>
|
||
<kbd>{shortcut.keys}</kbd>
|
||
<span>{shortcut.description}</span>
|
||
</React.Fragment>
|
||
))}
|
||
</div>
|
||
<p className="help-note">
|
||
Shortcuts are ignored while typing in text fields or other
|
||
editable controls.
|
||
</p>
|
||
</section>
|
||
|
||
<section className="help-section help-concepts">
|
||
<h3>Important concepts</h3>
|
||
<dl>
|
||
<div>
|
||
<dt>Browser-only processing</dt>
|
||
<dd>
|
||
PDF operations run in your browser. A self-hosted server only
|
||
delivers the static app files.
|
||
</dd>
|
||
</div>
|
||
<div>
|
||
<dt>Workspace</dt>
|
||
<dd>
|
||
A named local editing state, including the PDF binary, page
|
||
order, rotations, selection, and command history.
|
||
</dd>
|
||
</div>
|
||
<div>
|
||
<dt>Export</dt>
|
||
<dd>
|
||
A generated PDF download. Exported files are separate from
|
||
saved workspaces.
|
||
</dd>
|
||
</div>
|
||
<div>
|
||
<dt>History</dt>
|
||
<dd>
|
||
Undoable commands show what changed, when it changed, and
|
||
where the current point in history is.
|
||
</dd>
|
||
</div>
|
||
</dl>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default HelpDialog;
|