Files
pedigree-draw/src/components/App/App.tsx
gbanyan 7c2a624130
Some checks failed
Deploy to GitHub Pages / build (push) Has been cancelled
Deploy to GitHub Pages / deploy (push) Has been cancelled
Add welcome modal with author info and privacy policy
Features:
- Display welcome popup on first visit
- Auto-detect browser language (Chinese/English)
- Show author motivation (Usher syndrome research support)
- Privacy policy: no data collection, browser-only operations
- Use localStorage to remember dismissal
- Responsive design for mobile devices

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 01:20:24 +08:00

105 lines
3.2 KiB
TypeScript

/**
* App Component
*
* Main application layout
*/
import { useEffect, useState } from 'react';
import { PedigreeCanvas } from '../PedigreeCanvas/PedigreeCanvas';
import { Toolbar } from '../Toolbar/Toolbar';
import { PropertyPanel } from '../PropertyPanel/PropertyPanel';
import { RelationshipPanel } from '../RelationshipPanel/RelationshipPanel';
import { FilePanel } from '../FilePanel/FilePanel';
import { WelcomeModal } from '../WelcomeModal/WelcomeModal';
import { usePedigreeStore, useTemporalStore } from '@/store/pedigreeStore';
import styles from './App.module.css';
const WELCOME_DISMISSED_KEY = 'pedigree-draw-welcome-dismissed';
export function App() {
const {
clearSelection,
selectedPersonId,
selectedRelationshipId,
deletePerson,
deleteRelationship,
} = usePedigreeStore();
const temporal = useTemporalStore();
// Welcome modal state - check localStorage on init
const [showWelcome, setShowWelcome] = useState(() => {
return localStorage.getItem(WELCOME_DISMISSED_KEY) !== 'true';
});
const handleCloseWelcome = () => {
setShowWelcome(false);
};
// Keyboard shortcuts
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Don't handle shortcuts when typing in inputs
const target = e.target as HTMLElement;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.tagName === 'SELECT') {
return;
}
// Undo: Ctrl/Cmd + Z
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
e.preventDefault();
temporal.getState().undo();
}
// Redo: Ctrl/Cmd + Shift + Z or Ctrl/Cmd + Y
if ((e.ctrlKey || e.metaKey) && (e.key === 'y' || (e.key === 'z' && e.shiftKey))) {
e.preventDefault();
temporal.getState().redo();
}
// Escape: Clear selection
if (e.key === 'Escape') {
clearSelection();
}
// Delete or Backspace: Delete selected element
if (e.key === 'Delete' || e.key === 'Backspace') {
e.preventDefault();
if (selectedPersonId) {
deletePerson(selectedPersonId);
} else if (selectedRelationshipId) {
deleteRelationship(selectedRelationshipId);
}
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [clearSelection, selectedPersonId, selectedRelationshipId, deletePerson, deleteRelationship, temporal]);
return (
<div className={styles.app}>
<header className={styles.header}>
<h1 className={styles.title}>Pedigree Draw</h1>
<span className={styles.subtitle}>Professional Pedigree Chart Editor</span>
</header>
<Toolbar />
<main className={styles.main}>
<FilePanel />
<div className={styles.canvasArea}>
<PedigreeCanvas />
</div>
{selectedRelationshipId ? <RelationshipPanel /> : <PropertyPanel />}
</main>
<footer className={styles.footer}>
<span>Pedigree Draw - For genetic counselors and bioinformatics professionals</span>
<span>NSGC Standard Symbols</span>
</footer>
{showWelcome && <WelcomeModal onClose={handleCloseWelcome} />}
</div>
);
}