b1411625aa
Share-Erweiterung: ein PDF aus Mail landet ueber Teilen -> DataMetric360 als Tankbeleg. Alles Native liegt versioniert unter companion-app/native/, weil ios/ gitignored ist und npx cap add ios es sonst wieder frisst; scripts/ios-teilen-einrichten.mjs haengt es bei jedem Bau ins Xcode-Projekt und ist in ios-signieren.sh eingehaengt. Der pbxproj-Teil ist auf keinem Mac erprobt - er bricht vor dem Schreiben ab, und docs/SHARE_EXTENSION.md beschreibt dieselben Handgriffe fuer Xcode. "In den Kalender uebernehmen" ging in der App nie: WKWebView ignoriert das download-Attribut, der Klick lief ins Leere. Auf dem Geraet jetzt @capacitor/filesystem plus @capacitor/share. useTheme/useBoden legten je Aufrufer eigenen Zustand an - fuenf Kopien. Der Schalter aenderte nur seine eigene, data-theme in App.tsx blieb stehen, die Tag/Nacht-Umschaltung wirkte erst nach einem Neustart. Jetzt ein Speicher je Einstellung ueber useSyncExternalStore. Zwei Regressionstests, gegen den alten Stand als fehlschlagend nachgewiesen. Alle Popups nach Apple HIG: neue Bausteine Sheet und ActionSheet, blickdicht und mit abgedunkeltem Schleier, in beiden Codebasen. Popup portalierte nach document.body - ausserhalb von [data-theme] - und war deshalb im Tagmodus fast unsichtbar; Portal zielt jetzt auf .ads-root. ImagePlaceholder merkte sich "fehlgeschlagen" ohne die Adresse: nach einem leeren Galerieplatz zeigten auch die mit Foto nur noch den Platzhalter. Vier Regressionstests, ebenfalls gegen den alten Stand geprueft. Bildadressen tragen jetzt einen Cache-Brecher - HA liefert /local/ mit 31 Tagen Cache-Vorgabe aus, im Panel eingefuegte Radfotos erschienen in der App deshalb nicht. uebersichtsbild: Panel speichert den Namen, die App verglich gegen den Dateinamen - der Vergleich traf nie zu. Kanonisch ist der Name. Weiter: Navigationspfeil statt gleichschenkligem Dreieck auf der Streckenlinie (die Kerbe unterscheidet Kopf und Ende), CI-Symbol tour-s als Fahrten-Icon, Datumsfelder zeigen ihren Wert ohne erstes Antippen, feste Beispielnamen im Setup, Kopf-Gegengewicht fuer mittige Titel, Standort-Blatt mit festem Fussabstand, NSLocationWhenInUseUsageDescription, watchPosition fuer die Live-Ortung, Art-Pille wieder als Knopf, Zwischenablage fuer Tankbelege. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
75 lines
2.7 KiB
TypeScript
75 lines
2.7 KiB
TypeScript
import * as React from "react";
|
|
|
|
export interface ImagePlaceholderProps {
|
|
src?: string;
|
|
alt: string;
|
|
/** Caption shown on the load-error placeholder. */
|
|
label: React.ReactNode;
|
|
/** Small monospace hint under the label, e.g. an expected filename. */
|
|
hint?: React.ReactNode;
|
|
/** Placeholder icon override - defaults to a simple image glyph. */
|
|
icon?: React.ReactNode;
|
|
size?: "default" | "mini" | "round";
|
|
className?: string;
|
|
}
|
|
|
|
const DEFAULT_ICON = (
|
|
<svg viewBox="0 0 24 24" width="26" height="26" aria-hidden>
|
|
<path
|
|
fill="currentColor"
|
|
fillRule="evenodd"
|
|
d="m23.35 1.35-22 22-.7-.7 22-22zM1 3h17.5v1H2v16H1zm21 11.85V4h1v17H6v-1h16v-3.77l-3.24-3.08-4.57 4.48-2.85-2.84.7-.7 2.15 2.14 4.56-4.47 3.25 3.1ZM4 8.5a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0M6.5 7a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3"
|
|
clipRule="evenodd"
|
|
/>
|
|
</svg>
|
|
);
|
|
|
|
export function ImagePlaceholder({
|
|
src,
|
|
alt,
|
|
label,
|
|
hint,
|
|
icon,
|
|
size = "default",
|
|
className,
|
|
}: ImagePlaceholderProps) {
|
|
/*
|
|
* Gemerkt wird die Adresse, die fehlgeschlagen ist - nicht ein blosses
|
|
* "ist fehlgeschlagen".
|
|
*
|
|
* Der Unterschied ist der Fehler, den das behebt: In der Fotogalerie unter
|
|
* "Mein Audi" bleibt dieselbe Komponente stehen und bekommt nur eine neue
|
|
* src. Mit einem blossen Wahrheitswert blieb ein einmal gesetztes true fuer
|
|
* immer stehen - sobald ein noch leerer Platz durchlaufen war, zeigten auch
|
|
* alle folgenden Plaetze mit echtem Foto nur noch den Platzhalter. Erst ein
|
|
* Seitenwechsel setzte den Zustand zurueck, weil die Komponente dabei neu
|
|
* entsteht (gemeldet vom Eigentuemer, 2026-08-31).
|
|
*
|
|
* Bewusst ohne useEffect: ein Vergleich beim Rendern kommt ohne den
|
|
* zusaetzlichen Durchlauf aus, den ein Zuruecksetzen im Effekt braucht -
|
|
* und in dem waere fuer einen Wimpernschlag das falsche Bild zu sehen.
|
|
*/
|
|
const [fehlgeschlagen, setzeFehlgeschlagen] = React.useState<string | null>(null);
|
|
const classes = ["ads-bildbox"];
|
|
if (size === "mini") classes.push("ads-bildbox--mini");
|
|
if (size === "round") classes.push("ads-bildbox--round");
|
|
if (className) classes.push(className);
|
|
|
|
const showPlaceholder = !src || fehlgeschlagen === src;
|
|
|
|
return (
|
|
<div className={classes.join(" ")}>
|
|
{!showPlaceholder && (
|
|
<img src={src} alt={alt} onError={() => setzeFehlgeschlagen(src)} />
|
|
)}
|
|
{showPlaceholder && (
|
|
<div className="ads-platzhalter">
|
|
<span className="ads-platzhalter__icon">{icon ?? DEFAULT_ICON}</span>
|
|
<span className="ads-platzhalter__label">{label}</span>
|
|
{hint !== undefined && <span className="ads-platzhalter__hint">{hint}</span>}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|