feat: puppeteer chrome download on start and background dependencies status on UI
This commit is contained in:
parent
c3b3f15924
commit
e79c64ec21
9 changed files with 473 additions and 21 deletions
|
|
@ -7,13 +7,20 @@ import { startFastApiServer, startNextJsServer } from "./utils/servers";
|
|||
import { ChildProcessByStdio } from "child_process";
|
||||
import { appDataDir, baseDir, ensureDirectoriesExist, fastapiDir, isDev, localhost, nextjsDir, tempDir, userConfigPath, userDataDir } from "./utils/constants";
|
||||
import { setupIpcHandlers } from "./ipc";
|
||||
import { ipcMain } from "electron";
|
||||
import { setupLibreOfficeInstallHandlers } from "./ipc/libreoffice_install_handlers";
|
||||
import { checkLibreOfficeBeforeWindow, getSofficePath } from "./utils/libreoffice-check";
|
||||
import { checkPuppeteerChromiumBeforeWindow } from "./utils/puppeteer-check";
|
||||
|
||||
|
||||
var win: BrowserWindow | undefined;
|
||||
var fastApiProcess: ChildProcessByStdio<any, any, any> | undefined;
|
||||
var nextjsProcess: any;
|
||||
let isStopping = false;
|
||||
const startupStatus: Record<string, string> = {
|
||||
libreoffice: "checking",
|
||||
puppeteer: "checking",
|
||||
};
|
||||
|
||||
app.commandLine.appendSwitch('gtk-version', '3');
|
||||
|
||||
|
|
@ -55,7 +62,7 @@ const createWindow = () => {
|
|||
|
||||
async function startServers(fastApiPort: number, nextjsPort: number) {
|
||||
try {
|
||||
fastApiProcess = await startFastApiServer(
|
||||
const fastApi = await startFastApiServer(
|
||||
fastapiDir,
|
||||
fastApiPort,
|
||||
{
|
||||
|
|
@ -96,7 +103,10 @@ async function startServers(fastApiPort: number, nextjsPort: number) {
|
|||
},
|
||||
isDev,
|
||||
);
|
||||
nextjsProcess = await startNextJsServer(
|
||||
fastApiProcess = fastApi.process;
|
||||
await fastApi.ready;
|
||||
|
||||
const nextjs = await startNextJsServer(
|
||||
nextjsDir,
|
||||
nextjsPort,
|
||||
{
|
||||
|
|
@ -109,6 +119,8 @@ async function startServers(fastApiPort: number, nextjsPort: number) {
|
|||
},
|
||||
isDev,
|
||||
)
|
||||
nextjsProcess = nextjs.process;
|
||||
await nextjs.ready;
|
||||
} catch (error) {
|
||||
console.error("Server startup error:", error);
|
||||
}
|
||||
|
|
@ -116,12 +128,23 @@ async function startServers(fastApiPort: number, nextjsPort: number) {
|
|||
|
||||
async function stopServers() {
|
||||
if (fastApiProcess?.pid) {
|
||||
await killProcess(fastApiProcess.pid);
|
||||
console.log("Closing FastAPI...");
|
||||
try {
|
||||
await killProcess(fastApiProcess.pid);
|
||||
} catch {
|
||||
await killProcess(fastApiProcess.pid, "SIGKILL");
|
||||
}
|
||||
}
|
||||
if (nextjsProcess) {
|
||||
if (isDev) {
|
||||
await killProcess(nextjsProcess.pid);
|
||||
console.log("Closing NextJS...");
|
||||
try {
|
||||
await killProcess(nextjsProcess.pid);
|
||||
} catch {
|
||||
await killProcess(nextjsProcess.pid, "SIGKILL");
|
||||
}
|
||||
} else {
|
||||
console.log("Closing NextJS...");
|
||||
nextjsProcess.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -134,13 +157,30 @@ app.whenReady().then(async () => {
|
|||
// Register LibreOffice install handlers early so the installer window can use them
|
||||
setupLibreOfficeInstallHandlers();
|
||||
|
||||
// Check for LibreOffice (required for custom template from PPTX). Shows installer
|
||||
// window if missing. Never blocks; always proceeds.
|
||||
await checkLibreOfficeBeforeWindow();
|
||||
|
||||
createWindow();
|
||||
win?.loadFile(path.join(baseDir, "resources/ui/homepage/index.html"));
|
||||
|
||||
const sendStartupStatus = (name: string, status: string) => {
|
||||
startupStatus[name] = status;
|
||||
win?.webContents.send("startup:status", { name, status });
|
||||
};
|
||||
|
||||
win?.webContents.once("did-finish-load", async () => {
|
||||
// Emit initial status so the UI doesn't remain in "Checking..." if it
|
||||
// registers late.
|
||||
sendStartupStatus("libreoffice", startupStatus.libreoffice);
|
||||
sendStartupStatus("puppeteer", startupStatus.puppeteer);
|
||||
// Check for LibreOffice (required for custom template from PPTX). Shows installer
|
||||
// window if missing. Never blocks; always proceeds.
|
||||
await checkLibreOfficeBeforeWindow((status) =>
|
||||
sendStartupStatus("libreoffice", status)
|
||||
);
|
||||
// Check Puppeteer Chromium (used for export & template rendering).
|
||||
await checkPuppeteerChromiumBeforeWindow((status) =>
|
||||
sendStartupStatus("puppeteer", status)
|
||||
);
|
||||
});
|
||||
|
||||
setUserConfig({
|
||||
CAN_CHANGE_KEYS: process.env.CAN_CHANGE_KEYS,
|
||||
LLM: process.env.LLM,
|
||||
|
|
@ -177,12 +217,34 @@ app.whenReady().then(async () => {
|
|||
//? Setup environment variables to be used in the preloads
|
||||
setupEnv(fastApiPort, nextjsPort);
|
||||
setupIpcHandlers();
|
||||
ipcMain.handle("startup:get-status", () => startupStatus);
|
||||
|
||||
await startServers(fastApiPort, nextjsPort);
|
||||
win?.loadURL(`${localhost}:${nextjsPort}`);
|
||||
});
|
||||
|
||||
app.on("window-all-closed", async () => {
|
||||
await stopServers();
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on("before-quit", async (event) => {
|
||||
if (isStopping) return;
|
||||
isStopping = true;
|
||||
event.preventDefault();
|
||||
try {
|
||||
await stopServers();
|
||||
} finally {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("will-quit", async (event) => {
|
||||
if (isStopping) return;
|
||||
isStopping = true;
|
||||
event.preventDefault();
|
||||
try {
|
||||
await stopServers();
|
||||
} finally {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -29,4 +29,7 @@ contextBridge.exposeInMainWorld('electron', {
|
|||
telemetryStatus: () => ipcRenderer.invoke("api:telemetry-status"),
|
||||
getTemplates: () => ipcRenderer.invoke("api:templates"),
|
||||
getPresentationPptxModel: (presentationId: string) => ipcRenderer.invoke("presentation-to-pptx-model", presentationId),
|
||||
onStartupStatus: (callback: (payload: { name: string; status: string }) => void) =>
|
||||
ipcRenderer.on("startup:status", (_event, payload) => callback(payload)),
|
||||
getStartupStatus: () => ipcRenderer.invoke("startup:get-status"),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -70,14 +70,14 @@ export function setupEnv(fastApiPort: number, nextjsPort: number) {
|
|||
}
|
||||
|
||||
|
||||
export function killProcess(pid: number) {
|
||||
export function killProcess(pid: number, signal: NodeJS.Signals = "SIGTERM") {
|
||||
return new Promise((resolve, reject) => {
|
||||
treeKill(pid, "SIGTERM", (err: any) => {
|
||||
treeKill(pid, signal, (err: any) => {
|
||||
if (err) {
|
||||
console.error(`Error killing process ${pid}:`, err)
|
||||
reject(err)
|
||||
} else {
|
||||
console.log(`Process ${pid} killed`)
|
||||
console.log(`Process ${pid} killed (${signal})`)
|
||||
resolve(true)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ interface LibreOfficeCheckResult {
|
|||
path?: string;
|
||||
}
|
||||
|
||||
export type LibreOfficeStatus =
|
||||
| "checking"
|
||||
| "installed"
|
||||
| "missing"
|
||||
| "installing";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Platform helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -358,7 +364,10 @@ async function showLibreOfficeInstallerWindow(): Promise<void> {
|
|||
*
|
||||
* @returns Always `true` – the application should always proceed.
|
||||
*/
|
||||
export async function checkLibreOfficeBeforeWindow(): Promise<boolean> {
|
||||
export async function checkLibreOfficeBeforeWindow(
|
||||
onStatus?: (status: LibreOfficeStatus) => void
|
||||
): Promise<boolean> {
|
||||
onStatus?.("checking");
|
||||
let result = await isLibreOfficeInstalled();
|
||||
|
||||
if (result.installed) {
|
||||
|
|
@ -368,10 +377,13 @@ export async function checkLibreOfficeBeforeWindow(): Promise<boolean> {
|
|||
console.log(
|
||||
`[LibreOffice] Detected: ${result.version ?? "(version unknown)"} at ${resolvedSofficePath}`
|
||||
);
|
||||
onStatus?.("installed");
|
||||
return true;
|
||||
}
|
||||
|
||||
console.warn("[LibreOffice] Not found – showing installer window.");
|
||||
onStatus?.("missing");
|
||||
onStatus?.("installing");
|
||||
await showLibreOfficeInstallerWindow();
|
||||
|
||||
// Re-detect after the window closes (install may have succeeded)
|
||||
|
|
@ -379,6 +391,9 @@ export async function checkLibreOfficeBeforeWindow(): Promise<boolean> {
|
|||
if (result.installed && result.path) {
|
||||
resolvedSofficePath = result.path;
|
||||
console.log(`[LibreOffice] Detected after install: ${resolvedSofficePath}`);
|
||||
onStatus?.("installed");
|
||||
} else {
|
||||
onStatus?.("missing");
|
||||
}
|
||||
|
||||
// Always proceed – never block the app
|
||||
|
|
|
|||
100
electron/app/utils/puppeteer-check.ts
Normal file
100
electron/app/utils/puppeteer-check.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/**
|
||||
* puppeteer-check.ts
|
||||
*
|
||||
* Ensures Puppeteer's Chromium/Chrome-for-Testing binary is downloaded
|
||||
* before the main BrowserWindow is created.
|
||||
*/
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import puppeteer from "puppeteer";
|
||||
import { Browser, detectBrowserPlatform, install } from "@puppeteer/browsers";
|
||||
|
||||
function getPuppeteerCacheDir(): string {
|
||||
const configCache =
|
||||
(puppeteer as any).configuration?.cacheDirectory ??
|
||||
(puppeteer as any).defaultDownloadPath;
|
||||
return configCache ?? path.join(os.homedir(), ".cache", "puppeteer");
|
||||
}
|
||||
|
||||
function shouldSkipDownload(): boolean {
|
||||
if (process.env.PUPPETEER_SKIP_DOWNLOAD) {
|
||||
const value = process.env.PUPPETEER_SKIP_DOWNLOAD.trim().toLowerCase();
|
||||
return value === "1" || value === "true" || value === "yes";
|
||||
}
|
||||
return Boolean((puppeteer as any).configuration?.skipDownload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures Puppeteer has its browser binary available.
|
||||
* Never blocks app startup — always returns `true`.
|
||||
*/
|
||||
export type PuppeteerStatus =
|
||||
| "checking"
|
||||
| "installed"
|
||||
| "missing"
|
||||
| "downloading"
|
||||
| "downloaded"
|
||||
| "skipped"
|
||||
| "failed";
|
||||
|
||||
export async function checkPuppeteerChromiumBeforeWindow(
|
||||
onStatus?: (status: PuppeteerStatus) => void
|
||||
): Promise<boolean> {
|
||||
onStatus?.("checking");
|
||||
if (shouldSkipDownload()) {
|
||||
console.log("[Puppeteer] Skip download enabled.");
|
||||
onStatus?.("skipped");
|
||||
return true;
|
||||
}
|
||||
|
||||
const executablePath = puppeteer.executablePath();
|
||||
if (executablePath && fs.existsSync(executablePath)) {
|
||||
console.log(`[Puppeteer] Chromium found at ${executablePath}`);
|
||||
onStatus?.("installed");
|
||||
return true;
|
||||
}
|
||||
|
||||
onStatus?.("missing");
|
||||
const cacheDir = getPuppeteerCacheDir();
|
||||
const platform = detectBrowserPlatform();
|
||||
if (!platform) {
|
||||
console.warn("[Puppeteer] Unable to detect platform; skipping download.");
|
||||
onStatus?.("failed");
|
||||
return true;
|
||||
}
|
||||
|
||||
const buildId =
|
||||
(puppeteer as any).browserVersion ??
|
||||
(puppeteer as any).defaultBrowserRevision;
|
||||
|
||||
if (!buildId) {
|
||||
console.warn("[Puppeteer] Unable to resolve browser build; skipping download.");
|
||||
onStatus?.("failed");
|
||||
return true;
|
||||
}
|
||||
|
||||
console.warn("[Puppeteer] Chromium missing – downloading now...");
|
||||
onStatus?.("downloading");
|
||||
try {
|
||||
await install({
|
||||
cacheDir,
|
||||
platform,
|
||||
browser: Browser.CHROME,
|
||||
buildId,
|
||||
});
|
||||
const downloadedPath = puppeteer.executablePath();
|
||||
if (downloadedPath && fs.existsSync(downloadedPath)) {
|
||||
console.log(`[Puppeteer] Chromium downloaded to ${downloadedPath}`);
|
||||
onStatus?.("downloaded");
|
||||
} else {
|
||||
console.log("[Puppeteer] Chromium download finished.");
|
||||
onStatus?.("downloaded");
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn("[Puppeteer] Chromium download failed:", error);
|
||||
onStatus?.("failed");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -55,9 +55,10 @@ export async function startFastApiServer(
|
|||
fastApiProcess.on("error", (err) => {
|
||||
safeLog(`Spawn error: ${err.message}\n`, fastapiLogPath);
|
||||
});
|
||||
// Wait for FastAPI server to start
|
||||
await waitForServer(`${localhost}:${port}/docs`);
|
||||
return fastApiProcess;
|
||||
return {
|
||||
process: fastApiProcess,
|
||||
ready: waitForServer(`${localhost}:${port}/docs`),
|
||||
};
|
||||
}
|
||||
|
||||
export async function startNextJsServer(
|
||||
|
|
@ -100,9 +101,10 @@ export async function startNextJsServer(
|
|||
nextjsProcess = await startNextjsBuildServer(directory, port);
|
||||
}
|
||||
|
||||
// Wait for NextJS server to start
|
||||
await waitForServer(`${localhost}:${port}`);
|
||||
return nextjsProcess;
|
||||
return {
|
||||
process: nextjsProcess,
|
||||
ready: waitForServer(`${localhost}:${port}`),
|
||||
};
|
||||
}
|
||||
|
||||
function startNextjsBuildServer(directory: string, port: number): Promise<http.Server> {
|
||||
|
|
|
|||
|
|
@ -20,13 +20,163 @@
|
|||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.status-panel {
|
||||
position: fixed;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
background: rgba(17, 24, 39, 0.9);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
display: inline-flex;
|
||||
width: fit-content;
|
||||
max-width: 80vw;
|
||||
}
|
||||
|
||||
.status-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.status-name {
|
||||
flex: 1;
|
||||
color: #e5e7eb;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
display: inline-block;
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
.status-icon.loading {
|
||||
background: #3b82f6;
|
||||
animation: pulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.status-icon.ok {
|
||||
background: #10b981;
|
||||
}
|
||||
|
||||
.status-icon.error {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.35;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.35;
|
||||
}
|
||||
}
|
||||
|
||||
.status-tooltip {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
margin-top: 8px;
|
||||
background: rgba(17, 24, 39, 0.95);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 8px;
|
||||
padding: 8px 10px;
|
||||
font-size: 11px;
|
||||
color: #e5e7eb;
|
||||
white-space: normal;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 200px;
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
pointer-events: none;
|
||||
transition: opacity 120ms ease, transform 120ms ease;
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.28);
|
||||
}
|
||||
|
||||
.status-panel:hover .status-tooltip {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.status-tooltip-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.status-tooltip-line span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.status-tooltip-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.status-tooltip-label::before {
|
||||
content: "";
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 9999px;
|
||||
background: #6b7280;
|
||||
box-shadow: 0 0 0 2px rgba(107, 114, 128, 0.2);
|
||||
}
|
||||
|
||||
.status-tooltip-label.loading::before {
|
||||
background: #3b82f6;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.25);
|
||||
animation: pulse 1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.status-tooltip-label.ok::before {
|
||||
background: #10b981;
|
||||
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.25);
|
||||
}
|
||||
|
||||
.status-tooltip-label.error::before {
|
||||
background: #ef4444;
|
||||
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.25);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-900 text-white flex flex-col items-center justify-center h-screen">
|
||||
<img src="../assets/images/presenton_logo.png" alt="Presenton Logo" class="h-20">
|
||||
<p class="mt-20 text-lg">Just a moment...</p>
|
||||
<div class="loading-circle mt-10"></div>
|
||||
<p class="mt-16 text-lg">Just a moment...</p>
|
||||
<div class="loading-circle mt-8"></div>
|
||||
|
||||
<div class="status-panel" id="dependencies-panel">
|
||||
<div class="status-row">
|
||||
<span class="status-icon loading" id="icon-dependencies"></span>
|
||||
<span class="status-name" id="status-dependencies">Dependencies</span>
|
||||
</div>
|
||||
<div class="status-tooltip" id="dependencies-tooltip">
|
||||
<div class="status-tooltip-line">
|
||||
<span class="status-tooltip-label loading" id="tooltip-label-libreoffice">LibreOffice</span>
|
||||
<span id="tooltip-libreoffice">Checking...</span>
|
||||
</div>
|
||||
<div class="status-tooltip-line">
|
||||
<span class="status-tooltip-label loading" id="tooltip-label-puppeteer">Chromium</span>
|
||||
<span id="tooltip-puppeteer">Checking...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
window.addEventListener("DOMContentLoaded", () => {
|
||||
const statusMap = {
|
||||
checking: "Checking...",
|
||||
installed: "Installed",
|
||||
missing: "Missing",
|
||||
installing: "Installing...",
|
||||
downloading: "Downloading...",
|
||||
downloaded: "Downloaded",
|
||||
skipped: "Skipped",
|
||||
failed: "Failed",
|
||||
};
|
||||
const labelMap = {
|
||||
libreoffice: "LibreOffice",
|
||||
puppeteer: "Chromium",
|
||||
};
|
||||
|
||||
const dependenciesEl = document.getElementById("status-dependencies");
|
||||
const dependenciesIcon = document.getElementById("icon-dependencies");
|
||||
const dependenciesTooltip = document.getElementById("dependencies-tooltip");
|
||||
const libreofficeTooltip = document.getElementById("tooltip-libreoffice");
|
||||
const puppeteerTooltip = document.getElementById("tooltip-puppeteer");
|
||||
const libreofficeLabel = document.getElementById("tooltip-label-libreoffice");
|
||||
const puppeteerLabel = document.getElementById("tooltip-label-puppeteer");
|
||||
const currentStatus = {
|
||||
libreoffice: "checking",
|
||||
puppeteer: "checking",
|
||||
};
|
||||
|
||||
function setStatus(name, status) {
|
||||
if (currentStatus[name] !== undefined) {
|
||||
currentStatus[name] = status;
|
||||
}
|
||||
if (dependenciesEl) dependenciesEl.textContent = "Dependencies";
|
||||
|
||||
const statuses = Object.values(currentStatus);
|
||||
const hasError = statuses.some((s) => s === "missing" || s === "failed");
|
||||
const isBusy = statuses.some((s) => s === "checking" || s === "installing" || s === "downloading");
|
||||
const isDone = statuses.every((s) => s === "installed" || s === "downloaded" || s === "skipped");
|
||||
|
||||
let iconClass = "loading";
|
||||
let iconText = "";
|
||||
if (hasError) {
|
||||
iconClass = "error";
|
||||
iconText = "×";
|
||||
} else if (isDone && !isBusy) {
|
||||
iconClass = "ok";
|
||||
iconText = "✓";
|
||||
} else {
|
||||
iconClass = "loading";
|
||||
iconText = "";
|
||||
}
|
||||
|
||||
if (dependenciesIcon) {
|
||||
dependenciesIcon.className = `status-icon ${iconClass}`;
|
||||
}
|
||||
|
||||
const libreofficeStatus = currentStatus.libreoffice;
|
||||
const puppeteerStatus = currentStatus.puppeteer;
|
||||
const libreofficeText = statusMap[libreofficeStatus] || libreofficeStatus;
|
||||
const puppeteerText = statusMap[puppeteerStatus] || puppeteerStatus;
|
||||
|
||||
const toDotClass = (value) => {
|
||||
if (value === "missing" || value === "failed") return "error";
|
||||
if (value === "installed" || value === "downloaded" || value === "skipped") return "ok";
|
||||
return "loading";
|
||||
};
|
||||
|
||||
if (libreofficeTooltip) libreofficeTooltip.textContent = libreofficeText;
|
||||
if (puppeteerTooltip) puppeteerTooltip.textContent = puppeteerText;
|
||||
if (libreofficeLabel) libreofficeLabel.className = `status-tooltip-label ${toDotClass(libreofficeStatus)}`;
|
||||
if (puppeteerLabel) puppeteerLabel.className = `status-tooltip-label ${toDotClass(puppeteerStatus)}`;
|
||||
if (dependenciesTooltip) dependenciesTooltip.setAttribute("aria-live", "polite");
|
||||
}
|
||||
|
||||
if (window.electron?.onStartupStatus) {
|
||||
window.electron.onStartupStatus((payload) => {
|
||||
if (!payload) return;
|
||||
setStatus(payload.name, payload.status);
|
||||
});
|
||||
}
|
||||
if (window.electron?.getStartupStatus) {
|
||||
window.electron.getStartupStatus().then((statusMap) => {
|
||||
if (!statusMap) return;
|
||||
if (statusMap.libreoffice) setStatus("libreoffice", statusMap.libreoffice);
|
||||
if (statusMap.puppeteer) setStatus("puppeteer", statusMap.puppeteer);
|
||||
});
|
||||
}
|
||||
});
|
||||
32
package-lock.json
generated
Normal file
32
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "presenton",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "presenton",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.3.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "25.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.5.tgz",
|
||||
"integrity": "sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue