I have setup an angular2 / Electron app similar to the explanation in this video : https://www.youtube.com/watch?v=pLPCuFFeKOU. The project I am basing my code on can be found here : https://github.com/rajayogan/angular2-desktop
I am getting the error:
app.ts:16Uncaught TypeError: Cannot read property 'on' of undefined
When I try to run this code:
import { bootstrap } from '#angular/platform-browser-dynamic';
import { Component } from '#angular/core';
import { MenuComponent} from './menu';
import { ConfigEditorComponent } from './config-editor';
import { remote, ipcRenderer} from 'electron';
let {dialog} = remote;
//Functions used for select server xml callbacks.
const ipc = require('electron').ipcMain
const xml2js = require('xml2js')
const fs = require('fs')
var parser = new xml2js.Parser();
ipc.on('open-file-dialog', function (event) {
dialog.showOpenDialog({
title:"Select zOS Connect server.xml",
properties: ['openFile', 'openDirectory'],
filters: [
{name: 'XML', extensions: ['xml']},
{name: 'All Files', extensions: ['*']}
]
}, function (files) {
if (files){
fs.readFile(files[0], function(err, data) {
parser.parseString(data, function (err, result) {
console.dir(result);
process_server_xml(event,result);
})
})
}
})
})
function process_server_xml(event,json){
console.log("oh hello!")
event.sender.send('selected-directory', json)
console.log("oh im done!")
}
#Component({
selector: 'connect-toolkit',
templateUrl: 'app.component.html',
directives: [ MenuComponent, ConfigEditorComponent ]
})
export class AppComponent {
constructor() {
var menu = remote.Menu.buildFromTemplate([{
label: 'Raja',
submenu: [
{
label: 'open',
click: function(){
dialog.showOpenDialog((cb) => {
})
}
},
{
label: 'opencustom',
click: function(){
ipcRenderer.send('open-custom');
let notification = new Notification('Customdialog', {
body: 'This is a custom window created by us'
})
}
}
]
}])
remote.Menu.setApplicationMenu(menu);
}
}
bootstrap(AppComponent);
I think the problem may be:
const ipc = require('electron').ipcMain
const xml2js = require('xml2js')
const fs = require('fs')
var parser = new xml2js.Parser();
Is it possible require doesn't work here, and somehow I need to use import statements instead from my ts files? If this is the case how do I use the import in order to get the ipcMain object and my xml2js etc?
Why would that be the case? How can I make require work within the ts files if this is the problem.
Note that if I remove the require lines, and all the ipc.on code everything runs as expected and works fine (other than the fact that the ipc event is never received ;)
Calling ipcMain doesn't work because you're not on main (i.e., the electron side code, which is on electron index.js file), your are on renderer (web page). Therefore you must use ipcRenderer instead, which is already imported using es6 import syntax on top of your app.ts file. And if you want to make something using electron ipcMain, it have to be done from the electron code side.
import {remote, ipcRenderer} from 'electron';
Electron ipc notes:
ipcMain Communicate asynchronously from the main process to renderer processes.
ipcRenderer Communicate asynchronously from a renderer process to the main process.
Related
Strapi Version: 4.3.0
Operating System: Ubuntu 20.04
Database: SQLite
Node Version: 16.16
NPM Version: 8.11.0
Yarn Version: 1.22.19
I have created Preview button for an article collection type. I'm using the Strapi blog template. I managed to make the Preview button appear in the Content Manager. I hard coded the link to be opened when you click the Preview button and it works. Now, I want the plugin to use a link with environment variables instead of a hard coded link. I don't know how I can access the environment variables in the source code for the plugin.
My objective:
I want to replace
href={`http://localhost:3000?secret=abc&slug=${initialData.slug}`}
with
href={${CLIENT_FRONTEND_URL}?secret=${CLIENT_SECRET}&slug=${initialData.slug}`}
in ./src/plugins/previewbtn/admin/src/components/PreviewLink/index.js
where CLIENT_FRONTEND_URL and CLIENT_SECRET are environment variables declared like so in .env:
CLIENT_FRONTEND_URL=http://localhost:3000
CLIENT_PREVIEW_SECRET=abc
Here's a rundown of the code I used:
First, I created a strapi app using the blog template, then created a plugin.
// Create strapi app named backend with a blog template
$ yarn create strapi-app backend --quickstart --template #strapi/template-blog#1.0.0 blog && cd backend
// Create plugin
$ yarn strapi generate
Next, I created a PreviewLink file to provide a link for the Preview button
// ./src/plugins/previewbtn/admin/src/components/PreviewLink/index.js
import React from 'react';
import { useCMEditViewDataManager } from '#strapi/helper-plugin';
import Eye from '#strapi/icons/Eye';
import { LinkButton } from '#strapi/design-system/LinkButton';
const PreviewLink = () => {
const {initialData} = useCMEditViewDataManager();
if (!initialData.slug) {
return null;
}
return (
<LinkButton
size="S"
startIcon={<Eye/>}
style={{width: '100%'}}
href={`http://localhost:3000?secret=abc&slug=${initialData.slug}`}
variant="secondary"
target="_blank"
rel="noopener noreferrer"
title="page preview"
>Preview
</LinkButton>
);
};
export default PreviewLink;
Then I edited this pregenerated file in the bootstrap(app) { ... } section only
// ./src/plugins/previewbtn/admin/src/index.js
import { prefixPluginTranslations } from '#strapi/helper-plugin';
import pluginPkg from '../../package.json';
import pluginId from './pluginId';
import Initializer from './components/Initializer';
import PreviewLink from './components/PreviewLink';
import PluginIcon from './components/PluginIcon';
const name = pluginPkg.strapi.name;
export default {
register(app) {
app.addMenuLink({
to: `/plugins/${pluginId}`,
icon: PluginIcon,
intlLabel: {
id: `${pluginId}.plugin.name`,
defaultMessage: name,
},
Component: async () => {
const component = await import(/* webpackChunkName: "[request]" */ './pages/App');
return component;
},
permissions: [
// Uncomment to set the permissions of the plugin here
// {
// action: '', // the action name should be plugin::plugin-name.actionType
// subject: null,
// },
],
});
app.registerPlugin({
id: pluginId,
initializer: Initializer,
isReady: false,
name,
});
},
bootstrap(app) {
app.injectContentManagerComponent('editView', 'right-links', {
name: 'preview-link',
Component: PreviewLink
});
},
async registerTrads({ locales }) {
const importedTrads = await Promise.all(
locales.map(locale => {
return import(
/* webpackChunkName: "translation-[request]" */ `./translations/${locale}.json`
)
.then(({ default: data }) => {
return {
data: prefixPluginTranslations(data, pluginId),
locale,
};
})
.catch(() => {
return {
data: {},
locale,
};
});
})
);
return Promise.resolve(importedTrads);
},
};
And lastly this created this file to enable the plugin Reference
// ./config/plugins.js
module.exports = {
// ...
'preview-btn': {
enabled: true,
resolve: './src/plugins/previewbtn' // path to plugin folder
},
// ...
}
I solved this by adding a custom webpack configuration to enable Strapi's admin frontend to access the environment variables as global variables.
I renamed ./src/admin/webpack.example.config.js to ./src/admin/webpack.config.js. Refer to the v4 code migration: Updating the webpack configuration from the Official Strapi v4 Documentation.
I then inserted the following code, with help from Official webpack docs: DefinePlugin | webpack :
// ./src/admin/webpack.config.js
'use strict';
/* eslint-disable no-unused-vars */
module.exports = (config, webpack) => {
// Note: we provide webpack above so you should not `require` it
// Perform customizations to webpack config
// Important: return the modified config
config.plugins.push(
new webpack.DefinePlugin({
CLIENT_FRONTEND_URL: JSON.stringify(process.env.CLIENT_FRONTEND_URL),
CLIENT_PREVIEW_SECRET: JSON.stringify(process.env.CLIENT_PREVIEW_SECRET),
})
)
return config;
};
I rebuilt my app afterwards and it worked.
You shouldn't have to change the webpack config just find .env file in the root directory
add
AWS_ACCESS_KEY_ID = your key here
then just import by
accessKeyId: env('AWS_ACCESS_KEY_ID')
The Challenge
I am working on an Electron/Angular app that will display an emergency message to multiple computers in the event of a tornado or other incident. To accomplish this, I am using a WebSocket protocol (Signral R) on the backend to send the alert, which then triggers Electron's main.js (running in the background) to launch a window on any devices running the app.
The arrival of the message triggers the window to launch, but I also need to use the message in the Angular part of the app (which does not initialize until after the window is open) to indicate the nature of the emergency. To avoid timing problems, I planned to import a function into my app.component.ts that would be called at ngOnInit to retrieve an emergency variable from main.js.
The Problem
Angular does not like the import from raw JavaScript. Intuitively you would think Angular would only import the exported class. However it appears to try and import Electron as well (generating a conflict with 'fs' and 'path' that is explained here), generating the following error message:
./node_modules/electron/index.js:1:11-24 - Error: Module not found: Error: Can't resolve 'fs' in 'C:\Users\me\electron-app\node_modules\electron'
./node_modules/electron/index.js:3:13-28 - Error: Module not found: Error: Can't resolve 'path' in 'C:\Users\me\electron-app\node_modules\electron'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "path": require.resolve("path-browserify") }'
- install 'path-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "path": false }
In spite of doing considerable research, I have not found anything approaching a solution. Any ideas for only importing the information I want would be much appreciated. Here are my files:
main.js (simplified somewhat)
const signalR = require("#microsoft/signalr")
const { app, shell, BrowserWindow, Menu, nativeImage, Tray } = require('electron')
const path = require("path");
const url = require("url");
//various setup logic goes here
//...
webSocketConnection.on("EmergencyActivation", (jsonData) => {
createElectronWindow();
const emergencyMessage = jsonData
});
module.exports = class Emergency {
getEmergency() {
return emergencyMessage
}
}
app.component.ts
import { Component, OnInit } from '#angular/core';
import { Emergency } from './../../main';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
constructor(private emergency: Emergency) {}
ngOnInit() {
console.log(this.emergency.getEmergency())
}
}
Though I do not use Angular, I think the issue is communication between your main process and your render process (or
lack
thereof).
Don't try to communicate between processes by importing from the main process to the render process (which can't
be done). Instead, use Inter-Process Communication.
IE: Once an emergency message has been received via webSocket, create the window and then send an IPC message to that newly
created window.
I have needed to "mock" your webSocketConnection functionality for the sake of the below example.
main.js (main process)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const nodePath = require('path');
let window;
function createWindow() {
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', () => {
listenForEmergency();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// ---
// Mock of "webSocketConnection.on" function (for testing only)
function listenForEmergency() {
let message = 'Emergency: We have no beer'
console.log('Wait 5 seconds before "receiving" emergency message'); // Testing
setTimeout(() => { showEmergencyWindow(message); }, 5000);
}
// Create window and send message via IPC
function showEmergencyWindow(message) {
window = createWindow();
window.webContents.send('emergency', message); // <-- Use of IPC messaging
}
// Create window and send message via IPC
// webSocketConnection.on("EmergencyActivation", (jsonData) => {
// window = createWindow();
// window.webContents.send('emergency', jsonData); // <-- Use of IPC messaging
// });
preload.js (main process)
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
contextBridge.exposeInMainWorld(
'electronAPI', {
emergency: (message) => {
ipcRenderer.on('emergency', message)
}
});
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Emergency</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"/>
</head>
<body>
<h1>Emergency</h1>
<div id="message"></div>
</body>
<script>
window.electronAPI.emergency((event, message) => {
document.getElementById('message').innerText = message;
})
</script>
</html>
I want to override an action from cart module store. I am trying to extend this CartModule by following this link
Extending and Overriding Modules Doc
I have created a file /src/modules/cart/index.ts with following code
import { VueStorefrontModuleConfig, extendModule, VueStorefrontModule } from '#vue-storefront/core/lib/module'
import { CartModule } from '#vue-storefront/core/modules/cart'
import { cartModule } from './store'
const cartExtend: VueStorefrontModuleConfig = {
key: 'cart',
store: {modules: [{key: 'cart', module: cartModule}]},
afterRegistration: function () {
console.log('Cart module extended')
}
}
extendModule(cartExtend)
export const registerModules: VueStorefrontModule[] = [CartModule]
I am getting error that CarModule type does not match with VueStorefrontModule
Also I don't know what to do next in order to make it effective. Docs are not clear about it. Please help. Thanks
If you want to overwrite action of module you don't want to extend module but store.
Here is example:
Vuestorefront has CartModule (in core) and you need to change code of action refreshTotals.
Code your in file /src/modules/cart/index.ts:
import {StorefrontModule} from '#vue-storefront/core/lib/modules';
import {extendStore} from '#vue-storefront/core/helpers';
const cartModule = {
action: {
async refreshTotals({dispatch}, payload) {
//
// your new (and better!) code ;-)
//
}
},
}
export const MyAwesomeCart: StorefrontModule = function () {
extendStore('cart', cartModule);
}
In last step register this your new module under /src/modules/client.ts:
..
...
import {CartModule} from '#vue-storefront/core/modules/cart';
import {MyAwesomeCart} from "modules/cart/index";
export function registerClientModules() {
registerModule(CartModule); // original module
registerModule(MyAwesomeCart); // your new overwiritng module
...
..
I am working on a electron demo by following this tutorial.
just wondering what happened in the require line of code.
./menu/mainmenu.js defines the menu items.
const {Menu} = require('electron')
const electron = require('electron')
const app = electron.app
const template = [
{
label: 'Edit',
submenu: [
{
role: 'undo'
},
{
role: 'redo'
},
{
type: 'separator'
},
{
role: 'cut'
},
{
role: 'copy'
},
{
role: 'paste'
},
{
role: 'pasteandmatchstyle'
},
{
role: 'delete'
},
{
role: 'selectall'
}
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
main.js
const { app, BrowserWindow, ipcMain } = require('electron');
let win;
function createWindow () {
win = new BrowserWindow({
width: 880,
height: 660,
webPreferences: {
nodeIntegration: true
}
})
// and load the index.html of the app.
win.loadFile('index.html')
require('./menu/mainmenu') //does this line copied the whole mainmenu.js file?
}
does the require('./menu/mainmenu') copy whole file into main.js?
Or imported some modules? In the mainmenu.js file There is no export keyword.
according to the node.js documentation,
"The basic functionality of require is that it reads a JavaScript file, executes the file, and then proceeds to return the exports object."
require here doesn't copy file around (unlike c++ #include)
Instead it execute the file and return the exported items (if any)
Since there is no export in './menu/mainmenu' when you call require, it simply executed that file.
The problem with this approach is require would only process that file once*, the proper way is actually export something which can be used multiple times.
example:
./menu/mainmenu.js
//...
const menu = Menu.buildFromTemplate(template)
export default ()=>Menu.setApplicationMenu(menu)
main.js
const { app, BrowserWindow, ipcMain } = require('electron');
let win;
function createWindow () {
//...
const setmenu = require('./menu/mainmenu') // you can put this at top, too
setmenu();
// or you can execute it inline
// require('./menu/mainmenu')()
}
note: you may need https://www.npmjs.com/package/babel-plugin-add-module-exports or some workaround to make require and default export works together.
*the problem with this is you cannot really rely on it to work everytime, e.g. change menu from A -> B -> A , the second require('A') would silently do nothing.
My app was working perfectly till I externalized the collection definition from the client/main.js file to the ../imports/api/tasks.js file.
After this move I keep getting this error message in my browser: Uncaught TypeError: Cannot read property 'insert' of undefinedin my browser console. This error message points to line main.js:1206 which is:
/myApp
../client/main.js
import { Images } from '../imports/api/tasks.js';
Meteor.subscribe('Images');
FS.Utility.eachFile(event, function(file) {
var teste = Images.insert(file, function (err, fileObj) {
var insertedIdSession = teste._id;
session.set("insertedBuyId", insertedIdSession);
Images.find().count());
});
/myApp
../imports/api/tasks.js
import { Mongo } from "meteor/mongo";
Images = new FS.Collection("images", {
stores: [new FS.Store.FileSystem("images", {path: "~/uploads"})] });
/myApp
../server/main.js
import { Images } from '../imports/api/tasks.js';
Meteor.publish('Images', function(){
return Images.find();
});
I have researched but failed to find a solution for this. Kind point out where i am going wrong.
It is telling you that "Images" is not defined.
From what I can see of your code, ../imports/api/tasks.js does not export anything, which means that
import { Images } from '../imports/api/tasks.js';
won't give you anything, in fact it will replace the global variable Images with a null. So I think the solution is to replace the imports with this:
import '../imports/api/tasks.js';
Or you can put tasks.js in /common and that will do the same job
You need to export your Images collection in order to import it to other files, like this:
import { Mongo } from "meteor/mongo";
export const Images = new FS.Collection("images", {
stores: [new FS.Store.FileSystem("images", {path: "~/uploads"})]
});