waffensachkunde
Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.
| 1 | /** |
| 2 | * Deutschsprachiges Anwendungsmenü. |
| 3 | * |
| 4 | * Das Menü ist auch ein Barrierefreiheits-Merkmal: Zoom, Vollbild und |
| 5 | * Neu-Laden sind darüber per Tastatur und für Screenreader erreichbar |
| 6 | * beschriftet. Deshalb wird die Menüleiste nicht ausgeblendet. |
| 7 | */ |
| 8 | |
| 9 | import { app, BrowserWindow, Menu, type MenuItemConstructorOptions } from 'electron'; |
| 10 | |
| 11 | import { KANAL_MENUE_BEFEHL, type Menuebefehl } from '../shared/ipc'; |
| 12 | import { anzeigeVergroessern, anzeigeVerkleinern, anzeigeZuruecksetzen } from './anzeige'; |
| 13 | |
| 14 | /** |
| 15 | * Schickt einen Befehl an das Fenster, in dem gerade gearbeitet wird. |
| 16 | * |
| 17 | * Erst das fokussierte, sonst das erste vorhandene: Ein Menübefehl ohne |
| 18 | * Fenster ist kein Fehlerfall, der jemanden erreichen müsste – er ist |
| 19 | * einfach wirkungslos. |
| 20 | */ |
| 21 | function befehlSenden(befehl: Menuebefehl): void { |
| 22 | const fenster = BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0]; |
| 23 | fenster?.webContents.send(KANAL_MENUE_BEFEHL, befehl); |
| 24 | } |
| 25 | |
| 26 | export function menueEinrichten(): void { |
| 27 | const istMac = process.platform === 'darwin'; |
| 28 | |
| 29 | const macAnwendungsMenue: MenuItemConstructorOptions[] = istMac |
| 30 | ? [ |
| 31 | { |
| 32 | label: app.name, |
| 33 | submenu: [ |
| 34 | { role: 'about', label: `Über ${app.name}` }, |
| 35 | { type: 'separator' }, |
| 36 | { role: 'hide', label: `${app.name} ausblenden` }, |
| 37 | { role: 'hideOthers', label: 'Andere ausblenden' }, |
| 38 | { role: 'unhide', label: 'Alle einblenden' }, |
| 39 | { type: 'separator' }, |
| 40 | { role: 'quit', label: `${app.name} beenden` }, |
| 41 | ], |
| 42 | }, |
| 43 | ] |
| 44 | : []; |
| 45 | |
| 46 | const vorlage: MenuItemConstructorOptions[] = [ |
| 47 | ...macAnwendungsMenue, |
| 48 | { |
| 49 | label: '&Datei', |
| 50 | submenu: istMac |
| 51 | ? [{ role: 'close', label: 'Fenster schließen' }] |
| 52 | : [{ role: 'quit', label: 'Beenden' }], |
| 53 | }, |
| 54 | { |
| 55 | label: '&Bearbeiten', |
| 56 | submenu: [ |
| 57 | { role: 'undo', label: 'Rückgängig' }, |
| 58 | { role: 'redo', label: 'Wiederholen' }, |
| 59 | { type: 'separator' }, |
| 60 | { role: 'cut', label: 'Ausschneiden' }, |
| 61 | { role: 'copy', label: 'Kopieren' }, |
| 62 | { role: 'paste', label: 'Einfügen' }, |
| 63 | { role: 'selectAll', label: 'Alles auswählen' }, |
| 64 | ], |
| 65 | }, |
| 66 | { |
| 67 | label: '&Ansicht', |
| 68 | submenu: [ |
| 69 | { role: 'reload', label: 'Neu laden' }, |
| 70 | { type: 'separator' }, |
| 71 | /* Eigene Aktionen statt der Electron-Rollen: Nur so läuft der Zoom |
| 72 | durch dieselbe Stelle wie die Schaltflächen in den Einstellungen – |
| 73 | wird begrenzt, gespeichert und angezeigt. Die Rollen änderten die |
| 74 | Größe unbegrenzt und vergaben sie beim nächsten Start. */ |
| 75 | /* |
| 76 | `registerAccelerator: false` außerhalb von macOS: Die Kombination |
| 77 | steht im Menü, wird aber NICHT beim Betriebssystem angemeldet — |
| 78 | abgefangen wird sie im Renderer, siehe `hooks/useZoomtasten.ts`. |
| 79 | |
| 80 | Der Grund ist gemessen, nicht vermutet: Electron liest den Namen |
| 81 | `Plus` als die umgeschaltete Form von `=` und registriert damit |
| 82 | faktisch `Strg+Umschalt+=`. Auf einer deutschen Tastatur ist `+` |
| 83 | eine eigene Taste ohne Umschalt — die Kombination traf dort nie zu, |
| 84 | und `Strg` und `+` taten schlicht nichts. |
| 85 | |
| 86 | Unter macOS bleibt der Beschleuniger angemeldet: Dort ist die Option |
| 87 | wirkungslos, und beide Wege zusammen zoomten doppelt. |
| 88 | */ |
| 89 | { |
| 90 | label: 'Größer', |
| 91 | accelerator: 'CommandOrControl+Plus', |
| 92 | registerAccelerator: istMac, |
| 93 | click: () => { |
| 94 | anzeigeVergroessern(); |
| 95 | }, |
| 96 | }, |
| 97 | { |
| 98 | label: 'Kleiner', |
| 99 | accelerator: 'CommandOrControl+-', |
| 100 | registerAccelerator: istMac, |
| 101 | click: () => { |
| 102 | anzeigeVerkleinern(); |
| 103 | }, |
| 104 | }, |
| 105 | { |
| 106 | label: 'Normalgröße', |
| 107 | accelerator: 'CommandOrControl+0', |
| 108 | registerAccelerator: istMac, |
| 109 | click: () => { |
| 110 | anzeigeZuruecksetzen(); |
| 111 | }, |
| 112 | }, |
| 113 | { type: 'separator' }, |
| 114 | { role: 'togglefullscreen', label: 'Vollbild' }, |
| 115 | { role: 'toggleDevTools', label: 'Entwicklerwerkzeuge' }, |
| 116 | ], |
| 117 | }, |
| 118 | { |
| 119 | /* |
| 120 | Das Menue ist die einzige Flaeche des Fensters, die nicht wegrollt. |
| 121 | |
| 122 | Bis Fassung 0.25.0 fuehrte es zu keiner einzigen der (heute elf) |
| 123 | Ansichten. |
| 124 | Der Startbildschirm ist bei 1265 Bildpunkten Breite 8530 hoch |
| 125 | (gemessen an `docs/bildschirmfotos/01-startbildschirm-hell.png`), und |
| 126 | das Sprungmenue der Seite steht bei rund 1000 Bildpunkten: Wer daran |
| 127 | vorbeigerollt war, hatte fuer die restlichen rund 7500 Bildpunkte |
| 128 | keinen Weg mehr in eine andere Ansicht. |
| 129 | |
| 130 | Warum hier und nicht als klebende Leiste im Fenster: Eine solche |
| 131 | Leiste naehme bei 400 Prozent Anzeigegroesse gemessen 155 bis 192 von |
| 132 | 256 Bildpunkten Sichtfeld, und sie laege ueber den Sprungzielen, die |
| 133 | alle drei Sprungmechanismen mit `scrollIntoView({ block: 'start' })` |
| 134 | auf y = 0 setzen (WCAG 2.4.11). Ein Menue kostet keine Bildschirmhoehe |
| 135 | und steht im Zugaenglichkeitsbaum des Betriebssystems. |
| 136 | |
| 137 | Keine Beschleuniger: Strg und ein Buchstabe kollidierte mit den |
| 138 | Kuerzeln der Lernsitzung, und die Alt-Folge (Alt, G, ...) ist unter |
| 139 | Windows ohnehin da. Die Reihenfolge folgt dem Startbildschirm. |
| 140 | */ |
| 141 | label: '&Gehe zu', |
| 142 | submenu: [ |
| 143 | { |
| 144 | label: 'Startbildschirm', |
| 145 | click: () => { |
| 146 | befehlSenden('gehe-zu-start'); |
| 147 | }, |
| 148 | }, |
| 149 | { type: 'separator' }, |
| 150 | { |
| 151 | label: 'Kapitel wählen', |
| 152 | click: () => { |
| 153 | befehlSenden('gehe-zu-kapitelwahl'); |
| 154 | }, |
| 155 | }, |
| 156 | { |
| 157 | label: 'Prüfungssimulation', |
| 158 | click: () => { |
| 159 | befehlSenden('gehe-zu-pruefungswahl'); |
| 160 | }, |
| 161 | }, |
| 162 | { type: 'separator' }, |
| 163 | { |
| 164 | label: 'Fragen durchsuchen', |
| 165 | click: () => { |
| 166 | befehlSenden('gehe-zu-suche'); |
| 167 | }, |
| 168 | }, |
| 169 | { |
| 170 | label: 'Glossar', |
| 171 | click: () => { |
| 172 | befehlSenden('gehe-zu-glossar'); |
| 173 | }, |
| 174 | }, |
| 175 | { |
| 176 | label: 'Gesetze lesen', |
| 177 | click: () => { |
| 178 | befehlSenden('gehe-zu-gesetze'); |
| 179 | }, |
| 180 | }, |
| 181 | { type: 'separator' }, |
| 182 | { |
| 183 | label: 'Über diese Software', |
| 184 | click: () => { |
| 185 | befehlSenden('gehe-zu-ueber'); |
| 186 | }, |
| 187 | }, |
| 188 | ], |
| 189 | }, |
| 190 | { |
| 191 | label: '&Hilfe', |
| 192 | role: 'help', |
| 193 | /* |
| 194 | Vorher stand hier unter macOS ein leeres Menü und unter Windows nur |
| 195 | Electrons eigenes Info-Fenster – ein Hilfemenü ohne Hilfe. |
| 196 | |
| 197 | F1 ist bewusst ein Menü-Accelerator und kein Tastenfang im Renderer: |
| 198 | So wirkt die Taste auch dann, wenn der Fokus in einem Textfeld steht |
| 199 | (etwa bei einer offenen Frage), und sie steht sichtbar im Menü – |
| 200 | WCAG 3.2.6 verlangt eine auffindbare Hilfe, nicht eine geheime. |
| 201 | */ |
| 202 | submenu: [ |
| 203 | { |
| 204 | label: 'Handbuch', |
| 205 | accelerator: 'F1', |
| 206 | click: () => { |
| 207 | befehlSenden('hilfe'); |
| 208 | }, |
| 209 | }, |
| 210 | ...(istMac |
| 211 | ? [] |
| 212 | : [ |
| 213 | { type: 'separator' } as MenuItemConstructorOptions, |
| 214 | { role: 'about', label: `Über ${app.name}` } as MenuItemConstructorOptions, |
| 215 | ]), |
| 216 | ], |
| 217 | }, |
| 218 | ]; |
| 219 | |
| 220 | Menu.setApplicationMenu(Menu.buildFromTemplate(vorlage)); |
| 221 | } |