From 2f4d8a7172b0addffb3a7c1c9a59e59809e959ac Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 25 Mar 2026 18:00:44 +0100 Subject: [PATCH] deleted forgotten implement files and readme --- implement/README.md | 103 ------------------- implement/image-node.tsx | 187 ---------------------------------- implement/nodes-list-patch.ts | 37 ------- implement/storage.ts | 14 --- 4 files changed, 341 deletions(-) delete mode 100644 implement/README.md delete mode 100644 implement/image-node.tsx delete mode 100644 implement/nodes-list-patch.ts delete mode 100644 implement/storage.ts diff --git a/implement/README.md b/implement/README.md deleted file mode 100644 index c56b635..0000000 --- a/implement/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# Bild-Upload via Convex Storage — Einbau-Anleitung - -## Konzept - -Der Upload-Flow nutzt Convex File Storage in 3 Schritten: -1. **generateUploadUrl** → kurzlebige Upload-URL vom Backend -2. **fetch(POST)** → Datei direkt an Convex Storage senden -3. **updateData** → `storageId` im Node speichern - -Die **URL wird serverseitig** in der `nodes.list` Query aufgelöst — nicht -am Client. Das heißt: der Node speichert nur die `storageId`, und bei -jedem Query-Aufruf wird `ctx.storage.getUrl(storageId)` aufgerufen und -als `data.url` zurückgegeben. - -## Dateien - -``` -upload-files/ - convex/ - storage.ts → convex/storage.ts (NEU) - nodes-list-patch.ts → PATCH für convex/nodes.ts (NUR die list Query ersetzen) - components/canvas/nodes/ - image-node.tsx → ERSETZT alte Version - -Gesamt: 3 Dateien (1 neu, 1 Patch, 1 Ersatz) -``` - -## Einbau-Schritte - -### 1. `convex/storage.ts` anlegen -Kopiere die Datei direkt. Sie enthält eine einzige Mutation: `generateUploadUrl`. - -### 2. `convex/nodes.ts` — `list` Query patchen -Ersetze **nur die `list` Query** in deiner bestehenden `convex/nodes.ts` -mit der Version aus `nodes-list-patch.ts`. Der Rest der Datei -(create, move, resize, etc.) bleibt unverändert. - -Die Änderung: Nach dem `collect()` wird über alle Nodes iteriert. -Wenn ein Node `data.storageId` hat, wird `ctx.storage.getUrl()` aufgerufen -und das Ergebnis als `data.url` eingefügt. - -**Wichtig:** Du brauchst den `Id` Import oben in der Datei: -```ts -import type { Doc, Id } from "./_generated/dataModel"; -``` -(Du hast `Doc` wahrscheinlich schon importiert — füge `Id` hinzu falls nötig.) - -### 3. `image-node.tsx` ersetzen -Die neue Version hat: -- **Click-to-Upload**: Klick auf den leeren Node öffnet File-Picker -- **Drag & Drop**: Bilder direkt auf den Node ziehen (Files vom OS) -- **Ersetzen-Button**: Wenn bereits ein Bild vorhanden, oben rechts "Ersetzen" -- **Upload-Spinner**: Während des Uploads dreht sich ein Spinner -- **Dateiname**: Wird unter dem Bild angezeigt - -## Upload-Flow im Detail - -``` -User zieht Bild auf Image-Node - │ - ├─ handleDrop() → uploadFile(file) - │ - ├─ 1. generateUploadUrl() → Convex Mutation - │ ← postUrl (kurzlebig) - │ - ├─ 2. fetch(postUrl, { body: file }) - │ ← { storageId: "kg..." } - │ - ├─ 3. updateData({ nodeId, data: { storageId, filename, mimeType } }) - │ → Convex speichert storageId im Node - │ - └─ 4. nodes.list Query feuert automatisch neu (Realtime) - → ctx.storage.getUrl(storageId) → data.url - → Image-Node rendert das Bild -``` - -## Testing - -### Test 1: Click-to-Upload -- Erstelle einen Image-Node (Sidebar oder Toolbar) -- Klicke auf "Klicken oder hierhin ziehen" -- ✅ File-Picker öffnet sich -- Wähle ein Bild (PNG/JPG/WebP) -- ✅ Spinner erscheint kurz, dann wird das Bild angezeigt -- ✅ Convex Dashboard: `data.storageId` ist gesetzt - -### Test 2: Drag & Drop (File vom OS) -- Ziehe ein Bild aus dem Finder/Explorer direkt auf den Image-Node -- ✅ Drop-Zone wird blau hervorgehoben -- ✅ Bild wird hochgeladen und angezeigt - -### Test 3: Bild ersetzen -- Klicke "Ersetzen" oben rechts am Image-Node -- Wähle ein neues Bild -- ✅ Altes Bild wird ersetzt, neue storageId in Convex - -### Test 4: URL wird serverseitig aufgelöst -- Lade die Seite neu -- ✅ Bild wird weiterhin angezeigt (URL wird bei jedem Query neu aufgelöst) - -### Test 5: Nicht-Bild-Dateien werden ignoriert -- Versuche eine .txt oder .pdf auf den Node zu ziehen -- ✅ Nichts passiert (nur image/* wird akzeptiert) diff --git a/implement/image-node.tsx b/implement/image-node.tsx deleted file mode 100644 index 4d8e43b..0000000 --- a/implement/image-node.tsx +++ /dev/null @@ -1,187 +0,0 @@ -"use client"; - -import { useState, useCallback, useRef } from "react"; -import { Handle, Position, type NodeProps, type Node } from "@xyflow/react"; -import { useMutation } from "convex/react"; -import { api } from "@/convex/_generated/api"; -import type { Id } from "@/convex/_generated/dataModel"; -import BaseNodeWrapper from "./base-node-wrapper"; - -type ImageNodeData = { - storageId?: string; - url?: string; - filename?: string; - mimeType?: string; - _status?: string; - _statusMessage?: string; -}; - -export type ImageNode = Node; - -export default function ImageNode({ id, data, selected }: NodeProps) { - const generateUploadUrl = useMutation(api.storage.generateUploadUrl); - const updateData = useMutation(api.nodes.updateData); - const fileInputRef = useRef(null); - const [isUploading, setIsUploading] = useState(false); - const [isDragOver, setIsDragOver] = useState(false); - - const uploadFile = useCallback( - async (file: File) => { - if (!file.type.startsWith("image/")) return; - setIsUploading(true); - - try { - // 1. Upload-URL generieren - const uploadUrl = await generateUploadUrl(); - - // 2. Datei hochladen - const result = await fetch(uploadUrl, { - method: "POST", - headers: { "Content-Type": file.type }, - body: file, - }); - const { storageId } = await result.json(); - - // 3. Node-Data mit storageId aktualisieren - // Die URL wird serverseitig in der nodes.list Query aufgelöst - await updateData({ - nodeId: id as Id<"nodes">, - data: { - storageId, - filename: file.name, - mimeType: file.type, - }, - }); - } catch (err) { - console.error("Upload failed:", err); - } finally { - setIsUploading(false); - } - }, - [id, generateUploadUrl, updateData], - ); - - // Click-to-Upload - const handleClick = useCallback(() => { - if (!data.url && !isUploading) { - fileInputRef.current?.click(); - } - }, [data.url, isUploading]); - - const handleFileChange = useCallback( - (e: React.ChangeEvent) => { - const file = e.target.files?.[0]; - if (file) uploadFile(file); - }, - [uploadFile], - ); - - // Drag & Drop auf den Node - const handleDragOver = useCallback((e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - if (e.dataTransfer.types.includes("Files")) { - setIsDragOver(true); - e.dataTransfer.dropEffect = "copy"; - } - }, []); - - const handleDragLeave = useCallback((e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setIsDragOver(false); - }, []); - - const handleDrop = useCallback( - (e: React.DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - setIsDragOver(false); - - const file = e.dataTransfer.files?.[0]; - if (file && file.type.startsWith("image/")) { - uploadFile(file); - } - }, - [uploadFile], - ); - - // Bild ersetzen - const handleReplace = useCallback(() => { - fileInputRef.current?.click(); - }, []); - - return ( - -
-
-
- 🖼️ Bild -
- {data.url && ( - - )} -
- - {isUploading ? ( -
-
-
- - Wird hochgeladen… - -
-
- ) : data.url ? ( - {data.filename - ) : ( -
- 📁 - Klicken oder hierhin ziehen - PNG, JPG, WebP -
- )} - - {data.filename && data.url && ( -

- {data.filename} -

- )} -
- - - - - - ); -} diff --git a/implement/nodes-list-patch.ts b/implement/nodes-list-patch.ts deleted file mode 100644 index 36290f9..0000000 --- a/implement/nodes-list-patch.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * PATCH für convex/nodes.ts - * - * Ersetze die bestehende `list` Query mit dieser Version. - * Der einzige Unterschied: Für Nodes mit einem `storageId` im data-Objekt - * wird die Storage-URL aufgelöst und als `data.url` zurückgegeben. - */ - -export const list = query({ - args: { canvasId: v.id("canvases") }, - handler: async (ctx, { canvasId }) => { - const user = await requireAuth(ctx); - await getCanvasOrThrow(ctx, canvasId, user.userId); - - const nodes = await ctx.db - .query("nodes") - .withIndex("by_canvas", (q) => q.eq("canvasId", canvasId)) - .collect(); - - // Storage-URLs für Nodes mit storageId auflösen - return Promise.all( - nodes.map(async (node) => { - const data = node.data as Record | undefined; - if (data?.storageId) { - const url = await ctx.storage.getUrl( - data.storageId as Id<"_storage"> - ); - return { - ...node, - data: { ...data, url: url ?? undefined }, - }; - } - return node; - }) - ); - }, -}); diff --git a/implement/storage.ts b/implement/storage.ts deleted file mode 100644 index 9264e3f..0000000 --- a/implement/storage.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { mutation } from "./_generated/server"; -import { requireAuth } from "./helpers"; - -/** - * Generiert eine kurzlebige Upload-URL für Convex File Storage. - * Der Client POSTet die Datei direkt an diese URL. - */ -export const generateUploadUrl = mutation({ - args: {}, - handler: async (ctx) => { - await requireAuth(ctx); - return await ctx.storage.generateUploadUrl(); - }, -});