Like in any standard native application, also my electron's application needs to change the status (enabled/dsabled) of several menu item, based on live usage results.
I am setting up my menu in main.js:
function createWindow () {
...
...
require('./menu/mainmenu');
}
The MenuItem I need to change is defined in mainmenu:
{ label: "Show Colors",
accelerator: 'CmdOrCtrl+1',
enabled: getStatus(),
click() {getWebviewWebContents().send('switchToColors');}
},
where getStatus() is function returning false or true.
All this is not working in Electron, as the menu is created at application start and it can't be modified at all. I believe this is a serious lack, as dynamic menu items are very common (i.e.: menu checkboxes, enabled/disabled, etc).
Is there any workaround for this?
I have fixed this by setting an Id to the menu item,
{ label: "Show Colors",
id: 'color-scale',
accelerator: 'CmdOrCtrl+1',
enabled: getStatus(),
click() {getWebviewWebContents().send('switchToColors');}
},
and getting the menu item with:
myItem = menu.getMenuItemById('color-scale')
Then, when I need to enable/disable it programmatically, I am using:
myItem.enabled = true
or
myItem.enabled = false
The only workaround so far I aware and using is reconstruct whole menu each time menuitem changes. This is not very ergonomics friendly, but works suffeciently enough and doesn't cause lot of overhead.
Here is my solution:
main.js (main process)
const menuTemplate = [{
label: 'Options',
submenu: [
{
label: 'Config',
enabled: false,
click() { //do stuff }
}
]
}];
// Enable menu items when user login. Fetching value from renderer process
ipcMain.on('logged-in', (event, args) => {
if (args !== true) {
return;
}
// Modify menu item status
menuTemplate[0].submenu[0].enabled = true;
// Rebuild menu
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
});
Related
I know how to add an entry in the context menu of the Monaco editor, with editor.addAction. How to add an action which opens a dropdown list, as the "Command Palette" action?
As mentioned in this issue, the quick-open api is not yet implemented in monaco-editor. But after a series of in-depth research, I found a somewhat hacker solution.
First, you need to import IQuickInputService like below.
import * as monaco from 'monaco-editor/esm/vs/editor/editor.main.js';
import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput';
Then, create our editor.
// Create your editor instance...
let editor = monaco.editor.create(
// ...
);
// Add a new command, for getting an accessor.
let quickInputCommand = editor.addCommand(0, (accessor, func) => {
// a hacker way to get the input service
let quickInputService = accessor.get(IQuickInputService)
func(quickInputService)
});
Finally, as an example, we can add an action.
editor.addAction({
id: "myQuickInput",
label: "Test Quick Input",
run: (editor) => {
// create quick input
editor.trigger("", quickInputCommand, (quickInput) => {
quickInput.pick([
{
type: 'item',
id: "my_quick_item",
label: "MyQuickItem",
},
{
type: 'item',
id: "my_quick_item2",
label: "MyQuickItem2",
},
{
type: 'item',
id: "my_quick_item3",
label: "MyQuickItem3",
},
]).then((selected) => {
console.log(selected)
})
})
}
})
For the interface of IQuickInputService, please refer to here here
If you do it right, when you run the action, you should get the same result as I did.
I'm implementing Quill-Mention in Quill JS, using React Quill but I can't manage to update editor content when clicking an item from the list.
I can properly render the list when clicking the right symbol and it shows data accordingly. But, when I click it, it disappears and the item value is not added to editor content.
Here is how I'm testing it:
const suggestPeople = searchTerm => {
const allPeople = [
{
id: 1,
value: "Fredrik Sundqvist"
},
{
id: 2,
value: "Patrik Sjölin"
}
];
return allPeople.filter(person => person.value.includes(searchTerm));
};
/* Mention module config
====================== */
const mentionModule = {
allowedChars: /^[A-Za-z\sÅÄÖåäö]*$/,
mentionDenotationChars: ["·"],
source: async function(searchTerm, renderList) {
const matchedPeople = await suggestPeople(searchTerm);
renderList(matchedPeople);
}
};
Quill.register("modules/mentions", QuillMention);
const modules = {
syntax: true,
clipboard: {
matchVisual: false
},
toolbar: {
container: "#toolbar",
},
mention: mentionModule
};
Screenshot showing Quill Mention dropdown list working
Thank you!
I solved it.
I needed to add the onSelect method in the config object plus add "mention" as an element of the formats array.
Thank you either way :)
I come from a low-level programming background, so JS and NodeJS are a new realm for me.
I am trying to create an application that begins by displaying a CLI menu to the user. Upon the user selecting a menu option, a corresponding functionality will be carried out. Once that functionality completes, I want the menu to be re-displayed.
A very simple way of handling this in Python and embedded C is to enclose the menu in a while(1) loop and then terminate the program/script process when the user selects the corresponding menu option. However, in NodeJS, you cannot run a menu in a while(1) loop -- the functions called corresponding to each menu option never actually get called and the menu simply re-displays immediately.
In other words, what is the NodeJS equivalent of:
while(1) {
displayMenuToUser();
// Wait for user to select which menu option they want
if (quitMenuOptionSelectedByUser) {
terminateProcess();
} else {
executeFunctionCorrespondingToTheSelectedMenuOption();
// At this point the menu should be re-displayed so the user can select another option
}
}
You can use Inquirer.js
I made this example which keeps looping if you answer yes on the Go again? question:
var inquirer = require('inquirer');
const showMenu = () => {
inquirer
.prompt([{
name: 'age',
type: 'input',
message: 'What\'s your age?',
}, {
name: 'country',
type: 'list',
message: 'Where do you live?',
choices: ['USA', 'China', 'Germany', 'France'],
}, {
name: 'back',
type: 'input',
message: 'Go again?',
choices: ['yes', 'no'],
}]
).then((answers) => {
console.log(`\nMy age is ${answers.age} and I live in ${answers.country}.\n`);
if (answers.back === 'yes') {
return showMenu();
}
})
.catch((err) => {
console.log(err);
});
}
showMenu();
I have to developp an Single Application Page, i choose Mithril.
I need to render a component on button click, this is my code :
var accountView = {
controller: function (data) {
return {
showOtherPage: function () {
??? how to render an other component ?
}
}
},
view: function (ctrl) {
return [
m("button", { onclick: ctrl.showOtherPage }, "Account")
];
}
}
Thanks in advance
If you're using Mithril's routing functionality and want to show a whole new page, then you can use a link rather than using a button. (Personally, this is how I normally anticipate handling these scenarios.) eg,
m("a[href=/otherPage]", { config: m.route }, "Account")
Fiddle: https://jsfiddle.net/11qjx341/
(Alternatively, you could also call m.route('/otherPage') within the showOtherPage function if a link is not appropriate for some reason.)
Or, if you're not using Mithril's routing (eg if you're using m.mount), but still want to render a whole new page, you might want to call m.mount with the new view to have it rendered. eg
m.mount(document.body, otherView);
Fiddle: https://jsfiddle.net/91g9db6n/
As a third option, if your new view is actually meant to coexist with the current page, you can have a component that's shown or hidden based on state. eg
return [
m("button", { onclick: ctrl.showModal.bind(ctrl, !showModal) }, showModal ? "Hide Account" : "Account")
, showModal ? m.component(OtherView) : null
];
Fiddle: https://jsfiddle.net/mk27tfq1/
EDIT
I have a viewport that extends a TabPanel. In it, I set one of the tabBar buttons to load another TabPanel called subTabPanel. myApp.views.viewport.setActiveItem(index, options) works just fine. But myApp.views.subTabPanel.setActiveItem(index, options) only loads the appropriate panel card for a split second before it vanishes.
Strangely, it works just fine to make this call from within the subTabPanel's list item:
this.ownerCt.setActiveItem(index, options)
However, I want to avoid this, as I want such actions to live inside controllers so as to adhere to MVC.
Any thoughts on why the card disappears when called from the controller, but not when called from the containing subTabPanel?
(The subTabPanel card in question is an extension of Ext.Carousel.)
UPDATE
It looks like both subTabPanel and its carousel are being instantiated twice somehow, so that could be a big part of the problem...
The answer in this case was to prevent the duplicate creation of the subTabPanel and its carousel.
The viewport now looks like this:
myApp.views.Viewport = Ext.extend(Ext.TabPanel, {
fullscreen: true,
layout: 'card',
cardSwitchAnimation: 'slide',
listeners: {
beforecardswitch: function(cnt, newCard, oldCard, index, animated) {
//alert('switching cards...');
}
},
tabBar: {
ui: 'blue',
dock: 'bottom',
layout: { pack: 'center' }
},
items: [],
initComponent: function() {
//put instances of cards into myApp.views namespace
Ext.apply(myApp.views, {
subTabPanel: new myApp.views.SubTabPanel(),
tab2: new myApp.views.Tab2(),
tab3: new myApp.views.Tab3(),
});
//put instances of cards into viewport
Ext.apply(this, {
items: [
myApp.views.productList,
myApp.views.tab2,
myApp.views.tab3
]
});
myApp.views.Viewport.superclass.initComponent.apply(this, arguments);
}
});
And I've since removed the duplicate creation of those TabPanel items from the items: property and moved their tabBar-specific properties into the view classes SubTabPanel, Tab2 and Tab3 (each of which are extensions of either Ext.TabPanel or Ext.Panel).