Skip to content

Commit a898d77

Browse files
authored
Add files via upload
1 parent 82a4b9d commit a898d77

4 files changed

Lines changed: 142 additions & 0 deletions

File tree

config.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"topUrl": "https://awacs-portal2.icloud-prd.eu-west-1.aws.pmicloud.biz/speed/ro70-09-10/operators?plant=RO70&area=Secondary",
3+
"bottomUrl": "https://awacs-portal2.icloud-prd.eu-west-1.aws.pmicloud.biz/speed/ro70-11-12/operators?plant=RO70&area=Secondary",
4+
"dividerRatio": 0.55,
5+
"minWidth": 1200,
6+
"minHeight": 800,
7+
"userAgent": "",
8+
"lockToInitialOrigin": false
9+
}

main.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
2+
import { app, BrowserWindow, BrowserView } from 'electron';
3+
import path from 'node:path';
4+
import fs from 'node:fs';
5+
6+
let mainWindow, topView, bottomView, config;
7+
8+
// Mitigate blank window on some GPUs / VMs
9+
app.disableHardwareAcceleration();
10+
11+
function loadConfig() {
12+
const exeDir = path.dirname(app.getPath('exe'));
13+
const candidates = [
14+
path.join(exeDir, 'config.json'),
15+
path.join(process.cwd(), 'config.json')
16+
];
17+
for (const p of candidates) {
18+
try { if (fs.existsSync(p)) return normalize(JSON.parse(fs.readFileSync(p,'utf-8'))); } catch {}
19+
}
20+
return normalize({"topUrl": "https://awacs-portal2.icloud-prd.eu-west-1.aws.pmicloud.biz/speed/ro70-09-10/operators?plant=RO70&area=Secondary", "bottomUrl": "https://awacs-portal2.icloud-prd.eu-west-1.aws.pmicloud.biz/speed/ro70-11-12/operators?plant=RO70&area=Secondary", "dividerRatio": 0.55, "minWidth": 1200, "minHeight": 800, "userAgent": "", "lockToInitialOrigin": false});
21+
}
22+
23+
function normalize(cfg) {
24+
return {
25+
topUrl: cfg.topUrl || 'https://example.com',
26+
bottomUrl: cfg.bottomUrl || 'https://example.com',
27+
dividerRatio: Math.min(0.9, Math.max(0.1, cfg.dividerRatio ?? 0.55)),
28+
minWidth: cfg.minWidth ?? 1000,
29+
minHeight: cfg.minHeight ?? 700,
30+
userAgent: cfg.userAgent || '',
31+
lockToInitialOrigin: cfg.lockToInitialOrigin ?? false
32+
};
33+
}
34+
35+
function getOrigin(u) { try { const x = new URL(u); return `${x.protocol}//${x.host}`; } catch { return ''; } }
36+
37+
function guardNavigation(view, initialUrl) {
38+
if (!config.lockToInitialOrigin) return;
39+
const origin = getOrigin(initialUrl);
40+
view.webContents.on('will-navigate', (e, url) => { if (getOrigin(url) !== origin) e.preventDefault(); });
41+
view.webContents.setWindowOpenHandler((d) => { return (getOrigin(d.url) !== origin) ? {action:'deny'} : {action:'allow'}; });
42+
}
43+
44+
function layout() {
45+
if (!mainWindow || !topView || !bottomView) return;
46+
const [w,h] = mainWindow.getContentSize();
47+
const split = Math.round(h * config.dividerRatio);
48+
topView.setBounds({x:0,y:0,width:w,height:split});
49+
topView.setAutoResize({width:true,height:true});
50+
bottomView.setBounds({x:0,y:split,width:w,height:h-split});
51+
bottomView.setAutoResize({width:true,height:true});
52+
}
53+
54+
function createWindow() {
55+
config = loadConfig();
56+
57+
mainWindow = new BrowserWindow({
58+
width: 1200,
59+
height: 800,
60+
center: true,
61+
minWidth: config.minWidth,
62+
minHeight: config.minHeight,
63+
backgroundColor: '#111111',
64+
show: true,
65+
autoHideMenuBar: true,
66+
webPreferences: { nodeIntegration:false, contextIsolation:true, sandbox:true, backgroundThrottling:false }
67+
});
68+
69+
topView = new BrowserView({ webPreferences: { contextIsolation:true, sandbox:true } });
70+
bottomView = new BrowserView({ webPreferences: { contextIsolation:true, sandbox:true } });
71+
72+
mainWindow.setBrowserView(topView);
73+
mainWindow.addBrowserView(bottomView);
74+
75+
const loadOpts = config.userAgent ? { userAgent: config.userAgent } : undefined;
76+
77+
setTimeout(() => { try { mainWindow.show(); } catch {} }, 2000);
78+
79+
topView.webContents.loadURL(config.topUrl, loadOpts).catch(()=>{});
80+
bottomView.webContents.loadURL(config.bottomUrl, loadOpts).catch(()=>{});
81+
82+
guardNavigation(topView, config.topUrl);
83+
guardNavigation(bottomView, config.bottomUrl);
84+
85+
mainWindow.on('resize', layout);
86+
mainWindow.on('ready-to-show', () => { layout(); try { mainWindow.show(); } catch {} });
87+
88+
for (const wc of [topView.webContents, bottomView.webContents]) {
89+
wc.on('did-fail-load', (_e, code, desc, validatedURL) => {
90+
console.error('did-fail-load', code, desc, validatedURL);
91+
});
92+
wc.on('crashed', () => console.error('webContents crashed'));
93+
}
94+
}
95+
96+
app.whenReady().then(() => {
97+
createWindow();
98+
app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); });
99+
});
100+
101+
app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); });

package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "two-sites-viewer",
3+
"version": "1.0.1",
4+
"description": "Electron app: single window with two stacked websites",
5+
"main": "main.js",
6+
"private": true,
7+
"type": "module",
8+
"scripts": {
9+
"start": "set ELECTRON_ENABLE_LOGGING=1&& electron .",
10+
"build:win": "electron-builder --win portable"
11+
},
12+
"devDependencies": {
13+
"electron": "^31.0.0",
14+
"electron-builder": "^24.13.3"
15+
},
16+
"build": {
17+
"appId": "com.example.twositesviewer",
18+
"productName": "Two Sites Viewer",
19+
"files": [
20+
"main.js",
21+
"preload.js",
22+
"config.json",
23+
"package.json"
24+
],
25+
"win": {
26+
"target": [
27+
"portable"
28+
]
29+
}
30+
}
31+
}

preload.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// empty

0 commit comments

Comments
 (0)