Electron: how to open an url in the same window and after the user do some action go back to application in the same window - javascript

I have an Electron app that is displayed using BrowserWindow. I want to open an external URL in the same window so that the user can log in (to an external website) and after the user logs in it should display the Electron application again instead of the external website that the user used to log in.
I've been able to open the external url in the same window by using:
<a href="https://loginsite-example.com" target="_blank" rel="noreferrer">
site where you have to log in
</a>
However, I don't know how to show the Electron application again after the user successfully logs in to the external website. Also, I would like to keep the session from the external website so that I could consume its API inside the electron application.

Moving between window sources, whether it be local (file) or remote (URL) can be accomplished by just calling window.loadFile(...) or window.loadURL(...), but only after the instance of the window has been created.
main.js (main thread)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
// Prevent garbage collection
let window;
function createWindow() {
return new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
}
function showMainWindow() {
window.loadFile('index.html')
.then(() => { window.show(); })
}
function showLoginWindow() {
// window.loadURL('https://www.your-site.com/login')
window.loadFile('login.html') // For testing purposes only
.then(() => { window.show(); })
}
electronApp.on('ready', () => {
window = createWindow();
showMainWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// ----- IPC -----
electronIpcMain.on('message:loginShow', (event) => {
showLoginWindow();
})
electronIpcMain.on('message:loginSuccessful', (event, session) => {
showMainWindow();
})
index.html (render thread)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Main Window</title>
</head>
<body>
<div>Main Window</div><hr>
<button type="button" id="show-login">Login</button>
</body>
<script>
document.getElementById('show-login').addEventListener('click', () => {
window.ipcRender.send('message:loginShow');
});
</script>
</html>
login.html (render thread)
Used for testing purposes only as we do not have access to a real login page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login Window</title>
</head>
<body>
<div>Login Window</div><hr>
<label for="username">Username: </label>
<input type="text" id="username"><br>
<label for="password">Password: </label>
<input type="password" id="password"><br>
<button type="button" id="login">Login</button>
</body>
<script>
// For testing purposes only.
document.getElementById('login').addEventListener('click', () => {
window.ipcRender.send('message:loginSuccessful');
});
</script>
</html>
And finally, a preload.js script to communicate safely between the main thread and render thread(s).
preload.js (main thread)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'message:loginShow',
'message:loginSuccessful'
],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
A problem that will need to be overcome is once you submit your login credentials to the server, how are you going to get the session data? Your html Javascript will need to detect if and when the necessary session data is available. Once the session data is available, it is an easy process of transferring the session via IPC from the render thread to the main thread. To figure this out, one will require some additional information and understanding of your login system (a separate StackOverflow question).
As a pre-cursor, I suspect one would need to detect in the main thread when the login page has been submitted to the server via something like window.webContents.on('did-navigate', ...). Once detected, check the next loaded page quickly to see if a session exists. If so, get it, send it to the main thread and then redirect back to the index.html page.
I think there should be an easier way if you can login via an API. Then the whole process can be self-contained in your Electron application. IE: Show local (file) login.html, submit data to server and await a "success" or "fail" response. If successful, pass the session data in the response. If unsuccessful, display an appropriate error message.

Related

document.getElementById("myFile").value gets undefined using electron

I have a very basic html file (using electron);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> File Uploader </title>
<link rel="stylesheet" href="style.css">
<script defer src="render.js"></script>
</head>
<body>
<h1>Drive File Uploader</h1>
<input type="file" id="myFile" name="myFile">
<button onclick="FileUploadPing()">Upload your file</button>
</body>
</html>
and an event listener named render.js;
const ipcRenderer = require("electron").ipcRenderer;
const FileUploadPing = () => {
var input = document.getElementById("myFile").value
if (input) {
ipcRenderer.send("FileUploadPing",inputVal);
}else{console.log("no path value")}
};
ipcRenderer.on("FileRecievePing", (event, data) => {
alert(data)
});
But when I click submit, document.getElementById("myFile").value returns undefined
how can I pull that value?
This is an interesting issue that confronts many people using Electron. One could either use (via Electron) the native OS dialog.showOpenDialog([browserWindow, ]options) dialog or the html <input type="file"> tag.
To circumvent the need to manage the is prefixed with C:\fakepath\ issue, it is often better to use the native approach. After all, that is what Electron is best at.
Let me show you how to quickly set up a html button that when clicked, will open the native file selector dialog, and when a path is selected, return the path to the render thread for display.
In the below code, we will be using a preload.js script configured to communicate (using IPC) between the main thread and render thread(s). Context Isolation will describe this in more detail.
First off, let's code the main.js file which will include the creation of the native file dialog.
main.js (main thread)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronDialog = require('electron').dialog;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
// Prevent garbage collection
let window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 1000,
height: 700,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html')
.then(() => { window.show(); })
return window;
}
electronApp.on('ready', () => {
window = createWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// Open the file dialog
electronIpcMain.on('message:openDialog', (event) => {
let options = {
title: 'Select File',
properties: ['openFile']
};
electronDialog.showOpenDialog(window, options)
.then((result) => {
if (result.canceled) {
console.log('User cancelled file dialog.');
return;
}
event.reply('message:dialogPath', result.filePaths[0]);
})
.catch((error) => { console.error(error); });
})
Now, let's create the index.html file, which for the sake of simplicity, also includes the Javascript within the <script> tags.
NB: Instead of deferring your script in the <head>, you can include it just before the closing <html> tag. Placing it here effectively performs the same thing as defer in the <head> <script> tag.
index.html (render thread)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Drive File Uploader</title>
</head>
<body>
<div>Drive File Uploader</div>
<hr>
<label for="path">Path: </label>
<input type="text" id="path" name="path">
<input type="button" id="open-dialog" name="open-dialog" value="...">
<input type="button" id="upload" value="Upload">
</body>
<script>
// Let's declare it as it is used more than once
let filePath = document.getElementById('path');
// Event listeners
document.getElementById('open-dialog').addEventListener('click', () => { window.ipcRender.send('message:openDialog'); });
document.getElementById('upload').addEventListener('click', () => { console.log(filePath.value); });
// IPC message from the main thread
window.ipcRender.receive('message:dialogPath', (path) => { filePath.value = path; })
</script>
</html>
Finally, let's add the preload.js script to allow the main thread and render thread(s) to safely communicate with each other.
Note: This is where we define our whitelisted channel names.
preload.js (main thread)
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'message:openDialog'
],
// From main to render.
'receive': [
'message:dialogPath'
],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
Hopefully, the above outlines how simple it can be to use (via Electron) the native dialog boxes. The benefit being they have OS specific functionality and feel.
I dont know what I did different, but when I tried this it suddenly worked.
main.js;
const { app, BrowserWindow, ipcMain } = require("electron");
let win = null;
const createWindow = () => {
win = new BrowserWindow({
width: 800,
height: 600,
resizable: true,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
nativeWindowOpen: true,
},
});
win.loadFile("index.html");
};
app.whenReady().then(createWindow);
render.js;
const ipcRenderer = require("electron").ipcRenderer;
const {uploadFileToCloud} = require("C:/Users/efeba/desktop/pythonAndJS/ITGSProject/app.js")
const FileUploadPing = () => {
var name = document.getElementById("myFile").value
name = name.replace("C:\\fakepath\\","")
console.log(name)
var path = document.getElementById("myFile").files[0].path
path = path.replace(name,"")
path = path.replaceAll("\\", "/") // beautify the string
console.log(path)
if (path) {
uploadFileToCloud(name, path)
alert("File Uploaded")
}else{console.log("no path value")}
};
//file uploads but content is empty
//I suspect pulling the libraries in renderer has some problems, will
//try to make it by sending a ping to main.js
index.html;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title> File Uploader </title>
<link rel="stylesheet" href="style.css">
<script defer src="render.js"></script>
</head>
<body>
<h1>Drive File Uploader</h1>
<input type="file" id="myFile" name="myFile">
<button onclick="FileUploadPing()">Upload your file</button>
</body>
</html>
Hope I could help

Sending message from Main to Renderer

Could someone help me out here. I'm completely confused on how to solve this. I've now spent about a week trying to find a solution for this but have come up short and there appears to be a lack of a solid solution online. I've made a github repository trying to demonstrate the issue.
In short I've implemented a status bar in my application which i want to populate with various string messages. These messages would be sent from functions that are contained inside a js file that imports electron, which mean's it doesn't have direct access to the Renderer. So how would I send these messages to the Renderer. I'm assuming this needs to be done using the ContextBridge, but i have no clue how to successfully do this, so if your response is just linking me to the context bridge docs, don't bother, lol I've exhausted myself looking at that. The other alternative i was considering is using a custom event but but i'm not sure that would solve the problem either.
Here is a sample of what im trying to do along with repo on github. If you do a pull-request to fix the repo, ill gladly merge and keep the repo public for others to benefit from and share with the community. https://github.com/JokerMartini/statusbar
As a minor problem, im not sure why i can no longer call getPath from 'app' from within a js file that's not loaded into the render thread.
I trigger a method from Renderer
index.vue
const doWork = () => {
window.messenger.doWork();
}
electron-preload.js
import { contextBridge } from "electron";
const messenger = require("../src/helpers/messenger");
contextBridge.exposeInMainWorld("messenger", messenger);
messenger.js
const { app } = require("electron");
const path = require("path");
// using electron module to demonstrate this file can't be imported into renderer
export function showMessage(msg) {
const dir = path.join(app.getPath("documents"), "presets");
console.log(dir);
// TODO: send message to renderer...
}
export function doWork() {
console.log("Doing working...");
// step 1: long process
showMessage("Processing step 1...");
// step 2: long process
showMessage("Processing step 2...");
// step 3: long process
showMessage("Processing step 3...");
}
I'd like to display the messages sent from the main to renderer to be displayed in the status bar of
main.vue
<q-footer>
<q-bar>
<span class="text-caption">Show message here...</span>
</q-bar>
</q-footer>
** UPDATE 01 **
For some reason my message is not being received in the Renderer. Here are my code changes
electron-preload.js
import { contextBridge, ipcRenderer } from "electron";
contextBridge.exposeInMainWorld("electronAPI", {
setStatus: (callback, func) =>
ipcRenderer.on("set-status", (event, ...args) => func(...args)),
});
index.vue
<template>
<q-page class="flex flex-center">
<q-btn label="Show Message" #click="doWork" />
</q-page>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
setup() {
// send message for testing...
const doWork = () => {
window.electronAPI.setStatus("sfsfsdfsd");
};
// recieve status messages...
window.electronAPI.setStatus("set-status", (data) => {
console.log("STATUS:", data);
// TODO $store.dispatch("....");
});
return {
doWork,
};
},
});
</script>
A technique that works for me is not to use the preload.js script to define concrete implementations. Instead, I use the preload.js script to only define channel (names) that I can communicate with between the main and render threads. IE: Seperating your concerns. Implement your concrete functions within your main thread scripts and render thread scripts.
preload.js
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [],
// From main to render.
'receive': [
'message:update' // Here is your channel name
],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
Note: Though I do not use Vue.js, you should get the gist of the below two files.
main.js (main thread)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const nodePath = require("path");
let window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html')
.then(() => { window.show(); });
return window;
}
electronApp.on('ready', () => {
window = createWindow();
// Send a message to the window.
window.webContents.send('message:update', 'Doing work...');
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
index.html (render thread)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<span id="text-caption">Show message here...</span>
</body>
<script>
// Listen for message updates from the main thread.
window.ipcRender.receive('message:update', (message) => {
document.getElementById('text-caption').innerText = message;
});
</script>
</html>
You want to be using ipcMain (in the main process) and ipcRenderer (in the renderer process). If you compare your scripts to the examples at https://www.electronjs.org/docs/latest/tutorial/ipc that is what is missing.
(There is a section specifically on doing main to renderer.)
This did use to be clearer and simpler, but carried more potential for abuse. So best to ignore any online tutorials older than a year or so. (Though contextBridge is quite new, so if they mention it then they should be recent.)

ipcRenderer data not sent to ipcMain in Electron

I recently started developing desktop application using electron.
I want to send form details to main.js from the index.html on button click event. I have added a listener to the button in index.js. Searched online and found that I have to use ipcMain in main.js and ipcRenderer in index.js but the data is not being sent to ipcMain .
How do I get the form data in main.js ?
In index.html
<div class="btn-div fld">
<button id="loginButton" class="btn btn-primary st-btn" type="submit" name="button">Login</button>
</div>
<script src="index.js" charset="utf-8"></script>
In index.js
document.querySelector("#loginButton").addEventListener('click', function(){
userDetails = document.getElementById('login');
username = userDetails.username.value;
password = userDetails.password.value;
console.log("Hello");
const {ipcRenderer} = require('electron');
ipcRenderer.send('asynchronous-message', username);
})
In main.js
const { app, BrowserWindow , ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
console.log( arg );
});
While creating a browser window in electron using new BrowserWindow(options) where options is an object. Define the object as:
options = {
webPreferences: {
preload: preload.js, //You need to create a file named preload.js (or any name) in your code
nodeIntegration: true,
contextIsolation: false,
}
}
Now in a new file called preload.js:
window.ipcRenderer = require('electron').ipcRenderer;
In your snippet you added const { app } ... which should be done this way to inject the javascript using a preload property in the object.
Now in the main app.js file (whatever you named maybe index.js) where you created the browser window:
const ipc = require('electron').ipcMain; //Add to your pre-existing code
ipc.on("close-app", (event, message) => { //"close-app" can be anything but, you need to use the same key in the send message side (later in this answer)
browserWindow.close(); //If you named the browserwindow as browserWindow
});
Now in your HTML (i.e., send message side)
...
<script>
window.ipcRenderer("close-app", ""); //Second parameter is used if you want to send some extra message. The extra message can be viewed in the server side from the message parameter in the app.js code (just above this paragraph)
</script>
This is a bit difficult if you are doing it for the first time.
I've added more articles which will help you clear your confusions:
A related answer at StackOverflow
Relation with socket.io communication in NodeJS
While I have seen that the other answer to this question may have worked for others, it did not work for me... I was using webpack and, for the life of me, could not get it to work even when adding ExternalsPlugin commonjs and electron. The following worked instead:
main.js
ipcMain.on("download", (event, info) => {
info.properties.onProgress = status => win.webContents.send("downloadProgress", status);
});
preload.js
contextBridge.exposeInMainWorld('electron', {
api: {
//receiving message from main.js
responseProgress: (channel, func) => {
let validChannels = ["downloadProgress"];
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
},
process: (channel) => {
//example for process that is called by button in reactjs etc
}
});
ReactComponent.js
function ReactComponent() {
useEffect(() => {
//retrieving information from preload.js originally sent from main.js
window.electron.api.responseProgress("downloadProgress", (progress) => {
console.log(progress);
console.log(progress.percent);
});
}, []);
return (
//example of calling api on button click
<button onClick={() => {
window.electron.api.process("toMain");
}}>Download</button>
)
}

How to securely preload content in the main process before creating the main window?

I'm building an App (Electron based) where I need to get an information from a third party website before the main window is created, but I'm a little bit confused about security measures. I'm using axios to do the HTTP request inside the main process because it is promise based and I can create the window after the website is fetched. My concerns are:
Enabling nodeIntegration is not good when messing with the renderer process because of cross-site-scripting attack. Should I include all nodejs modules in a preload.js like the following, for example.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="script-src 'self';">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Viewer</title>
</head>
<body>
<div id="box">
<form id='fo'>
<input type="text" id="num">
<button type="button" id="bttn">Random</button>
</form>
</div>
<script src="renderer.js"></script>
</body>
</html>
main.js
const electron = require('electron');
const cheerio = require('cheerio');
const axios = require('axios').default;
const path = require('path');
const {app, BrowserWindow, ipcMain, Menu, MenuItem,session} = electron;
let win;
let url = 'sampletext';
function createWindow() {
win = new BrowserWindow({
width: 400,
height: 250,
webPreferences:{
nodeIntegration: false,
contextIsolation: true,
preload: path.join(app.getAppPath(), 'preload.js')
},
show: false,
});
win.loadFile('index.html');
win.once('ready-to-show', () =>{
win.show();
});
win.on('closed', () =>{
win = null;
});
}
app.whenReady().then(getRequest().then(res => {
const $ = cheerio.load(res);
if($('infoNeeded')){
random = get_numbers($('infoNeeded').attr('href'));
}
createWindow();
}));
app.on('window-all-closed', () =>{
app.quit();
});
function getRequest() {
return axios.get(url).then(res => res.data).catch(err => console.log(err));
}
preload.js
//Instead of using getRequest() on main.js use this file
const electron = require('electron');
const remote = require('electron').remote;
const cheerio = require('cheerio');
const axios = require('axios').default;
let url = 'sampletext';
//So I can use it in renderer.js
window.getReq = function () {
return axios.get(url).then(res => res.data).catch(err => console.log(err));
}
window.parseInfo = function (data) {
const $ = cheerio.load(data);
if($('infoNeeded')){
return random = get_numbers($('infoNeeded').attr('href'));
}
return;
}
//Preload first request
window.getReq().then(doStuffHere);
renderer.js
let info;
//Keep updating the info
setInterval( () =>{
window.getReq().then(data => {
info = window.parseInfo(data);
});
}, 10000);
1) Is it ok to do nodejs require inside main process? If not, what's the secure way of doing it?
2) May I make HTTP requests inside main process? If yes, should I send a CSP header when doing so?
3) Instead of doing the request inside the main.js, should I use "webPreferences: preload" property and make the first HTTP request inside preload.js (Just like the above example) ? (I need to get the info before sending it to renderer.js)
I've already read https://www.electronjs.org/docs/tutorial/security, but I couldn't grasp their teaching. If you could provide an answer for how and when to use preload.js and CSP header I'll be very grateful.
Yes it is ok to use node.js require in the main process(use any library with error handling, cause it may crash the app)
You can make an HTTP request from the main process
You can use Preload.js if you need the code execution result in the renderer process.(You can also use the ipc)

Electron showOpenDialog arrow function (event.send) not working

I'm following the dialog example for opening files from: https://github.com/electron/electron-api-demos
I copied the code from the example. The open file dialog does in fact work and I'm able to select a file but can't figure out why the arrow function to send the file path back to renderer doesn't work(nothing is logged with console.log).
Can anyone spot what's wrong?
The project was started using electron-forge and my OS is linux.
Thanks
index.js
const { app, BrowserWindow, ipcMain, dialog, } = require('electron');
require('electron-reload')(__dirname);
const path = require('path');
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
app.quit();
}
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});
// and load the index.html of the app.
mainWindow.loadFile(path.join(__dirname, 'index.html'));
// Open the DevTools.
mainWindow.webContents.openDevTools();
};
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.
ipcMain.on('open-file-dialog', (event) => {
dialog.showOpenDialog(
{
properties: ['openFile',]
},
(files) => {
console.log('ok')
if (files) {
event.sender.send('select-file', files)
}
})
})
index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div>
<div>
<button class="demo-button" id="select-directory">Select file</button>
<span class="demo-response" id="selected-file"></span>
</div>
<br><br>
</div>
<script>
const electron = require('electron')
const { ipcRenderer } = electron
const selectDirBtn = document.getElementById('select-directory')
selectDirBtn.addEventListener('click', (event) => {
ipcRenderer.send('open-file-dialog')
})
ipcRenderer.on('select-file', (event, path) => {
console.log(path)
document.getElementById('selected-file').innerHTML = `You selected: ${path}`
})
</script>
</body>
</html>
The dialog API has been modified with the release of Electron 6.
dialog.showOpenDialog() and other dialog functions now return promises and no longer take callback functions. There also are synchronous counterparts which return the selection result in a blocking fashion, e.g. dialog.showOpenDialogSync().
Example usage (in renderer process)
const remote = require("electron").remote
const dialog = remote.dialog
dialog.showOpenDialog(remote.getCurrentWindow(), {
properties: ["openFile", "multiSelections"]
}).then(result => {
if (result.canceled === false) {
console.log("Selected file paths:")
console.log(result.filePaths)
}
}).catch(err => {
console.log(err)
})
As of February 2020, the electron-api-demos use Electron 5. That is why their dialog calling code still uses the old form.

Categories

Resources