Skip to content

bigarobas/plugincc

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

46 Commits
 
 
 
 
 
 

Repository files navigation

Toolbox for building Adobe CC extensions with CEP

It probably needs a better name ^^

Purpose :

  • minimize code duplication in both contexts (JS / JSX)
  • ease communication between both contexts (JS / JSX)
  • bring independent tools as well as a full framework and workflow based on those tools.
  • no dependencies (except JSON and of course CEP)

To have a better understanding of what is at stake here I recommand you to read this wiki page about "mixed contexts" : Mixed context in Adobe CC extensions with CEP

JSXBridge

This is the central module around which this toolset is made. When creating a JSXBridge for an Object this object is auto implemented with the following methodes :

  • mirror(function_name,function_args,callback_or_expression)
  • bridgeCall(client_id,function_name,function_args,callback_or_expression,scope)
  • getContext()
  • checkContext(ctx)
  • listen(type,handler)
  • dispatch(type,data,scope)

These methodes allow us to :

  • easy mirroring methodes on both sides (JS/JSX)
//Let's say you have 2 files one on JS and the other on JSX context with code on global level :

//on JS side
a = new ClassA();
a.log("HELLO FROM JS");
function ClassA () { 
    this.bridge = new JSXBridge(this,"BRIDGE_ID_X");
    this.log = function(message) {
      //console is defined is JS context so we can use it
      console.log(message);
    }
}

//on JSX side
b = new ClassB();
b.log("HELLO FROM JSX");
function ClassB () { 
    this.bridge = new JSXBridge(this,"BRIDGE_ID_X");
    this.log = function(message) {
      //console is NOT defined is JSX context so we mirror the action to the JS side
      this.mirror('log',message);
    }
}

//RESULT in chrome console (JS context) :
> HELLO FROM JS
> HELLO FROM JSX
  • MIXED CONTEXT : you can also do the same thing with only 1 file (loaded both on JS and JSX context)
var a = new ClassA();
a.log();

function ClassA () { 
    this.bridge = new JSXBridge(this,"BRIDGE_ID_X");
    this.log("HELLO FROM "+this.getContext());
}

ClassA.prototype.log = function (message) {
  if (this.checkContext("js") {
    //console is defined is JS context so we can use it
    console.log(message);
  } else {
    //console is NOT defined is JSX context so we mirror the action to the JS side
    this.mirror('log',message);
  }
}

//RESULT in chrome console (JS context) :
> HELLO FROM js
> HELLO FROM jsx
  • it's also possible to easyly call methodes on both context using bridge names with 5 different scopes :
    • JS (only on JS context)
    • JSX (only on JSX context)
    • CURRENT (only in the CURRENT context (JS or JSX))
    • MIRROR (only in the MIRROR ("opposite") context (JS or JSX))
    • BOTH (in BOTH contexts (JS and JSX))
//on JS side
var a = new ClassA();
function ClassA () { 
    this.bridge = new JSXBridge(this,"BRIDGE_ID_X");
    this.doSomething = function() {
        //DOES SOMETHING
    }
}

//on JSX side
var b = new ClassB();
function ClassB () { 
    this.bridge = new JSXBridge(this,"BRIDGE_ID_X");
    this.doSomething = function() {
      //DOES SOMETHING ELSE
    }
}

//SOME WHERE IN JS OR JSX CONTEXT
var c = new ClassC();
function ClassC () { 
    this.bridge = new JSXBridge(this,"ANOTHER_BRIDGE_ID");
    this.bridgeCall("BRIDGE_ID_X","doSomething",{some_args_if_needed},callback_function_or_expression,"both");
}

//RESULT 
> c calls methode "doSomething" on "BRIDGE_ID_X" bridge (linked to a in JS conetxt and b in JSX context) with scope = "both".
> which means that both "doSomething" methodes from both context (a in JS and b in JSX) are called :
> a DOES SOMETHING in JS context
> b DOES SOMETHING ELSE in JSX context
  • the mirror methode takes a callback_or_expression argument depending on the context it's called on :
    • a callback function if it's called from JS context (that will be called with the mirror JSX function return value).
    • a callback expression if it's called from JSX context (that will be evaluated with the JS function return value). This expression contain key_words ({bridge} and {args}) which will dynamically be replaced before the expression is evaluated.
// imagine we want to synch a MIXED OBJECT (1 jsx file loaded in both contexts JS & JSX)
// we need to push the update to the other context and retrieve the new updated state to synch back with the first object
// 
this.synch = function(onComplete) {
    this.onSynchComplete = onComplete;
    var _self = this;
    if (this.checkContext("jsx")) {
        this.mirror(
            'update',
            this.data,
            '(function() {\
                {bridge}.update({args});\
                {bridge}.onSynchComplete({args});\
            })();'
        );
    } else {
        this.mirror(
            'update',
            this.data,
            function(json) {
                _self.update(json);
                _self.onSynchComplete(json);
            }
        );
    }  
}

this.update(data) {
    //update with data
    //return new state
}
  • easy communication between objects in both contexts with a custom Observer pattern that let you dispatch custom JSXBridgeEvents with 5 different scopes :
    • JS (only JSXBridge objects on JS context can receive the event)
    • JSX (only JSXBridge objects on JSX context can receive the event)
    • SAME (only JSXBridge objects in the SAME context (JS or JSX) can receive the event)
    • MIRROR (only JSXBridge objects in the MIRROR ("opposite") context (JS or JSX) can receive the event)
    • BOTH (JSXBridge objects in BOTH contexts (JS and JSX) can receive the event)
//IMPORTING THE MODULE
  // JS SIDE
  Configuration =  require(__EXTENTION_PATH__ + "/CORE/mixed/JSXBridge.jsx");
  // JSX SIDE
  $.evalFile(__EXTENTION_PATH__ + "/CORE/mixed/JSXBridge.jsx");
  
// IN AN OBJECT IN JS CONTEXT
var _bridge = new JSXBridge(this,"SOME_BRIDGE_ID");
this.listen("a_custom_event_type",function(event) {(...)});
this.dispatch("a_custom_event_type",some_data_object,"mirror");

// IN AN OBJECT IN JSX CONTEXT
var _bridge = new JSXBridge(this,"SAME_OR_OTHER_BRIDGE_ID");
this.listen("a_custom_event_type",function(event) {(...)});
this.dispatch("a_custom_event_type",some_data_object,"both")

/*
# RESULT ON JSX SIDE :
The JSX bridge will receive 2 events with "a_custom_event_type".
1 from its own dispatch because it was on "both" scope.
1 from JS context's dispatch because it was on "mirror" scope = the opposite context of JS = JSX.
In this case the mirror (opposite) of JS is of course JSX.

# RESULT ON JS SIDE :
The JS bridge will receive only 1 event with "a_custom_event_type".
0 from its own dispatch because it was on "mirror" scope = the opposite context of it's own = JSX.
1 from JSX context's dispatch because it was on "both" scope.
*/

Configuration

  • Mixed Configuration object synched and available in both contexts (JS ad JSX)
//IMPORTING THE MODULE
  // JS SIDE
  Configuration =  require(__EXTENTION_PATH__ + "/CORE/mixed/Configuration.jsx");

  // JSX SIDE
  $.evalFile(__EXTENTION_PATH__ + "/CORE/mixed/Configuration.jsx");
  
// ON BOTH SIDES
CONFIG = new Configuration("CONFIG");
CONFIG.update(json); // update with some json. existing keys are updated / non existing keys are created
CONFIG.set("some_key",some_value); // set a value to a key
CONFIG.get("some_key"); // get a value of a key
CONFIG.synch(); // synch the config with the other context

synch() is the most interresting part :

It synchronizes the config with the other context (JSX if you are in JS and JS if you're in JSX).

For now the synch is a "push prioritised". This means that the values are pushed to the the other side. Existing keys are updated / non existing keys are created. Then key/values are pulled to synch. Existing keys are updated (which should not happen) / non existing keys are created.

We might have the other option "pull prioritised" too in the future. This means the key/values are pulled from the other context. Existing keys are updated / non existing keys are created. Then key/values are pushed to synch. Existing keys are updated (which should not happen) / non existing keys are created.

Debugger

  • Mixed Multichanel Debugger available in both contexts (JS ad JSX)
// IMPORTING THE MODULE
  // JS SIDE
  Debugger = require(__EXTENTION_PATH__ + "/CORE/mixed/Debugger.jsx");
  // JSX SIDE
  $.evalFile(__EXTENTION_PATH__ + "/CORE/mixed/Debugger.jsx");
  
// USING THE MODULE (ON BOTH SIDE)
DEBUG = new Debugger();
DEBUG.log("Hello World");
  • create channels and sub channels :
DEBUG.channel("main_channel_branch_name").channel("sub_channel_branch_name").log("Hello World");
  • manage / costumise channels :
DEBUG.channel("main_channel_branch_name").mute(isCompletelyMuted);
DEBUG.channel("main_channel_branch_name").setVerbose(isWriteMuted,isAlertMutedn,isChannelIdPrefixDisplayed); 
DEBUG.channel("main_channel_branch_name").setSeparator("-._.-._.-._.-._.-._.-._.-._.-._.-._.-");
  • supporting multiple write & alert methodes :
    • write(message)
    DEBUG.channel("some_channel_name").write("Hello World");
    // JSX : JSX : mirrored to chrome.console.log instead of $.write / JS : chrome.console.log
    • writeln(message)
    DEBUG.channel("some_channel_name").writeln("Hello World");
    // JSX : mirrored to chrome.console.log instead of $.writeln / JS : chrome.console.log
    • log(message)
    DEBUG.channel("some_channel_name").log("Hello World");
    // JSX : mirrored to chrome.console.log instead of $.writeln / JS : chrome.console.log
    // the same than .writeln()
    • json(someObject)
    DEBUG.channel("some_channel_name").json(someObject);
    // .writeln() + JSON.stringify()
    • popup(message)
    DEBUG.channel("some_channel_name").popup(message);
    // application alert - event if called from chrome side (js)
    • popupJson
    DEBUG.channel("some_channel_name").json(someObject);
    // .popup() + JSON.stringify()
  • stack / flush
DEBUG.channel("some_channel_name").stack("Hello");
// (some code)
DEBUG.channel("some_channel_name").stack("How");
// (some code)
DEBUG.channel("some_channel_name").stack("Are");
// (some code)
DEBUG.channel("some_channel_name").stack("You");
// (some code)
DEBUG.channel("some_channel_name").stack("?");
// (some code)
DEBUG.channel("some_channel_name").flush();

// log formatted stack :
// ---------------------------------
// Hello
// How
// Are
// You
// ?
// ---------------------------------
  • chained notation
DEBUG.channel("some_channel_name")
  .mute(false)
  .setVerbose(true,false,false)
  .stack("A")
  .stack("B")
  .log("1")
  .stack("C")
  .popup("X")
  .flush();

JSXHelper(s)

A collection of handy functions for some Adobe CC applications : (This still needs serious cleaning / harmonizing / old projects great functions hunting / testing)

  • JSXHelper (global functions for all applications)
  • JSXHelper_AEFT.jsx (After Effects functions)
  • JSXHelper_PHSP.jsx (Photoshop functions)
  • JSXHelper_PPRO.jsx (Premiere functions)
  • JSXHelper_INDS.jsx (InDesign functions)
  • (more to come...)

CORE

This needs a better name (or not ^^). This is the framework approach which uses the modules above to bring a more complete solution to build your panel.

  • JSXBridge : Scoped event dispatcher and mixed context Helper.
  • Configuration based initialisation of your panel.
    • 1 core json configuration file (that you won't need to change)
    • 1 panel json configuration file specific to your panel were you can define :
      • all your project specific values (jsx files to load, paths, etc.)
      • all your custom key/values
  • Mixed Configuration object synched and available is both contexts (JS ad JSX)
  • Mixed Environement object synched and available is both contexts (JS ad JSX)
  • Mixed Multi Channel Debugger available is both contexts (JS ad JSX)
  • JSXHelper(s) : a collection of handy functions for some Adobe CC applications.
  • Event driven CORE launch sequence managing both sides (JS and JSX)
//CORE
CORE.JS.START
CORE.JS.INIT.BEGIN
CORE.JS.INIT.END
CORE.JSX.INIT.BEGIN
CORE.JSX.INIT.END
CORE.READY

//ENVIRONMENT
CORE.JS.ENV.INIT.BEGIN
CORE.JS.ENV.INIT.END
CORE.JSX.ENV.INIT.BEGIN
CORE.JSX.ENV.INIT.END
CORE.ENV.SYNCH.BEGIN
CORE.ENV.SYNCH.END
CORE.ENV.READY

//DEBUGGER
CORE.JS.DEBUGGER.INIT.BEGIN
CORE.JS.DEBUGGER.INIT.END
CORE.JSX.DEBUGGER.INIT.BEGIN
CORE.JSX.DEBUGGER.INIT.END
CORE.DEBUGGER.READY

//CONFIGURATION
CORE.JS.CONFIG.INIT.BEGIN
CORE.JS.CONFIG.INIT.END
CORE.JS.CONFIG.CORE.BEGIN
CORE.JS.CONFIG.CORE.END
CORE.JS.CONFIG.PANEL.BEGIN
CORE.JS.CONFIG.PANEL.END
CORE.JSX.CONFIG.INIT.BEGIN
CORE.JSX.CONFIG.INIT.END
CORE.CONFIG.INIT.SYNCH.BEGIN
CORE.CONFIG.INIT.SYNCH.END
CORE.CONFIG.READY

//INCLUDES
CORE.INCLUDE.JSX.CORE.BEGIN
CORE.INCLUDE.JSX.CORE.END
CORE.INCLUDE.JSX.PANEL.BEGIN
CORE.INCLUDE.JSX.PANEL.END
CORE.INCLUDE.JSX.READY

//MODULES
CORE.JS.MODULES.LOAD.BEGIN
CORE.JS.MODULES.LOAD.END
CORE.JS.MODULES.BUILD.BEGIN
CORE.JS.MODULES.BUILD.END
CORE.JS.MODULES.INIT.BEGIN
CORE.JS.MODULES.INIT.END
CORE.JS.MODULES.START.BEGIN
CORE.JS.MODULES.START.END
CORE.MODULES.READY

WORK IN PROGRESS / WONDERING / ROADMAP :

  • [✔] Making JSXBridge linker resolve bridge path based on registred object instead of evaluated string path based on existing global variables.
  • [✖] Config : evaluate values : internal key/value injection / external key/value injection
  • [✖] Config : add user localy saved key/values
  • [✖] Modules : auto-detected mixed context modules from a specific project folder which let you developpe reusable modules
  • [✖] Panel : a ready to use Panel Class that you can inherite from to startup even more quickly
  • [✖] Commenting/Documenting the code
  • [✖] (?) Getting away from singelton model for CORE (?)
  • [✖] (?) Thinking multi-panel and cross-app (?)
  • [✖] (?) Thinking external framework (angular, react, vue, knockoutjs, etc.) compatibility / helpers (?)
  • [✖] optional es5-shim integration via config for JSX (ES3 JSX power up to ES5)
  • (more to come...)