I have a vue3 app with electron and need to send a variable from the ipcRenderer to my Vue3 page. I cant figure out how to do it especially given vue strips away lots of js. What im trying to do is to save the path of a folder which so far works fine, then display it in the vue3 app either in a span or whatever. I successfully got the value i need to display to the ipcRenderer but cant access it using my vue app.
Vue3 page
<q-btn
id="showpath"
dark
flat
size="xl"
label="show Path"
type="submit"
#click="showpath"
/>
</div>
export default {
name: "Settings",
props: {},
methods: {
loader() {
window.postMessage({
type: "select-dirs",
});
},
showpath() {
const testa = window.postMessage({ type: "pathtf"})
console.log("Vue page says :"+ testa)
},
},
};
</script>
All I get here is "undefined
Preloader script
const { ipcRenderer } = require('electron');
const settings = require('./settings');
process.once('loaded', () => {
window.addEventListener('message', evt => {
if (evt.data.type === 'select-dirs') {
ipcRenderer.send('select-dirs')
}
}),
window.addEventListener('message', evt => {
if (evt.data.type === 'pathtf') {
const pathtf = settings.gettfpath("pathtf")
console.log(pathtf)
}
})
})
The console.log in the preload file works and displays the value, but i cant get that value to my vue3 page.
Any tip? Thank you
I would suggest that you use electron's contextBridge to expose certain methods (send/receive) to the renderer process.
I do not know what your gettfPath method does but if that variable is available to you in the preloadjs file you should be able to expose it as a variable like so:
const {contextBridge} = require("electron");
contextBridge.exposeInMainWorld("electronApi", {
pathtf: settings.gettfPath()
});
With this your path will be exposed to your renderer process as window.electronApi.pathtf
Here are two resources you may find helpful:
How to use preload.js properly in Electron
https://medium.com/swlh/how-to-safely-set-up-an-electron-app-with-vue-and-webpack-556fb491b83 (This one may not be exactly what you are looking for but it has a good explanation / example of how to use ipc with view and electron)
Related
in my Game Launcher Electron project I was trying to open a subwindow with a buttonclick, and in there, offer additional features. These features include for example: opening exe files. For this I need node.js so I've tried to create methods for this in the preload script and expose them via contextBridge. These methods work fine in the main BrowserWindow. However, they are not reachable from the subwindow (Uncaught ReferenceError: game is not defined). See the code below:
preload.js
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
const { BrowserWindow } = require('electron');
contextBridge.exposeInMainWorld(
'game', {
open: () => {
console.log('test');
},
openAddGamePage: () => {
console.log('test');
}
}
);
renderer.js (script for main window), the exposed methods work fine here.
document.getElementById('openAddGameWindow').addEventListener('click', () => {
const childWindow = window.open('features/addGame/addGamePage.html', '_blank', 'nodeIntegration=yes')
game.open()
game.openAddGamePage()
})
addGame.js (script from addGamePage.html): The methods dont work here
document.getElementById('addGameButton').addEventListener('click', () => {
game.open()
})
I also tried to include the functions directly in the "addGame.js" and enable nodeIntegration when creating the child window, however this did not work either (require is not defined)
For my electron app, I would like to open another Svelte-window (or load different windows/components depending on the startup variables).
So let's say I used this tutorial to set up a basic structure, with my App.svelte looking like this:
<script>
const openLauncher = () => {
window.api.openWindow("launcher");
};
</script>
<button on:click={openLauncher}>Open Launcher</button>
As you can see, I added an IPC function to open a new window. The corresponding index.js:
const { app, BrowserWindow, ipcMain } = require("electron");
const { join } = require("path");
app.on("ready", () => {
const mainWindow = new BrowserWindow({
webPreferences: {
preload: join(__dirname, "./preload.js"),
}
});
mainWindow.loadFile(join(__dirname, "../public/index.html"));
});
ipcMain.on("openLauncher", (event, args) => {
const launcher = new BrowserWindow({
webPreferences: {
preload: join(__dirname, "./preload.js"),
}
});
launcher.loadFile(join(__dirname, "../public/launcher.html"));
});
preload.js:
const { contextBridge, ipcRenderer } = require("electron");
const API = {
openWindow: (obj) => ipcRenderer.send("openLauncher", obj),
}
contextBridge.exposeInMainWorld("api", API);
This does work and opens a new window with the launcher.html, but I can't figure out how to get the Svelte components working in that new file.
One idea I had was modifying main.js file so that the body component changes, like so:
import App from './App.svelte';
import LauncherApp from './LauncherApp.svelte';
const bodyID = document.getElementsByTagName('body')[0].id;
const app = {};
if (bodyID == "index") {
app = new App({
target: document.body,
});
}
else if (bodyID == "launcher") {
app = new LauncherApp({
target: document.body,
});
}
export default app;
This works for the main window (i.e. if I switch the IDs, it loads the correct component at startup) but since it doesn't load any Svelte whe opening the new window, this doesn't work.
So I would really appreciate any ideas on how to get Svelte to load for new/different windows/html-files! And if there is a way to do this with SvelteKit, even better!
Thank you in advance!
The obvious quick fix I see is using an #if block on bodyID in App.svelte containing two components, MainApp (content of App.svelte) and LauncherApp, and then simply changing bodyID depending on in which mode you are.
When using sveltekit I think it would make sense to treat LauncherApp as a separate route (I believe this is the only way to have "separated" pages with sveltekit, though I am not 100%). So when opening a new window you navigate the new instance of your application to the LauncherApp route. If you don't want the same base layout as in the main app, you can add a __layout.reset.svelte file.
I don't know why your solution didn't work, it was quite elegant.
As this post helped me alot to create seperate windows using Svelte and Electron, i just had to create my account after lurking Stack Overflow for years. I think i've cracked your problem without the "hackery" workaround.
I got it working without the #IF statements in my original App.svelte. This is how i did it:
My main.js in the src map (renderer side) is as follows:
import App from './App.svelte';
import Window2 from './Window2.svelte';
let bodyID = document.getElementsByTagName('body')[0].id;
let app;
if (bodyID == "index"){
app = new App({
target: document.body,
});
}
else if (bodyID == "window2"){
app = new Window2({
target: document.body,
});
}
export default app;
I think however that the real magic happens in my index.html and Window2.html. I made mine using the excellent YouTube videos provided by Tylerlaceby.
Basicly, the index.js in the main folder (so the Electron main js) opens the window with the following lines:
const main_window = new BrowserWindow({//Your settings
});
main_window.loadFile(join(__dirname, "../public/index.html"));
main_window.on("ready-to-show", main_window.show);
And the index.html that is in the public folder contains the following head:
`
<title>Main Window</title>
<link rel='icon' type='image/png' href='./favicon.ico'>
<link rel='stylesheet' href='global.css'>
<link rel='stylesheet' href='build/bundle.css'>
<script defer src='build/bundle.js'></script>
The body is empty, but has the id attached which i use in the main.js renderer side script.
I believe that the header in the index.html loads the build folder after the electron app has been build, containing all of your converted Svelte files.
The Window2.html is the same. The secondary window loads it the same way as the main window does in the second code block from the main.js on the electron side but refers to the Window2.html. The Window2.html contains a body with the ID Window2.
If the above is not the solution, it could also be because i use let instead of const. Once a variable has been assigned to const, it cannot be changed which might explain why it works the first time, but not the second time.
I'm sorry if i did not get all the formatting and refering to previous awnsers correctly. I'm still using to learn Stackoverflow but was eager to share how i made it work for me.
I have a blog run on Gatsby, and every time I push and deploy new blog posts I need to do a refresh on my page to see the new data.
I tried following the suggestions from this post and adding an onServiceWorkerUpdate function but that doesn't seem to have done anything.
Anyone have workarounds to this issue, and if so will there be a way to test locally? Changes already automatically update when I test in gatsby develop mode.
This is the entirety of my gatsby-browser.js file
export const onServiceWorkerUpdateReady = () => window.location.reload();
You need to install gatsby-plugin-offline first. Leaving your gatsby-config.js with something similar to:
{
plugins: [
{
resolve: `gatsby-plugin-manifest`,
options: {
...
}
},
'gatsby-plugin-offline'
]
}
Note: plugin's order matters in this case.
The plugin will register a service worker and will load it into the client.
Then, in your gatsby-browser.js file you can simply add:
export const onServiceWorkerUpdateReady = () => {
const answer = window.confirm(
`This application has been updated. ` +
`Reload to display the latest version?`
)
if (answer === true) window.location.reload()
}
Additionally, you may want to add the following hack (very common across the repositories):
export const onRouteUpdate = () => {
navigator.serviceWorker.register('/sw.js').then((reg) => {
reg.update();
});
};
Basically, it forces the registration of the service-worker across the site upon the onServiceWorkerUpdateReady refresh.
Check this interesting thread for further caveats and workarounds for some specific scenarios: https://github.com/gatsbyjs/gatsby/issues/9087#issuecomment-774680408
It will not work using Link provided by gatsby
import { Link as GatsbyLink } from 'gatsby';
<GatsbyLink to={to} {...props} />
It works using traditional a tag:
<a href={props.to} target="_blank">
{props.children}
</a>
I am currently building an electron app in electron version 5.0.0
I am using electron's power monitor functionality but this can only beaccess from the main electron js file but I need to let a vue component know. I tried using event bus
powerMonitor.on('lock-screen', () => {
console.log("locked")
EventBus.$emit('logout');
})
})
but it looks like evenbus only works within vue components. Anyone got any ideas on how i can just send an even from the electron main js file into a vue component
Okay I finally figured out how its done.
You have to use the ipcRenderer listener wil using webContents to send the event. It should look something like this.
//main.js
powerMonitor.on('unlock-screen', () => {
console.log("unlocked")
win.webContents.send('computer-unlock')
})
//Vue Component
require('electron').ipcRenderer.on('computer-unlock', () => {
console.log("logging in");
_this.computerLocked = false;
})
I am very new to the electron. Can anyone suggest me how to get a local folder's relative path using the electron? JavaScript does not have that capability.
I have a Choose File button(see snapshot), so my question is that when I select a folder and click on the open button then it should return a whole directory path.
As #phuongle pointed out in the comments you want to use showOpenDialog(). Something like this:
var remote = require('remote');
var dialog = remote.require('electron').dialog;
var path = dialog.showOpenDialog({
properties: ['openDirectory']
});
UPDATE: If the above isn't working for your current Electron version, you should try more modern importing:
const {dialog} = require('electron').remote;
In addition, in order to use remote, you need to set enableRemoteModule when creating your window in your main process:
const myWindow = new BrowserWindow({
webPreferences: {
enableRemoteModule: true
}
});
Following the official IPC tutorial worked for me
main process:
import {dialog, ipcMain} from 'electron'
function createWindow () {
mainWindow = new BrowserWindow({/*Your electron window boilerplate*/})
ipcMain.handle('dialog:openDirectory', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
properties: ['openDirectory']
})
if (canceled) {
return
} else {
return filePaths[0]
}
})
}
preload script:
import {contextBridge, ipcRenderer} from 'electron'
contextBridge.exposeInMainWorld('myAPI', {
selectFolder: () => ipcRenderer.invoke('dialog:openDirectory')
})
Now you can call the selectFolder method from your application code and get the user input.
window.myAPI.selectFolder().then(result=>{/* Do something with the folder path*/})
In Electron we can select the directory by specifying simple input element with type="file" and webkitdirectory attribute'.
<input id="myFile" type="file" webkitdirectory /> and we can get the directory full path with the path property of File object document.getElementById("myFile").files[0].path
You would use Node's path.relative for that.
The solution for me was simply using all in lowercase, with a value true as string in my react component. No extra configuration was required.
Like so:
<input
id="path-picker"
type="file"
webkitdirectory="true"
/>
Edit
It turns out that, as mentioned by #cbartondock, it will recursively look for files in the directory, which is not good!
I ended up using the required electron remote's dialog.