(Again) Create Layer on top of every other layer through pure batchPlay

I’ve seen many topics regarding moving / selecting top layer and issues caused by nested groups or bugs.

After days of trials and errors I decided to ask for your help. I almost gave up.
As mentioned in my previous post here, there is a change in relation to “move” through batchplay. I can’t anymore use Move command if only one layer exists.

Let’s imagine that I want to create an empty layer on top of everything - layers groups and nested groups.
I don’t know how many layers exists, every time the count can be different.

How to make it with pure batchPlay if after layer creation I’ll have other BP descriptors and I want to keep everything within one history state?

this are 3 most common layer scenarios:

Screenshot 2022-06-28 at 15.47.17

Screenshot 2022-06-28 at 15.47.23

I’ve tried selecting top layer like this and then creating new layer:

       {
            _obj: "select",
            _target: {
              _ref: "layer",
              _id: app.activeDocument.layers[0]._id,
            },
            _isCommand: true,
            _options: {},
          }

It doesn’t work for nested groups

Move to front can’t be used if there are no other layers:

       {
        _obj: "move",
        _target: {
            _ref: "layer",
            _enum: "ordinal",
            _value: "targetEnum"
        },
        to: {
            _ref: "layer",
            _enum: "ordinal",
            _value: "front"
        },
        _options: {
            dialogOptions: "dontDisplay"
        }
    }

Do you have any ideas?

Thanks!

What do you mean? You shouldn’t care about nesting at all if you want to create/move (not sure which one we’re talking about here) on the very top.


You should never use private properties like _id here. I believe simple public id is available.

If debugging exposes any private fields and methods, do not attempt to use them. Plugins referring to private APIs may be rejected or removed from the Plugin Marketplace, and will be prone to breaking unexpectedly.


You could try selecting the top-most layer first and then create a new layer (or move other layer to that index). From your description not sure what exactly is not working

BTW, it doesn’t matter how many BPs or other operations you have, you still can have everything under a single history state with executeAsModal()

You can use this code to warp all your code under one single history state.

https://forums.creativeclouddeveloper.com/t/confused-about-suspend-history-usage/4855/5

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

document.getElementById("btn").addEventListener("click", async function () {
  await ExecuteAsModal(functionName);
});

async function functionName(executionContext) {
  let hostControl = executionContext.hostControl;
  // let documentID = await getTargetDocument();

  let suspensionID = await hostControl.suspendHistory({
    documentID: app.activeDocument?.id,
    name: "History name",
  });


  // your functions
// batchPlay
//batchPlay 
// ……

  await hostControl.resumeHistory(suspensionID);
}

Edit to add the missing variables :sweat_smile:

Hi.

how to put some information within the function call?

normally I am able to call a function like:

functionname("name", 24,"text");

how to do it here?

await ExecuteAsModal(functionName("name",24,"text");

?

await ExecuteAsModal(exec);

async function exec(executionContext) {
  // Start recording for history and make it a single entry
  let hostControl = executionContext.hostControl;
  let suspensionID = await hostControl.suspendHistory({
    documentID: app.activeDocument?.id,
    name: "History name",
  });

  functionname("name", 24,"text")
  // ... other actions for same history state

  // Resume history
  await hostControl.resumeHistory(suspensionID);
}
1 Like

I am too stupid for this.

If I start it in Debug I get the following:

await ExecuteAsModal(exec);

(index):1 Uncaught ReferenceError: ExecuteAsModal is not defined
    at <anonymous>:1:1

the other way I try is:

await require("photoshop").core.executeAsModal(exec);
Uncaught ReferenceError: app is not defined

in the plugin nothing happens…

Put above

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

Most examples in the forum assume you’re already familiar with basic JS, so they are not fully functional without importing the obvious stuff :slight_smile:

1 Like

Now it is working fine.

Thanks a lot. You safed my life… again. :wink:

now the next question :wink:

is it possible to call the exec function like this:

await ExecuteAsModel(function(){exec("name",24, "text");});

I have to give some variables to the function.

await ExecuteAsModel(() => exec("name",24, "text"));

But this way you lose history state management. What is wrong with my example above? You are able to pass these params and keep history management

I have 3 buttons that run the same function but with different parameters.
then I have to use an other way to set this values.

Thanks for your support.

const yourFunction = (arg1, arg2, arg3) => {
  // do your stuff with arguments
}

const onClick = (arg1, arg2, arg3) => {
  await ExecuteAsModal((executionContext) => {
    let hostControl = executionContext.hostControl;
    let suspensionID = await hostControl.suspendHistory({
      documentID: app.activeDocument?.id,
      name: "History name",
    });

    yourFunction(arg1, arg2, arg3)

    await hostControl.resumeHistory(suspensionID);
  });
}

button.addEventListener("click", () => onClick("name",24, "text"));

Hi @Wanokuni,

Thanks again, suspend history works for the whole function as you’ve explained. This is solved.
Now I’m only struggling with creating layer on top of everything.

I’m not sure how reliable the front enum currently is, I remember it not working correctly. So this is a reliable way:

 const bgIdx = getDocumentProperty('hasBackgroundLayer') ? 1 : 0
  suspendHistory(() => {
    bpSync([
      __makeLayer(),
      __moveLayerToIndex(getLayerCount() + 1 + bgIdx) // +1 due to new layer, +1 for BG
    ])
  }, "New Layer on Top");

__makeLayer, __moveLayerToIndex, getDocumentProperty, suspendHistory, bpSync and getLayerCount are some of my functions, but it should be obvious what they do. Just did some quick testing.

Hi @Karmalakas,

Thank you for your reply. My Issue is quite complex but I want to do simple thing - no matter how many layers or groups I have and no matter what is selected, I want to create the new layer on top of everything to do more stuff on it later (via batchPlay).

Screenshot 2022-06-29 at 10.35.35
Let’s assume that Layer 1 was selected before running the function, I would like to create the layer in the position shown on the screenshoot.

Thanks to you and @Wanokuni, I’ve managed to merge everything within one history step.
Now I just can’t find a good way to place the layer where it should be in some cases. (especially when I have many groups).
I decided to create layer and move it to the top via DOM but I’m experiencing some strange behaviour.
Ive recorded what’s happening.

This was the first step:


document.getElementById("btndustAndScratches").addEventListener("click", async function () {
  await ExecuteAsModal(DaS);
});

async function DaS(executionContext) {

    let hostControl = executionContext.hostControl;
    let suspensionID = await hostControl.suspendHistory({
      documentID: app.activeDocument?.id,
      name: "Dust And Scratches",
    });

  const docexists = () => {
    return Boolean(app.documents?.length);
  };
  const exists = docexists();

  if (exists === true) {

    const newLayer = await doc.createLayer({name: "DustAndScratches"}) // I have no idea why the layer is being moved as shown on the video
    
  } else {
    PhotoshopCore.showAlert({ message: "Open the document first" });
  }

  await hostControl.resumeHistory(suspensionID);
}

this is what’s happening:

I assume that the layer should be just created and not moved. I don’t know what’s happening.

Then I was trying to move the layer as explained by @Pierre_G here


document.getElementById("btndustAndScratches").addEventListener("click", async function () {
  await ExecuteAsModal(DaS);
});

async function DaS(executionContext) {

    let hostControl = executionContext.hostControl;
    let suspensionID = await hostControl.suspendHistory({
      documentID: app.activeDocument?.id,
      name: "Dust And Scratches",
    });

  const docexists = () => {
    return Boolean(app.documents?.length);
  };
  const exists = docexists();

  if (exists === true) {

    const newLayer = await doc.createLayer({name: "DustAndScratches"}) // I have no idea why the layer is being moved as shown on the video

// THIS IS WHAT I'VE ADDED
    const layers = doc.activeLayers; // should I add await?   .layers or .activeLayers?
    const SelectedLayer = layers[0]; // should I add await?
    SelectedLayer.moveAbove(doc.layerTree[0]);
//

  } else {
    PhotoshopCore.showAlert({ message: "Open the document first" });
  }

  await hostControl.resumeHistory(suspensionID);
}

All I’m getting is a weird loop, there is no history for that in PS:

And this is the whole code (that doesn’t work):

/////////////////////////////////////////////////////////////////////////////////////
/////////                           GLOBAL CONSTS                           /////////
/////////////////////////////////////////////////////////////////////////////////////
const app = require("photoshop").app;
const batchPlay = require("photoshop").action.batchPlay;
const core = require("photoshop").core;
const PhotoshopCore = require('photoshop').core;
const doc = app.activeDocument;
const ExecuteAsModal = require("photoshop").core.executeAsModal;


// FUNCTION

document.getElementById("btndustAndScratches").addEventListener("click", async function () {
  await ExecuteAsModal(DaS);
});

async function DaS(executionContext) {

    let hostControl = executionContext.hostControl;
    let suspensionID = await hostControl.suspendHistory({
      documentID: app.activeDocument?.id,
      name: "Dust And Scratches",
    });

  const docexists = () => {
    return Boolean(app.documents?.length);
  };
  const exists = docexists();

  if (exists === true) {

    const newLayer = await doc.createLayer({name: "DustAndScratches"}) // I have no idea why the layer is being moved as shown on the video
    const layers = doc.activeLayers; // should I add await?   .layers or .activeLayers?
    const SelectedLayer = layers[0]; // should I add await?
    SelectedLayer.moveAbove(doc.layerTree[0]);

  await batchPlay(
        [
            {
                _obj: "mergeVisible",
                duplicate: true,
                _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"
                }
            }
        ], {});

    
  } else {
    PhotoshopCore.showAlert({ message: "Open the document first" });
  }

  await hostControl.resumeHistory(suspensionID);
}

I have no Idea what I’m doing wrong, probably many things, I feel stupid.

Thank you @simonhenke,

const bgIdx = getDocumentProperty('hasBackgroundLayer') ? 1 : 0
  suspendHistory(() => {
    bpSync([
      __makeLayer(),
      __moveLayerToIndex(getLayerCount() + 1 + bgIdx) // +1 due to new layer, +1 for BG
    ])
  }, "New Layer on Top");

Looks like this could be the solution but unfortunately it’s not so obvious for me how I should build the functions you’re mentioning. Would it be too much if I would ask you for an extended version with functions included? Of course I’ll understand if you don’t want to share too much, I know it’s your work.

I think the logic would be something like this:

  • check if the active document has only the background layer if yes then just make new layer.
  • second statement to check if the active doc have more than one layers then select the top layer make it the active then create new layer ( but the active top layer may be a group so you need to use move the new created layer to top)
    Those are my thought process for this :slightly_smiling_face:.

I think @simonhenke code is good start for this. But He’s code structure a bit more advanced for me at moment :sweat_smile:

Hello,
if you just want to create a layer and move it to the top layer, I think it’s easy to implement.

async function test() {

    await require("photoshop").core.executeAsModal(CreateLayerToTop, {
        "commandName": "test"
    });

}

async function CreateLayerToTop() {


    await require("photoshop").action.batchPlay([
        Layer_Add(),
    ], {
        synchronousExecution: false
    });

    await require("photoshop").action.batchPlay([
        Layer_Move_Top(),
    ], {
        synchronousExecution: false
    });


    function Layer_Add() {

        const json = {
            "_obj": "make",
            "_target": [{
                "_ref": "layer"
            }],
            "_isCommand": true
        }
        return json;

    }

    function Layer_Move_Top() {

        const json = {
            "_obj": "move",
            "_target": [{
                "_ref": "layer",
                "_enum": "ordinal",
                "_value": "targetEnum"
            }],
            "to": {
                "_ref": "layer",
                "_enum": "ordinal",
                "_value": "front"
            },
            "_isCommand": true
        }
        return json;

    }

}

Hi @MrL,

Thank you but unfortunately it’s not that easy if there are groups / nested groups.

If I had “Layer 1” selected, that’s what happens when I’m running your function (I’m creating “Layer 5”):