PdfDragButton: PDF per Drag-and-Drop aus Bankkarte/Ausweis ziehen
Neue wiederverwendbare Komponente PdfDragButton: ziehbares Element, mit dem der hinterlegte Scan direkt aus dem Browser in ein Mail-Fenster (Anhang) oder den Datei-Explorer gezogen werden kann (Chromium- DownloadURL, Format <mime>:<name>:<absolute-url>). Bewusste Plattform-Grenze: PDF per Strg+V als Datei einfuegen geht im Browser nicht (Web-Clipboard darf keine OS-Datei-Zwischenablage befuellen) -> Drag-and-Drop. DownloadURL nur Chrome/Edge, nicht Firefox (dort Klick-Fallback: Datei im Tab oeffnen). Eingebaut an: ContractForm (neben IBAN/Ausweisnummer-Copy), Vertrags- ansicht (Bankkarte/Ausweis-Card), Kundenakte-Tabs Bankkarten+Ausweise. Nur bei vorhandenem documentPath. Nutzt bestehende fileUrl()-Download- URL (Token als Query, Per-File-Ownership-Check unveraendert) - kein neuer Endpoint, keine neue Angriffsflaeche. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { GripVertical, FileDown } from 'lucide-react';
|
||||
import { fileUrl } from '../../utils/fileUrl';
|
||||
|
||||
/**
|
||||
* Ziehbares Element, mit dem eine im Backend hinterlegte Datei (i.d.R. ein
|
||||
* PDF-Scan) direkt aus dem Browser heraus in eine fremde Anwendung – z.B.
|
||||
* das Anhang-Feld eines Mail-Fensters oder den Datei-Explorer – gezogen
|
||||
* werden kann.
|
||||
*
|
||||
* Technik: Beim `dragstart` setzen wir den Chromium-spezifischen
|
||||
* `DownloadURL`-DataTransfer-Eintrag (`<mime>:<dateiname>:<absolute-url>`).
|
||||
* Chrome/Edge laden die Datei beim Ablegen von dieser URL nach und legen
|
||||
* sie am Zielort als echte Datei ab.
|
||||
*
|
||||
* WICHTIG – Grenzen der Browser-Plattform:
|
||||
* - „PDF per Strg+V als DATEI einfügen" ist im Browser NICHT möglich
|
||||
* (die Web-Zwischenablage darf die OS-Datei-Zwischenablage / CF_HDROP
|
||||
* nicht befüllen). Deshalb Drag-and-Drop statt Clipboard.
|
||||
* - `DownloadURL` funktioniert nur in Chromium-Browsern (Chrome/Edge),
|
||||
* nicht in Firefox. Dort passiert beim Ziehen nichts – der Klick-
|
||||
* Fallback (Datei im neuen Tab öffnen) bleibt aber nutzbar.
|
||||
*/
|
||||
interface PdfDragButtonProps {
|
||||
/** Server-Pfad der Datei (`documentPath`). Ohne Pfad wird nichts gerendert. */
|
||||
path: string | null | undefined;
|
||||
/** Dateiname beim Ablegen. Ohne Endung wird sie aus dem Pfad ergänzt. */
|
||||
filename?: string;
|
||||
label?: string;
|
||||
title?: string;
|
||||
className?: string;
|
||||
size?: 'sm' | 'md';
|
||||
}
|
||||
|
||||
const MIME_BY_EXT: Record<string, string> = {
|
||||
pdf: 'application/pdf',
|
||||
png: 'image/png',
|
||||
jpg: 'image/jpeg',
|
||||
jpeg: 'image/jpeg',
|
||||
gif: 'image/gif',
|
||||
webp: 'image/webp',
|
||||
};
|
||||
|
||||
function extOf(path: string): string {
|
||||
const clean = path.split('?')[0].split('#')[0];
|
||||
const dot = clean.lastIndexOf('.');
|
||||
return dot === -1 ? '' : clean.slice(dot + 1).toLowerCase();
|
||||
}
|
||||
|
||||
export default function PdfDragButton({
|
||||
path,
|
||||
filename,
|
||||
label = 'PDF ziehen',
|
||||
title,
|
||||
className = '',
|
||||
size = 'sm',
|
||||
}: PdfDragButtonProps) {
|
||||
if (!path) return null;
|
||||
|
||||
const ext = extOf(path);
|
||||
const mime = MIME_BY_EXT[ext] || 'application/octet-stream';
|
||||
const base = (filename && filename.trim()) || 'dokument';
|
||||
// Ungültige Dateinamen-Zeichen entschärfen, Endung sicherstellen.
|
||||
const safeBase = base.replace(/[\\/:*?"<>|]+/g, '_');
|
||||
const name = safeBase.includes('.') ? safeBase : `${safeBase}${ext ? `.${ext}` : ''}`;
|
||||
|
||||
// Absolute URL, damit der Drop in fremde Apps die Datei laden kann.
|
||||
// fileUrl() hängt das Auth-Token als Query-Parameter an (Download).
|
||||
const absolute = `${window.location.origin}${fileUrl(path)}`;
|
||||
|
||||
const handleDragStart = (e: React.DragEvent) => {
|
||||
try {
|
||||
e.dataTransfer.setData('DownloadURL', `${mime}:${name}:${absolute}`);
|
||||
} catch {
|
||||
/* manche Browser werfen bei DownloadURL – dann nur die URI-Fallbacks */
|
||||
}
|
||||
e.dataTransfer.setData('text/uri-list', absolute);
|
||||
e.dataTransfer.setData('text/plain', absolute);
|
||||
e.dataTransfer.effectAllowed = 'copy';
|
||||
};
|
||||
|
||||
const iconSize = size === 'sm' ? 'w-3.5 h-3.5' : 'w-4 h-4';
|
||||
|
||||
return (
|
||||
<span
|
||||
draggable
|
||||
onDragStart={handleDragStart}
|
||||
onClick={() => window.open(fileUrl(path, { inline: true }), '_blank', 'noopener')}
|
||||
title={title || 'In Outlook/Explorer ziehen (Chrome/Edge). Klick: im Tab öffnen.'}
|
||||
className={`inline-flex items-center gap-1 cursor-grab active:cursor-grabbing select-none rounded border border-gray-200 bg-gray-50 px-1.5 py-0.5 text-xs text-gray-600 hover:text-blue-600 hover:border-blue-300 hover:bg-blue-50 transition-colors ${className}`}
|
||||
>
|
||||
<GripVertical className={`${iconSize} text-gray-400`} />
|
||||
<FileDown className={iconSize} />
|
||||
<span>{label}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user