Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions electron/main.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ const binaryName = platform === "win32" ? "pdflatex.exe" : "pdflatex";

const isDev = !app.isPackaged;

async function runSetupTinyTex(userDataPath, onProgress) {
async function runSetupTinyTex(userDataPath, isDev, onProgress) {
const { setupTinyTex } = await import("../scripts/setup-tinytex.js");
await setupTinyTex(userDataPath, onProgress);
await setupTinyTex(userDataPath, isDev, onProgress);
}

async function runSetupExtraPackages(userDataPath, isDev, onProgress) {
const { setupExtraPackages } = await import("../scripts/setup-init-pkg.js");
await setupExtraPackages(userDataPath, isDev, onProgress);
}

function getSidecarPath() {
Expand Down Expand Up @@ -219,7 +224,9 @@ app.whenReady().then(async () => {
if (fs.existsSync(src)) {
// If destination doesn't exist, OR it exists but is empty, copy it.
// This handles cases where server.js might have created an empty folder first.
const shouldCopy = !fs.existsSync(dest) || (fs.lstatSync(dest).isDirectory() && fs.readdirSync(dest).length === 0);
const shouldCopy =
!fs.existsSync(dest) ||
(fs.lstatSync(dest).isDirectory() && fs.readdirSync(dest).length === 0);
if (shouldCopy) {
await fs.copy(src, dest);
}
Expand All @@ -242,6 +249,13 @@ app.whenReady().then(async () => {
mainWindow.once("ready-to-show", () => {
splashWindow.close();
mainWindow.show();

// Start background package installation after the window is shown
runSetupExtraPackages(userDataPath, isDev, (msg) => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("setup-progress", msg);
}
});
});
});

Expand Down
7 changes: 5 additions & 2 deletions electron/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ contextBridge.exposeInMainWorld("electronAPI", {
ipcRenderer.on("window-is-maximized", () => callback()),
onUnmaximize: (callback) =>
ipcRenderer.on("window-is-unmaximized", () => callback()),
onSetupProgress: (callback) =>
ipcRenderer.on("setup-progress", (event, msg) => callback(msg)),
onSetupProgress: (callback) => {
const subscription = (event, msg) => callback(msg);
ipcRenderer.on("setup-progress", subscription);
return () => ipcRenderer.removeListener("setup-progress", subscription);
},
savePDF: (arrayBuffer, defaultName) =>
ipcRenderer.invoke("save-pdf", { arrayBuffer, defaultName }),
});
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"projects/**/*",
"templates/**/*",
"settings/**/*",
"scripts/**/*",
"package.json"
],
"files": [
Expand Down
21 changes: 1 addition & 20 deletions public/splash.html
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
<img src="dociereLogo.png" />
</div>

<div class="title">DocierePro</div>
<div class="title">DocièrePro</div>

<div class="tagline">Starting application</div>

Expand All @@ -170,22 +170,3 @@
</div>
</body>
</html>

<!-- <!DOCTYPE html>
<html>
<body style="background: #222; color: white; text-align: center; font-family: sans-serif; padding-top: 50px;">
<img src="logo.png" width="100">
<h2 id="status">Starting Dociere...</h2>
<p id="subtext"></p>

<script>
const urlParams = new URLSearchParams(window.location.search);
const isFirstRun = urlParams.get('firstRun') === 'true';

if (isFirstRun) {
document.getElementById('status').innerText = "Setting up Environment...";
document.getElementById('subtext').innerText = "Downloading LaTeX components. This may take a few minutes.";
}
</script>
</body>
</html> -->
119 changes: 119 additions & 0 deletions scripts/pkg-installer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { spawn } from "child_process";
import fs from "fs-extra";
// import path from "path";
import { fileURLToPath } from "url";
// import { dirname } from "path";

// const __filename = fileURLToPath(import.meta.url);
// const __dirname = dirname(__filename);

import { getTinyTexBinPath } from "./setup-tinytex.js";

// Helper to determine tlmgr path
export const getTlmgrPath = () => {
// For standalone/dev use, we assume isDev=true and no userDataPath
return getTinyTexBinPath(process.env.USER_DATA_PATH, !process.env.USER_DATA_PATH, "tlmgr");
};

const getPackageNameFromFile = (tlmgr, fileName) => {
return new Promise((resolve) => {
// Search for the package that contains the specific file
const searchPattern = fileName.includes('.') ? `/${fileName}` : `/${fileName}.sty`;

const proc = spawn(tlmgr, [
"search",
"--global",
"--file",
searchPattern,
]);

let output = "";
proc.stdout.on("data", (d) => (output += d.toString()));
proc.on("close", () => {
const lines = output.split(/[\r\n]+/);
const pkgLine = lines.find((line) => line.trim().endsWith(":"));
if (pkgLine) {
resolve(pkgLine.trim().replace(":", ""));
} else {
// Fallback: remove common extensions if search fails
resolve(fileName.replace(/\.(sty|cls|def|fmt)$/, ""));
}
});
});
};

const runTlmgrCommand = (tlmgr, args, onProgress = () => {}) => {
return new Promise((resolve, reject) => {
const proc = spawn(tlmgr, args);
proc.stdout.on("data", (data) => {
const txt = data.toString();
const match =
txt.match(/install:\s+([a-zA-Z0-9_-]+)/i) ||
txt.match(/installing\s+([a-zA-Z0-9_-]+)/i);
if (match) {
onProgress(`Installing ${match[1]}...`);
}
});
proc.stderr.on("data", (data) => console.error(`[tlmgr stderr]: ${data.toString()}`));
proc.on("close", (code) => {
if (code === 0) resolve();
else reject(new Error(`tlmgr failed with code ${code}`));
});
});
};

/**
* Detects missing LaTeX packages from log text and installs them.
* @param {string} logText - The LaTeX log content
* @param {string} tlmgr - Path to tlmgr binary (optional)
* @param {function} onProgress - Progress callback
* @returns {Promise<string[]>} - List of installed packages
*/
export async function installMissingPackages(
logText,
tlmgr = null,
onProgress = (m) => console.log(m),
) {
const packageRegex =
/! LaTeX Error: File [`']?([^' ]+)[`']? not found|you do not have the ([^' ]+) package installed/gi;
const matches = [...logText.matchAll(packageRegex)];
const fileSet = new Set();

for (const match of matches) {
const fileName = match[1] || match[2];
if (fileName) fileSet.add(fileName);
}

if (fileSet.size === 0) return [];

const tlmgrPath = tlmgr || getTlmgrPath();
if (!fs.existsSync(tlmgrPath)) {
throw new Error(`tlmgr not found at: ${tlmgrPath}`);
}

onProgress(`Resolving ${fileSet.size} dependencies...`);
const actualPackages = await Promise.all(
[...fileSet].map((file) => getPackageNameFromFile(tlmgrPath, file)),
);

const uniquePkgs = [...new Set(actualPackages)];
if (uniquePkgs.length > 0) {
onProgress(`Installing packages: ${uniquePkgs.join(", ")}`);
await runTlmgrCommand(tlmgrPath, ["install", ...uniquePkgs], onProgress);
}

return uniquePkgs;
}

// Standalone execution for testing
if (process.argv[1] === fileURLToPath(import.meta.url)) {
const logFile = process.argv[2] || "scripts/test.log";
if (fs.existsSync(logFile)) {
const text = fs.readFileSync(logFile, "utf8");
installMissingPackages(text)
.then((pkgs) => console.log(`Finished installation: ${pkgs.join(", ") || "None"}`))
.catch((err) => console.error("Error:", err));
} else {
console.error(`Log file not found: ${logFile}`);
}
}
136 changes: 136 additions & 0 deletions scripts/setup-init-pkg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { spawn } from "child_process";
import fs from "fs-extra";
import { getTinyTexBinPath } from "./setup-tinytex.js";

/**
* Installs additional LaTeX packages in the background.
* @param {string} userDataPath - Application user data path
* @param {boolean} isDev - Whether the app is in development mode
* @param {function} onProgress - Progress callback
*/
export async function setupExtraPackages(
userDataPath,
isDev,
onProgress = () => {},
) {
const tlmgrPath = getTinyTexBinPath(userDataPath, isDev, "tlmgr");

if (!fs.existsSync(tlmgrPath)) {
console.error(`tlmgr not found at: ${tlmgrPath}`);
return;
}

const runCommandAndGetOutput = (cmd, args) => {
return new Promise((resolve, reject) => {
const proc = spawn(cmd, args);
let output = "";
proc.stdout.on("data", (data) => (output += data.toString()));
proc.on("close", (code) => {
if (code === 0) resolve(output);
else reject(new Error(`Exit code ${code}`));
});
});
};

const runCommand = (cmd, args) => {
return new Promise((resolve, reject) => {
const proc = spawn(cmd, args);
proc.stdout.on("data", (data) => {
const txt = data.toString();
const match =
txt.match(/install:\s+([a-zA-Z0-9_-]+)/i) ||
txt.match(/installing\s+([a-zA-Z0-9_-]+)/i);
if (match) {
onProgress(`Installing ${match[1]}...`);
}
});
proc.stderr.on("data", (data) => console.error(data.toString()));
proc.on("close", (code) => {
if (code === 0) resolve();
else reject(new Error(`Command failed with exit code ${code}`));
});
});
};

const essentials = [
"amsmath",
"geometry",
"xcolor",
"graphics",
"tools",
"etoolbox",
"hyperref",
"microtype",
"fancyhdr",
"enumitem",
"setspace",
"titlesec",
"pgf",
"float",
"caption",
"booktabs",
"listings",
"tcolorbox",
"cleveref",
"biblatex",
"graphics-def",
"amsfonts",
"natbib",
"url",
"xstring",
"logreq",
"biber",
"parskip",
"mathtools",
"physics",
"mhchem",
"babel",
"fontspec",
"multirow",
"tocloft",
"pdflscape",
"pgfplots",
"pdfpages",
"fancyvrb",
"csquotes",
"latexmk",
"algorithms",
"cite",
"cm-super",
"ragged2e",
];

try {
console.log("Checking for missing LaTeX packages in binary...");
onProgress("Checking installed packages...");

const installedOutput = await runCommandAndGetOutput(tlmgrPath, [
"list",
"--only-installed",
"--data",
"name",
]);
const installedSet = new Set(
installedOutput.split(/\r?\n/).map((s) => s.trim()),
);

const missingPackages = essentials.filter((pkg) => !installedSet.has(pkg));

if (missingPackages.length > 0) {
console.log(`Installing ${missingPackages.length} missing packages...`);
onProgress(`Installing ${missingPackages.length} packages...`);
// Install in segments to avoid command line length limits or just to show more granular progress
for (const pkg of missingPackages) {
onProgress(`Installing ${pkg}...`);
await runCommand(tlmgrPath, ["install", pkg]);
}
} else {
console.log("All essential packages are already present.");
}
} catch (error) {
console.error("Error during background package check/install:", error);
}

console.log("✅ Background LaTeX setup complete!");
onProgress("LaTeX Setup Complete!");
}
Loading