feat(logos): Dialog „Logo hochladen“ und gemerkter Anzeigename
Task 7: der Dialog zum Hochladen eines Community-Logos (Spec § 4) und der zuletzt benutzte Anzeigename, den er mit dem Veröffentlichen-Dialog teilt. - logos/logo-dialog.ts (neu): logoUploadDialog öffnet ein eigenes <dialog> nach dem Muster von publish-dialog.ts (showModal, Fokus zurück an den Auslöser, Texte nur als Text; beim Hochladen sind Abbrechen, Esc und Hintergrund wirkungslos, nur das Signal schließt immer; ein zweites Absenden wird ignoriert). Die gewählte Datei geht erst durch validateLogoFile, dann durch prepare (Standard prepareLogo); währenddessen ist „Hochladen“ gesperrt, eine neue Wahl macht die laufende Verarbeitung hinfällig. Das fertige PNG erscheint als Object-URL zweimal, auf dunklem und hellem Grund; „Einfarbig“ (nur wählbar mit transparenten Bereichen, bei jeder neuen Datei zurückgesetzt) zeigt stattdessen eine Maske auf dieselbe URL, gefüllt mit TONE_COLOR (Weiß bzw. #111111). Die URL wird beim Schließen und beim Ersetzen freigegeben. Der Name wird aus dem Dateinamen ohne Endung vorbelegt (höchstens 40 Zeichen, nie mitten in einem Emoji), ein selbst getippter Name bleibt. Hochladen über uploadPreparedLogo mit withLogin (abgelaufene Sitzung → Anmeldung); Fehler erscheinen als „Hochladen fehlgeschlagen – <Grund>“ (ApiError über apiErrorText, Texte von prepareLogo im Wortlaut, sonst errorMessage), eine abgebrochene Anmeldung als „Bitte melde dich an“. validateLogoForm prüft Datei, Name (leer, Steuerzeichen, 40), Anzeigename (Steuerzeichen, 30) und Pflicht-Häkchen mit den Texten aus Spec § 4 und § 11. - logos/logos.css (neu): Breite des Dialogs, Datei-Feld, die zwei Vorschauflächen und die Maske; Hülle, Felder und Knöpfe kommen aus community.css und account.css. - account/display-name.ts (neu): DISPLAY_NAME_KEY (aircard.displayName), rememberedDisplayName und rememberDisplayName; jeder Zugriff in try/catch, ein leerer Name entfernt den Schlüssel. - community/publish-dialog.ts: der Anzeigename ist aus dem gemerkten Namen vorbelegt und wird nach erfolgreichem Veröffentlichen gemerkt; CONTROL_CHARACTERS und DISPLAY_NAME_MAX werden exportiert (der Logo-Dialog prüft mit denselben Regeln). - i18n/en.ts: englische Texte zu allen neuen Beschriftungen, Hinweisen und Meldungen („Einfarbig“ heißt „Single color“). - Tests: logo-dialog.test.ts (validateLogoForm, Aufbau, Dateiwahl mit Vorschau, Namensvorschlag, Maske, Object-URLs, veraltete Verarbeitung, Prüfung, Senden mit Doppelklick und Sperren, Fehlertexte in beiden Sprachen, Standardwerte, Schließen), display-name.test.ts, publish-dialog.test.ts (Vorbelegung und Speichern). Vitest 49 Dateien, 1566 Tests grün, tsc und Build sauber. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
56d841b8e2
commit
e22919f4dc
8 files changed
+1665
-2
No files matched your search
@@ -0,0 +1,118 @@
|
||||
/* Community-Logos. Dialog „Logo hochladen“: Hülle, Felder, Pflicht-Häkchen, Meldungen und Knöpfe kommen aus community.css
|
||||
(Melden-/Veröffentlichen-Dialog) und account.css; hier steht nur, was neu ist: das Datei-Feld und die Vorschau. */
|
||||
|
||||
/* Etwas breiter als der Veröffentlichen-Dialog (480 px): zwei Vorschauen nebeneinander. Mit zwei Klassen, damit die Breite
|
||||
nicht von der Reihenfolge der Stylesheets abhängt. */
|
||||
.report-dialog.logo-dialog {
|
||||
width: min(520px, calc(100vw - 32px));
|
||||
}
|
||||
|
||||
/* Datei-Feld: Rahmen und Fokus wie ein Textfeld (.auth-input), der Knopf darin wie die übrigen Knöpfe */
|
||||
|
||||
.auth-input.logo-file {
|
||||
padding: 6px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo-file::file-selector-button {
|
||||
min-height: 32px;
|
||||
margin: 0 12px 0 0;
|
||||
padding: 0 14px;
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
font: inherit;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo-file:hover:not(:disabled)::file-selector-button {
|
||||
border-color: var(--sel-line);
|
||||
background: var(--panel-2);
|
||||
}
|
||||
|
||||
.logo-file:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Vorschau: dasselbe Logo einmal auf dunklem und einmal auf hellem Grund (nebeneinander, solange es ein Logo gibt) */
|
||||
|
||||
.logo-previews {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.logo-previews:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logo-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
margin: 0; /* <figure> hat sonst einen Außenabstand */
|
||||
}
|
||||
|
||||
.logo-preview-surface {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 96px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* Die Karte hängt nicht vom Theme ab: dunkler Kartengrund wie im Renderer (#1B2738), heller Grund Weiß. */
|
||||
.logo-preview-dark .logo-preview-surface {
|
||||
background: #1b2738;
|
||||
}
|
||||
|
||||
.logo-preview-light .logo-preview-surface {
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.logo-preview-img,
|
||||
.logo-preview-mask {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.logo-preview-img {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Einfarbig: nur die Deckkraft des PNGs zählt (Maske auf dessen Object-URL, `--logo-url` setzt der Dialog), die Farbe kommt
|
||||
als Hintergrund (Weiß auf dunklem, #111111 auf hellem Grund, ebenfalls vom Dialog gesetzt). */
|
||||
.logo-preview-mask {
|
||||
-webkit-mask-image: var(--logo-url);
|
||||
mask-image: var(--logo-url);
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
mask-position: center;
|
||||
-webkit-mask-size: contain;
|
||||
mask-size: contain;
|
||||
}
|
||||
|
||||
.logo-preview-caption {
|
||||
color: var(--muted);
|
||||
font-size: 0.85rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* „Einfarbig“: das Häkchen im Stil des Pflicht-Häkchens; gesperrt (noch keine Datei oder keine transparenten Bereiche) blasser */
|
||||
|
||||
.logo-mono .auth-hint {
|
||||
margin: 6px 0 0 4px;
|
||||
}
|
||||
|
||||
.logo-mono .publish-consent:has(input:disabled) {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
Reference in new issue
Block a user