From 785233f763be05205f52260670d60af9a5729f7c Mon Sep 17 00:00:00 2001 From: mrleo1nid <43583385+mrleo1nid@users.noreply.github.com> Date: Mon, 26 Oct 2026 16:10:26 +0300 Subject: [PATCH] fix: show unsaved F95 covers and clarify import results --- backend/app/routers/metadata.py | 6 +++ backend/tests/test_f95.py | 4 ++ frontend/src/pages/GamePage.tsx | 10 ++--- frontend/src/pages/NewGame.tsx | 13 +++--- frontend/src/styles.css | 14 ++++++ frontend/src/ui/F95Fetch.tsx | 62 ++++++++++++++++++++++----- frontend/tests/f95-browser.test.mjs | 66 +++++++++++++++++++++++++++++ 7 files changed, 152 insertions(+), 23 deletions(-) diff --git a/backend/app/routers/metadata.py b/backend/app/routers/metadata.py index 10de0f1..7a8febd 100644 --- a/backend/app/routers/metadata.py +++ b/backend/app/routers/metadata.py @@ -1,3 +1,4 @@ +import base64 from urllib.parse import quote from fastapi import APIRouter, HTTPException, status @@ -6,6 +7,7 @@ from pydantic import BaseModel, SecretStr from starlette.concurrency import run_in_threadpool from app import f95 +from app.blobs import read_bytes from app.deps import DB, CurrentUser, has_perm from app.f95_session import open_session, seal from app.routers.games import make_cover @@ -31,6 +33,7 @@ class MetaOut(BaseModel): description: str = "" tags: list[str] = [] cover_hash: str | None = None + cover_preview: str | None = None cover_warning: str | None = None auth_required: bool = False metadata_available: bool = True @@ -47,6 +50,9 @@ def load_metadata(url: str, cookie: str = "", user_agent: str = "") -> dict: if image := metadata.pop("image_url", None): try: metadata["cover_hash"] = make_cover(f95.fetch_image(image, metadata["url"], cookie, user_agent)) + # Unsaved covers cannot be read through the protected /blobs route yet. + metadata["cover_preview"] = "data:image/webp;base64," + base64.b64encode( + read_bytes(metadata["cover_hash"])).decode("ascii") except f95.F95AuthError as e: metadata["auth_required"] = True metadata["cover_warning"] = f"{e}. Остальные данные загружены." diff --git a/backend/tests/test_f95.py b/backend/tests/test_f95.py index fbe688d..635cf3e 100644 --- a/backend/tests/test_f95.py +++ b/backend/tests/test_f95.py @@ -1,4 +1,5 @@ import asyncio +import base64 import io import urllib.error from email.message import Message @@ -123,6 +124,9 @@ def test_import_stores_local_webp_and_keeps_metadata_on_image_failure(tmp_path, assert cover.format == "WEBP" assert cover.size == (640, 427) assert "image_url" not in result + prefix, encoded = result["cover_preview"].split(",", 1) + assert prefix == "data:image/webp;base64" + assert base64.b64decode(encoded) == blob_path(result["cover_hash"]).read_bytes() assert metadata.MetaOut(**result).cover_hash == result["cover_hash"] monkeypatch.setattr(f95, "fetch_image", lambda *_: b"not an image") failed = metadata.load_metadata(THREAD) diff --git a/frontend/src/pages/GamePage.tsx b/frontend/src/pages/GamePage.tsx index cc5f29c..6ad854d 100644 --- a/frontend/src/pages/GamePage.tsx +++ b/frontend/src/pages/GamePage.tsx @@ -4,7 +4,7 @@ import { api, formatSize, uploadRaw, type GameDetail, type Version } from "../ap import { useAuth } from "../auth"; import { getRecent, pushRecent } from "../recent"; import { Cover, ErrorNote, Spinner } from "../ui/Bits"; -import { F95Fetch } from "../ui/F95Fetch"; +import { F95CoverPreview, F95Fetch } from "../ui/F95Fetch"; import { Icon } from "../ui/Icon"; import { LanguageMenu } from "../ui/LanguageMenu"; import { aggregateLanguages, versionLanguages } from "./languages"; @@ -364,6 +364,7 @@ function Report({ report }: { report: Record | null }) { function GameEditor({ game, onDone }: { game: GameDetail; onDone: (changed: boolean) => void }) { const [coverHash, setCoverHash] = useState(null); + const [coverPreview, setCoverPreview] = useState(null); const [title, setTitle] = useState(game.title); const [description, setDescription] = useState(game.description); const [tags, setTags] = useState(game.tags.join(", ")); @@ -386,12 +387,9 @@ function GameEditor({ game, onDone }: { game: GameDetail; onDone: (changed: bool setDescription(m.description); setTags(m.tags.join(", ")); setCoverHash(m.cover_hash); + setCoverPreview(m.cover_preview); }} /> - {coverHash &&
- Новая обложка из F95zone - Превью новой обложки - -
} + {coverHash && { setCoverHash(null); setCoverPreview(null); }} />}