Inventsable / bombino

Supercharged Adobe CEP panel generator for Vue with dynamic template support for Vue-CLI and Quasar-CLI
MIT License
98 stars 16 forks source link

How to talk to host via csInterface #19

Closed Jean-Zombie closed 3 years ago

Jean-Zombie commented 3 years ago

Hey there,

I successfully set up a "bombino-vue-basic" extension template and am trying to connect to my host app (InDesign) via the csInterface but I struggle.

<template>
  <div class="centered">
    <img alt="Vue logo" src="../assets/logo.png" />
    <h1 class="centered">{{ msg }}</h1>
    <div class="button" @click="test()">Click</div>
  </div>
</template> 

<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String
  },
  methods: {
    test() {
      this.app.csInterface.evalScript(alert("Inside of host"));  // I'd expected this to work
      // alert("Outside of host");  // this code works if not commented out
    }
  }
};
</script>
<!-- code cut of for clarity -->

I have to admit I am punching way above my weight here, since I only ever used plain .jsx scripts before. But since the bombino templating works so well, I hoped to get into it more.

Is there maybe a simple test file for a working csInterface implementation?

Edit: Console log shows Cannot read property 'evalScript' of undefined so csInterface is not assigned it seems.

Inventsable commented 3 years ago

Hello Jean,

You caught me at an unfortunate time, as (depending on your version of Illustrator) new CORS updates to Adobe mean that bombino must be altered to work fluently in newest versions of AI, PS, and InDesign as it was just a month ago. I'll be adjusting this and all templates very soon and I'll ping you here once that's done in case this is part of the issue.

With regards to your code, I see that you're mixing JSX and JS syntax. What you're trying to do would likely look like the below:

<template>
  <div class="centered">
    <img alt="Vue logo" src="../assets/logo.png" />
    <h1 class="centered">{{ msg }}</h1>
    <!-- 
      You should not include brackets in the function for 
      @click, else you're invoking the function on runtime 
    -->
    <div class="button" @click="test">Click</div>
  </div>
</template> 

<script>
// Importing the evalScript command from our library
import {evalScript} from 'brutalism'

export default {
  name: "HelloWorld",
  props: {
    msg: String
  },
  methods: {
    // Notice that I'm using an async function here:
    async test() {
      let result = await evalScript(`alert("Inside of host")`);  // EvalScript contents must be a string
      console.log(result); // In the event you wanted to return a value, you'd do so like this
    }
  }
};
</script>
<!-- code cut of for clarity -->

Console log shows Cannot read property 'evalScript' of undefined so csInterface is not assigned it seems.

This is not the case in your code, actually. You reference this.app.csInterface, which does not exist. this in the context of Vue refers to the Vue instance as a framework, but there is no app assigned to the Vue instance and further, no csInterface assigned to that this.app object. For clarity, if you're trying to access a property in App.vue from HelloWorld.vue (or any other file), the correct reference would be this.$root.$children[0][property], as in I have a property named message defined in App.vue's data section, I can access that within HelloWorld.vue via this.$root.$children[0].message, whereas within the App.vue file this would be this.message. Without being overbearing and overwhelming you with new information, Vue does not utilize Single-Source-of-Truth data structures until you use VUEX (which there is a bombino template for). Otherwise this.app likely does not exist unless you've explicitly defined it -- and if it did exist, it's not an Adobe app, it's rather the HTML rendering of your panel.

Another thing to point out is that CSInterface.evalScript as a method expects (and can only accept) a string. Whereas you have this.app.csInterface.evalScript(alert("Inside of host")) with contents being alert("Inside of host") (and alert here is an object/function, not a string), it would need to be more akin to evalScript('alert("Inside of host")'), evalScript(\"alert("Inside of host")\") or a template literal (backticks, which I personally prefer) to work as you expect. With regards to evalScript, you must send a string and receive a string from CEP > JSX > CEP. These are not shared environments and each time you jump from one to another, it must be via string. Conventionally you would use JSON to do this though JSON isn't implemented in JSX there are polyfills here in bombino to cover for this.

Give me a few days to sort out my workload and I'll create a sample repository with a basic evalScript implementation for IDSN for you.

Jean-Zombie commented 3 years ago

Dear Tom,

thank you very much for taking your to time to address my problem with that much depth and clarity. I am well aware that I also need to dive deeper into the inner workings of vue framework ;-)

Your proposed code changes work actually quite well, besides me being on the latest InDesign version. I successfully tested closing a document via the extension, but I assume a more complex example would be needed to show the problems caused by the CORS update.

    async test() {
      let result = await evalScript(`
      var myDocument = app.documents.item(0);
      myDocument.close()
      `); 
    }
  }
};

I am really looking forward to the sample repository, but take your time, I am in no rush. I just assumed I might represent a subset of potential bombino users: having a bit of JSX experience and just dangerously little frontend experience to get a bombino template running. Getting CSInterface up was/is my missing link.

Again, thx a lot!

Inventsable commented 3 years ago

No worries. It wasn't too long ago that I was in the very same position -- scripting experience and nothing else. If I could make a suggestion, I'd whole-heartedly recommend taking an HTML/CSS intro course some place like udemy along with a Javascript one, then finally a Vue course. Often times people try playing Beethoven before Mary Had a Little Lamb and we become frustrated at how bad we are at playing Beethoven -- our abilities don't match the ideas we have in our heads. If you were to take 2 months to go tackle some basic HTML, CSS, JS and Vue, you'd come back and be able to make a panel per day rather than spending 2 months working on the same panel (and possibly never getting it to work). I can't recommend this enough, since the change when I did this myself was night and day. I'd spend days struggling to get a single function in JSX working prior and had mountains of sloppy code, but afterwards I was literally creating one panel per day (which is the point I made bombino).

but I assume a more complex example would be needed to show the problems caused by the CORS update.

I can demonstrate to save you any headache in the future. You should notice there are two "contexts" within the panel: DEVELOPER and PRODUCTION. The only difference between these is that the developer context inside a bombino panel (via npm run serve) points to the ./public/index-dev.html file, which if you look at, you can see is simply an <iframe> HTML element pointing to the localhost currently being served through the npm run serve command. The reason for this is to have Hot Module Replacement so that the moment you save a file, the panel will update with no need to refresh and you can see changes in real time as you code.

The new CORS policy interferes with iframe elements specifically, which messes with the current bombino developer context (but has no effect on the production context). So for instance, you can return a value from evalScript by wrapping it in an anonymous function, say if I want to return some string from JSX back to CEP I can normally do so like this:

let result = await evalScript(`
  (function() {
    alert("Hello world");     // This will always run in both contexts
    return app.name;          // This will only run in production (for now)
  }())
`)
console.log(result);          // This will only work/run in production context
console.log("Goodbye");       // This will always run in both contexts

^ You can't simply evalScript("return app.name") because return keyword only exists within the scope of a function. Prior to ILST 24.3 and similar new versions of IDSN or other apps, this code would work perfectly fine in both contexts. Nowadays, CORS will block the return value/callback of evalScript for developer contexts. It's unclear to me why this is and I filed a bug report about it, Adobe implies this is a security precaution but that doesn't make much sense that I can still invoke scripting from within an iframe since that gives me file system access and, if I had malicious intent, that's really all that I would need. Regardless, newest versions of the app cannot return a value from evalScript within developer context. Production is fine, but this throws a wrench in the setup for bombino that I've yet to implement a fix for except in my own private, business projects. I'll be taking care of it soon, though.

The evalScript shown above will still run in both contexts, even in newer apps. You'll still see "Hello world" as an alert, but if you have a panel which relies on grabbing certain information about the document and returning this, then we have an issue and the return value is never given, at least when you're in the act of developing. It isn't difficult to fix per se, but it becomes very complicated in the scope of the entire brutalism library specifically components like <Menus> and <Panel>.

Jean-Zombie commented 3 years ago

You are right about starting with the basics. Your analogy is spot on. I am going to. I am just tempted sometimes to get a fast solution going by stitching together snippets and have a look at stack overflow ;-) But indeed, given the scope here, it is a bit of an megalomaniac endeavor.

Your explanation of the impact of the new CORS is very insightful. I regret not opening the issue on the main bombino repo for more visibility for others.

I tested your code (in dev) and there is something I noticed: When using an async function none of the two console.log seems to get called. If I switch to a plain function "Goodbye" gets outputted as expected and the first console.log returns "Promise {}". But I fear this again is connected to my limited JS-knowledge, so, please ignore it if so. I'll figure it out along the way.

Inventsable commented 3 years ago

Your explanation of the impact of the new CORS is very insightful. I regret not opening the issue on the main bombino repo for more visibility for others.

No worries, I've transferred it to the main bombino repo.


I've published the new version of evalScript to npm as the package workaround (which I was shocked to see wasn't already taken), but have yet to implement the required event listeners in the templates (though I've already done this in my own projects and have proven it to work). I'll be fixing all templates within the next few days, likely tomorrow.

Jean-Zombie commented 3 years ago

I am closing this, since the workaround package did solve my issue.