feat: das Konto zieht hinter den Namen, und die Ansicht merkt sich

DAS KONTO WAR EIN EIGENER REITER -- gleichrangig neben Galerie und Verlauf,
obwohl man sein Passwort zweimal im Jahr aendert und die Galerie taeglich
oeffnet. Jetzt haengt es hinter dem eigenen Namen oben rechts, zusammen mit dem
Abmelden. Das ist der Platz, an dem jeder danach sucht.

Der Pfad /konto BLEIBT bestehen: ein Lesezeichen darauf soll nicht ins Leere
fuehren, nur weil der Reiter verschwunden ist. Dafuer gibt es jetzt eine
zweite, kleine Liste "versteckt" neben den Reitern -- Seiten mit Adresse, aber
ohne Knopf.

Das Menue schliesst bei Klick daneben und bei Escape. Ein Menue, das offen
bleibt, bis man genau den Knopf wieder trifft, steht im Weg.

DIE ANSICHT MERKT SICH. Seitengroesse, Sortierung und Richtung in der Galerie,
Seitengroesse im Verlauf: wer 120 pro Seite einstellt, meint das dauerhaft und
nicht bis zum naechsten F5. Der Auffrisch-Schalter tat das schon von Hand und
geht jetzt denselben Weg.

BEWUSST NICHT gemerkt werden Filter -- Ordner, Suche, Medienart. Ein gemerkter
Filter fuehrt zu der Frage "warum sehe ich nichts", und die Antwort steht dann
in einer Leiste, auf die niemand schaut, weil er sie nicht selbst gesetzt hat.
Was die Ansicht formt, wird gemerkt; was die Menge einschraenkt, nicht.

localStorage kann werfen (privates Fenster, voller Speicher). Dann verhaelt
sich der Haken wie ein gewoehnlicher Zustand, statt die Seite mitzureissen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 20:12:49 +02:00
co-authored by Claude Opus 5
parent 66c5de1e31
commit 37bf6a68ed
6 changed files with 161 additions and 32 deletions
+102
View File
@@ -0,0 +1,102 @@
import { useEffect, useRef, useState } from 'react'
import { ChevronDown, LogOut, UserCog } from 'lucide-react'
import { run, useStore } from '../store'
import { gehZu } from '../router'
import { Avatar } from './Avatar'
/** Das Konto hinter dem eigenen Namen.
*
* Vorher war es ein eigener Reiter neben Galerie und Verlauf — und stand damit
* gleichrangig neben Dingen, die man täglich benutzt, obwohl man sein Passwort
* zweimal im Jahr ändert. Hinter dem Namen ist der Platz, an dem jeder danach
* sucht.
*
* Der Pfad /konto bleibt bestehen: ein Lesezeichen darauf soll nicht ins Leere
* führen, nur weil der Reiter verschwunden ist. */
export function Kontomenue() {
const user = useStore((s) => s.user)
const logout = useStore((s) => s.logout)
const [offen, setOffen] = useState(false)
const huelle = useRef<HTMLDivElement>(null)
// Klick daneben und Escape schließen. Ein Menü, das offen bleibt, bis man
// genau den Knopf wieder trifft, ist ein Menü, das im Weg steht.
useEffect(() => {
if (!offen) return
const daneben = (e: MouseEvent) => {
if (!huelle.current?.contains(e.target as Node)) setOffen(false)
}
const taste = (e: KeyboardEvent) => {
if (e.key === 'Escape') setOffen(false)
}
document.addEventListener('mousedown', daneben)
window.addEventListener('keydown', taste)
return () => {
document.removeEventListener('mousedown', daneben)
window.removeEventListener('keydown', taste)
}
}, [offen])
if (!user) return null
const eintrag =
'flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-schrift-leise ' +
'transition-colors hover:bg-flaeche-hoch hover:text-schrift'
return (
<div ref={huelle} className="relative">
<button
onClick={() => setOffen((o) => !o)}
aria-expanded={offen}
className="flex items-center gap-2 rounded-md px-1.5 py-1 text-sm text-schrift-leise
transition-colors hover:bg-flaeche-hoch hover:text-schrift"
>
<Avatar name={user.username} url={user.avatar} groesse={26} />
<span className="hidden max-w-32 truncate sm:inline">{user.username}</span>
<ChevronDown
size={14}
className={`transition-transform ${offen ? 'rotate-180' : ''}`}
/>
</button>
{offen && (
<div
className="absolute right-0 z-50 mt-1 w-56 overflow-hidden rounded-lg border
border-rand bg-flaeche shadow-xl shadow-black/40"
>
<div className="flex items-center gap-2 border-b border-rand px-3 py-2.5">
<Avatar name={user.username} url={user.avatar} groesse={32} />
<div className="min-w-0">
<p className="truncate text-sm font-medium">{user.username}</p>
<p className="text-[11px] text-schrift-leise">
{user.discord ? 'über Discord' : 'mit Passwort'}
</p>
</div>
</div>
<button
className={eintrag}
onClick={() => {
setOffen(false)
gehZu('/konto')
}}
>
<UserCog size={15} />
Konto und Passwort
</button>
<button
className={`${eintrag} border-t border-rand hover:text-schlecht`}
onClick={() => {
setOffen(false)
void run(logout, 'Abgemeldet.')
}}
>
<LogOut size={15} />
Abmelden
</button>
</div>
)}
</div>
)
}