Native iOS-Huelle in Betrieb, drei Darstellungsfehler behoben
Xcode ist vorhanden, die Huelle liess sich also wirklich bauen statt nur vorzubereiten: Capacitor 8 fuer iOS und Android, Build fuer den Simulator erfolgreich, App laeuft und zeigt echte Daten vom Server. CapacitorHttp eingeschaltet. Das ist keine Feinheit: die native Huelle liefert die Oberflaeche unter eigenem Ursprung aus, jede Anfrage an Home Assistant ist damit ursprungsuebergreifend, und die WebView lehnt sie ohne CORS-Freigabe ab. Nativ gestellte Anfragen kennen keine CORS-Pruefung - die App braucht dadurch keine CORS-Einstellung am Server. Drei Fehler, die erst die Bildschirmfotos zeigten: 1. Der Fahrzeug-Block auf der Uebersicht war unsichtbar. Ursache war der Flexbox-Fallstrick: "overflow: hidden" setzt die automatische Mindesthoehe auf 0, und sobald die Seite laenger ist als der Bildschirm, quetscht flex-shrink den Block auf Hoehe 0. Modellname, Typenschild und Kennzeichen waren damit schlicht weg. 2. Neben dem Typenschild stand der Modellname noch einmal komplett, obwohl das Schild ihn bereits zeigt. Das alte Panel loest das laengst richtig - nur der Zusatz gehoert daneben. Nachgezogen, samt Entdopplung, wenn die Ausfuehrung schon im Namen steckt. 3. In der Fotogalerie liefen die Dateinamen ueber den Rand der Miniaturen. Dazu: Datumszeile in Listen bricht um statt abzuschneiden, Statistik nutzt auf der Flaeche mehrere Spalten. Im Backend war das Kilometerstand-Screening kaputt: es rief urlopen direkt auf, was Home Assistant seit 2026.8 als blockierenden Aufruf abbricht. Jede Fahrt blieb dadurch ohne Strecke - sichtbar nur als Warnung im Protokoll. Laeuft jetzt ueber task.executor und rechnet an der Testinstanz wieder echte Strecken aus dem Verlauf. Die CORS-Freigabe fuer die Web-Fassung ist in der Testkonfiguration hinterlegt. Sie greift in Home Assistant 2026.8 allerdings nicht - deshalb wird die App aus Home Assistant selbst ausgeliefert (gleicher Ursprung, kein CORS), was ohnehin der geplante Weg ist. Die erzeugten Ordner ios/ und android/ bleiben ungetrackt; sie entstehen jederzeit neu aus dem Webbuendel.
This commit is contained in:
@@ -23,13 +23,16 @@ export function Bild({
|
||||
groesse?: "default" | "mini" | "round"
|
||||
}) {
|
||||
const url = bildUrl(datei)
|
||||
// Der Dateiname hilft nur, wenn er lesbar ist — in der 78px breiten
|
||||
// Miniaturansicht liefe er über den Rand hinaus.
|
||||
const mitDateiname = groesse !== "mini" && groesse !== "round"
|
||||
return (
|
||||
<ImagePlaceholder
|
||||
{...(url ? { src: url } : {})}
|
||||
{...(groesse ? { size: groesse } : {})}
|
||||
{...(mitDateiname && datei ? { hint: datei } : {})}
|
||||
alt={beschreibung}
|
||||
label={name}
|
||||
hint={datei ?? undefined}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function Statistik() {
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dm-raster">
|
||||
<Tile>
|
||||
<span className="ads-eyebrow">Distanz und Fahrzeit {a.jahr}</span>
|
||||
<Fig value={de(a.kmJahr)} unit="km" size={40} />
|
||||
@@ -74,7 +74,7 @@ export function Statistik() {
|
||||
/>
|
||||
</Tile>
|
||||
|
||||
<Tile>
|
||||
<Tile className="dm-raster__breit">
|
||||
<span className="ads-eyebrow">Langzeitverbrauch</span>
|
||||
<Fig value={deOderStrich(a.langzeit.wert, 1)} unit="l/100 km" size={40} />
|
||||
<Werteliste
|
||||
@@ -150,6 +150,6 @@ export function Statistik() {
|
||||
}
|
||||
/>
|
||||
</Tile>
|
||||
</>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,23 +17,30 @@ const SCHILDER = import.meta.glob<string>("../assets/audi/typenschilder/*.svg",
|
||||
query: "?url",
|
||||
})
|
||||
|
||||
/** Modellname aus dem Profil → Dateikürzel des Typenschilds. */
|
||||
export function schildKuerzel(titel: string): string | null {
|
||||
const norm = titel.toLowerCase().replace(/\s+/g, " ").trim()
|
||||
const treffer = /\b(rs)\s?([34567])\b/.exec(norm) ?? /\b(s)\s?(q7|3)\b/.exec(norm)
|
||||
if (!treffer) return null
|
||||
const [, reihe, zahl] = treffer
|
||||
return `${reihe}${zahl}`.replace("sq7", "sq7")
|
||||
/**
|
||||
* Erkennt aus dem Modellnamen das passende Typenschild und trennt ab, was
|
||||
* darüber hinaus im Namen steht.
|
||||
*
|
||||
* Das ist die Regel aus dem alten Panel (`badge()`): Der Modellname selbst
|
||||
* steckt schon im Schild — daneben gehört nur, was danach kommt („Avant
|
||||
* competition"). Sonst stünde „RS 4" zweimal da.
|
||||
*/
|
||||
export function schildErkennen(
|
||||
titel: string,
|
||||
): { kuerzel: string; zusatz: string } | null {
|
||||
const treffer = /^\s*(RS\s?[34567]|S\s?Q7|S\s?3)\b/i.exec(titel)
|
||||
if (!treffer?.[1]) return null
|
||||
const kuerzel = treffer[1].replace(/\s+/g, "").toLowerCase()
|
||||
return { kuerzel, zusatz: titel.slice(treffer[0].length).trim() }
|
||||
}
|
||||
|
||||
export function Typenschild({ modell, hoehe = 22 }: { modell: string; hoehe?: number }) {
|
||||
const [theme] = useTheme()
|
||||
const kuerzel = schildKuerzel(modell)
|
||||
if (!kuerzel) return null
|
||||
const erkannt = schildErkennen(modell)
|
||||
if (!erkannt) return null
|
||||
|
||||
const variante = theme === "nacht" ? "negative" : "positive"
|
||||
const pfad = `../assets/audi/typenschilder/${kuerzel}-${variante}.svg`
|
||||
const quelle = SCHILDER[pfad]
|
||||
const quelle = SCHILDER[`../assets/audi/typenschilder/${erkannt.kuerzel}-${variante}.svg`]
|
||||
if (!quelle) return null
|
||||
|
||||
return <img src={quelle} alt={modell} height={hoehe} style={{ height: hoehe, width: "auto" }} />
|
||||
|
||||
@@ -15,7 +15,7 @@ import { datum, datumZeit, dauer, de, deOderStrich, eur } from "../format"
|
||||
import type { SeitenName } from "../navigation"
|
||||
import { Leerzustand, NaviKachel, Wertzeile, Werteliste } from "./bausteine"
|
||||
import { Bild } from "./Bild"
|
||||
import { Typenschild } from "./Typenschild"
|
||||
import { Typenschild, schildErkennen } from "./Typenschild"
|
||||
|
||||
export function Uebersicht({ geheZu }: { geheZu: (name: SeitenName, id?: string) => void }) {
|
||||
const { fahrzeug, einstellungen, fahrten, tankvorgaenge, jetztAktualisieren, statusStand } =
|
||||
@@ -24,6 +24,22 @@ export function Uebersicht({ geheZu }: { geheZu: (name: SeitenName, id?: string)
|
||||
|
||||
if (!fahrzeug || !einstellungen) return null
|
||||
|
||||
// Der Zusatz stammt aus dem Modellnamen selbst (alles nach dem Typenschild)
|
||||
// plus der eigens gepflegten Ausführung — so macht es auch das alte Panel.
|
||||
const schild = schildErkennen(einstellungen.fahrzeugtitel)
|
||||
const ausfuehrung = einstellungen.ausfuehrung.trim()
|
||||
// Die Ausführung steht oft schon im Modellnamen („RS 4 Avant competition"
|
||||
// plus Ausführung „competition") — dann nicht doppelt anhängen.
|
||||
const zusatz = [
|
||||
schild?.zusatz,
|
||||
ausfuehrung && !schild?.zusatz.toLowerCase().includes(ausfuehrung.toLowerCase())
|
||||
? ausfuehrung
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")
|
||||
.trim()
|
||||
|
||||
const letzteFahrt = fahrten
|
||||
.slice()
|
||||
.sort((a, b) => new Date(b.ts_start).getTime() - new Date(a.ts_start).getTime())[0]
|
||||
@@ -59,11 +75,18 @@ export function Uebersicht({ geheZu }: { geheZu: (name: SeitenName, id?: string)
|
||||
/>
|
||||
<div className="dm-szene__text">
|
||||
<div className="dm-szene__marke">
|
||||
<Typenschild modell={einstellungen.fahrzeugtitel} hoehe={20} />
|
||||
<div className="ads-title">
|
||||
{einstellungen.fahrzeugtitel}{" "}
|
||||
{einstellungen.fahrzeugzusatz && (
|
||||
<span className="ads-sport">{einstellungen.fahrzeugzusatz}</span>
|
||||
<Typenschild modell={einstellungen.fahrzeugtitel} hoehe={26} />
|
||||
{/* Neben dem Schild steht nur, was im Namen darüber hinausgeht —
|
||||
das Modell selbst zeigt bereits das Schild. Ohne erkanntes
|
||||
Schild tritt der volle Name an dessen Stelle. */}
|
||||
<div className="ads-title dm-szene__zusatz">
|
||||
{schild ? (
|
||||
zusatz && <span className="ads-sport">{zusatz}</span>
|
||||
) : (
|
||||
<>
|
||||
{einstellungen.fahrzeugtitel}{" "}
|
||||
{zusatz && <span className="ads-sport">{zusatz}</span>}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -61,6 +61,15 @@
|
||||
gap: var(--sp-3);
|
||||
}
|
||||
|
||||
/* Inhalte dürfen nie gestaucht werden — die Fläche scrollt stattdessen.
|
||||
Ohne das quetscht Flex jeden Block mit `overflow: hidden` auf Höhe 0,
|
||||
sobald die Seite länger ist als der Bildschirm: `overflow: hidden` setzt
|
||||
die automatische Mindesthöhe auf 0, und dann greift flex-shrink ungebremst.
|
||||
Genau so verschwand das Fahrzeugbild samt Modellname und Kennzeichen. */
|
||||
.dm-inhalt > * {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.dm-inhalt--breit {
|
||||
padding: var(--sp-5) 32px 32px;
|
||||
}
|
||||
|
||||
@@ -421,9 +421,10 @@
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
color: var(--fg3);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
/* Umbrechen statt abschneiden: Datum, Uhrzeit und Dauer sind alle drei
|
||||
wichtig, und "11.08.2026 · 12:27 Uhr · 3…" verliert genau die Angabe,
|
||||
die man sucht. */
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------- Detailseiten */
|
||||
@@ -568,3 +569,27 @@
|
||||
background: var(--canvas);
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.dm-szene__zusatz {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Der Zusatz neben dem Typenschild darf umbrechen — abgeschnittener Text mit
|
||||
Auslassungspunkten sieht nach Fehler aus, ein Umbruch nicht. */
|
||||
.dm-szene__zusatz {
|
||||
min-width: 0;
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
font-size: 19px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* Die Galerie richtet sich nach der festen Kachelbreite der Bibliothek (78px),
|
||||
damit die Kacheln nicht auseinandergezogen werden und ihre Beschriftung
|
||||
innerhalb des Rahmens bleibt. */
|
||||
.dm-galerie {
|
||||
grid-template-columns: repeat(auto-fill, 78px);
|
||||
justify-content: space-between;
|
||||
gap: var(--sp-3);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user