- {state === "loading" && (
+ {showOverlay && (
+
+ {showSpinner ? (
- )}
- {state === "empty" && (
-
- Compile to see the preview.
-
- )}
- {state === "error" && (
-
- No preview yet. Check the compile log for errors.
-
+ ) : (
+
{placeholder}
)}
)}
diff --git a/src/components/editor/Toolbar.tsx b/src/components/editor/Toolbar.tsx
index 9177138..8c9e0cf 100644
--- a/src/components/editor/Toolbar.tsx
+++ b/src/components/editor/Toolbar.tsx
@@ -38,6 +38,8 @@ function statusLabel(
};
case "error":
return { text: "Compile errors", className: "text-danger" };
+ case "empty":
+ return { text: "Empty document", className: "text-text-muted" };
default:
return { text: "", className: "" };
}
diff --git a/src/components/editor/useCompile.ts b/src/components/editor/useCompile.ts
index f8d249c..607f2c3 100644
--- a/src/components/editor/useCompile.ts
+++ b/src/components/editor/useCompile.ts
@@ -2,7 +2,12 @@
import { useCallback, useRef, useState } from "react";
-export type CompileStatus = "idle" | "running" | "success" | "error";
+export type CompileStatus =
+ | "idle"
+ | "running"
+ | "success"
+ | "error"
+ | "empty";
// Drives compilation for one project. Reads the Server-Sent Events stream from
// the compile route, accumulating the log and learning when the PDF is ready.
@@ -22,6 +27,7 @@ export function useCompile(projectId: string) {
setLog("");
let success = false;
+ let empty = false;
let duration: number | null = null;
try {
@@ -57,6 +63,7 @@ export function useCompile(projectId: string) {
else if (event.type === "error") setLog((prev) => prev + event.message);
else if (event.type === "done") {
success = event.success;
+ empty = event.empty;
duration = event.durationMs;
}
}
@@ -70,6 +77,8 @@ export function useCompile(projectId: string) {
if (success) {
setStatus("success");
setPdfVersion((v) => v + 1);
+ } else if (empty) {
+ setStatus("empty");
} else {
setStatus("error");
}
diff --git a/src/lib/compiler.ts b/src/lib/compiler.ts
index 3a61642..7661f69 100644
--- a/src/lib/compiler.ts
+++ b/src/lib/compiler.ts
@@ -6,6 +6,9 @@ import { mainSourcePath, outputPdfPath, ensureProjectDirs } from "./storage";
export interface CompileResult {
success: boolean;
+ // the document compiled cleanly but had nothing to typeset (an empty body).
+ // not a failure, just nothing to preview yet.
+ empty: boolean;
log: string;
durationMs: number;
passes: number;
@@ -66,14 +69,14 @@ export async function runCompile(
} catch {
const message = `Unknown engine: ${engineId}\n`;
onLog?.(message);
- return { success: false, log: message, durationMs: 0, passes: 0 };
+ return { success: false, empty: false, log: message, durationMs: 0, passes: 0 };
}
const mainPath = mainSourcePath(projectId, engine);
if (!fs.existsSync(mainPath)) {
const message = `No ${engine.mainFileName} found for this project.\n`;
onLog?.(message);
- return { success: false, log: message, durationMs: 0, passes: 0 };
+ return { success: false, empty: false, log: message, durationMs: 0, passes: 0 };
}
ensureProjectDirs(projectId);
@@ -99,8 +102,11 @@ export async function runCompile(
}
const pdfExists = fs.existsSync(outputPdfPath(projectId, engine));
+ // an empty body makes xelatex finish cleanly with no PDF and this notice
+ const empty = !pdfExists && /no pages of output/i.test(log);
return {
success: lastCode === 0 && pdfExists,
+ empty,
log,
durationMs: Date.now() - start,
passes,
@@ -132,6 +138,7 @@ export function compileStream(
send({
type: "done",
success: result.success,
+ empty: result.empty,
durationMs: result.durationMs,
passes: result.passes,
});