freeze v0.2.0
This commit is contained in:
166
src/components/HelpDialog.tsx
Normal file
166
src/components/HelpDialog.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
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. 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: '5. 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;
|
||||
@@ -3,9 +3,10 @@ import { APP_VERSION } from '../version';
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
onOpenHelp?: () => void;
|
||||
}
|
||||
|
||||
const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
const Layout: React.FC<LayoutProps> = ({ children, onOpenHelp }) => {
|
||||
return (
|
||||
<div className="app-root">
|
||||
<header className="app-header">
|
||||
@@ -18,8 +19,22 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="app-version" title={`Version ${APP_VERSION}`}>
|
||||
v{APP_VERSION}
|
||||
<div className="app-header-actions">
|
||||
{onOpenHelp && (
|
||||
<button
|
||||
type="button"
|
||||
className="app-help-button"
|
||||
onClick={onOpenHelp}
|
||||
aria-haspopup="dialog"
|
||||
title="Open help and keyboard shortcuts (F1 or ?)"
|
||||
>
|
||||
Help <span aria-hidden="true">?</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<div className="app-version" title={`Version ${APP_VERSION}`}>
|
||||
v{APP_VERSION}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
Reference in New Issue
Block a user