-
-
Save vmatyagin/2e453a22a0ca3e355350872bbfa96bca to your computer and use it in GitHub Desktop.
electron issue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useEffect, useState } from 'react'; | |
import { createPortal } from 'react-dom'; | |
import { createRoot } from 'react-dom/client'; | |
let windowMemory: Window | null = null; | |
declare global { | |
interface Window { | |
_common: { | |
restoreMedia: VoidFunction; | |
getMediaURL: () => Promise<string>; | |
hideMedia: VoidFunction; | |
}; | |
} | |
} | |
const createMediaWindow = async (silent?: boolean): Promise<Window> => { | |
if (windowMemory) { | |
!silent && window._common.restoreMedia(); | |
return windowMemory; | |
} | |
const location = await window._common.getMediaURL(); | |
windowMemory = window.open(location, '_blank', silent ? 'silent' : undefined); | |
if (!windowMemory) { | |
throw new Error('Security restrictions check failed'); | |
} | |
return windowMemory; | |
}; | |
const MediaWindow = () => { | |
const [externalWindow, setExternalWindow] = useState<Window | null>(null); | |
useEffect(() => { | |
return () => { | |
window._common.hideMedia(); | |
}; | |
}, []); | |
const createWindow = async () => { | |
const win = await createMediaWindow(); | |
setExternalWindow(win); | |
}; | |
const close = () => { | |
window._common.hideMedia(); | |
setExternalWindow(null); | |
}; | |
const root = externalWindow?.document.body; | |
return ( | |
<div> | |
{root ? ( | |
<button onClick={close}>Close portal</button> | |
) : ( | |
<button onClick={createWindow}>Open portal</button> | |
)} | |
{root && | |
createPortal( | |
<div | |
onClick={close} | |
style={{ width: 500, height: 500, background: 'green' }} | |
></div>, | |
root, | |
)} | |
</div> | |
); | |
}; | |
createRoot(document.getElementById('app')).render(<MediaWindow />); | |
// window warmup | |
createMediaWindow(true); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Forge Configuration | |
const path = require('path'); | |
const rootDir = process.cwd(); | |
module.exports = { | |
// Forge Plugins | |
plugins: [ | |
{ | |
name: '@electron-forge/plugin-webpack', | |
config: { | |
// Fix content-security-policy error when image or video src isn't same origin | |
// Remove 'unsafe-eval' to get rid of console warning in development mode. | |
devContentSecurityPolicy: `default-src 'self' 'unsafe-inline' data:; script-src 'self' 'unsafe-inline' data:`, | |
// Webpack Dev Server port | |
port: 3000, | |
// Logger port | |
loggerPort: 9000, | |
// Main process webpack configuration | |
mainConfig: path.join(rootDir, 'tools/webpack/webpack.main.js'), | |
// Renderer process webpack configuration | |
renderer: { | |
// Configuration file path | |
config: path.join(rootDir, 'tools/webpack/webpack.renderer.js'), | |
// Entrypoints of the application | |
entryPoints: [ | |
{ | |
// Window process name | |
name: 'app_window', | |
// React Hot Module Replacement (HMR) | |
rhmr: 'react-hot-loader/patch', | |
// HTML index file template | |
html: path.join(rootDir, 'src/renderer/index.html'), | |
// App Renderer | |
js: path.join(rootDir, 'src/renderer/appRenderer.tsx'), | |
// App Preload | |
preload: { | |
js: path.join(rootDir, 'src/renderer/preload.ts'), | |
}, | |
}, | |
{ | |
// Window process name | |
name: 'media_window', | |
// React Hot Module Replacement (HMR) | |
rhmr: 'react-hot-loader/patch', | |
// HTML index file template | |
html: path.join(rootDir, 'src/renderer/media.html'), | |
js: path.join(rootDir, 'src/renderer/empty.ts'), | |
}, | |
], | |
}, | |
devServer: { | |
liveReload: false, | |
}, | |
}, | |
}, | |
], | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<body> | |
<div id="app"></div> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { app, BrowserWindow, ipcMain, screen } from 'electron'; | |
declare const APP_WINDOW_WEBPACK_ENTRY: string; | |
declare const APP_WINDOW_PRELOAD_WEBPACK_ENTRY: string; | |
declare const MEDIA_WINDOW_WEBPACK_ENTRY: string; | |
const recalcBounds = (() => { | |
let displayMemory: Electron.Display | null = null; | |
return async () => { | |
console.log('\nBounds recalc started'); | |
const result = { isWindowChanged: false }; | |
const bounds = appWindow.getBounds(); | |
const display = screen.getDisplayMatching(bounds); | |
console.log('current main display', display.label); | |
console.log('current display bound', bounds); | |
if (display.id !== displayMemory?.id) { | |
displayMemory = display; | |
result.isWindowChanged = true; | |
mediaWindow.setBounds({ | |
height: display.bounds.height, | |
width: display.bounds.width, | |
x: display.bounds.x, | |
y: display.bounds.y, | |
}); | |
console.log('recalc success, display changed\n'); | |
} else { | |
console.log('recalc skipped\n'); | |
} | |
}; | |
})(); | |
let isMediaOpened = false; | |
const showMediaPage = async () => { | |
if (mediaWindow) { | |
await recalcBounds(); | |
mediaWindow.show(); | |
} | |
}; | |
ipcMain.handle('GET_MEDIA_URL', () => MEDIA_WINDOW_WEBPACK_ENTRY); | |
ipcMain.handle('RESTORE_MEDIA', () => { | |
isMediaOpened = true; | |
showMediaPage(); | |
}); | |
ipcMain.handle('HIDE_MEDIA', () => { | |
isMediaOpened = false; | |
mediaWindow.hide(); | |
}); | |
let appWindow: BrowserWindow; | |
let mediaWindow: BrowserWindow; | |
function createAppWindow(): BrowserWindow { | |
appWindow = new BrowserWindow({ | |
width: 500, | |
height: 500, | |
show: false, | |
movable: true, | |
webPreferences: { | |
nodeIntegration: false, | |
contextIsolation: true, | |
sandbox: true, | |
preload: APP_WINDOW_PRELOAD_WEBPACK_ENTRY, | |
}, | |
}); | |
appWindow.loadURL(APP_WINDOW_WEBPACK_ENTRY); | |
appWindow.on('ready-to-show', () => appWindow.show()); | |
appWindow.on('close', () => { | |
appWindow = null; | |
app.quit(); | |
}); | |
return appWindow; | |
} | |
app.on('ready', createAppWindow); | |
app.on('activate', () => { | |
if (BrowserWindow.getAllWindows().length === 0) { | |
createAppWindow(); | |
} | |
}); | |
app.on('window-all-closed', () => { | |
if (process.platform !== 'darwin') { | |
app.quit(); | |
} | |
}); | |
const getMainWindowDisplayBounds = (): Electron.Rectangle => { | |
if (!appWindow) { | |
throw new Error('Window is not defined'); | |
} | |
const bounds = appWindow.getBounds(); | |
const display = screen.getDisplayMatching(bounds); | |
return { | |
height: display.bounds.height, | |
width: display.bounds.width, | |
x: display.bounds.x, | |
y: display.bounds.y, | |
}; | |
}; | |
app.on('web-contents-created', (_, contents) => { | |
contents.setWindowOpenHandler(({ url, features }) => { | |
if (url === MEDIA_WINDOW_WEBPACK_ENTRY) { | |
app.once('browser-window-created', (_, window) => { | |
console.log('media window created'); | |
mediaWindow = window; | |
// window.setAlwaysOnTop(true, 'screen-saver') | |
window.setHiddenInMissionControl(true); | |
window.on('show', () => { | |
console.log('show'); | |
}); | |
window.on('hide', () => { | |
console.log('hide'); | |
if (!isMediaOpened) { | |
console.log('aborted'); | |
return; | |
} | |
/** | |
* We dont know will user back after hide event that was occured by | |
* Mission Control opening, so hiding and will open it on parent focus | |
*/ | |
window.hide(); | |
appWindow?.blur(); | |
appWindow?.once('focus', () => { | |
appWindow?.blur(); | |
mediaWindow.focus(); | |
console.log('parent focus'); | |
showMediaPage(); | |
}); | |
}); | |
appWindow.on('move', () => { | |
console.log('parent move'); | |
recalcBounds(); | |
}); | |
window.on('close', (event) => { | |
console.log('close'); | |
event.preventDefault(); | |
mediaWindow.hide(); | |
isMediaOpened = false; | |
}); | |
}); | |
const isSilent = features.includes('silent'); | |
return { | |
action: 'allow', | |
overrideBrowserWindowOptions: { | |
...getMainWindowDisplayBounds(), | |
hasShadow: false, | |
// resizable: false, | |
// alwaysOnTop: true, | |
enableLargerThanScreen: true, | |
roundedCorners: false, | |
fullscreen: false, | |
fullscreenable: false, | |
hiddenInMissionControl: true, | |
// window warmup | |
show: !isSilent, | |
paintWhenInitiallyHidden: true, | |
webPreferences: { | |
nodeIntegration: false, | |
contextIsolation: true, | |
sandbox: true, | |
webviewTag: false, | |
backgroundThrottling: false, | |
devTools: false, | |
}, | |
}, | |
}; | |
} | |
return { | |
action: 'deny', | |
}; | |
}); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>MEDIA</title> | |
<style> | |
body, | |
html { | |
margin: 0; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
body { | |
background: yellow; | |
} | |
</style> | |
</head> | |
<body></body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "example", | |
"main": ".webpack/main", | |
"version": "0.0.1", | |
"scripts": { | |
"start": "cross-env NODE_ENV=development electron-forge start", | |
"package": "electron-forge package", | |
"make": "electron-forge make", | |
"publish": "electron-forge publish", | |
"lint": "eslint src/ --ext .ts,.js,.tsx,.jsx" | |
}, | |
"config": { | |
"forge": "./tools/forge/forge.config.js" | |
}, | |
"devDependencies": { | |
"@electron-forge/cli": "7.2.0", | |
"@electron-forge/maker-deb": "7.2.0", | |
"@electron-forge/maker-rpm": "7.2.0", | |
"@electron-forge/maker-squirrel": "7.2.0", | |
"@electron-forge/maker-zip": "7.2.0", | |
"@electron-forge/plugin-webpack": "7.2.0", | |
"@marshallofsound/webpack-asset-relocator-loader": "^0.5.0", | |
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", | |
"@types/node": "^20.10.8", | |
"@types/react": "^18.2.47", | |
"@types/react-dom": "^18.2.18", | |
"@types/webpack-env": "^1.18.4", | |
"@typescript-eslint/eslint-plugin": "^6.18.1", | |
"@typescript-eslint/parser": "^6.18.1", | |
"@vercel/webpack-asset-relocator-loader": "^1.7.3", | |
"classnames": "^2.5.1", | |
"cross-env": "^7.0.3", | |
"css-loader": "^6.9.0", | |
"electron": "^29.1.4", | |
"eslint": "^8.56.0", | |
"eslint-import-resolver-alias": "^1.1.2", | |
"eslint-plugin-import": "^2.29.1", | |
"eslint-plugin-react": "^7.33.2", | |
"file-loader": "^6.2.0", | |
"fork-ts-checker-webpack-plugin": "^9.0.2", | |
"node-loader": "^2.0.0", | |
"react-refresh": "^0.14.0", | |
"sass": "^1.69.7", | |
"sass-loader": "^13.3.3", | |
"style-loader": "^3.3.4", | |
"ts-loader": "9.5.1", | |
"typescript": "^5.3.3", | |
"webpack": "^5.89.0" | |
}, | |
"dependencies": { | |
"react": "^18.2.0", | |
"react-dom": "^18.2.0" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { contextBridge, ipcRenderer } from 'electron'; | |
contextBridge.exposeInMainWorld('_common', { | |
restoreMedia: () => ipcRenderer.invoke('RESTORE_MEDIA'), | |
hideMedia: () => ipcRenderer.invoke('HIDE_MEDIA'), | |
getMediaURL: () => ipcRenderer.invoke('GET_MEDIA_URL'), | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@fauzank339 Hi. Thanks for your interest, I've added a config file. You may also need webpack config, you can get it there: https://github.com/codesbiome/electron-react-webpack-typescript-2024. i used this project for gist