fix(ui): remove the broken Generate Cover button from the metadata editor
Generate Cover has never worked: it fetched the book file using a URL scraped from the cover preview <img> tag (so it downloaded either the existing cover JPEG or, when no cover existed, the detail page HTML), then handed it to foliate-js, which rejects both. Its fixed-layout path also called view.renderer.renderPage(), a method that does not exist in the pinned foliate fork. Every click ended in the same generic 'Cover generation failed' toast. The working alternative already exists server-side: the scanner's PDF/EPUB cover extraction plus the per-book Rescan button, now that the rasterizer renders the CropBox. Users who want a specific image can upload one. Delete web/src/cover-generator.ts, the modal buttons, and the dead generateCover()/coverGenerating/fileUrl plumbing in book-detail.ts.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { Alpine } from "./alpine";
|
||||
import { showToast } from "./toast";
|
||||
import { generateCoverBlob } from "./cover-generator";
|
||||
import { searchTagSuggestions, type TagSuggestion } from "./tag-dropdown";
|
||||
|
||||
function getMediaId(): string {
|
||||
@@ -91,7 +90,6 @@ export {
|
||||
|
||||
interface MetadataEditorState {
|
||||
openSections: Record<string, boolean>;
|
||||
coverGenerating: boolean;
|
||||
hasExistingCover: boolean;
|
||||
newCoverPreview: string;
|
||||
coverFile: Blob | null;
|
||||
@@ -108,7 +106,6 @@ interface MetadataEditorState {
|
||||
showMetadataEditor(): void;
|
||||
hideMetadataEditor(): void;
|
||||
handleCoverUpload(event: Event): void;
|
||||
generateCover(): Promise<void>;
|
||||
removeCover(): void;
|
||||
saveMetadata(): Promise<void>;
|
||||
rescanBook(): Promise<void>;
|
||||
@@ -130,12 +127,6 @@ Alpine.data("bookDetail", () => {
|
||||
coverImg?.src &&
|
||||
!coverImg.src.includes("placeholder-book.svg");
|
||||
|
||||
const coverPreviewEl = document.querySelector(
|
||||
".aspect-\\[2\\/3\\] img",
|
||||
) as HTMLImageElement | null;
|
||||
const coverSrc = coverPreviewEl?.src || "";
|
||||
const fileUrl = coverSrc && !coverSrc.includes("placeholder") ? coverSrc : "";
|
||||
|
||||
const initialTags: string[] = [];
|
||||
const tagBadges = document.querySelectorAll("#metadata-editor-modal [data-editor-tag]");
|
||||
tagBadges.forEach((el) => {
|
||||
@@ -145,7 +136,6 @@ Alpine.data("bookDetail", () => {
|
||||
|
||||
return {
|
||||
openSections: { basic: true } as Record<string, boolean>,
|
||||
coverGenerating: false,
|
||||
hasExistingCover: !!hasCover,
|
||||
newCoverPreview: "",
|
||||
coverFile: null as Blob | null,
|
||||
@@ -462,33 +452,6 @@ Alpine.data("bookDetail", () => {
|
||||
reader.readAsDataURL(file);
|
||||
},
|
||||
|
||||
async generateCover() {
|
||||
this.coverGenerating = true;
|
||||
try {
|
||||
const formatGroup =
|
||||
document
|
||||
.querySelector('[data-format-group]')
|
||||
?.getAttribute("data-format-group") || "reflowable";
|
||||
|
||||
const blob = await generateCoverBlob(fileUrl, formatGroup);
|
||||
if (!blob) return;
|
||||
|
||||
this.coverFile = blob;
|
||||
this.coverAction = "upload";
|
||||
|
||||
const preview = document.getElementById(
|
||||
"metadata-cover-preview",
|
||||
) as HTMLImageElement;
|
||||
if (preview) {
|
||||
preview.src = URL.createObjectURL(blob);
|
||||
}
|
||||
this.newCoverPreview = URL.createObjectURL(blob);
|
||||
showToast("Cover generated successfully", "success");
|
||||
} finally {
|
||||
this.coverGenerating = false;
|
||||
}
|
||||
},
|
||||
|
||||
removeCover() {
|
||||
this.coverAction = "remove";
|
||||
this.coverFile = null;
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import { showToast } from "./toast";
|
||||
|
||||
function getToken(): string {
|
||||
return localStorage.getItem("token") || "";
|
||||
}
|
||||
|
||||
export async function generateCoverBlob(
|
||||
fileUrl: string,
|
||||
formatGroup: string,
|
||||
): Promise<Blob | null> {
|
||||
try {
|
||||
const View = (await import("foliate-js/view.js")).default;
|
||||
const view = new View();
|
||||
|
||||
const resp = await fetch(fileUrl, {
|
||||
headers: { Authorization: `Bearer ${getToken()}` },
|
||||
});
|
||||
if (!resp.ok) {
|
||||
showToast("Failed to fetch book file for cover generation", "error");
|
||||
return null;
|
||||
}
|
||||
|
||||
const blob = await resp.blob();
|
||||
const file = new File([blob], "book", { type: blob.type });
|
||||
|
||||
const pdfOptions =
|
||||
formatGroup === "fixed_layout"
|
||||
? {
|
||||
pdf: {
|
||||
cMapUrl: "/static/vendor/pdfjs/cmaps/",
|
||||
standardFontDataUrl: "/static/vendor/pdfjs/standard_fonts/",
|
||||
},
|
||||
}
|
||||
: {};
|
||||
|
||||
await view.open(file, pdfOptions);
|
||||
|
||||
if (!view.book?.sections?.length) {
|
||||
showToast("Could not read book sections", "error");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (formatGroup === "fixed_layout") {
|
||||
const canvas = document.createElement("canvas");
|
||||
await view.renderer?.renderPage(view.book.sections[0], canvas);
|
||||
return new Promise((resolve) => {
|
||||
canvas.toBlob(
|
||||
(b) => resolve(b),
|
||||
"image/jpeg",
|
||||
0.85,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
const coverHref = view.book.cover;
|
||||
if (coverHref) {
|
||||
const coverBlob = await coverHref.blob();
|
||||
if (coverBlob.type.startsWith("image/")) {
|
||||
return coverBlob;
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const c = document.createElement("canvas");
|
||||
c.width = img.naturalWidth;
|
||||
c.height = img.naturalHeight;
|
||||
c.getContext("2d")?.drawImage(img, 0, 0);
|
||||
c.toBlob((b) => resolve(b), "image/jpeg", 0.85);
|
||||
};
|
||||
img.onerror = () => resolve(null);
|
||||
img.src = URL.createObjectURL(coverBlob);
|
||||
});
|
||||
}
|
||||
|
||||
showToast("No cover found in book file", "error");
|
||||
return null;
|
||||
} catch (e) {
|
||||
console.error("Cover generation failed:", e);
|
||||
showToast("Cover generation failed", "error");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user