Move Layer to Front through batch-play works differently in PS 23.4.1

@kerrishotts, could you please take a look on this? What was changed in 23.4.1 and how can we deal with that now?

out of curiosity I tried your code on my plugin which in API 2 Manifest 5, Photoshop 23.4.1 it worked well:
here is your whole code with what I changed

const ExecuteAsModal = require("photoshop").core.executeAsModal;

document
  .getElementById("btndustAndScratches")
  .addEventListener("click", async function () {
    await ExecuteAsModal(() => dustAndScratches());
  });
async function dustAndScratches() {
  const docexists = () => {
    return Boolean(app.documents?.length);
  };
  const exists = docexists();

  if (exists === true) {
    await batchPlay(
      [
        {
          _obj: "select",
          _target: {
            _ref: "layer",
            _id: app.activeDocument.layers[0]._id,
          },
          _isCommand: true,
          _options: {},
        },
        {
          _obj: "make",
          _target: {
            _ref: "layer",
          },
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
        {
          _obj: "mergeVisible",
          duplicate: true,
          _options: {
            dialogOptions: "dontDisplay",
          },
        },

        {
          _obj: "set",
          _target: {
            _ref: "layer",
            _enum: "ordinal",
            _value: "targetEnum",
          },
          to: {
            _obj: "layer",
            name: "Dust and Scratches",
          },
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
        {
          _obj: "select",
          _target: {
            _ref: "menuItemClass",
            _enum: "menuItemType",
            _value: "view200Percent",
          },
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
        {
          _obj: "dustAndScratches",
          _options: {
            dialogOptions: "display",
          },
        },
        {
          _obj: "make",
          new: {
            _class: "channel",
          },
          at: {
            _ref: "channel",
            _enum: "channel",
            _value: "mask",
          },
          using: {
            _enum: "userMaskEnabled",
            _value: "revealAll",
          },
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
        {
          _obj: "invert",
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
        {
          _obj: "select",
          _target: {
            _ref: "channel",
            _enum: "channel",
            _value: "mask",
          },
          makeVisible: false,
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
        {
          _obj: "select",
          _target: {
            _ref: "paintbrushTool",
          },
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
        {
          _obj: "reset",
          _target: {
            _ref: "color",
            _property: "colors",
          },
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
        {
          _obj: "select",
          _target: {
            _ref: "menuItemClass",
            _enum: "menuItemType",
            _value: "fitOnScreen",
          },
          _options: {
            dialogOptions: "dontDisplay",
          },
        },
      ],
      {
        historyStateInfo: {
          name: "Dust and Sctartches",
          target: {
            _ref: "document",
            _enum: "ordinal",
            _value: "targetEnum",
          },
        },
      }
    );
  } else {
    PhotoshopCore.showAlert({ message: "Open the document first" });
  }
}

edited to add missing Variable

Hi @Wanokuni

interesting, when I’m using your version of the function, nothing happens after clicking the button. I’m looking for a reason now.

I forgot to add this line:

const ExecuteAsModal = require("photoshop").core.executeAsModal;

as I have those variables declared at top in the index file, that’s why. could you add it and check again if it works.

Check for inline handlers – or, if you’re using webpack, check how your sourcemaps are being handled – default is to use eval – which will cause the issue.

If you’re using webpack, you can (for dev purposes), add allowCodeGenerationFromStrings: true to your requiredPermissions section in the manifest, and the error should go away.

Downgraded Ps to v23.3.2 (don’t have v23.4.0 on the list of Other versions) and it works perfectly

@kerrishotts, could you please ping someone? This really needs to be fixed (or at least some workaround given). As mentioned, same issue on v23.5 beta

@heewoo @samgannaway FYI ^^^^

1 Like

This is my code. It can activate layers. Maybe it can help you.

    await ActiveLayer()
    async function ActiveLayer() {

        let cl = await app.activeDocument.activeLayers[0];

        if (cl == undefined) {
            let layers = await require("photoshop").app.activeDocument.layers;
            await require("photoshop").core.executeAsModal(async () => {
                try {

                    await require("photoshop").action.batchPlay([{
                        "_obj": "select",
                        "_target": [{
                            "_ref": "layer",
                            "_id": layers[0]._id
                        }],
                        "_isCommand": true
                    }, ], {
                        "synchronousExecution": false
                    });
                } catch (e) {
                    console.error(e);
                }
            }, {
                commandName: "Test"
            });
        }
    }

Activation for me also works just fine. Even mask deletion works fine. Just not mask creation :frowning:

Still reading through this thread, but I’ve verified that we did have a change in behavior with the 23.4.1 update which will result in “command ‘Move’ not available” appearing when you attempt to run layer arrangements on documents with only one layer. We’ve got a bug logged and will investigate. A quick glance while scrolling down to the bottom of this thread and I get a sense there may be some other items which may have changed as well. Or there may be multiple ways in which the new behavior manifests. I’ll keep reading through the thread and continue testing.

hi @BrettN ,

We can workaround new “Move” logic, but the biggest issue from my perspective is unpredictable behaviour of mask creation. I think that it’s a bug. Once it works, next time it doesn’t. I can’t find the pattern at the moment. It occurs for sure on Mac (M1), API 2, Manifest 5.

Hi @Wanokuni, I Can confirm that everything works now! Did you change anything apart from the listener and its relation to html button? I can’t understand how this could affect mask creation that didn’t work before. Anyway, Thanks!

Hi @Adam Great it worked! Yes I have added an await to the batch play. from my understanding is that Photoshop will try to run commands as fast as possible without waiting for other commends to finish special after a dialog box, create a mask or moving a layer become a promise that doesn’t get fulfilled hope this make sense. I think @DavideBarranca explained this concept very well in this video https://www.youtube.com/watch?v=sf7YPWUppSM

1 Like

That’s very good explanation, thank you! I’ll have to edit dozens of functions now but at leas there is a light in the end of the tunnel :slight_smile:

My commands are all inside execModal() and all BPs are synchronous - no luck here :frowning:

Hmm, the issue seems to be with the filter dialog. It fails when I apply it but not when I cancel. I’ll log a ticket for investigation.

Yes, adding async/await does resolve the issue. I’d like to get a better handle on the situation and a better error for this type of situation.

Hi @simonhenke,

I’m trying to implement your solution for suspendHistory helper function. It works if I’m using only BP.

But… if I’m trying to do:

batchPlay
DOM
batchplay

or

batchPlay
DOM

I’m getting two history states. I have no idea what I’m doing wrong.
Screenshot 2022-07-03 at 22.58.29

Could you please take a look at the code structure:


async function dbgrey() {

    const docexists = () => {
        return Boolean(app.documents?.length)
      }
      const dexists = docexists()
      
if (dexists === true) {

    suspendHistory(async () => {

        await batchPlay([

         // descriptor 1
         // descriptor 2
         // ...

            ], {});

        await app.activeDocument.activeLayers[0].moveAbove(await app.activeDocument.layers[0]);    

        await batchPlay(
            [

         // descriptor 1
         // descriptor 2
         // ...

            ], {});
        }, "Just one step")
}
else {  PhotoshopCore.showAlert({message: 'Open the document first'});}
}

document.getElementById("btndbgrey").addEventListener("click", dbgrey);

This is your helper that I’m using (I’ve removed “export”):

async function suspendHistory(fn, historyName = "(Plugin command)", commandName = "Waiting for plugin command...") {
    async function executeAsModal(executionContext) {
      const hostControl = executionContext.hostControl;
      const suspensionID = await hostControl.suspendHistory({
          "historyStateInfo": {
              "name": historyName,
              "target": { _ref: 'document', _enum: 'ordinal', _value: 'targetEnum' }
          }
      });
      try {
        fn(executionContext);  
      } catch(e) {console.error(e)}
     await hostControl.resumeHistory(suspensionID);
    }
    await require("photoshop").core.executeAsModal(executeAsModal, {commandName});
  }

I would be very grateful if you could tell me what I’m doing wrong.

I’m using PS 23.4.1 on Mac, API 2, Manifest 5

Thank you!

I think you’re missing await in suspendHistory():

await fn(executionContext);

I believe what happens, is right after you call the callback, also resumeHistory() is called before your function finishes

1 Like

@Karmalakas, You are right, thank you!

You can also do it in React. Here is an example:

import { app, constants, core } from "photoshop";
import React from "react";

const App = () => {
  const handleClick = async () => {
    try {
      await core.executeAsModal(
        async (executionContext) => {
          let hostControl = executionContext.hostControl;
          let suspensionID = await hostControl.suspendHistory({
            documentID: app.activeDocument.id,
            name: "Custom command",
          });
          const activeLayer = app.activeDocument.activeLayers[0];
          activeLayer?.move(
            app.activeDocument.layers[0],
            constants.ElementPlacement.PLACEBEFORE
          );
          hostControl.resumeHistory(suspensionID);
        },
        { commandName: "Moving layer to the front" }
      );
    } catch (error) {
      console.log(error);
    }
  };

  return <sp-button onClick={handleClick}>Move Layer to Front</sp-button>;
};

export default App;