I’m trying to implement a simple feature in my UXP plugin: Enabling and Disabling “Extras” from the View
menu.
const {executeAsModal} = require("photoshop").core;
const {batchPlay} = require("photoshop").action;
const app = require('photoshop').app
async function enableExtras() {
const result = await batchPlay(
[
{
_obj: "select",
_target: [
{
_ref: "menuItemClass",
_enum: "menuItemType",
_value: "toggleShowExtras"
}
],
_options: {
dialogOptions: "dontDisplay"
}
}
],
{
synchronousExecution: false,
modalBehavior: "execute"
}
);
}
async function btnDisableExtras_Click() {
await executeAsModal(disableExtras, {"commandName": "Disable Extras"});
}
async function btnEnableExtras_Click() {
await executeAsModal(enableExtras, {"commandName": "Enable Extras"});
}
document.getElementById("btnDisableExtras").addEventListener("click", btnDisableExtras_Click);
document.getElementById("btnEnableExtras").addEventListener("click", btnEnableExtras_Click);
The problem is that there is no value to explicitly disable or enable extras, just a toggle:
_value: "toggleShowExtras"
So I can make a “Toggle Extras” button, but I can’t seem to make two buttons that explicitly disable and enable the extras. Does anyone know how to get around this limitation? There are a lot of toggle based BatchPlay _value:
commands, but nothing binary (on/off).
Any help greatly appreciated.
1 Like
Maher
May 3, 2023, 12:46pm
2
in my search I couldn’t find a way to set the checked
value of a menu item so I went with 2nd best thing.
I get the current state of the menu item and and toggle only if needed.
async function showExtras(state){
let extrasIsChecked = (await batchPlay([{
"_obj": "get",
"_target": [{"_property": "menuBarInfo"},{"_ref": "application", "_enum": "ordinal", "_value": "targetEnum"},]
}], {}))[0].menuBarInfo.submenu.find( m => m.name == "View").submenu.find( m => m.name == "Extras").checked
if (extrasIsChecked != state) {
await core.performMenuCommand({commandID: 3500})
}
}
that function can be used as follows:
//Show Extras
await showExtras(true);
//Hide Extras
await showExtras(false);
EDIT:
as pointed out the previous function may fail on non English versions of PS, so here’s another way using IDs instead of names:
async function showExtras(state){
let extrasIsChecked = (await batchPlay([{
"_obj": "get",
"_target": [{"_property": "menuBarInfo"},{"_ref": "application", "_enum": "ordinal", "_value": "targetEnum"},]
}], {}))[0].menuBarInfo.submenu.find( m => m.menuID== 9).submenu.find( m => m.command == 3500).checked
if (extrasIsChecked != state) {
await core.performMenuCommand({commandID: 3500})
}
}
Jarda
May 3, 2023, 1:22pm
3
This will fail if the language in PS is not English.
Maher
May 3, 2023, 1:23pm
4
how about menu IDs, are they reliable?
Positive IDs should be reliable, but Adobe proved that’s not always the case when they completely changed “Select Subject” menu Negative IDs may always change (even on Ps restart IIRC)
Jarda
May 3, 2023, 1:30pm
6
Another option is to use localization strings to generate translated strings and then you compare in any language. But that could also be a subject of change but it is much safer.
Maher
May 3, 2023, 1:32pm
8
well, we do have core.getMenuCommandTitle
but it uses command id anyway, how would you get localization strings?
Maher
May 3, 2023, 1:33pm
9
getMenuCommandState
is pretty much useless
Returns whether a command menu item is available for invoking.
it does not return if the menu item is “checked” but if it’s “available”
I’m also interested in localization strings. Never managed to figure out how to use them or how they work.
Jarda
May 3, 2023, 1:34pm
11
I am working on small plugin to help you with an errors and the way it detects specific error dialog is this:
2 Likes
But where do we get full list of these string paths ? I mean here you know this error message translations uses this path-ID, but where did you find it? How did you figure it’s the one you need?
Maher
May 3, 2023, 1:39pm
13
just found them
C:\Program Files\Adobe\Adobe Photoshop 2023\Locales\xx_XX\Support Files
there should be a .dat file there
for this specific case:
async function showExtras(state){
let viewLocalString = core.translateUIString("$$$/Menu/View");
let extrasLocalString = core.translateUIString("$$$/Menu/View/Extras");
let extrasIsChecked = (await batchPlay([{
"_obj": "get",
"_target": [{"_property": "menuBarInfo"},{"_ref": "application", "_enum": "ordinal", "_value": "targetEnum"},]
}], {}))[0].menuBarInfo.submenu.find( m => m.name == viewLocalString ).submenu.find( m => m.name == extrasLocalString).checked
if (extrasIsChecked != state) {
await core.performMenuCommand({commandID: 3500})
}
}
thanks @Jarda for the valuable insight!
1 Like
The last supplied function fails silently. Here’s the code (ignore irrelevant functions):
const {executeAsModal} = require("photoshop").core;
const {batchPlay} = require("photoshop").action;
const app = require('photoshop').app
async function toggleSnapToPixels(executionContext, toggle) {
const result = await batchPlay(
[
{
_obj: "set",
_target: [
{
_ref: "property",
_property: "generalPreferences"
},
{
_ref: "application",
_enum: "ordinal",
_value: "targetEnum"
}
],
to: {
_obj: "generalPreferences",
transformsSnapToPixels: toggle
}
}
],{}
);
}
async function showExtras(state){
let viewLocalString = core.translateUIString("$$$/Menu/View");
let extrasLocalString = core.translateUIString("$$$/Menu/View/Extras");
let extrasIsChecked = (await batchPlay([{
"_obj": "get",
"_target": [{"_property": "menuBarInfo"},{"_ref": "application", "_enum": "ordinal", "_value": "targetEnum"},]
}], {}))[0].menuBarInfo.submenu.find( m => m.name == viewLocalString ).submenu.find( m => m.name == extrasLocalString).checked
if (extrasIsChecked != state) {
await core.performMenuCommand({commandID: 3500})
}
}
async function openNeuralFilters() {
const result = await batchPlay(
[
{
_obj: "neuralGalleryFilters",
_options: {
dialogOptions: "display"
}
}
],
{
synchronousExecution: false,
modalBehavior: "wait"
}
);
}
async function btnDisableSnapToPixels_Click() {
await executeAsModal(toggleSnapToPixels, {"commandName": "Disable Snap to Pixels", "descriptor": false});
}
async function btnEnableSnapToPixels_Click() {
await executeAsModal(toggleSnapToPixels(true), {"commandName": "Enable Snap to Pixels", "descriptor": true});
}
async function btnDisableExtras_Click() {
await executeAsModal(showExtras, {"commandName": "Disable Extras", "descriptor": false});
}
async function btnEnableExtras_Click() {
await executeAsModal(showExtras, {"commandName": "Enable Extras", "descriptor": true});
}
async function btnOpenNeuralFilters_Click() {
await executeAsModal(openNeuralFilters, {"commandName": "Open Neural Filters"});
}
document.getElementById("btnEnableSnapToPixels").addEventListener("click", btnEnableSnapToPixels_Click);
document.getElementById("btnDisableSnapToPixels").addEventListener("click", btnDisableSnapToPixels_Click);
document.getElementById("btnOpenNeuralFilters").addEventListener("click", btnOpenNeuralFilters_Click);
document.getElementById("btnDisableExtras").addEventListener("click", btnDisableExtras_Click);
document.getElementById("btnEnableExtras").addEventListener("click", btnEnableExtras_Click);
I don’t think state
is being passed correctly. I don’t know. I’ll try to do some debugging.
Any further help would be massively appreciated.
Jay
1 Like
Also, when I add the following into the function:
alert(state);
I just get a generic result of [object Object]
. How can I get more information than that when I want to inspect a variable?
Jarda
May 3, 2023, 6:42pm
16
fmotion1:
modalBehavior: "wait"
Never use this within modal execution. Because it will wait forever.
Ok, gotcha. When should I use "Wait"
? Is there a documentation/Photoshop UXP API page where I can get clarification on this?
Should I change it to "Execute"
in this case?
Thanks for the help.
Jay
Jarda
May 3, 2023, 6:50pm
18
"execute"
all lower case as it is case sensitive. I don’t see any documentation yet. This was more usefull before in DOM version 1 when execute as modal did not exist yet. Right now it makes only sense for “get” command as it can be executed without modal state. So if user would have some dialog open… it would wait until user close the dialog and then read value for you.
Still having some trouble solving this problem. Here is my current code:
const {executeAsModal} = require("photoshop").core;
const {batchPlay} = require("photoshop").action;
const app = require('photoshop').app
const PhotoshopCore = require('photoshop').core;
async function showExtras(toggle) {
const batchCommands = {
_obj: "get",
_target: [
{
_property: "menuBarInfo"
},
{
_ref: "application",
_enum: "ordinal",
_value: "targetEnum"
}
],
};
let state = (await require('photoshop').action.batchPlay([batchCommands], {}));
let viewMenu = (state[0].menuBarInfo.submenu.find(m => m.name == 'View'));
let extrasMenu = (viewMenu.submenu.find(m => m.name == 'Extras'));
let extrasIsChecked = extrasMenu.checked;
if (extrasIsChecked != toggle) {
PhotoshopCore.performMenuCommand({ commandID: 3500 });
}
console.log(state);
console.log(viewMenu);
console.log(extrasMenu);
console.log(extrasIsChecked);
}
document.getElementById("btnDisableExtras").addEventListener('click', async () => {
await showExtras(false);
// console.log("result:" + result);
// // a message in the result means error
// if (result[0].message) {
// showAlert(result[0].message);
// }
})
document.getElementById("btnEnableExtras").addEventListener('click', async () => {
await showExtras(true);
//console.log("result:" + result);
// // a message in the result means error
// if (result[0].message) {
// showAlert(result[0].message);
// }
})
All of my logging is reporting correct information from the end of the showExtras
function:
let state = (await require('photoshop').action.batchPlay([batchCommands], {}));
let viewMenu = (state[0].menuBarInfo.submenu.find(m => m.name == 'View'));
let extrasMenu = (viewMenu.submenu.find(m => m.name == 'Extras'));
let extrasIsChecked = extrasMenu.checked;
Is returning:
Despite this, when I run the following at the end of the function:
if (extrasIsChecked != toggle) {
PhotoshopCore.performMenuCommand({ commandID: 3500 });
}
PhotoshopCore.performMenuCommand({ commandID: 3500 });
seems to do absolutely nothing. It doesn’t deselect (or re-select) the Extras
menu item. Right now it’s always set to true. Hitting my “Disable Extras” button doesn’t deselect extras.
I’m sure the commandID of 3500
is correct - Here’s the devtools output:
Maher
May 3, 2023, 9:29pm
20
have you tried running the code as it is with no modification.
I tested it and it does work.
also make sure that checked
is returning the correct value. strangely in my tests sometimes it reported the opposite of visible state.
also try wrapping your code with a try/catch it may help debug the issue