Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
74 lines
2.4 KiB
TypeScript
74 lines
2.4 KiB
TypeScript
'use client'
|
|
|
|
import dynamic from 'next/dynamic'
|
|
import { useEffect, useRef, useState, type ComponentProps } from 'react'
|
|
import { chatText } from '@/lib/i18n/chat'
|
|
import { SkeletonLine } from '@/components/Skeleton'
|
|
import type { Chat } from './Chat'
|
|
|
|
const ChatInner = dynamic(() => import('./Chat').then((modul) => modul.Chat), {
|
|
loading: () => <Platzhalter />,
|
|
// Der Chat wird ohnehin erst nach dem Scrollen gebraucht und lebt von einer
|
|
// offenen Verbindung. Ihn auf dem Server mitzurendern brächte nichts und
|
|
// zöge sein Bündel in die erste Ladung der Fallakte.
|
|
ssr: false,
|
|
})
|
|
|
|
/**
|
|
* Lädt den Chat erst, wenn er in Sichtweite kommt.
|
|
*
|
|
* Zwei Gründe, beide handfest: die Fallakte ist das, weswegen die Seite
|
|
* geöffnet wird - ihr Bündelbudget soll nicht für einen Chat draufgehen, den
|
|
* viele nie aufklappen. Und der Chat hält eine offene Verbindung zum
|
|
* Ereignisstrom; wer nur die Akte liest, soll sie nicht aufbauen.
|
|
*
|
|
* Der Platzhalter hat die Höhe des Chats, damit beim Nachladen nichts springt.
|
|
* Siehe docs/09-performance-und-ux.md.
|
|
*/
|
|
export function ChatLazy(props: ComponentProps<typeof Chat>) {
|
|
const [sichtbar, setSichtbar] = useState(false)
|
|
const anker = useRef<HTMLDivElement>(null)
|
|
|
|
useEffect(() => {
|
|
const element = anker.current
|
|
if (!element) return
|
|
|
|
// Ohne IntersectionObserver (sehr alte Browser) sofort laden: lieber
|
|
// grösser als kaputt.
|
|
if (typeof IntersectionObserver === 'undefined') {
|
|
setSichtbar(true)
|
|
return
|
|
}
|
|
|
|
const beobachter = new IntersectionObserver(
|
|
(eintraege) => {
|
|
if (eintraege.some((eintrag) => eintrag.isIntersecting)) {
|
|
setSichtbar(true)
|
|
beobachter.disconnect()
|
|
}
|
|
},
|
|
// Etwas vorher anfangen, damit er beim Ankommen schon steht.
|
|
{ rootMargin: '300px' },
|
|
)
|
|
beobachter.observe(element)
|
|
return () => beobachter.disconnect()
|
|
}, [])
|
|
|
|
return <div ref={anker}>{sichtbar ? <ChatInner {...props} /> : <Platzhalter />}</div>
|
|
}
|
|
|
|
function Platzhalter() {
|
|
return (
|
|
<div className="border border-line">
|
|
<div className="border-b border-line px-4 py-2">
|
|
<p className="font-condensed text-xs text-text-dim">{chatText.heading}</p>
|
|
</div>
|
|
<div className="space-y-3 px-4 py-4">
|
|
<SkeletonLine width="12rem" />
|
|
<SkeletonLine width="70%" />
|
|
<SkeletonLine width="55%" />
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|