Baustein 10a: PWA, Offlinebetrieb, Druckansichten
Installierbar: Manifest, Symbole aus dem eigenen Zeichen erzeugt (scripts/symbole.ts, kein Bilddienst), Verknuepfungen zu Lernmodus, Berichtsheft und Handouts. Offline gilt eine Regel, und die steht im Dienstarbeiter: Nur das Lernmaterial liegt im Browserspeicher - Handouts, Merkkarten, Glossar, die eigene Woche. Lernzielkontrollen, Berichtsheft, Bewertungen, Feedbackgespraeche, Anhaenge und jedes PDF niemals. Das ist keine Sparsamkeit: Was im Browsercache liegt, ist an der serverseitigen Rechtepruefung vorbei. Bewertungen ohne Netz gehen in eine Warteschlange und werden nachgereicht. Die Schlange haengt an der Benutzerkennung, nicht am Geraet - auf einem geteilten Werkstattgeraet bekaeme sonst der Zweite die offenen Bewertungen des Ersten aufs Konto. Der Tag der Bewertung kommt vom Geraet und wird auf heute gedeckelt (tagDerBewertung, 4 Tests): Eine vorgestellte Uhr darf das Wiederholen nicht wegstellen. Druck: Wer die dunkle Fassung eingestellt hatte, druckte bisher schwarze Flaechen. Im Druck wird jetzt die komplette helle Palette gesetzt, dazu A4-Raender, keine Umbrueche mitten in Karten und Zeilen, aufgeklappte details. Druckknoepfe bei Handout, Glossar und Auswertung. Durchstich in scripts/pruefungen/baustein-10.mjs mit echtem Abschalten des Netzes: 33 Pruefungen, darunter der Nachweis, dass nichts Bewertungsrelevantes im Speicher landet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
be2a674c7a
commit
2babef9293
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Erzeugt die Symbole für die installierte Anwendung aus dem Zeichen selbst –
|
||||
* ohne Bilddienst, ohne Netz.
|
||||
*
|
||||
* npm run symbole
|
||||
*
|
||||
* Zwei Fassungen, weil Android sie unterschiedlich behandelt:
|
||||
*
|
||||
* symbol-192.png / symbol-512.png das Zeichen, wie es ist
|
||||
* symbol-maskabel-512.png derselbe Inhalt, aber randvoll auf
|
||||
* Petrol und nach innen gerückt. Android
|
||||
* schneidet das Symbol in eine eigene
|
||||
* Form – ein Kreis, ein Tropfen, was das
|
||||
* Gerät gerade mag. Was zu weit außen
|
||||
* liegt, ist dann weg.
|
||||
*/
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { chromium } from '@playwright/test';
|
||||
|
||||
const ZIEL = path.join(process.cwd(), 'public');
|
||||
|
||||
/** Das Zeichen. Gleicher Inhalt wie `public/lernbar.svg`. */
|
||||
function zeichen(randAnteil: number, eckenRadius: number): string {
|
||||
// Die drei Balken liegen im Original in einem Feld von 64 × 64 Punkten.
|
||||
// `randAnteil` rückt sie nach innen, ohne den Grund zu verkleinern.
|
||||
const innen = 64 * (1 - 2 * randAnteil);
|
||||
const versatz = 64 * randAnteil;
|
||||
|
||||
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<rect width="64" height="64" rx="${eckenRadius}" fill="#1F4E5F"/>
|
||||
<g transform="translate(${versatz} ${versatz}) scale(${innen / 64})">
|
||||
<rect x="14" y="16" width="10" height="32" rx="2" fill="#EDF3F5"/>
|
||||
<rect x="27" y="16" width="10" height="32" rx="2" fill="#EDF3F5"/>
|
||||
<rect x="40" y="16" width="10" height="32" rx="2" fill="#D98A2B"/>
|
||||
</g>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
type Auftrag = { datei: string; groesse: number; svg: string };
|
||||
|
||||
const auftraege: Auftrag[] = [
|
||||
{ datei: 'symbol-192.png', groesse: 192, svg: zeichen(0, 12) },
|
||||
{ datei: 'symbol-512.png', groesse: 512, svg: zeichen(0, 12) },
|
||||
// Randvoll und nach innen gerückt: der Beschnitt darf nichts abschneiden.
|
||||
{ datei: 'symbol-maskabel-512.png', groesse: 512, svg: zeichen(0.14, 0) },
|
||||
];
|
||||
|
||||
async function main() {
|
||||
const browser = await chromium.launch();
|
||||
|
||||
for (const auftrag of auftraege) {
|
||||
const seite = await browser.newPage({
|
||||
viewport: { width: auftrag.groesse, height: auftrag.groesse },
|
||||
deviceScaleFactor: 1,
|
||||
});
|
||||
|
||||
await seite.setContent(
|
||||
`<!doctype html><style>html,body{margin:0;padding:0}svg{display:block;width:100vw;height:100vh}</style>${auftrag.svg}`,
|
||||
);
|
||||
|
||||
const datei = path.join(ZIEL, auftrag.datei);
|
||||
await seite.screenshot({ path: datei, omitBackground: false });
|
||||
await seite.close();
|
||||
|
||||
console.log(` public/${auftrag.datei} (${auftrag.groesse}×${auftrag.groesse}, ${fs.statSync(datei).size} Bytes)`);
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
console.log('Symbole erzeugt.');
|
||||
}
|
||||
|
||||
main().catch((fehler) => {
|
||||
console.error('Die Symbole konnten nicht erzeugt werden.');
|
||||
console.error(fehler instanceof Error ? fehler.message : fehler);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user