round70: Icons ruckeln nicht mehr nach - Raster wartet auf alle Datenquellen bevor es rendert

This commit is contained in:
2026-07-26 23:15:37 +02:00
parent 0a1052f217
commit 1e90eb2905
2 changed files with 82 additions and 42 deletions

View File

@@ -235,6 +235,14 @@ export interface MobileAndroidHomeProps {
onToggleFavorite: (item: SearchResult) => void; onToggleFavorite: (item: SearchResult) => void;
onAddReadLater: (url: string) => void; onAddReadLater: (url: string) => void;
isOnline: boolean; isOnline: boolean;
/**
* Wird erst true, wenn ALLE für das Raster nötigen Daten (Dienste,
* Lesezeichen, Kategorien, Zuletzt-besucht, Einstellungen/Layout)
* mindestens einmal geladen sind - siehe Kommentar in HomePage.tsx. Bis
* dahin wird ein einfacher Platzhalter statt des Rasters gezeigt, damit
* nicht einzelne Kacheln nacheinander "nachrücken".
*/
isReady: boolean;
} }
export function MobileAndroidHome({ export function MobileAndroidHome({
@@ -249,6 +257,7 @@ export function MobileAndroidHome({
onToggleFavorite, onToggleFavorite,
onAddReadLater, onAddReadLater,
isOnline, isOnline,
isReady,
}: MobileAndroidHomeProps) { }: MobileAndroidHomeProps) {
const [searchOpen, setSearchOpen] = useState(false); const [searchOpen, setSearchOpen] = useState(false);
const [query, setQuery] = useState(""); const [query, setQuery] = useState("");
@@ -537,6 +546,8 @@ export function MobileAndroidHome({
{/* Haupt-Raster */} {/* Haupt-Raster */}
<div data-drop-area="grid" className="min-h-[40vh] px-4 pb-32 pt-2"> <div data-drop-area="grid" className="min-h-[40vh] px-4 pb-32 pt-2">
{isReady ? (
<>
<div className="grid grid-cols-4 gap-x-3 gap-y-5"> <div className="grid grid-cols-4 gap-x-3 gap-y-5">
{recentVisits.length > 0 ? ( {recentVisits.length > 0 ? (
<button <button
@@ -577,6 +588,21 @@ export function MobileAndroidHome({
Markiere Dienste/Lesezeichen im Adminbereich als Favorit sie erscheinen dann hier. Markiere Dienste/Lesezeichen im Adminbereich als Favorit sie erscheinen dann hier.
</p> </p>
) : null} ) : null}
</>
) : (
// Platzhalter statt des Rasters, solange noch nicht ALLE nötigen
// Daten da sind (siehe isReady-Kommentar an der Prop weiter oben) -
// verhindert, dass z. B. "Zuletzt besucht" erst nachträglich
// auftaucht und alle Icons danach verschiebt (siehe Bugreport).
<div className="grid grid-cols-4 gap-x-3 gap-y-5" aria-hidden="true">
{Array.from({ length: 8 }).map((_, i) => (
<div key={i} className="flex flex-col items-center gap-1.5">
<div className="h-14 w-14 animate-pulse rounded-[20px] bg-black/10 dark:bg-white/10" />
<div className="h-2.5 w-10 animate-pulse rounded bg-black/10 dark:bg-white/10" />
</div>
))}
</div>
)}
</div> </div>
{/* Dock */} {/* Dock */}
@@ -588,7 +614,9 @@ export function MobileAndroidHome({
className="flex min-h-[4.5rem] min-w-[5rem] items-center gap-3 rounded-[28px] border border-white/40 className="flex min-h-[4.5rem] min-w-[5rem] items-center gap-3 rounded-[28px] border border-white/40
bg-white/70 px-4 py-3 shadow-xl backdrop-blur-xl dark:border-white/10 dark:bg-neutral-900/70" bg-white/70 px-4 py-3 shadow-xl backdrop-blur-xl dark:border-white/10 dark:bg-neutral-900/70"
> >
{layout.dock.length === 0 ? ( {!isReady ? (
<div className="h-9 w-9 animate-pulse rounded-xl bg-black/10 dark:bg-white/10" aria-hidden="true" />
) : layout.dock.length === 0 ? (
<span className="px-2 text-xs text-black/30 dark:text-white/30">Icon hierher ziehen zum Anheften</span> <span className="px-2 text-xs text-black/30 dark:text-white/30">Icon hierher ziehen zum Anheften</span>
) : ( ) : (
layout.dock.map((slot) => renderTile(slot, "dock", "small")) layout.dock.map((slot) => renderTile(slot, "dock", "small"))

View File

@@ -128,9 +128,9 @@ export function HomePage() {
const { data: services, isLoading: servicesLoading, isError: servicesError } = useServices(); const { data: services, isLoading: servicesLoading, isError: servicesError } = useServices();
const { data: bookmarks, isLoading: bookmarksLoading } = useBookmarks(); const { data: bookmarks, isLoading: bookmarksLoading } = useBookmarks();
const { data: devices } = useDevices(); const { data: devices } = useDevices();
const { data: settings } = useSettings(); const { data: settings, isLoading: settingsLoading } = useSettings();
const { data: categories } = useCategories(); const { data: categories, isLoading: categoriesLoading } = useCategories();
const { data: recentVisits } = useRecentVisits(); const { data: recentVisits, isLoading: recentVisitsLoading } = useRecentVisits();
const { data: readLaterItems } = useReadLater(); const { data: readLaterItems } = useReadLater();
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const searchContainerRef = useRef<HTMLDivElement>(null); const searchContainerRef = useRef<HTMLDivElement>(null);
@@ -334,6 +334,17 @@ export function HomePage() {
}, [settings?.mobileHomeStyle]); }, [settings?.mobileHomeStyle]);
const effectiveMobileHomeStyle = settings?.mobileHomeStyle ?? cachedMobileHomeStyle ?? "classic"; const effectiveMobileHomeStyle = settings?.mobileHomeStyle ?? cachedMobileHomeStyle ?? "classic";
const useAndroidHome = isMobile && effectiveMobileHomeStyle === "android"; const useAndroidHome = isMobile && effectiveMobileHomeStyle === "android";
// WICHTIG: Dienste/Lesezeichen/Kategorien/"Zuletzt besucht"/Einstellungen
// laden über getrennte, unabhängig voneinander abgeschlossene
// Netzwerk-Anfragen - kämen sie einzeln nacheinander im Raster an, würde
// z. B. die "Zuletzt besucht"-Kachel nachträglich auftauchen und alle
// Icons danach verschieben (genau das im Bugreport beschriebene
// "Verschieben, weil manche Icons schneller da sind als andere"). Daher
// wird das komplette Raster erst gerendert, wenn ALLE beteiligten
// Abfragen mindestens einmal durch sind - dann in einem Rutsch, ohne
// Nachrücken einzelner Kacheln.
const androidHomeDataReady =
!servicesLoading && !bookmarksLoading && !categoriesLoading && !recentVisitsLoading && !settingsLoading;
const addReadLaterMutation = useMutation({ const addReadLaterMutation = useMutation({
mutationFn: (url: string) => saveReadLaterRequest(url), mutationFn: (url: string) => saveReadLaterRequest(url),
@@ -354,6 +365,7 @@ export function HomePage() {
onToggleFavorite={(item) => toggleFavorite.mutate(item)} onToggleFavorite={(item) => toggleFavorite.mutate(item)}
onAddReadLater={(url) => addReadLaterMutation.mutate(url)} onAddReadLater={(url) => addReadLaterMutation.mutate(url)}
isOnline={isOnline} isOnline={isOnline}
isReady={androidHomeDataReady}
/> />
); );
} }