@berhalak/preview
Version:
A CLI tool to preview HTML, JS, and TS files in an Electron window with auto-reload
147 lines (136 loc) • 4.73 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Preview</title>
<style>
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}
#reload-toast {
position: fixed;
top: 20px;
right: 20px;
background: #4caf50;
color: white;
padding: 12px 24px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
opacity: 0;
transition: opacity 0.3s;
z-index: 10000;
pointer-events: none;
}
#reload-toast.show {
opacity: 1;
}
</style>
</head>
<body>
<div id="reload-toast">Reloaded ✨</div>
<div id="app"></div>
<script>
(() => {
const { ipcRenderer } = require('electron');
const path = require('path');
const fs = require('fs');
const os = require('os');
// Show reload toast on window reload
let isFirstLoad = true;
window.addEventListener('load', () => {
if (!isFirstLoad) {
const toast = document.getElementById('reload-toast');
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, 2000);
}
isFirstLoad = false;
});
// Get file info from main process
ipcRenderer.invoke('get-file-info').then(({ filePath, fileName, fileExt, cwd, tempDir }) => {
console.log('Loading file:', fileName);
// Set the document title to the filename without extension
const fileNameWithoutExt = path.basename(filePath, fileExt);
document.title = `${fileNameWithoutExt}`;
// Expose Preview API globally before loading the script
window.PreviewAPI = {
/**
* Set a custom menu for the window
* @param {Array} menuTemplate - Electron menu template
* @example
* PreviewAPI.setMenu([
* {
* label: 'File',
* submenu: [
* { label: 'New', click: () => console.log('New') },
* { label: 'Open', click: () => console.log('Open') }
* ]
* }
* ]);
*/
setMenu: async (menuTemplate) => {
return await ipcRenderer.invoke('set-menu', menuTemplate);
},
/**
* Add a menu item to the window
* @param {string} label - Menu label
* @param {Array} submenu - Array of submenu items
* @example
* PreviewAPI.addMenuItem('Custom', [
* { label: 'Action 1', accelerator: 'CmdOrCtrl+1' },
* { label: 'Action 2', accelerator: 'CmdOrCtrl+2' }
* ]);
*/
addMenuItem: async (label, submenu) => {
return await ipcRenderer.invoke('add-menu-item', { label, submenu });
},
/**
* Register a menu action handler
* @param {string} id - Unique action ID
* @param {Function} handler - Function to call when action is triggered
*/
onMenuAction: (id, handler) => {
ipcRenderer.on(`menu-action-${id}`, handler);
}
};
// Function to load the user's script
const loadScript = (scriptPath) => {
const script = document.createElement('script');
script.src = scriptPath;
script.onerror = (e) => {
console.error('Failed to load script:', e);
document.body.innerHTML = `
<div style="padding: 20px; color: #f44336;">
<h2>Error loading script</h2>
<p>Failed to load: ${scriptPath}</p>
</div>
`;
};
document.body.appendChild(script);
};
let scriptPath = filePath;
// If TypeScript or JSX, load the compiled version
if (fileExt === '.ts' || fileExt === '.tsx' || fileExt === '.jsx') {
const baseName = path.basename(filePath, fileExt);
scriptPath = path.join(tempDir, `${baseName}.js`); // Wait for the file to be compiled if it doesn't exist yet
const checkAndLoad = () => {
if (fs.existsSync(scriptPath)) {
loadScript(scriptPath);
} else {
console.log('Waiting for transpilation...');
setTimeout(checkAndLoad, 100);
}
};
checkAndLoad();
} else {
// For .js files, load directly
loadScript(scriptPath);
}
});
})();
</script>
</body>
</html>