I am beginner to Electron, and I am developing an educational game. When the user clicks the start button, I want the main process to fetch information from the storage folder (I am ok with data being open to users). The main process accomplishes that without a problem.
main.js
ipcMain.on('load-sets', (event) => {
const directoriesInDIrectory = fs.readdirSync(folderName, { withFileTypes: true })
.filter((item) => item.isDirectory())
.map((item) => item.name);
event.sender.send("loaded-sets", directoriesInDIrectory);
})
Then, I use ipcRenderer in preload.js to receive the message.
preload.js
ipcRenderer.on("loaded-sets", (event, package) => {
window.loaded_sets = [];
for (var i = 0; i < package.length; i++) {
window.loaded_sets[i] = package[i];
}
})
Finally, I expose the sets via contextBridge:
contextBridge.exposeInMainWorld("api", {
LoadFiles,
quit,
sets: () => window.loaded_sets,
sentToRender,
})
However, when I run the following code in render.js:
console.log(window.api.sets())
it outputs undefined.
I've already tried using postMessage and eventListeners associated with it. Also, I've tried to get the variable via another function:
function sentToRender() {
return window.loaded_sets;
The function was also exposed and could be called in the renderer process. Yet, the output was still undefined.
For those wondering why I won't send the data straight to the renderer, the renderer returns error when I try require ipcRenderer and I heard that it is a good practice to navigate data through preload. Is there a solution?
In main.js
ipcMain.on('load-sets', (event) => {
const directoriesInDIrectory = fs.readdirSync(folderName, {
withFileTypes: true })
`enter code here`.filter((item) => item.isDirectory())
.map((item) => item.name);
event.sender.send("loaded-sets", directoriesInDIrectory);
})
In preload.js
contextBridge.exposeInMainWorld("api", (package)=> ipcRenderer.on("loaded-sets", (package)))
In renderer.js
window.api((event, package) => {
window.loaded_sets = [];
for (var i = 0; i < package.length; i++) {
window.loaded_sets[i] = package[i];
}
console.log(window.loaded_sets)
})
Add event emitting from renderer process if it’s wasn’t done.
ipcRenderer.send(“load-sets”);
To make the function work on main process side.
But if you don’t want to send the event from renderer process, then remove ipcMain listener and just send the event by doing
yourRenderedWindow.webContents.send("loaded-sets", directoriesInDIrectory);
Related
I'm creating a button to record a canvas using FFMPEG. Here's the code that finalizes the download process.
const recordButton = document.querySelector("#record")
recordButton.addEventListener('click', function () {
function startRecording() {
const { createFFmpeg } = FFmpeg;
const ffmpeg = createFFmpeg({
log: true
});
var transcode = async (webcamData) => {
var name = `record${id}.webm`;
await ffmpeg.load();
await ffmpeg.write(name, webcamData);
await ffmpeg.transcode(name, `output${id}.mp4`);
var data = ffmpeg.read(`output${id}.mp4`);
var video = document.getElementById('output-video');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
dl.href = video.src;
}
fn().then(async ({ url, blob }) => {
transcode(new Uint8Array(await (blob).arrayBuffer()));
})
...
id += 1}
The problem arises with the transcode variable. While the button works initially, every other attempt (on a single page load) fails just the async function. I'm not well versed enough in the function to know why it would only work once. That said, I do know it is the only bit of code that does not fire off upon second attempt.
It could be a few things. This is borrowed code, and I've repurposed it for multiple uses. I may have messed up the declarations. It may be an async issue. I tried to use available values to rig up a secondary, similar function, but that would defeat the purpose of the first.
I tried clearing and appending the DOM elements affected, but that doesn't do anything.
It seems to have something to do with:
await ffmpeg.load();
While FFMPEG has to download and load the library initially, it does not have to do so the second initialization. That my be the trigger that is not activating with successive uses.
In my gulpfile, I have a task that processes all of my pages and another task that watches my pages for changes and processes just the changed page. It looks like this:
const buildPages = path => cb => {
gulp.src(path)
// some lines of piping
.pipe(gulp.dest(pages.dist));
cb();
}
const watchPages = () =>
gulp.watch(pages.src).on('change', path =>
gulp.src(path)
// the same piping as above
.pipe(gulp.dest(pages.dist))
);
The .on() method of the chokidar watcher object returned by gulp.watch() does not receive a callback function, while the gulp task above it requires one. So to remove code duplication, I can do this:
const buildPages = path =>
gulp.src(path)
// some lines of piping
.pipe(gulp.dest(pages.dist));
const buildPagesWithCallback = path => cb => {
buildPages(path)
cb();
}
const watchPages = () =>
gulp.watch(pages.src).on('change', path =>
buildPages(path)
);
Is this the right way to go about it, or is there a way to remove duplication without creating an extra function (perhaps by making the watcher receive a callback)?
Not sure what's your other requirements/needs, but given your description, I would usually have a setup like this (assuming you're using gulp 4):
const src = [ './src/pageA/**/*.js', ...others ];
const dist = './dist';
const buildPages = () => gulp.src(src).pipe(...).pipe(gulp.dest(dist));
const callback = () => { /* do stuff */ };
exports.buildPageWithCallback = gulp.series(buildPages, callback);
exports.watchPages = () => gulp.watch(src, gulp.series(buildPages));
exports.watchPageWithCallback = () => gulp.watch(src, gulp.series(buildPages, callback));
I would use gulp.series to run callback without explicitly passing a callback to a task maker function, and pass a task directly to gulp.watch.
If your requirements demand buildPages to take in a path, then I think the way you're doing it is right, sorry if I misunderstand the question
I face quite an extraordinary side effect I never faced before. Let me describe it thoroughly:
Client (browser) has an input tag, where files are being chosen. On clicking a specific button, an action emits, where those files are being transmitted to the server by websockets (by library Socket.io).
// Starts an upload after clicking on an upload button.
async upload() {
if (this.state.fileList.length === 0) { return; }
// Dispatch a start uploading action.
this.props.startUp();
// We map original files from the state's file list.
let originFiles = _.map(this.state.fileList, (file: any) => {
return file.originFileObj;
});
// Send files over the socket streams to the server.
await ApiService.upload(originFiles, () => {
// Update the state whenever a file has been uploaded.
this.setState({ ...this.state, finishedFiles: this.state.finishedFiles + 1 })
});
this.clearItemList();
// Dispatch an end uploading action.
this.props.endUp();
}
This function is called whenever the button is clicked. As you can see, there is an api service, that gets called on that filelist, and streams those files to the server. The files are streamed through the sockets.
import * as io from 'socket.io-client';
import * as ios from 'socket.io-stream';
export function upload(data: any[], onUpload?: () => void): Promise<any> {
return new Promise((res, rej) => {
const up = io.connect("ws://localhost:8000/");
// Right after we connect.
up.on('connect', () => {
// Connect to the upload socket point.
up.emit('upload', {});
// Whenever we receive an 'ok' status, we send files over the wire.
up.on('ok', async () => {
// If we sent all the files, notify the server to end.
if (data.length === 0) {
up.emit('end', {});
up.disconnect();
// Resolve this promise.
res();
// Otherwise, emit a 'data' action, that sends the files.
} else {
let blob = data.pop();
let stream = ios.createStream();
ios(up).emit('data', stream, { size: blob.size });
ios.createBlobReadStream(blob, { highWaterMark: 500000 }).pipe(stream);
// Callback for the onUpload event.
onUpload();
}
});
up.on('error', () => {
rej();
});
});
});
}
Everything works well, until I switch tabs on the client (browser) and the progress gets paused. After I switch to the client tab, the progress automatically resumes.
My colleague presumed, this might be a problem with a browser itself, which stops the files to be piped whenever I lose focus of the tab.
Any ideas on how to solve this problem and/or tweak the code a bit will be much appreciated.
Thank you.
It doesn't pause the stream, it slows it down only. check this option in Chrome: "--disable-background-timer-throttling"
I am new to react-native development and using RNFetchBlob to work with files in internal storage, I wanted to create a gallery application and for that I need to fetch all the images present in the phone.
What i was able to do is fetch all files from a particular directory and search for images in it.
RNFetchBlob.fs.ls(RNFetchBlob.fs.dirs.DCIMDir).then((files) => {
//Returns all files present in the directory
console.log(files);
//Returns files with .png or .jpg extension.
console.log(files.find((element) => {return element.match('/(.png)$|(.jpg)$|(.jpeg)$/g')}));
}).catch((err) =>{
console.log(err);
});
But with this approach I need to search in every directory by iterating into them with recursion, I wanted to know if there is some way fetching all the image files just with one command.
We can have a recursive function something like below. I assume there must be some other optimized way but I ended up doing this. Besides we can cut off traversal inside directory if lastModified has not been changed.
async function findAllFilesByExtension(path = INTERNAL_STORAGE_PATH) {
let files = [];
const lsList = await RNFetchBlob.fs.lstat(path);
if (!!lsList && Array.isArray(lsList)) {
let dirs = [];
lsList.forEach(item => {
if (item.type === 'directory') {
dirs.push(item);
return;
}
if (item.type === 'file' && item.filename.match('/(.png)$|(.jpg)$|(.jpeg)$/g')) {
files.push(item)
return;
}
});
const response = await Promise.all(unmodifiedDirs.map(item=>findAllFilesByExtension(item.path)));
response.forEach(nestedFiles => {
files = [...files, ...nestedFiles];
});
}
return files;
}
What is the best way to manipulate DOM within an electron app?
I made some tutorials from docs using ipc and webcontents with no luck
My app is so simple, I just want to use the web like a console and showing messages (render proc) comming from the results of several sync functions (main proc)
I updated the question with real code.
I'm going to put another code, more simple to see and more simple to test (I think), is real code and works (but not like I want)
When I launch electron only writes the last message.
Ok, the response is really fast and I may not see the first messsage but to discard that I put a setTimeout and a large for() loop too, to make the uppercase function takes longer
index.js
const electron = require('electron');
const {app} = electron;
const {BrowserWindow} = electron;
const ipc = require('electron').ipcMain
app.on('ready', () => {
let win = new BrowserWindow({width: 800, height: 500});
win.loadURL('file://' + __dirname + '/index.html');
// Emitted when the window is closed.
win.on('closed', () => {
win = null;
});
// Event that return the arg in uppercase
ipc.on('uppercase', function (event, arg) {
event.returnValue = arg.toUpperCase()
})
});
index.html
<html>
<body>
<div id="konsole">...</div>
<script>
const ipc = require('electron').ipcRenderer
const konsole = document.getElementById('konsole')
// Functions
let reply, message
// First MSG
reply = ipc.sendSync('uppercase', 'Hi, I'm the first msg')
message = `Uppercase message reply: ${reply}`
document.getElementById('konsole').innerHTML = message
// Second MSG
reply = ipc.sendSync('uppercase', 'Hi, I'm the second msg')
message = `Uppercase message reply: ${reply}`
document.getElementById('konsole').innerHTML = message
</script>
</body>
</html>
You can comminicate between your frond-end and back-end with webContents and IPC. Then, you'll be able to manipulate your codes in front-end with backend's directive.
For Instance (Backend to Frontend);
Your main.js is sending a message to your front-end.
mainwindow.webContents.send('foo', 'do something for me');
Your frond-end will welcome that message here;
const {ipcRenderer} = require('electron');
ipcRenderer.on('foo', (event, data) => {
alert(data); // prints 'do something for me'
});
For Instance (Frontend to Backend);
Your frond-end;
const {ipcRenderer} = require('electron');
ipcRenderer.send('bar',"I did something for you");
Your back-end;
const {ipcMain} = require('electron');
ipcMain.on('bar', (event, arg) => {
console.log(arg) // prints "I did something for you"
event.sender.send('foo', 'done') // You can also send a message like that
})
UPDATE AFTER UPDATING QUESTION;
I tried your codes on my local, It seems like working.
Could you please try it with insertAdjacentHTML instead of 'innerHTML' to just make sure or just use console.log.
Like this;
document.getElementById('konsole').insertAdjacentHTML('beforeend',message);
"result" is a reference type value. "result" always chenge value when result = funcC() or another; Try this:
$('#msg').text(result.ToString());