5c837aecd8
Introduces a new Big Picture Mode with a controller-friendly, console-style UI optimized for Steam Deck and handheld devices. Adds new renderer files (HTML, CSS, JS) for the mode, updates main and preload scripts to support window management and IPC for Big Picture Mode, and documents features and usage in BIG_PICTURE_MODE.md. Updates settings and navigation to allow launching and exiting Big Picture Mode.
181 lines
6.1 KiB
JavaScript
181 lines
6.1 KiB
JavaScript
// preload.js - Optimized version
|
|
const { contextBridge, ipcRenderer } = require('electron');
|
|
|
|
// Cache DOM references for performance
|
|
let domReady = false;
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
domReady = true;
|
|
console.log("Browser UI loaded.");
|
|
});
|
|
|
|
// Optimized API exposure with error handling and caching
|
|
const electronAPI = {
|
|
send: (ch, ...args) => {
|
|
try {
|
|
return ipcRenderer.send(ch, ...args);
|
|
} catch (err) {
|
|
console.error('IPC send error:', err);
|
|
}
|
|
},
|
|
// Send message to embedding page (webview host)
|
|
sendToHost: (ch, ...args) => {
|
|
try {
|
|
return ipcRenderer.sendToHost(ch, ...args);
|
|
} catch (err) {
|
|
console.error('IPC sendToHost error:', err);
|
|
}
|
|
},
|
|
invoke: (ch, ...args) => {
|
|
try {
|
|
return ipcRenderer.invoke(ch, ...args);
|
|
} catch (err) {
|
|
console.error('IPC invoke error:', err);
|
|
return Promise.reject(err);
|
|
}
|
|
},
|
|
on: (ch, fn) => {
|
|
try {
|
|
return ipcRenderer.on(ch, (e, ...args) => fn(...args));
|
|
} catch (err) {
|
|
console.error('IPC on error:', err);
|
|
}
|
|
},
|
|
// Add removeListener for cleanup
|
|
removeListener: (ch, fn) => {
|
|
try {
|
|
return ipcRenderer.removeListener(ch, fn);
|
|
} catch (err) {
|
|
console.error('IPC removeListener error:', err);
|
|
}
|
|
},
|
|
toggleDevTools: () => {
|
|
try {
|
|
return ipcRenderer.invoke('open-devtools');
|
|
} catch (err) {
|
|
console.error('IPC open-devtools error:', err);
|
|
return Promise.reject(err);
|
|
}
|
|
},
|
|
openLocalFile: async () => {
|
|
try {
|
|
return await ipcRenderer.invoke('show-open-file-dialog');
|
|
} catch (err) {
|
|
console.error('IPC openLocalFile error:', err);
|
|
return null;
|
|
}
|
|
},
|
|
showContextMenu: (params) => {
|
|
try {
|
|
return ipcRenderer.invoke('show-context-menu', params);
|
|
} catch (err) {
|
|
console.error('IPC showContextMenu error:', err);
|
|
}
|
|
},
|
|
saveImageToDisk: async (suggestedName, dataUrl) => ipcRenderer.invoke('save-image-from-dataurl', { suggestedName, dataUrl }),
|
|
saveImageFromNet: async (url) => ipcRenderer.invoke('save-image-from-url', { url })
|
|
};
|
|
|
|
// Cache for bookmarks to reduce IPC calls
|
|
let bookmarksCache = null;
|
|
let bookmarksCacheTime = 0;
|
|
const CACHE_DURATION = 5000; // 5 seconds
|
|
|
|
const bookmarksAPI = {
|
|
load: async () => {
|
|
const now = Date.now();
|
|
if (bookmarksCache && (now - bookmarksCacheTime) < CACHE_DURATION) {
|
|
return bookmarksCache;
|
|
}
|
|
try {
|
|
bookmarksCache = await ipcRenderer.invoke('load-bookmarks');
|
|
bookmarksCacheTime = now;
|
|
return bookmarksCache;
|
|
} catch (err) {
|
|
console.error('Bookmarks load error:', err);
|
|
return [];
|
|
}
|
|
},
|
|
save: async (data) => {
|
|
try {
|
|
bookmarksCache = data; // Update cache immediately
|
|
bookmarksCacheTime = Date.now();
|
|
return await ipcRenderer.invoke('save-bookmarks', data);
|
|
} catch (err) {
|
|
console.error('Bookmarks save error:', err);
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Expose APIs to main world
|
|
contextBridge.exposeInMainWorld('electronAPI', electronAPI);
|
|
contextBridge.exposeInMainWorld('bookmarksAPI', bookmarksAPI);
|
|
|
|
// Minimal about API for settings page
|
|
contextBridge.exposeInMainWorld('aboutAPI', {
|
|
getInfo: () => ipcRenderer.invoke('get-about-info')
|
|
});
|
|
|
|
// Big Picture Mode API - Steam Deck / Console UI
|
|
contextBridge.exposeInMainWorld('bigPictureAPI', {
|
|
// Get screen info to determine if Big Picture Mode is recommended
|
|
getScreenInfo: () => ipcRenderer.invoke('get-screen-info'),
|
|
// Check if device is likely a Steam Deck or handheld
|
|
isSuggested: () => ipcRenderer.invoke('is-bigpicture-suggested'),
|
|
// Launch Big Picture Mode
|
|
launch: () => ipcRenderer.invoke('launch-bigpicture'),
|
|
// Exit Big Picture Mode
|
|
exit: () => ipcRenderer.invoke('exit-bigpicture'),
|
|
// Navigate to URL (from Big Picture Mode)
|
|
navigate: (url) => ipcRenderer.send('bigpicture-navigate', url)
|
|
});
|
|
|
|
// Relay context-menu commands from main to active renderer context (open new tabs etc.)
|
|
ipcRenderer.on('context-menu-command', (event, payload) => {
|
|
window.dispatchEvent(new CustomEvent('nebula-context-command', { detail: payload }));
|
|
});
|
|
|
|
// Downloads API exposed to renderer
|
|
contextBridge.exposeInMainWorld('downloadsAPI', {
|
|
list: () => ipcRenderer.invoke('downloads-get-all'),
|
|
action: (id, action) => ipcRenderer.invoke('downloads-action', { id, action }),
|
|
clearCompleted: () => ipcRenderer.invoke('downloads-clear-completed'),
|
|
onStarted: (handler) => ipcRenderer.on('downloads-started', (_e, payload) => handler(payload)),
|
|
onUpdated: (handler) => ipcRenderer.on('downloads-updated', (_e, payload) => handler(payload)),
|
|
onDone: (handler) => ipcRenderer.on('downloads-done', (_e, payload) => handler(payload)),
|
|
onCleared: (handler) => ipcRenderer.on('downloads-cleared', handler),
|
|
onScanStarted: (handler) => ipcRenderer.on('downloads-scan-started', (_e, payload) => handler(payload)),
|
|
onScanResult: (handler) => ipcRenderer.on('downloads-scan-result', (_e, payload) => handler(payload))
|
|
});
|
|
|
|
// Auto-Updater API exposed to renderer
|
|
contextBridge.exposeInMainWorld('updaterAPI', {
|
|
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
|
|
downloadUpdate: () => ipcRenderer.invoke('download-update'),
|
|
installUpdate: () => ipcRenderer.invoke('install-update'),
|
|
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
|
|
onUpdateStatus: (handler) => ipcRenderer.on('update-status', (_e, payload) => handler(payload))
|
|
});
|
|
|
|
// ----------------------------------------
|
|
// Plugin renderer preloads
|
|
// ----------------------------------------
|
|
// We request a list of absolute file paths from main and require() them here.
|
|
// Each file can optionally call contextBridge.exposeInMainWorld to add APIs.
|
|
(async () => {
|
|
try {
|
|
const preloads = await ipcRenderer.invoke('plugins-get-renderer-preloads');
|
|
if (Array.isArray(preloads)) {
|
|
for (const p of preloads) {
|
|
try {
|
|
// eslint-disable-next-line global-require, import/no-dynamic-require
|
|
require(p);
|
|
} catch (e) {
|
|
console.error('[Plugins] Failed to load renderer preload:', p, e);
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn('[Plugins] No renderer preloads:', e);
|
|
}
|
|
})(); |