Files
dogfather-universe/tools/app-symbole-einbinden.mjs
DogFatherGitandClaude Opus 5 7fe6a51233 ZockerAnstalt und Analyse: Symbole unter den richtigen Namen
Der erste Entwurf legte sie unter eigenen Namen daneben
(dogfather-zocker-192.png). Die Dateien lagen da, die Manifeste zeigten
weiter auf icon-192.png -- das neue Symbol waere nie angekommen. Der
Kopiervorgang meldete trotzdem 6 Symbole kopiert.

Jetzt werden die vorhandenen Namen ueberschrieben. Damit greifen
Manifest und HTML ohne weitere Aenderung, und es gibt keine zweite
Stelle, an der ein Verweis uebersehen werden kann. Fehlt ein erwarteter
Name, wird das gemeldet statt still uebergangen.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-05 11:48:17 +02:00

312 lines
13 KiB
JavaScript

/* ===================================================================
Bindet die gebauten App-Symbole überall ein.
Getrennt von tools/app-symbole.mjs, weil es zwei verschiedene Dinge
sind: dort werden Bilder ERZEUGT, hier werden Dateien VERÄNDERT. Wer
nur den Stil nachjustieren will, soll das tun können, ohne dass dabei
vierzig HTML-Dateien angefasst werden.
WAS DIESES WERKZEUG ANFASST:
* die Manifeste (vorhandene ändern, fehlende anlegen)
* in jeder HTML-Seite: das Reiter-Symbol (favicon), das
Startbildschirm-Symbol (apple-touch-icon) und der Verweis aufs
richtige Manifest
MEHRFACH AUFRUFBAR. Jede Änderung prüft erst, ob sie schon da ist.
Ein zweiter Lauf ändert deshalb nichts mehr -- wichtig, weil dieses
Werkzeug nach jeder Stiländerung erneut laufen soll.
NICHT ANGEFASST werden Name, Beschreibung, Farben und Startadresse
der vorhandenen Manifeste. Die sind gewachsen und gehören nicht zu
dieser Aufgabe.
Aufrufen mit: node tools/app-symbole-einbinden.mjs
=================================================================== */
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, copyFileSync, unlinkSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { APPS, FARBEN, STIL_JE_APP, STILE } from "./app-symbole.mjs";
const WURZEL = join(dirname(fileURLToPath(import.meta.url)), "..");
const SYM = join(WURZEL, "assets", "img", "app-symbole");
if (!existsSync(SYM) || readdirSync(SYM).length < 60) {
console.error("Die Symbole fehlen. Erst node tools/app-symbole.mjs bauen laufen lassen.");
process.exit(1);
}
const farbeVon = Object.fromEntries(FARBEN.map((f) => [f.schluessel, f]));
let geaendert = 0;
const berichte = [];
/* ---------------------------------------------------------------------
1. Die Manifeste
------------------------------------------------------------------ */
/** Der Symbolblock, wie ihn ein Manifest braucht. */
const symbolBlock = (schluessel, pfadVorn) => [
{ src: `${pfadVorn}${schluessel}-192.png`, sizes: "192x192", type: "image/png", purpose: "any" },
{ src: `${pfadVorn}${schluessel}-512.png`, sizes: "512x512", type: "image/png", purpose: "any" },
{ src: `${pfadVorn}${schluessel}-192-maskable.png`, sizes: "192x192", type: "image/png", purpose: "maskable" },
{ src: `${pfadVorn}${schluessel}-512-maskable.png`, sizes: "512x512", type: "image/png", purpose: "maskable" },
];
const PFAD_VORN = "/assets/img/app-symbole/";
/* Vorhandene Manifeste: nur die Symbole und die Themenfarbe tauschen. */
const MANIFESTE_VORHANDEN = [
["manifest.json", "universe"],
["manifest-verwaltung.json", "verwaltung"],
["webdesign/app.webmanifest", "webdesign"],
["webdesign/verwaltung.webmanifest", "wdverwalt"],
];
for (const [datei, schluessel] of MANIFESTE_VORHANDEN) {
const pfad = join(WURZEL, datei);
if (!existsSync(pfad)) { berichte.push(` ? ${datei} gibt es nicht — übersprungen`); continue; }
const inhalt = JSON.parse(readFileSync(pfad, "utf8"));
const neu = symbolBlock(schluessel, PFAD_VORN);
const f = farbeVon[schluessel];
/* NUR DIE SYMBOLE. Themenfarbe und Hintergrundfarbe bleiben, wie sie
sind.
Ein Zwischenstand vom 05.09.2026 hatte sie mitgesetzt -- der
Gedanke war, dass die Statusleiste der installierten App zur
Symbolfarbe passen sollte. Filipe dazu: *"wieso cloud ich will doch
nur dass die symbole anders aussehen wenn ich sie installiere mehr
nicht."* Das war eindeutig und richtig: Bestellt waren die Symbole.
Alles andere anzufassen ist Eigenmacht, auch wenn es gut gemeint
ist und gut aussieht.
Die Farben wurden deshalb auf ihre alten Werte zurückgesetzt. Wer
sie später doch angleichen will, macht das als eigene Entscheidung
-- nicht als Nebenwirkung eines Symboltauschs. */
const vorher = JSON.stringify(inhalt);
inhalt.icons = neu;
if (JSON.stringify(inhalt) === vorher) {
berichte.push(` = ${datei} war schon richtig`);
continue;
}
writeFileSync(pfad, JSON.stringify(inhalt, null, 2) + "\n");
geaendert++;
berichte.push(` ✓ ${datei} → ${schluessel}`);
}
/* Fehlende Manifeste: Kundenportal und Workspace hatten keins.
Das Portal lief bisher unter dem Manifest der Webdesign-Seite -- damit
trug die Verknüpfung deren Namen und deren Symbol. Der Workspace hatte
überhaupt keins; er war als App gar nicht installierbar. */
const MANIFESTE_NEU = [
{
datei: "webdesign/portal.webmanifest", schluessel: "portal",
seite: "webdesign/portal.html",
inhalt: {
name: "Kundenportal — Dogfather Webdesign",
short_name: "Kundenportal",
description: "Der eigene Bereich für Kundinnen und Kunden: Projekte, Anfragen, "
+ "Zahlungen und Postfach an einem Ort.",
start_url: "/webdesign/portal.html",
id: "/webdesign/portal.html",
scope: "/webdesign/",
display: "standalone", orientation: "any", lang: "de", dir: "ltr",
},
},
{
datei: "workspace/app.webmanifest", schluessel: "workspace",
seite: "workspace/start.html",
inhalt: {
name: "Creator Workspace — Dogfather Universe",
short_name: "Workspace",
description: "Betreuung an einem Ort: Aufgaben, Termine, Calls, Bereiche, "
+ "Dateien und Reports. Nur mit persönlichem Zugangscode.",
start_url: "/workspace/start.html",
id: "/workspace/start.html",
scope: "/workspace/",
display: "standalone", orientation: "any", lang: "de", dir: "ltr",
},
},
];
for (const { datei, schluessel, inhalt, seite } of MANIFESTE_NEU) {
const pfad = join(WURZEL, datei);
/* Diese beiden Manifeste sind neu, es gibt also keinen alten Wert,
auf den man zurueckfallen koennte. Genommen wird deshalb die Farbe,
die die SEITE schon hat -- nicht die Farbe des neuen Symbols. Auch
hier gilt: geaendert wird nur das Symbol; ein neues Manifest darf
nicht durch die Hintertuer ein neues Aussehen mitbringen. */
let seitenFarbe = "#05070b";
try {
const html = readFileSync(join(WURZEL, seite), "utf8");
const m = html.match(/<meta[^>]*name="theme-color"[^>]*content="([^"]*)"/i);
if (m) seitenFarbe = m[1];
} catch { /* Seite nicht lesbar -- dann der dunkle Vorgabewert */ }
const fertig = {
...inhalt,
background_color: seitenFarbe,
theme_color: seitenFarbe,
icons: symbolBlock(schluessel, PFAD_VORN),
};
const alt = existsSync(pfad) ? readFileSync(pfad, "utf8") : "";
const neu = JSON.stringify(fertig, null, 2) + "\n";
if (alt === neu) { berichte.push(` = ${datei} war schon richtig`); continue; }
mkdirSync(dirname(pfad), { recursive: true });
writeFileSync(pfad, neu);
geaendert++;
berichte.push(` ${alt ? "✓" : "+"} ${datei} → ${schluessel}${alt ? "" : " (neu angelegt)"}`);
}
/* ---------------------------------------------------------------------
2. Die HTML-Seiten
------------------------------------------------------------------ */
/** Welche Seite gehört zu welcher App? Die genaueste Regel gewinnt,
* deshalb steht das Besondere vor dem Allgemeinen. */
const ZUORDNUNG = [
{ treffer: (p) => p === "webdesign/portal.html", app: "portal", manifest: "/webdesign/portal.webmanifest" },
{ treffer: (p) => p === "webdesign/verwaltung.html", app: "wdverwalt", manifest: "/webdesign/verwaltung.webmanifest" },
{ treffer: (p) => p.startsWith("webdesign/"), app: "webdesign", manifest: "/webdesign/app.webmanifest" },
{ treffer: (p) => p.startsWith("workspace/"), app: "workspace", manifest: "/workspace/app.webmanifest" },
{ treffer: (p) => p === "verwaltung.html", app: "verwaltung", manifest: "/manifest-verwaltung.json" },
{ treffer: (p) => !p.includes("/"), app: "universe", manifest: "/manifest.json" },
];
function htmlSeiten() {
const raus = [];
const gehen = (ordner, vorn = "") => {
for (const n of readdirSync(ordner, { withFileTypes: true })) {
if (n.name === "node_modules" || n.name.startsWith(".")) continue;
const p = join(ordner, n.name);
const rel = vorn ? `${vorn}/${n.name}` : n.name;
if (n.isDirectory()) {
if (["webdesign", "workspace"].includes(rel)) gehen(p, rel);
continue;
}
if (n.name.endsWith(".html")) raus.push(rel);
}
};
gehen(WURZEL);
return raus;
}
for (const rel of htmlSeiten()) {
const regel = ZUORDNUNG.find((z) => z.treffer(rel));
if (!regel) continue;
const pfad = join(WURZEL, rel);
let text = readFileSync(pfad, "utf8");
const vorher = text;
const s = regel.app;
/* Reiter-Symbol. Ersetzt jeden vorhandenen icon-Verweis; gibt es
keinen, kommt er hinter <head>. */
const favicon = `<link rel="icon" type="image/png" href="${PFAD_VORN}${s}-32.png" />`;
if (/<link[^>]*rel="icon"[^>]*>/i.test(text)) {
text = text.replace(/<link[^>]*rel="icon"[^>]*>/i, favicon);
} else {
text = text.replace(/<head>/i, `<head>\n${favicon}`);
}
/* Startbildschirm-Symbol. */
const apple = `<link rel="apple-touch-icon" href="${PFAD_VORN}${s}-180.png" />`;
if (/<link[^>]*rel="apple-touch-icon"[^>]*>/i.test(text)) {
text = text.replace(/<link[^>]*rel="apple-touch-icon"[^>]*>/i, apple);
} else {
text = text.replace(favicon, `${favicon}\n${apple}`);
}
/* Manifest -- nur ersetzen, nicht neu erfinden: Seiten ohne Manifest
sollen auch keins bekommen (etwa Diagnoseseiten). */
if (/<link[^>]*rel="manifest"[^>]*>/i.test(text)) {
text = text.replace(/<link[^>]*rel="manifest"[^>]*href="[^"]*"/i,
`<link rel="manifest" href="${regel.manifest}"`);
}
/* Die Themenfarbe der Seite bleibt unangetastet -- siehe die
Begründung oben bei den Manifesten. Bestellt waren die Symbole. */
if (text !== vorher) { writeFileSync(pfad, text); geaendert++; }
}
/* ---------------------------------------------------------------------
3. Die zwei Apps in eigenen Ordnern
------------------------------------------------------------------ */
/* Je Projekt: welcher Dateiname DORT benutzt wird -> welche Groesse von
uns dorthin gehoert. Die Namen stammen aus dem jeweiligen Manifest,
nicht aus einer Vermutung. */
const AUSWAERTS = [
{
name: "Dogi's ZockerAnstalt", schluessel: "zocker",
ziel: "C:/Users/qciga/Documents/Obelix/Spielehalle/public/icons",
namen: {
"icon-192.png": "192",
"icon-512.png": "512",
"icon-maskable-192.png": "192-maskable",
"icon-maskable-512.png": "512-maskable",
"apple-touch-icon.png": "180",
},
},
{
name: "DogFather Analyse", schluessel: "analyse",
ziel: "C:/Users/qciga/Documents/Obelix/Analyse/logos",
namen: {
"pwa-icon-192.png": "192",
"pwa-icon-512.png": "512",
"pwa-icon-maskable-512.png": "512-maskable",
"pwa-apple-touch-icon.png": "180",
"pwa-favicon-32.png": "32",
},
},
];
/* DIE VORHANDENEN NAMEN ÜBERSCHREIBEN, keine neuen danebenlegen.
Der erste Entwurf legte die Symbole unter eigenen Namen ab
(`dogfather-zocker-192.png`). Das Ergebnis: Die Dateien lagen da, das
Manifest der ZockerAnstalt zeigte weiter auf `icon-192.png` -- und
das neue Symbol wäre nie irgendwo angekommen. Aufgefallen erst beim
Nachsehen, nicht beim Kopieren; der Kopiervorgang meldete brav
"6 Symbole kopiert".
Jetzt werden die Namen überschrieben, die dort schon benutzt werden.
Damit greifen Manifest, HTML und alles andere ohne eine einzige
weitere Änderung -- und es gibt keine zweite Stelle, an der jemand
einen Verweis übersehen kann.
Fehlt ein erwarteter Name, wird das gemeldet statt still übergangen:
Ein Symbol, das niemand einbindet, ist kein erledigter Auftrag. */
for (const { name, schluessel, ziel, namen } of AUSWAERTS) {
if (!existsSync(ziel)) { berichte.push(` ? ${name}: ${ziel} gibt es nicht`); continue; }
let ersetzt = 0;
const fehlend = [];
for (const [dort, meins] of Object.entries(namen)) {
const quelle = join(SYM, `${schluessel}-${meins}.png`);
const zielDatei = join(ziel, dort);
if (!existsSync(zielDatei)) { fehlend.push(dort); continue; }
copyFileSync(quelle, zielDatei);
ersetzt++;
}
/* Die alten Dateien mit eigenem Namen aus dem ersten Entwurf wegräumen. */
for (const g of ["192", "512", "192-maskable", "512-maskable", "180", "32"]) {
const alt = join(ziel, `dogfather-${schluessel}-${g}.png`);
if (existsSync(alt)) { try { unlinkSync(alt); } catch { /* egal */ } }
}
berichte.push(` ${fehlend.length ? "!" : "✓"} ${name}: ${ersetzt} Symbole ersetzt`
+ (fehlend.length ? ` — NICHT GEFUNDEN: ${fehlend.join(", ")}` : ""));
}
/* ---------------------------------------------------------------------
Bericht
------------------------------------------------------------------ */
console.log("Manifeste und Ordner:");
for (const z of berichte) console.log(z);
console.log(`\nGeänderte Dateien insgesamt: ${geaendert}`);
console.log("\nZuordnung:");
for (const app of APPS) {
const f = farbeVon[app.schluessel];
console.log(` ${app.kurz.padEnd(12)} ${STILE[STIL_JE_APP[app.schluessel]].name.padEnd(18)}`
+ ` ${f.mittel} ${app.wo}`);
}