diff --git a/README.md b/README.md index ea4cf9e..898c4aa 100644 --- a/README.md +++ b/README.md @@ -18,30 +18,70 @@ A professional pedigree (family tree) drawing tool for genetic counselors and bi You need **Node.js** (version 18 or higher) installed on your computer. -Check if you have it: +#### Check if Node.js is installed: ```bash node --version +npm --version ``` -If not installed, download from: https://nodejs.org/ (choose LTS version) +If both commands show version numbers (e.g., `v20.10.0` and `10.2.0`), you're ready to go! + +#### If Node.js is NOT installed: + +**Windows:** +1. Go to https://nodejs.org/ +2. Download the **LTS** version (recommended) +3. Run the installer, click "Next" through all steps +4. Restart your terminal/command prompt after installation + +**macOS:** +```bash +# Using Homebrew (recommended) +brew install node + +# Or download from https://nodejs.org/ +``` + +**Linux (Ubuntu/Debian):** +```bash +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt-get install -y nodejs +``` --- -### Step 1: Start the Server +### Step 1: Install Dependencies (First Time Only) Open your terminal and navigate to the project folder: ```bash -cd /home/gbanyan/projects/pedigree-draw +cd /path/to/pedigree-draw ``` +Install the required packages: + +```bash +npm install +``` + +This will download all necessary dependencies. You only need to do this once (or after updating the code). + +--- + +### Step 2: Start the Server + Run the start script: ```bash ./start.sh ``` +**On Windows**, use: +```bash +npm run dev +``` + You should see output like: ``` ========================================== @@ -57,7 +97,7 @@ You should see output like: --- -### Step 2: Open in Browser +### Step 3: Open in Browser Open your web browser and go to: @@ -66,7 +106,7 @@ Open your web browser and go to: --- -### Step 3: Stop the Server +### Step 4: Stop the Server When you're done, stop the server: @@ -74,6 +114,8 @@ When you're done, stop the server: ./stop.sh ``` +**On Windows**, press `Ctrl+C` in the terminal to stop the server. + --- ## How to Use the Application @@ -145,11 +187,27 @@ You can also: | Export PNG | Raster image (for documents, presentations) | | Export PED | GATK PED format file | -### Undo/Redo +### Keyboard Shortcuts -Use the Undo/Redo buttons in the toolbar, or: -- **Ctrl+Z** to Undo -- **Ctrl+Y** to Redo +| Shortcut | Action | +|----------|--------| +| **Delete** or **Backspace** | Delete selected person or relationship | +| **Escape** | Clear selection | +| **Ctrl+Z** | Undo | +| **Ctrl+Y** or **Ctrl+Shift+Z** | Redo | + +### Editing Relationships + +1. Click on the **connection line** between two people to select the relationship +2. The right panel will show relationship options: + - **Add Child**: Add a Male/Female/Unknown child to this couple + - **Partnership Status**: Married, Unmarried, Separated, Divorced + - **Consanguinity**: Mark as consanguineous (blood relatives) + - **Children Status**: Infertility or No children by choice + +### Auto Align + +If elements get messy after dragging, click the **Auto Align** button in the toolbar to automatically reposition all family members according to their generation and relationships. --- diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 00ff9d8..160197e 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -14,12 +14,24 @@ import { usePedigreeStore, useTemporalStore } from '@/store/pedigreeStore'; import styles from './App.module.css'; export function App() { - const { clearSelection, selectedRelationshipId } = usePedigreeStore(); + const { + clearSelection, + selectedPersonId, + selectedRelationshipId, + deletePerson, + deleteRelationship, + } = usePedigreeStore(); const temporal = useTemporalStore(); // 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(); @@ -36,11 +48,21 @@ export function App() { 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, temporal]); + }, [clearSelection, selectedPersonId, selectedRelationshipId, deletePerson, deleteRelationship, temporal]); return (
diff --git a/src/components/RelationshipPanel/RelationshipPanel.tsx b/src/components/RelationshipPanel/RelationshipPanel.tsx index bca3f87..65dc661 100644 --- a/src/components/RelationshipPanel/RelationshipPanel.tsx +++ b/src/components/RelationshipPanel/RelationshipPanel.tsx @@ -9,6 +9,8 @@ import { RelationshipType, PartnershipStatus, ChildlessReason, + Sex, + createPerson, } from '@/core/model/types'; import styles from './RelationshipPanel.module.css'; @@ -16,9 +18,12 @@ export function RelationshipPanel() { const { pedigree, selectedRelationshipId, + addPerson, + updatePerson, updateRelationship, deleteRelationship, clearSelection, + recalculateLayout, } = usePedigreeStore(); const selectedRelationship = selectedRelationshipId && pedigree @@ -55,6 +60,48 @@ export function RelationshipPanel() { clearSelection(); }; + const handleAddChild = (sex: Sex) => { + if (!pedigree || !selectedRelationship) return; + + const parent1 = pedigree.persons.get(selectedRelationship.person1Id); + const parent2 = pedigree.persons.get(selectedRelationship.person2Id); + if (!parent1 || !parent2) return; + + // Determine father and mother based on sex + const father = parent1.sex === Sex.Male ? parent1 : (parent2.sex === Sex.Male ? parent2 : null); + const mother = parent1.sex === Sex.Female ? parent1 : (parent2.sex === Sex.Female ? parent2 : null); + + // Create child + const childId = `P${Date.now().toString(36)}`; + const child = createPerson(childId, pedigree.familyId, sex); + child.metadata.label = childId; + child.fatherId = father?.id ?? null; + child.motherId = mother?.id ?? null; + + // Position child below parents + const parentX = ((parent1.x ?? 0) + (parent2.x ?? 0)) / 2; + const parentY = Math.max(parent1.y ?? 0, parent2.y ?? 0); + child.x = parentX; + child.y = parentY + 120; + + addPerson(child); + + // Update parents' childrenIds + updatePerson(parent1.id, { + childrenIds: [...parent1.childrenIds, childId], + }); + updatePerson(parent2.id, { + childrenIds: [...parent2.childrenIds, childId], + }); + + // Update relationship's childrenIds + updateRelationship(selectedRelationship.id, { + childrenIds: [...selectedRelationship.childrenIds, childId], + }); + + recalculateLayout(); + }; + return (
@@ -150,6 +197,31 @@ export function RelationshipPanel() { )}
+ {/* Add Child Section */} +
+
Add Child
+
+ + + +
+
+ {/* Childlessness Section */}
Children Status
diff --git a/src/components/Toolbar/Toolbar.module.css b/src/components/Toolbar/Toolbar.module.css index b8ce68c..fc72f11 100644 --- a/src/components/Toolbar/Toolbar.module.css +++ b/src/components/Toolbar/Toolbar.module.css @@ -23,8 +23,8 @@ } .toolButton { - width: 36px; height: 36px; + padding: 0 10px; border: 1px solid #ddd; background: white; border-radius: 4px; @@ -32,8 +32,15 @@ display: flex; align-items: center; justify-content: center; + gap: 6px; color: #333; transition: all 0.15s ease; + font-size: 12px; + white-space: nowrap; +} + +.toolButton svg { + flex-shrink: 0; } .toolButton:hover:not(:disabled) { diff --git a/src/components/Toolbar/Toolbar.tsx b/src/components/Toolbar/Toolbar.tsx index ae0ae9c..c466a3e 100644 --- a/src/components/Toolbar/Toolbar.tsx +++ b/src/components/Toolbar/Toolbar.tsx @@ -151,26 +151,26 @@ export function Toolbar() { return (
- Tools
- Add Person
- Relationships
- Edit
- History +
+ +
+ +
+
@@ -347,3 +366,18 @@ function ParentsIcon() { ); } + +function AutoAlignIcon() { + return ( + + {/* Grid lines */} + + + + + {/* Alignment arrows */} + + + + ); +}